diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000000000000000000000000000000000000..5dc1307dd2c57eca3d4f2e37f7ebbee95382d37a --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,8 @@ +version = 1 + +[[analyzers]] +name = "go" +enabled = true + + [analyzers.meta] + import_paths = ["github.com/GoAdminGroup/go-admin"] diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000000000000000000000000000000000000..773f044511db1f9914da55a8122845c0d455db35 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,173 @@ +--- +kind: pipeline +type: docker +name: api_mysql + +trigger: + event: + - pull_request + +clone: + disable: true + +services: +- name: db_mysql + image: mysql:5.7 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: go-admin-test + +steps: +- name: api + image: chg80333/goadmin-test:v9 + environment: + GO111MODULE: on + GOPROXY: https://goproxy.cn + commands: + - cd /go/src/github.com/GoAdminGroup/go-admin + - git pull + - git fetch origin pull/$DRONE_PULL_REQUEST/head:pr$DRONE_PULL_REQUEST + - git checkout pr$DRONE_PULL_REQUEST + - go version + - sleep 80 + - GOPROXY=https://goproxy.cn make mysql-test + +# --- +# kind: pipeline +# type: docker +# name: api_mssql + +# trigger: +# event: +# - pull_request + +# clone: +# disable: true + +# volumes: +# - name: data +# temp: {} + +# services: +# - name: db_mssql +# image: mcr.microsoft.com/mssql/server:2017-latest +# volumes: +# - name: data +# path: /home/data +# environment: +# ACCEPT_EULA: Y +# SA_PASSWORD: Aa123456 + +# steps: +# - name: api +# image: chg80333/goadmin-test:v9 +# volumes: +# - name: data +# path: /go/src/github.com/GoAdminGroup/go-admin/tests/data +# environment: +# GO111MODULE: on +# GOPROXY: https://goproxy.cn +# commands: +# - cd /go/src/github.com/GoAdminGroup/go-admin +# - git pull +# - git fetch origin pull/$DRONE_PULL_REQUEST/head:pr$DRONE_PULL_REQUEST +# - git checkout pr$DRONE_PULL_REQUEST +# - go version +# - sleep 80 +# - GOPROXY=https://goproxy.cn make ms-test + +--- +kind: pipeline +type: docker +name: api_postgres + +trigger: + event: + - pull_request + +clone: + disable: true + +services: +- name: db_pgsql + image: postgres:10 + environment: + POSTGRES_USER: postgres + POSTGRES_DB: go-admin-test + POSTGRES_PASSWORD: root + +steps: +- name: api + image: chg80333/goadmin-test:v9 + environment: + GO111MODULE: on + GOPROXY: https://goproxy.cn + commands: + - cd /go/src/github.com/GoAdminGroup/go-admin + - git pull + - git fetch origin pull/$DRONE_PULL_REQUEST/head:pr$DRONE_PULL_REQUEST + - git checkout pr$DRONE_PULL_REQUEST + - go version + - sleep 80 + - GOPROXY=https://goproxy.cn make pg-test + +--- +kind: pipeline +type: docker +name: api_sqlite + +trigger: + event: + - pull_request + +clone: + disable: true + +steps: +- name: api + image: chg80333/goadmin-test:v9 + environment: + GO111MODULE: on + GOPROXY: https://goproxy.cn + commands: + - cd /go/src/github.com/GoAdminGroup/go-admin + - git pull + - git fetch origin pull/$DRONE_PULL_REQUEST/head:pr$DRONE_PULL_REQUEST + - git checkout pr$DRONE_PULL_REQUEST + - go version + - sleep 80 + - GOPROXY=https://goproxy.cn make sqlite-test + +--- +kind: pipeline +type: docker +name: frontend + +trigger: + event: + - pull_request + +clone: + disable: true + +services: +- name: db_mysql + image: mysql:5.7 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: go-admin-test + +steps: +- name: chrome + image: chg80333/goadmin-test:v9 + environment: + GO111MODULE: on + GOPROXY: https://goproxy.cn + commands: + - cd /go/src/github.com/GoAdminGroup/go-admin + - git pull + - git fetch origin pull/$DRONE_PULL_REQUEST/head:pr$DRONE_PULL_REQUEST + - git checkout pr$DRONE_PULL_REQUEST + - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost & + - sleep 8 + - GOPROXY=https://goproxy.cn make web-test diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..b24e13eb5e2a3969db73b436013a91c3fa5f8814 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +tests/data/admin_ms.bak filter=lfs diff=lfs merge=lfs -text diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..4dca4d52ea197034957628b5b74b8593f812664f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: +patreon: +open_collective: go-admin +ko_fi: +tidelift: +community_bridge: +liberapay: +issuehunt: +otechie: +custom: ['https://www.paypal.me/cg80333'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000000000000000000000000000000..b676e871cba75eb57491525d68a2979c4589fbef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,25 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: "\U0001F41Bbug" +assignees: '' + +--- + +### Bug Description [describe the bug in detail] + +### How to reproduce [describe the steps how to reproduce the bug] + +### Expect [describe your expect result] + +### Reproduction code [here to show your codes or examples] + +### Versions + +- GoAdmin version: [e.g. 1.0.0] +- golang version +- Browser +- OS [e.g. mac OS] + +### Others [screenshots or others info here] diff --git a/.github/ISSUE_TEMPLATE/bug_report_zh.md b/.github/ISSUE_TEMPLATE/bug_report_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..c8fa4073eb0db5771778448cf1b1498bf6b84864 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report_zh.md @@ -0,0 +1,25 @@ +--- +name: Bug 报告 +about: 提交bug帮助我们修复 +title: "[BUG]" +labels: "\U0001F41Bbug" +assignees: '' + +--- + +### bug 描述 [详细地描述 bug,让大家都能理解] + +### 复现步骤 [清晰描述复现步骤,让别人也能看到问题] + +### 期望结果 [描述你原本期望看到的结果] + +### 复现代码 [提供可复现的代码,仓库,或线上示例] + +### 版本信息: + +- GoAdmin 版本: +- golang 版本: +- 浏览器环境: +- 开发环境: + +### 其他信息 [如截图等其他信息可以贴在这里] diff --git a/.github/ISSUE_TEMPLATE/proposal.md b/.github/ISSUE_TEMPLATE/proposal.md new file mode 100644 index 0000000000000000000000000000000000000000..95b7c99a7040368967a278f4debc6d5f7dbd687d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/proposal.md @@ -0,0 +1,14 @@ +--- +name: Proposal +about: Any advice for GoAdmin +title: "[Proposal]" +labels: '' +assignees: '' + +--- + +### Description [describe your advice] + +### Solution [if any solutions, describe here] + +### Others [screenshots and other info] diff --git a/.github/ISSUE_TEMPLATE/proposal_zh.md b/.github/ISSUE_TEMPLATE/proposal_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..f7746c8fcbf7d70e1f1100217c9c8ec774f5d268 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/proposal_zh.md @@ -0,0 +1,14 @@ +--- +name: 功能需求 +about: 对 GoAdmin 的需求或建议 +title: "[Proposal]" +labels: '' +assignees: '' + +--- + +### 需求描述 [详细地描述需求,让大家都能理解] + +### 解决方案 [如果你有解决方案,在这里清晰地阐述] + +### 其他信息 [如截图等其他信息可以贴在这里] diff --git a/.github/ISSUE_TEMPLATE/questions.md b/.github/ISSUE_TEMPLATE/questions.md new file mode 100644 index 0000000000000000000000000000000000000000..1fc713ed8b61a8af0abfa1e87a2a61816f428a4a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/questions.md @@ -0,0 +1,14 @@ +--- +name: Questions +about: Any questions when using GoAdmin +title: "[Question]" +labels: '' +assignees: '' + +--- + +### Description [describe your questions] + +### Example code [If you have any code info] + +### Others [screenshots or other info] diff --git a/.github/ISSUE_TEMPLATE/questions_zh.md b/.github/ISSUE_TEMPLATE/questions_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..35cbaced5a8d32b222f6a43abeca324067112459 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/questions_zh.md @@ -0,0 +1,14 @@ +--- +name: 疑问或需要帮助 ❓ +about: 关于 GoAdmin 的问题 +title: "[Question]" +labels: '' +assignees: '' + +--- + +### 问题描述 [详细地描述问题,让大家都能理解] + +### 示例代码 [如果有必要,展示代码,线上示例,或仓库] + +### 其他信息 [如截图等其他信息可以贴在这里] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..62f092114091fc54ba7a9531c3156df308a59f52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.idea +.vscode +.DS_Store +demo/config.json +demo/config +demo/deploy.yml +demo/hosts +demo/go-admin +demo/Makefile +demo/deploy.retry +vendor/** +!vendor/vendor.json +logs +adm/build +template/login/assets/login +uploads \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..a0e436d1df5201b802a3334ae0782f6e95cff1b8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers 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, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at chg80333@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..1656ab7e4ce0381fb629ca47ecd1c4de3678a248 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,84 @@ +# Contributing + +If you want to contribute, but not sure what to do, here's a list of things that I always need help with: + +* Translations + * README.md + * [docs](https://github.com/GoAdminGroup/docs/issues/1) +* Bug-hunting +* Finding security problems +* Themes and Plugins + +See [manual](https://github.com/GoAdminGroup/go-admin/projects/3) for more information. + +You can view all open issues on github, which is usually a good starting point if you want to start contributing: + +https://github.com/search?q=org%3AGoAdminGroup+is%3Aopen+is%3Aissue+archived%3Afalse&type=Issues + +## how to + +GoAdmin uses GitHub to manage reviews of pull requests: + +- If you have a trivial fix or improvement, go ahead and create a pull request. +- If you plan to do something more involved, discuss your ideas on the relevant GitHub issue. + +For now, you need to add your fork as a remote on the original **\$GOPATH**/src/github.com/GoAdminGroup/go-admin clone, so: + +```bash + +$ go get github.com/GoAdminGroup/go-admin +$ cd $GOPATH/src/github.com/GoAdminGroup/go-admin # GOPATH is $HOME/go by default. + +$ git remote add +``` + +And before you commit, remember to execute the command: + +``` +make test +``` + +See the Makefile for more details. + +Notice: `go get` return `package github.com/GoAdminGroup/go-admin: no Go files in /go/src/github.com/GoAdminGroup/go-admin` is normal. + +### Dependency management + +We uses [Go modules](https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more) to manage dependencies on external packages. +This requires a working Go environment with version 1.13 or greater and git installed. + +To add or update a new dependency, use the `go get` command: + +```bash +# Pick the latest tagged release. +go get example.com/some/module/pkg + +# Pick a specific version. +go get example.com/some/module/pkg@vX.Y.Z +``` + +Tidy up the `go.mod` and `go.sum` files: + +```bash +go mod tidy +go mod vendor +git add go.mod go.sum vendor +git commit +``` + +You have to commit the changes to `go.mod` and `go.sum` before submitting the pull request. + +# Support + +You can also donate or become a patreon, which helps out covering server costs and potentially make it possible to put out bounties: + +* **Support on [Open Collective](https://opencollective.com/go-admin)** +* Donate via [PayPal](https://paypal.me/cg80333) + +# Members + +If you are a member of the official GoAdmin developer Team: + +* [Discussions](http://forum.go-admin.cn) +* [Tasks](https://github.com/GoAdminGroup/go-admin/projects) +* [Chat](https://t.me/joinchat/NlyH6Bch2QARZkArithKvg) \ No newline at end of file diff --git a/CONTRIBUTING_CN.md b/CONTRIBUTING_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..a2ef93bf2e10311daffa80b2fdbcad5fea37d9a9 --- /dev/null +++ b/CONTRIBUTING_CN.md @@ -0,0 +1,84 @@ +# 贡献 + +如果你想要对项目作出贡献,却不知道怎么做,下面有一些帮助: + +* 翻译 + * README.md + * [docs](https://github.com/GoAdminGroup/docs/issues/1) +* 寻找BUG +* 寻找安全问题 +* 主题和插件 + +在这里:[功能规划](https://github.com/GoAdminGroup/go-admin/projects/3) 可以获得更多信息。 + +你也可以看一下所有开放的issues,从这里去入手: + +https://github.com/search?q=org%3AGoAdminGroup+is%3Aopen+is%3Aissue+archived%3Afalse&type=Issues + +## 如何做贡献 + +GoAdmin 使用 GitHub 来管理项目代码: + +- 如果你发现一些微不足道的fix或者功能增加,直接提pr即可; +- 如果你有一些提议,那么你可以先开一个issue进行讨论; + +然后,你需要fork远程的master分支到你本地 **\$GOPATH**/src/github.com/GoAdminGroup/go-admin : + +```bash + +$ go get github.com/GoAdminGroup/go-admin +$ cd $GOPATH/src/github.com/GoAdminGroup/go-admin # GOPATH is $HOME/go by default. + +$ git remote add +``` + +在你提交代码之前,记得执行下面这个命令: + +``` +make test +``` + +看根目录下的```Makefile```获得更多信息。 + +注意了: `go get` 返回 `package github.com/GoAdminGroup/go-admin: no Go files in /go/src/github.com/GoAdminGroup/go-admin` 是正常的。 + +### 依赖管理 + +我们使用 [Go modules](https://golang.org/cmd/go/#hdr-Modules__module_versions__and_more) 来管理依赖。 +这需要 golang 版本大于1.11,以及安装了 git + +要增加或更新依赖,就使用 `go get` 命令: + +```bash +# Pick the latest tagged release. +go get example.com/some/module/pkg + +# Pick a specific version. +go get example.com/some/module/pkg@vX.Y.Z +``` + +整理好 `go.mod` 和 `go.sum`: + +```bash +go mod tidy +go mod vendor +git add go.mod go.sum vendor +git commit +``` + +直接提交 `go.mod` and `go.sum` 的修改。 + +# 赞助 + +你可以捐助或参与众筹来帮助我们维护服务器费用,以及提供一些奖金资助项目发展。 + +* **Support on [Open Collective](https://opencollective.com/go-admin)** +* Donate via [PayPal](https://paypal.me/cg80333) + +# 成员 + +如果你已经是GoAdmin的官方开发组成员: + +* [Discussions](http://forum.go-admin.cn) +* [Tasks](https://github.com/GoAdminGroup/go-admin/projects) +* [Chat](https://t.me/joinchat/NlyH6Bch2QARZkArithKvg) \ No newline at end of file diff --git a/DONATION.md b/DONATION.md new file mode 100644 index 0000000000000000000000000000000000000000..51509e20e285174f08d93a3f9b0a271d322f7aa9 --- /dev/null +++ b/DONATION.md @@ -0,0 +1 @@ +# Donation List 捐赠名单(排名不分先后) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6fb2b513d3dbabc0671889726e3203d03ae93213 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# This file describes the standard way to build GoAdmin develop env image, and using container +# +# Usage: +# +# # Assemble the code dev environment, get related database tools in docker-compose.yml, It is slow the first time. +# docker build -t goadmin:1.0 . +# +# # Mount your source code to container for quick developing: +# docker run -v `pwd`:/home/goadmin --name -d goadmin:1.0 +# docker exec -it goadmin /bin/bash +# # if your local code has been changed ,you can restart the container to take effect +# docker restart goadmin +# + +FROM golang:latest +MAINTAINER josingcjx +COPY . /home/goadmin +ENV GOPATH=$GOPATH:/home/goadmin/ GOPROXY=https://mirrors.aliyun.com/goproxy,https://goproxy.cn,direct +RUN apt-get update --fix-missing && \ + apt-get install -y zip vim postgresql mysql-common default-mysql-server && \ + tar -C / -xvf /home/goadmin/tools/godependacy.tgz + #if install dependacy tools failed, you can copy local's to remote + #mkdir -p /go/bin && \ + #mv /home/goadmin/tools/{gotest,goimports,golint,golangci-lint,adm} /go/bin + #go get golang.org/x/tools/cmd/goimports && \ + #go get github.com/rakyll/gotest && \ + #go get -u golang.org/x/lint/golint && \ + #go install github.com/GoAdminGroup/adm@latest && \ + #go get -u github.com/golangci/golangci-lint/cmd/golangci-lint +WORKDIR /home/goadmin \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..58b0d54e79f9c77b3070c42976c7617bb834a976 --- /dev/null +++ b/Makefile @@ -0,0 +1,130 @@ +GOCMD = go +GOBUILD = $(GOCMD) build + +TEST_CONFIG_PATH=./../../common/config.json +TEST_CONFIG_PQ_PATH=./../../common/config_pg.json +TEST_CONFIG_SQLITE_PATH=./../../common/config_sqlite.json +TEST_CONFIG_MS_PATH=./../../common/config_ms.json +TEST_FRAMEWORK_DIR=./tests/frameworks + +## database configs +MYSQL_HOST = db_mysql +MYSQL_PORT = 3306 +MYSQL_USER = root +MYSQL_PWD = root + +POSTGRESSQL_HOST = db_pgsql +POSTGRESSQL_PORT = 5432 +POSTGRESSQL_USER = postgres +POSTGRESSQL_PWD = root + +TEST_DB = go-admin-test + +all: test + +## tests + +test: cp-mod black-box-test web-test restore-mod + +## tests: black box tests + +black-box-test: mysql-test pg-test sqlite-test ms-test + +mysql-test: $(TEST_FRAMEWORK_DIR)/* + go get github.com/ugorji/go/codec@none + for file in $^ ; do \ + make import-mysql ; \ + go test -mod=mod -gcflags=all=-l -v ./$${file}/... -args $(TEST_CONFIG_PATH) ; \ + done + +sqlite-test: $(TEST_FRAMEWORK_DIR)/* + for file in $^ ; do \ + make import-sqlite ; \ + go test -mod=mod -gcflags=all=-l ./$${file}/... -args $(TEST_CONFIG_SQLITE_PATH) ; \ + done + +pg-test: $(TEST_FRAMEWORK_DIR)/* + for file in $^ ; do \ + make import-postgresql ; \ + go test -mod=mod -gcflags=all=-l ./$${file}/... -args $(TEST_CONFIG_PQ_PATH) ; \ + done + +ms-test: $(TEST_FRAMEWORK_DIR)/* + for file in $^ ; do \ + make import-mssql ; \ + go test -mod=mod -gcflags=all=-l ./$${file}/... -args $(TEST_CONFIG_MS_PATH) ; \ + done + +## tests: user acceptance tests + +web-test: import-mysql + go test -mod=mod ./tests/web/... + rm -rf ./tests/web/User* + +web-test-debug: import-mysql + go test -mod=mod ./tests/web/... -args true + +## tests: unit tests + +unit-test: + go test -mod=mod ./adm/... + go test -mod=mod ./context/... + go test -mod=mod ./modules/... + go test -mod=mod ./plugins/admin/controller/... + go test -mod=mod ./plugins/admin/modules/parameter/... + go test -mod=mod ./plugins/admin/modules/table/... + go test -mod=mod ./plugins/admin/modules/... + +## tests: helpers + +import-sqlite: + rm -rf ./tests/common/admin.db + cp ./tests/data/admin.db ./tests/common/admin.db + +import-mysql: + mysql -h$(MYSQL_HOST) -P${MYSQL_PORT} -u${MYSQL_USER} -p${MYSQL_PWD} -e "create database if not exists \`${TEST_DB}\`" + mysql -h$(MYSQL_HOST) -P${MYSQL_PORT} -u${MYSQL_USER} -p${MYSQL_PWD} ${TEST_DB} < ./tests/data/admin.sql + +import-postgresql: + PGPASSWORD=${POSTGRESSQL_PWD} dropdb -h ${POSTGRESSQL_HOST} -p ${POSTGRESSQL_PORT} -U ${POSTGRESSQL_USER} ${TEST_DB} + PGPASSWORD=${POSTGRESSQL_PWD} createdb -h ${POSTGRESSQL_HOST} -p ${POSTGRESSQL_PORT} -U ${POSTGRESSQL_USER} ${TEST_DB} + PGPASSWORD=${POSTGRESSQL_PWD} psql -h ${POSTGRESSQL_HOST} -p ${POSTGRESSQL_PORT} -d ${TEST_DB} -U ${POSTGRESSQL_USER} -f ./tests/data/admin_pg.sql + +import-mssql: + /opt/mssql-tools/bin/sqlcmd -S db_mssql -U SA -P Aa123456 -Q "RESTORE DATABASE [goadmin] FROM DISK = N'/home/data/admin_ms.bak' WITH FILE = 1, NOUNLOAD, REPLACE, RECOVERY, STATS = 5" + +backup-mssql: + docker exec mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P Aa123456 -Q "BACKUP DATABASE [goadmin] TO DISK = N'/home/data/admin_ms.bak' WITH NOFORMAT, NOINIT, NAME = 'goadmin-full', SKIP, NOREWIND, NOUNLOAD, STATS = 10" + +cp-mod: + cp go.mod go.mod.old + cp go.sum go.sum.old + +restore-mod: + mv go.mod.old go.mod + mv go.sum.old go.sum + +## code style check + +lint: fmt golint govet cilint + +fmt: + GO111MODULE=off go fmt ./... + GO111MODULE=off goimports -l -w . + +govet: + GO111MODULE=off go vet ./... + +cilint: + GO111MODULE=off golangci-lint run + +golint: + GO111MODULE=off golint ./... + +build-tmpl: + ## form tmpl build + adm compile tpl --src ./template/types/tmpls/ --dist ./template/types/tmpl.go --package types --var tmpls + ## generator tmpl build + adm compile tpl --src ./plugins/admin/modules/table/tmpl --dist ./plugins/admin/modules/table/tmpl.go --package table --var tmpls + +.PHONY: all fmt golint govet cp-mod restore-mod test black-box-test mysql-test sqlite-test import-sqlite import-mysql import-postgresql pg-test lint cilint cli diff --git a/README.md b/README.md index 97348b277de35562e1ed7e0b5d785f6ece33e2f5..ef18ee09e5aab9fef4453eb5f24437cc41c7c1f2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,88 @@ ---- -title: Admin -emoji: 👁 -colorFrom: green -colorTo: red -sdk: docker -pinned: false ---- - -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +

+ + go-admin + +

+ +

+ the missing golang data admin panel builder tool. +

+ +

+ Documentation | + 中文文档 | + 中文介绍 | + DEMO | + 中文DEMO | + Twitter | + Forum +

+ +

+ Build Status + Go Report Card + golang + discord + telegram + license +

+ +

+ Inspired by laravel-admin +

+ +## Preface + +GoAdmin is a toolkit to help you build a data visualization admin panel for your golang app. + +Online demo: [https://demo.go-admin.com](https://demo.go-admin.com) + +![interface](http://file.go-admin.cn/introduction/interface_en_3.png) + +## Features + +- 🚀 **Fast**: build a production admin panel app in **ten** minutes. +- 🎨 **Theming**: beautiful ui themes supported(default adminlte, more themes are coming.) +- 🔢 **Plugins**: many plugins to use(more useful and powerful plugins are coming.) +- ✅ **Rbac**: out of box rbac auth system. +- ⚙️ **Frameworks**: support most of the go web frameworks. + +## Translation +We need your help: [https://github.com/GoAdminGroup/docs/issues/1](https://github.com/GoAdminGroup/docs/issues/1) + +## Who is using + +[Comment the issue to tell us](https://github.com/GoAdminGroup/go-admin/issues/71). + +## How to + +Following three steps to run it. + +```shell +$ mkdir new_project && cd new_project +$ go install github.com/GoAdminGroup/adm@latest +$ adm init web +``` + +## Example + +Quick follow up example: + +- [pure golang](https://github.com/GoAdminGroup/example), simple and less dependency +- [golang with frontend template](https://github.com/GoAdminGroup/example_with_frontend), change template by yourself +- [golang with vue](https://github.com/GoAdminGroup/example_with_vue), if you have vue experience + +See the [docs](https://book.go-admin.cn) for more details. + +## Backers + + Your support will help me do better! [[Become a backer](https://opencollective.com/go-admin#backer)] + + +## Contribution + +[here for contribution guide](CONTRIBUTING.md) + +here to join into the develop team + +[join telegram](https://t.me/joinchat/NlyH6Bch2QARZkArithKvg) diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..a33c0a8cf841f0199481aa733e4f3f36793449de --- /dev/null +++ b/README_CN.md @@ -0,0 +1,83 @@ +

+ + go-admin + +

+

+ 遗失的Golang编写的数据可视化与管理平台构建框架 +

+

+ Build Status + Go Report Card + golang + discord + telegram + license +

+

+ 由laravel-admin启发 +

+ +## 前言 + +GoAdmin 可以帮助你的golang应用快速实现数据可视化,搭建一个数据管理平台。 + +[文档](http://doc.go-admin.cn/zh) | [论坛](http://discuss.go-admin.com) | [Demo](https://demo.go-admin.cn) | [上手例子](https://github.com/GoAdminGroup/example/blob/master/README_CN.md) | [GoAdmin+vue 例子](https://github.com/GoAdminGroup/goadmin-vue-example) + + +![](http://file.go-admin.cn/introduction/interface_3.png) + +## 特征 + +- 🚀 **高生产效率**: 10分钟内做一个好看的管理后台 +- 🎨 **主题**: 默认为adminlte,更多好看的主题正在制作中,欢迎给我们留言 +- 🔢 **插件化**: 提供插件使用,真正实现一个插件解决不了问题,那就两个 +- ✅ **认证**: 开箱即用的rbac认证系统 +- ⚙️ **框架支持**: 支持大部分框架接入,让你更容易去上手和扩展 + +## 例子 + +- [纯golang](https://github.com/GoAdminGroup/example), 简单很少依赖 +- [golang + 前端模版](https://github.com/GoAdminGroup/example_with_frontend), 你可以自己修改模版 +- [golang + vue](https://github.com/GoAdminGroup/example_with_vue), 如果你会vue的话,不妨试试 + +## 翻译 +我们需要您的帮忙: [https://github.com/GoAdminGroup/docs/issues/1](https://github.com/GoAdminGroup/docs/issues/1) + +## 谁在使用GoAdmin + +[评论这个issue告诉我们](https://github.com/GoAdminGroup/go-admin/issues/71). + +## 使用 + +```shell +$ go install github.com/GoAdminGroup/adm@latest +$ mkdir new_project && cd new_project +$ adm init web -l cn +``` + +运行后将会启动一个网页安装程序,根据程序内容填写安装运行即可。 + +## 贡献 + +[这里有一份贡献指南](CONTRIBUTING_CN.md) + +非常欢迎提pr,这里可以加入开发小组 + +QQ群:[874825430](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=f1LB6fiIKDi8hwR__CjB70pqylsbQsef&authKey=PsKZe803zC1VvhhFg10zbVBTAOzGbqMSU3%2BrA8mMwK3fhasaoilz9dFj7f%2FZyku6&noverify=0&group_code=874825430),记得备注加群来意 + +这里是[开发计划](https://github.com/GoAdminGroup/go-admin/projects) + +[点击这里申请加微信群(记得备注加群)](http://quick.go-admin.cn/resource/wechat_qrcode_02.jpg) + +注:在社区中如有问题提问,请务必清晰描述,包括但不限于问题详叙/问题代码/复现方法/已经尝试过的方法。 + +## 十分感谢 + +inspired by [laravel-admin](https://github.com/z-song/laravel-admin) + +## 打赏 + +留下您的github/gitee用户名,我们将会展示在[捐赠名单](DONATION.md)中。 + + \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000000000000000000000000000000..e7c9ad5951ce3e4f2fb6ce814c3dd58f103ed10b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security issues to `chg80333@gmail.com` \ No newline at end of file diff --git a/adapter/adapter.go b/adapter/adapter.go new file mode 100644 index 0000000000000000000000000000000000000000..b69fcbc2ed13485a79ed33bdabce8c9f0dd65635 --- /dev/null +++ b/adapter/adapter.go @@ -0,0 +1,196 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package adapter + +import ( + "bytes" + "fmt" + "net/http" + "net/url" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/constant" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/menu" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" +) + +// WebFrameWork is an interface which is used as an adapter of +// framework and goAdmin. It must implement two methods. Use registers +// the routes and the corresponding handlers. Content writes the +// response to the corresponding context of framework. +type WebFrameWork interface { + // Name return the web framework name. + Name() string + + // Use method inject the plugins to the web framework engine which is the + // first parameter. + Use(app interface{}, plugins []plugins.Plugin) error + + // Content add the panel html response of the given callback function to + // the web framework context which is the first parameter. + Content(ctx interface{}, fn types.GetPanelFn, fn2 context.NodeProcessor, navButtons ...types.Button) + + // User get the auth user model from the given web framework context. + User(ctx interface{}) (models.UserModel, bool) + + // AddHandler inject the route and handlers of GoAdmin to the web framework. + AddHandler(method, path string, handlers context.Handlers) + + DisableLog() + + Static(prefix, path string) + + Run() error + + // Helper functions + // ================================ + + SetApp(app interface{}) error + SetConnection(db.Connection) + GetConnection() db.Connection + SetContext(ctx interface{}) WebFrameWork + GetCookie() (string, error) + Lang() string + Path() string + Method() string + Request() *http.Request + FormParam() url.Values + Query() url.Values + IsPjax() bool + Redirect() + SetContentType() + Write(body []byte) + CookieKey() string + HTMLContentType() string +} + +// BaseAdapter is a base adapter contains some helper functions. +type BaseAdapter struct { + db db.Connection +} + +// SetConnection set the db connection. +func (base *BaseAdapter) SetConnection(conn db.Connection) { + base.db = conn +} + +// GetConnection get the db connection. +func (base *BaseAdapter) GetConnection() db.Connection { + return base.db +} + +// HTMLContentType return the default content type header. +func (*BaseAdapter) HTMLContentType() string { + return "text/html; charset=utf-8" +} + +// CookieKey return the cookie key. +func (*BaseAdapter) CookieKey() string { + return auth.DefaultCookieKey +} + +// GetUser is a helper function get the auth user model from the context. +func (*BaseAdapter) GetUser(ctx interface{}, wf WebFrameWork) (models.UserModel, bool) { + cookie, err := wf.SetContext(ctx).GetCookie() + + if err != nil { + return models.UserModel{}, false + } + + user, exist := auth.GetCurUser(cookie, wf.GetConnection()) + return user.ReleaseConn(), exist +} + +// GetUse is a helper function adds the plugins to the framework. +func (*BaseAdapter) GetUse(app interface{}, plugin []plugins.Plugin, wf WebFrameWork) error { + if err := wf.SetApp(app); err != nil { + return err + } + + for _, plug := range plugin { + for path, handlers := range plug.GetHandler() { + if plug.Prefix() == "" { + wf.AddHandler(path.Method, path.URL, handlers) + } else { + wf.AddHandler(path.Method, config.Url("/"+plug.Prefix()+path.URL), handlers) + } + } + } + + return nil +} + +func (*BaseAdapter) Run() error { panic("not implement") } +func (*BaseAdapter) DisableLog() { panic("not implement") } +func (*BaseAdapter) Static(_, _ string) { panic("not implement") } + +// GetContent is a helper function of adapter.Content +func (base *BaseAdapter) GetContent(ctx interface{}, getPanelFn types.GetPanelFn, wf WebFrameWork, + navButtons types.Buttons, fn context.NodeProcessor) { + + var ( + newBase = wf.SetContext(ctx) + cookie, hasError = newBase.GetCookie() + ) + + if hasError != nil || cookie == "" { + newBase.Redirect() + return + } + + user, authSuccess := auth.GetCurUser(cookie, wf.GetConnection()) + + if !authSuccess { + newBase.Redirect() + return + } + + var ( + panel types.Panel + err error + ) + + gctx := context.NewContext(newBase.Request()) + + if !auth.CheckPermissions(user, newBase.Path(), newBase.Method(), newBase.FormParam()) { + panel = template.WarningPanel(gctx, errors.NoPermission, template.NoPermission403Page) + } else { + panel, err = getPanelFn(ctx) + if err != nil { + panel = template.WarningPanel(gctx, err.Error()) + } + } + + fn(panel.Callbacks...) + + tmpl, tmplName := template.Default(gctx).GetTemplate(newBase.IsPjax()) + + buf := new(bytes.Buffer) + hasError = tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(gctx, &types.NewPageParam{ + User: user, + Menu: menu.GetGlobalMenu(user, wf.GetConnection(), newBase.Lang()).SetActiveClass(config.URLRemovePrefix(newBase.Path())), + Panel: panel.GetContent(config.IsProductionEnvironment()), + Assets: template.GetComponentAssetImportHTML(gctx), + Buttons: navButtons.CheckPermission(user), + TmplHeadHTML: template.Default(gctx).GetHeadHTML(), + TmplFootJS: template.Default(gctx).GetFootJS(), + Iframe: newBase.Query().Get(constant.IframeKey) == "true", + })) + + if hasError != nil { + logger.Error(fmt.Sprintf("error: %s adapter content, ", newBase.Name()), hasError) + } + + newBase.SetContentType() + newBase.Write(buf.Bytes()) +} diff --git a/adapter/beego/beego.go b/adapter/beego/beego.go new file mode 100644 index 0000000000000000000000000000000000000000..68b0b3e55f228584c219452fac029b17cafc9b98 --- /dev/null +++ b/adapter/beego/beego.go @@ -0,0 +1,170 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package beego + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + gctx "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/astaxie/beego" + "github.com/astaxie/beego/context" +) + +// Beego structure value is a Beego GoAdmin adapter. +type Beego struct { + adapter.BaseAdapter + ctx *context.Context + app *beego.App +} + +func init() { + engine.Register(new(Beego)) +} + +// User implements the method Adapter.User. +func (bee *Beego) User(ctx interface{}) (models.UserModel, bool) { + return bee.GetUser(ctx, bee) +} + +// Use implements the method Adapter.Use. +func (bee *Beego) Use(app interface{}, plugs []plugins.Plugin) error { + return bee.GetUse(app, plugs, bee) +} + +// Content implements the method Adapter.Content. +func (bee *Beego) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn gctx.NodeProcessor, navButtons ...types.Button) { + bee.GetContent(ctx, getPanelFn, bee, navButtons, fn) +} + +type HandlerFunc func(ctx *context.Context) (types.Panel, error) + +func Content(handler HandlerFunc) beego.FilterFunc { + return func(ctx *context.Context) { + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(*context.Context)) + }) + } +} + +// SetApp implements the method Adapter.SetApp. +func (bee *Beego) SetApp(app interface{}) error { + var ( + eng *beego.App + ok bool + ) + if eng, ok = app.(*beego.App); !ok { + return errors.New("beego adapter SetApp: wrong parameter") + } + bee.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (bee *Beego) AddHandler(method, path string, handlers gctx.Handlers) { + bee.app.Handlers.AddMethod(method, path, func(c *context.Context) { + for key, value := range c.Input.Params() { + if c.Request.URL.RawQuery == "" { + c.Request.URL.RawQuery += strings.ReplaceAll(key, ":", "") + "=" + value + } else { + c.Request.URL.RawQuery += "&" + strings.ReplaceAll(key, ":", "") + "=" + value + } + } + ctx := gctx.NewContext(c.Request) + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.ResponseWriter.Header().Add(key, head[0]) + } + c.ResponseWriter.WriteHeader(ctx.Response.StatusCode) + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + c.WriteString(buf.String()) + } + }) +} + +// Name implements the method Adapter.Name. +func (*Beego) Name() string { + return "beego" +} + +// SetContext implements the method Adapter.SetContext. +func (*Beego) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx *context.Context + ok bool + ) + if ctx, ok = contextInterface.(*context.Context); !ok { + panic("beego adapter SetContext: wrong parameter") + } + return &Beego{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (bee *Beego) Redirect() { + bee.ctx.Redirect(http.StatusFound, config.Url(config.GetLoginUrl())) +} + +// SetContentType implements the method Adapter.SetContentType. +func (bee *Beego) SetContentType() { + bee.ctx.ResponseWriter.Header().Set("Content-Type", bee.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (bee *Beego) Write(body []byte) { + _, _ = bee.ctx.ResponseWriter.Write(body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (bee *Beego) GetCookie() (string, error) { + return bee.ctx.GetCookie(bee.CookieKey()), nil +} + +// Lang implements the method Adapter.Lang. +func (bee *Beego) Lang() string { + return bee.ctx.Request.URL.Query().Get("__ga_lang") +} + +// Path implements the method Adapter.Path. +func (bee *Beego) Path() string { + return bee.ctx.Request.URL.Path +} + +// Method implements the method Adapter.Method. +func (bee *Beego) Method() string { + return bee.ctx.Request.Method +} + +// FormParam implements the method Adapter.FormParam. +func (bee *Beego) FormParam() url.Values { + _ = bee.ctx.Request.ParseMultipartForm(32 << 20) + return bee.ctx.Request.PostForm +} + +// IsPjax implements the method Adapter.IsPjax. +func (bee *Beego) IsPjax() bool { + return bee.ctx.Request.Header.Get(constant.PjaxHeader) == "true" +} + +// Query implements the method Adapter.Query. +func (bee *Beego) Query() url.Values { + return bee.ctx.Request.URL.Query() +} + +// Request implements the method Adapter.Request. +func (bee *Beego) Request() *http.Request { + return bee.ctx.Request +} \ No newline at end of file diff --git a/adapter/beego2/beego2.go b/adapter/beego2/beego2.go new file mode 100644 index 0000000000000000000000000000000000000000..0674a2546a3ee6904874fccb8b9942c5087e9339 --- /dev/null +++ b/adapter/beego2/beego2.go @@ -0,0 +1,138 @@ +package beego2 + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + gctx "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/context" +) + +type Beego2 struct { + adapter.BaseAdapter + ctx *context.Context + app *web.HttpServer +} + +func init() { + engine.Register(new(Beego2)) +} + +func (*Beego2) Name() string { + return "beego2" +} + +func (bee2 *Beego2) Use(app interface{}, plugins []plugins.Plugin) error { + return bee2.GetUse(app, plugins, bee2) +} + +func (bee2 *Beego2) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn gctx.NodeProcessor, navButtons ...types.Button) { + bee2.GetContent(ctx, getPanelFn, bee2, navButtons, fn) +} + +func (bee2 *Beego2) User(ctx interface{}) (models.UserModel, bool) { + return bee2.GetUser(ctx, bee2) +} + +func (bee2 *Beego2) AddHandler(method, path string, handlers gctx.Handlers) { + bee2.app.Handlers.AddMethod(method, path, func(c *context.Context) { + for key, value := range c.Input.Params() { + if c.Request.URL.RawQuery == "" { + c.Request.URL.RawQuery += strings.ReplaceAll(key, ":", "") + "=" + value + } else { + c.Request.URL.RawQuery += "&" + strings.ReplaceAll(key, ":", "") + "=" + value + } + } + ctx := gctx.NewContext(c.Request) + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.ResponseWriter.Header().Add(key, head[0]) + } + c.ResponseWriter.WriteHeader(ctx.Response.StatusCode) + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + c.WriteString(buf.String()) + } + }) +} + +func (bee2 *Beego2) SetApp(app interface{}) error { + var ( + eng *web.HttpServer + ok bool + ) + if eng, ok = app.(*web.HttpServer); !ok { + return errors.New("beego2 adapter SetApp: wrong parameter") + } + bee2.app = eng + return nil +} + +func (*Beego2) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx *context.Context + ok bool + ) + if ctx, ok = contextInterface.(*context.Context); !ok { + panic("beego2 adapter SetContext: wrong parameter") + } + return &Beego2{ctx: ctx} +} + +func (bee2 *Beego2) GetCookie() (string, error) { + return bee2.ctx.GetCookie(bee2.CookieKey()), nil +} + +func (bee2 *Beego2) Lang() string { + return bee2.ctx.Request.URL.Query().Get("__ga_lang") +} + +func (bee2 *Beego2) Path() string { + return bee2.ctx.Request.URL.Path +} + +func (bee2 *Beego2) Method() string { + return bee2.ctx.Request.Method +} + +func (bee2 *Beego2) FormParam() url.Values { + _ = bee2.ctx.Request.ParseMultipartForm(32 << 20) + return bee2.ctx.Request.PostForm +} + +func (bee2 *Beego2) Query() url.Values { + return bee2.ctx.Request.URL.Query() +} + +func (bee2 *Beego2) IsPjax() bool { + return bee2.ctx.Request.Header.Get(constant.PjaxHeader) == "true" +} + +func (bee2 *Beego2) Redirect() { + bee2.ctx.Redirect(http.StatusFound, config.Url(config.GetLoginUrl())) +} + +func (bee2 *Beego2) SetContentType() { + bee2.ctx.ResponseWriter.Header().Set("Content-Type", bee2.HTMLContentType()) +} + +func (bee2 *Beego2) Write(body []byte) { + _, _ = bee2.ctx.ResponseWriter.Write(body) +} + +// Request implements the method Adapter.Request. +func (bee2 *Beego2) Request() *http.Request { + return bee2.ctx.Request +} \ No newline at end of file diff --git a/adapter/buffalo/buffalo.go b/adapter/buffalo/buffalo.go new file mode 100644 index 0000000000000000000000000000000000000000..a8b5eec774af25a146b0a0796158ff9646298810 --- /dev/null +++ b/adapter/buffalo/buffalo.go @@ -0,0 +1,214 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package buffalo + +import ( + "bytes" + "errors" + "net/http" + neturl "net/url" + "regexp" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/gobuffalo/buffalo" +) + +// Buffalo structure value is a Buffalo GoAdmin adapter. +type Buffalo struct { + adapter.BaseAdapter + ctx buffalo.Context + app *buffalo.App +} + +func init() { + engine.Register(new(Buffalo)) +} + +// User implements the method Adapter.User. +func (bu *Buffalo) User(ctx interface{}) (models.UserModel, bool) { + return bu.GetUser(ctx, bu) +} + +// Use implements the method Adapter.Use. +func (bu *Buffalo) Use(app interface{}, plugs []plugins.Plugin) error { + return bu.GetUse(app, plugs, bu) +} + +// Content implements the method Adapter.Content. +func (bu *Buffalo) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + bu.GetContent(ctx, getPanelFn, bu, btns, fn) +} + +type HandlerFunc func(ctx buffalo.Context) (types.Panel, error) + +func Content(handler HandlerFunc) buffalo.Handler { + return func(ctx buffalo.Context) error { + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(buffalo.Context)) + }) + return nil + } +} + +// SetApp implements the method Adapter.SetApp. +func (bu *Buffalo) SetApp(app interface{}) error { + var ( + eng *buffalo.App + ok bool + ) + if eng, ok = app.(*buffalo.App); !ok { + return errors.New("buffalo adapter SetApp: wrong parameter") + } + bu.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (bu *Buffalo) AddHandler(method, path string, handlers context.Handlers) { + url := path + reg1 := regexp.MustCompile(":(.*?)/") + reg2 := regexp.MustCompile(":(.*?)$") + url = reg1.ReplaceAllString(url, "{$1}/") + url = reg2.ReplaceAllString(url, "{$1}") + + getHandleFunc(bu.app, strings.ToUpper(method))(url, func(c buffalo.Context) error { + + if c.Request().URL.Path[len(c.Request().URL.Path)-1] == '/' { + c.Request().URL.Path = c.Request().URL.Path[:len(c.Request().URL.Path)-1] + } + + ctx := context.NewContext(c.Request()) + + params := c.Params().(neturl.Values) + + for key, param := range params { + if c.Request().URL.RawQuery == "" { + c.Request().URL.RawQuery += strings.ReplaceAll(key, ":", "") + "=" + param[0] + } else { + c.Request().URL.RawQuery += "&" + strings.ReplaceAll(key, ":", "") + "=" + param[0] + } + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.Response().Header().Set(key, head[0]) + } + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + c.Response().WriteHeader(ctx.Response.StatusCode) + _, _ = c.Response().Write(buf.Bytes()) + } else { + c.Response().WriteHeader(ctx.Response.StatusCode) + } + return nil + }) +} + +// HandleFun is type of route methods of buffalo. +type HandleFun func(p string, h buffalo.Handler) *buffalo.RouteInfo + +func getHandleFunc(eng *buffalo.App, method string) HandleFun { + switch method { + case "GET": + return eng.GET + case "POST": + return eng.POST + case "PUT": + return eng.PUT + case "DELETE": + return eng.DELETE + case "HEAD": + return eng.HEAD + case "OPTIONS": + return eng.OPTIONS + case "PATCH": + return eng.PATCH + default: + panic("wrong method") + } +} + +// Name implements the method Adapter.Name. +func (*Buffalo) Name() string { + return "buffalo" +} + +// SetContext implements the method Adapter.SetContext. +func (*Buffalo) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx buffalo.Context + ok bool + ) + if ctx, ok = contextInterface.(buffalo.Context); !ok { + panic("buffalo adapter SetContext: wrong parameter") + } + return &Buffalo{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (bu *Buffalo) Redirect() { + _ = bu.ctx.Redirect(http.StatusFound, config.Url(config.GetLoginUrl())) +} + +// SetContentType implements the method Adapter.SetContentType. +func (bu *Buffalo) SetContentType() { + bu.ctx.Response().Header().Set("Content-Type", bu.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (bu *Buffalo) Write(body []byte) { + bu.ctx.Response().WriteHeader(http.StatusOK) + _, _ = bu.ctx.Response().Write(body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (bu *Buffalo) GetCookie() (string, error) { + return bu.ctx.Cookies().Get(bu.CookieKey()) +} + +// Lang implements the method Adapter.Lang. +func (bu *Buffalo) Lang() string { + return bu.ctx.Request().URL.Query().Get("__ga_lang") +} + +// Path implements the method Adapter.Path. +func (bu *Buffalo) Path() string { + return bu.ctx.Request().URL.Path +} + +// Method implements the method Adapter.Method. +func (bu *Buffalo) Method() string { + return bu.ctx.Request().Method +} + +// FormParam implements the method Adapter.FormParam. +func (bu *Buffalo) FormParam() neturl.Values { + _ = bu.ctx.Request().ParseMultipartForm(32 << 20) + return bu.ctx.Request().PostForm +} + +// IsPjax implements the method Adapter.IsPjax. +func (bu *Buffalo) IsPjax() bool { + return bu.ctx.Request().Header.Get(constant.PjaxHeader) == "true" +} + +// Query implements the method Adapter.Query. +func (bu *Buffalo) Query() neturl.Values { + return bu.ctx.Request().URL.Query() +} + +// Request implements the method Adapter.Request. +func (bu *Buffalo) Request() *http.Request { + return bu.ctx.Request() +} \ No newline at end of file diff --git a/adapter/chi/chi.go b/adapter/chi/chi.go new file mode 100644 index 0000000000000000000000000000000000000000..901feae644ede75d7b80622bbe3964c74899f49d --- /dev/null +++ b/adapter/chi/chi.go @@ -0,0 +1,230 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package chi + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + cfg "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/go-chi/chi" +) + +// Chi structure value is a Chi GoAdmin adapter. +type Chi struct { + adapter.BaseAdapter + ctx Context + app *chi.Mux +} + +func init() { + engine.Register(new(Chi)) +} + +// User implements the method Adapter.User. +func (ch *Chi) User(ctx interface{}) (models.UserModel, bool) { + return ch.GetUser(ctx, ch) +} + +// Use implements the method Adapter.Use. +func (ch *Chi) Use(app interface{}, plugs []plugins.Plugin) error { + return ch.GetUse(app, plugs, ch) +} + +// Content implements the method Adapter.Content. +func (ch *Chi) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + ch.GetContent(ctx, getPanelFn, ch, btns, fn) +} + +type HandlerFunc func(ctx Context) (types.Panel, error) + +func Content(handler HandlerFunc) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + ctx := Context{ + Request: request, + Response: writer, + } + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(Context)) + }) + } +} + +// SetApp implements the method Adapter.SetApp. +func (ch *Chi) SetApp(app interface{}) error { + var ( + eng *chi.Mux + ok bool + ) + if eng, ok = app.(*chi.Mux); !ok { + return errors.New("chi adapter SetApp: wrong parameter") + } + ch.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (ch *Chi) AddHandler(method, path string, handlers context.Handlers) { + url := path + reg1 := regexp.MustCompile(":(.*?)/") + reg2 := regexp.MustCompile(":(.*?)$") + url = reg1.ReplaceAllString(url, "{$1}/") + url = reg2.ReplaceAllString(url, "{$1}") + + if len(url) > 1 && url[0] == '/' && url[1] == '/' { + url = url[1:] + } + + getHandleFunc(ch.app, strings.ToUpper(method))(url, func(w http.ResponseWriter, r *http.Request) { + + if r.URL.Path[len(r.URL.Path)-1] == '/' { + r.URL.Path = r.URL.Path[:len(r.URL.Path)-1] + } + + ctx := context.NewContext(r) + + params := chi.RouteContext(r.Context()).URLParams + + for i := 0; i < len(params.Values); i++ { + if r.URL.RawQuery == "" { + r.URL.RawQuery += strings.ReplaceAll(params.Keys[i], ":", "") + "=" + params.Values[i] + } else { + r.URL.RawQuery += "&" + strings.ReplaceAll(params.Keys[i], ":", "") + "=" + params.Values[i] + } + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + w.Header().Set(key, head[0]) + } + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + w.WriteHeader(ctx.Response.StatusCode) + _, _ = w.Write(buf.Bytes()) + } else { + w.WriteHeader(ctx.Response.StatusCode) + } + }) +} + +// HandleFun is type of route methods of chi. +type HandleFun func(pattern string, handlerFn http.HandlerFunc) + +func getHandleFunc(eng *chi.Mux, method string) HandleFun { + switch method { + case "GET": + return eng.Get + case "POST": + return eng.Post + case "PUT": + return eng.Put + case "DELETE": + return eng.Delete + case "HEAD": + return eng.Head + case "OPTIONS": + return eng.Options + case "PATCH": + return eng.Patch + default: + panic("wrong method") + } +} + +// Context wraps the Request and Response object of Chi. +type Context struct { + Request *http.Request + Response http.ResponseWriter +} + +// SetContext implements the method Adapter.SetContext. +func (*Chi) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx Context + ok bool + ) + if ctx, ok = contextInterface.(Context); !ok { + panic("chi adapter SetContext: wrong parameter") + } + return &Chi{ctx: ctx} +} + +// Name implements the method Adapter.Name. +func (*Chi) Name() string { + return "chi" +} + +// Redirect implements the method Adapter.Redirect. +func (ch *Chi) Redirect() { + http.Redirect(ch.ctx.Response, ch.ctx.Request, cfg.Url(cfg.GetLoginUrl()), http.StatusFound) +} + +// SetContentType implements the method Adapter.SetContentType. +func (ch *Chi) SetContentType() { + ch.ctx.Response.Header().Set("Content-Type", ch.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (ch *Chi) Write(body []byte) { + ch.ctx.Response.WriteHeader(http.StatusOK) + _, _ = ch.ctx.Response.Write(body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (ch *Chi) GetCookie() (string, error) { + cookie, err := ch.ctx.Request.Cookie(ch.CookieKey()) + if err != nil { + return "", err + } + return cookie.Value, err +} + +// Lang implements the method Adapter.Lang. +func (ch *Chi) Lang() string { + return ch.ctx.Request.URL.Query().Get("__ga_lang") +} + +// Path implements the method Adapter.Path. +func (ch *Chi) Path() string { + return ch.ctx.Request.URL.Path +} + +// Method implements the method Adapter.Method. +func (ch *Chi) Method() string { + return ch.ctx.Request.Method +} + +// FormParam implements the method Adapter.FormParam. +func (ch *Chi) FormParam() url.Values { + _ = ch.ctx.Request.ParseMultipartForm(32 << 20) + return ch.ctx.Request.PostForm +} + +// IsPjax implements the method Adapter.IsPjax. +func (ch *Chi) IsPjax() bool { + return ch.ctx.Request.Header.Get(constant.PjaxHeader) == "true" +} + +// Query implements the method Adapter.Query. +func (ch *Chi) Query() url.Values { + return ch.ctx.Request.URL.Query() +} + +// Request implements the method Adapter.Request. +func (ch *Chi) Request() *http.Request { + return ch.ctx.Request +} \ No newline at end of file diff --git a/adapter/echo/echo.go b/adapter/echo/echo.go new file mode 100644 index 0000000000000000000000000000000000000000..3778fe68760397748d24bb69a76c327acd3de894 --- /dev/null +++ b/adapter/echo/echo.go @@ -0,0 +1,179 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package echo + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/labstack/echo/v4" +) + +// Echo structure value is an Echo GoAdmin adapter. +type Echo struct { + adapter.BaseAdapter + ctx echo.Context + app *echo.Echo +} + +func init() { + engine.Register(new(Echo)) +} + +// User implements the method Adapter.User. +func (e *Echo) User(ctx interface{}) (models.UserModel, bool) { + return e.GetUser(ctx, e) +} + +// Use implements the method Adapter.Use. +func (e *Echo) Use(app interface{}, plugs []plugins.Plugin) error { + return e.GetUse(app, plugs, e) +} + +// Content implements the method Adapter.Content. +func (e *Echo) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + e.GetContent(ctx, getPanelFn, e, btns, fn) +} + +type HandlerFunc func(ctx echo.Context) (types.Panel, error) + +func Content(handler HandlerFunc) echo.HandlerFunc { + return func(ctx echo.Context) error { + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(echo.Context)) + }) + return nil + } +} + +// SetApp implements the method Adapter.SetApp. +func (e *Echo) SetApp(app interface{}) error { + var ( + eng *echo.Echo + ok bool + ) + if eng, ok = app.(*echo.Echo); !ok { + return errors.New("echo adapter SetApp: wrong parameter") + } + e.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (e *Echo) AddHandler(method, path string, handlers context.Handlers) { + e.app.Add(strings.ToUpper(method), path, func(c echo.Context) error { + ctx := context.NewContext(c.Request()) + + for _, key := range c.ParamNames() { + if c.Request().URL.RawQuery == "" { + c.Request().URL.RawQuery += strings.ReplaceAll(key, ":", "") + "=" + c.Param(key) + } else { + c.Request().URL.RawQuery += "&" + strings.ReplaceAll(key, ":", "") + "=" + c.Param(key) + } + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.Response().Header().Set(key, head[0]) + } + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + _ = c.String(ctx.Response.StatusCode, buf.String()) + } else { + c.Response().WriteHeader(ctx.Response.StatusCode) + } + return nil + }) +} + +// Name implements the method Adapter.Name. +func (*Echo) Name() string { + return "echo" +} + +// SetContext implements the method Adapter.SetContext. +func (*Echo) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx echo.Context + ok bool + ) + if ctx, ok = contextInterface.(echo.Context); !ok { + panic("echo adapter SetContext: wrong parameter") + } + return &Echo{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (e *Echo) Redirect() { + _ = e.ctx.Redirect(http.StatusFound, config.Url(config.GetLoginUrl())) +} + +// SetContentType implements the method Adapter.SetContentType. +func (e *Echo) SetContentType() { + e.ctx.Response().Header().Set("Content-Type", e.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (e *Echo) Write(body []byte) { + e.ctx.Response().WriteHeader(http.StatusOK) + _, _ = e.ctx.Response().Write(body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (e *Echo) GetCookie() (string, error) { + cookie, err := e.ctx.Cookie(e.CookieKey()) + if err != nil { + return "", err + } + return cookie.Value, err +} + +// Lang implements the method Adapter.Lang. +func (e *Echo) Lang() string { + return e.ctx.Request().URL.Query().Get("__ga_lang") +} + +// Path implements the method Adapter.Path. +func (e *Echo) Path() string { + return e.ctx.Request().URL.Path +} + +// Method implements the method Adapter.Method. +func (e *Echo) Method() string { + return e.ctx.Request().Method +} + +// FormParam implements the method Adapter.FormParam. +func (e *Echo) FormParam() url.Values { + _ = e.ctx.Request().ParseMultipartForm(32 << 20) + return e.ctx.Request().PostForm +} + +// IsPjax implements the method Adapter.IsPjax. +func (e *Echo) IsPjax() bool { + return e.ctx.Request().Header.Get(constant.PjaxHeader) == "true" +} + +// Query implements the method Adapter.Query. +func (e *Echo) Query() url.Values { + return e.ctx.Request().URL.Query() +} + +// Request implements the method Adapter.Request. +func (e *Echo) Request() *http.Request { + return e.ctx.Request() +} \ No newline at end of file diff --git a/adapter/fasthttp/fasthttp.go b/adapter/fasthttp/fasthttp.go new file mode 100644 index 0000000000000000000000000000000000000000..2d59eee5317114682e7ebeaec5151e6d9340843d --- /dev/null +++ b/adapter/fasthttp/fasthttp.go @@ -0,0 +1,246 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package fasthttp + +import ( + "bytes" + "errors" + "io" + "net/http" + "net/url" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/buaazp/fasthttprouter" + "github.com/valyala/fasthttp" +) + +// Fasthttp structure value is a Fasthttp GoAdmin adapter. +type Fasthttp struct { + adapter.BaseAdapter + ctx *fasthttp.RequestCtx + app *fasthttprouter.Router +} + +func init() { + engine.Register(new(Fasthttp)) +} + +// User implements the method Adapter.User. +func (fast *Fasthttp) User(ctx interface{}) (models.UserModel, bool) { + return fast.GetUser(ctx, fast) +} + +// Use implements the method Adapter.Use. +func (fast *Fasthttp) Use(app interface{}, plugs []plugins.Plugin) error { + return fast.GetUse(app, plugs, fast) +} + +// Content implements the method Adapter.Content. +func (fast *Fasthttp) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + fast.GetContent(ctx, getPanelFn, fast, btns, fn) +} + +type HandlerFunc func(ctx *fasthttp.RequestCtx) (types.Panel, error) + +func Content(handler HandlerFunc) fasthttp.RequestHandler { + return func(ctx *fasthttp.RequestCtx) { + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(*fasthttp.RequestCtx)) + }) + } +} + +// SetApp implements the method Adapter.SetApp. +func (fast *Fasthttp) SetApp(app interface{}) error { + var ( + eng *fasthttprouter.Router + ok bool + ) + if eng, ok = app.(*fasthttprouter.Router); !ok { + return errors.New("fasthttp adapter SetApp: wrong parameter") + } + + fast.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (fast *Fasthttp) AddHandler(method, path string, handlers context.Handlers) { + fast.app.Handle(strings.ToUpper(method), path, func(c *fasthttp.RequestCtx) { + httpreq := convertCtx(c) + ctx := context.NewContext(httpreq) + + var params = make(map[string]string) + c.VisitUserValues(func(i []byte, i2 interface{}) { + if value, ok := i2.(string); ok { + params[string(i)] = value + } + }) + + for key, value := range params { + if httpreq.URL.RawQuery == "" { + httpreq.URL.RawQuery += strings.ReplaceAll(key, ":", "") + "=" + value + } else { + httpreq.URL.RawQuery += "&" + strings.ReplaceAll(key, ":", "") + "=" + value + } + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.Response.Header.Set(key, head[0]) + } + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + _, _ = c.WriteString(buf.String()) + } + c.Response.SetStatusCode(ctx.Response.StatusCode) + }) +} + +func convertCtx(ctx *fasthttp.RequestCtx) *http.Request { + var r http.Request + + body := ctx.PostBody() + r.Method = string(ctx.Method()) + r.Proto = "HTTP/1.1" + r.ProtoMajor = 1 + r.ProtoMinor = 1 + r.RequestURI = string(ctx.RequestURI()) + r.ContentLength = int64(len(body)) + r.Host = string(ctx.Host()) + r.RemoteAddr = ctx.RemoteAddr().String() + + hdr := make(http.Header) + ctx.Request.Header.VisitAll(func(k, v []byte) { + sk := string(k) + sv := string(v) + switch sk { + case "Transfer-Encoding": + r.TransferEncoding = append(r.TransferEncoding, sv) + default: + hdr.Set(sk, sv) + } + }) + r.Header = hdr + r.Body = &netHTTPBody{body} + rURL, err := url.ParseRequestURI(r.RequestURI) + if err != nil { + ctx.Logger().Printf("cannot parse requestURI %q: %s", r.RequestURI, err) + ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError) + return &r + } + r.URL = rURL + return &r +} + +type netHTTPBody struct { + b []byte +} + +func (r *netHTTPBody) Read(p []byte) (int, error) { + if len(r.b) == 0 { + return 0, io.EOF + } + n := copy(p, r.b) + r.b = r.b[n:] + return n, nil +} + +func (r *netHTTPBody) Close() error { + r.b = r.b[:0] + return nil +} + +// Name implements the method Adapter.Name. +func (*Fasthttp) Name() string { + return "fasthttp" +} + +// SetContext implements the method Adapter.SetContext. +func (*Fasthttp) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx *fasthttp.RequestCtx + ok bool + ) + if ctx, ok = contextInterface.(*fasthttp.RequestCtx); !ok { + panic("fasthttp adapter SetContext: wrong parameter") + } + return &Fasthttp{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (fast *Fasthttp) Redirect() { + fast.ctx.Redirect(config.Url(config.GetLoginUrl()), http.StatusFound) +} + +// SetContentType implements the method Adapter.SetContentType. +func (fast *Fasthttp) SetContentType() { + fast.ctx.Response.Header.Set("Content-Type", fast.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (fast *Fasthttp) Write(body []byte) { + _, _ = fast.ctx.Write(body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (fast *Fasthttp) GetCookie() (string, error) { + return string(fast.ctx.Request.Header.Cookie(fast.CookieKey())), nil +} + +// Lang implements the method Adapter.Lang. +func (fast *Fasthttp) Lang() string { + return string(fast.ctx.Request.URI().QueryArgs().Peek("__ga_lang")) +} + +// Path implements the method Adapter.Path. +func (fast *Fasthttp) Path() string { + return string(fast.ctx.Path()) +} + +// Method implements the method Adapter.Method. +func (fast *Fasthttp) Method() string { + return string(fast.ctx.Method()) +} + +// FormParam implements the method Adapter.FormParam. +func (fast *Fasthttp) FormParam() url.Values { + f, _ := fast.ctx.MultipartForm() + if f != nil { + return f.Value + } + return url.Values{} +} + +// IsPjax implements the method Adapter.IsPjax. +func (fast *Fasthttp) IsPjax() bool { + return string(fast.ctx.Request.Header.Peek(constant.PjaxHeader)) == "true" +} + +// Query implements the method Adapter.Query. +func (fast *Fasthttp) Query() url.Values { + queryStr := fast.ctx.URI().QueryString() + queryObj, err := url.Parse(string(queryStr)) + + if err != nil { + return url.Values{} + } + + return queryObj.Query() +} + +// Request implements the method Adapter.Request. +func (fast *Fasthttp) Request() *http.Request { + return convertCtx(fast.ctx) +} \ No newline at end of file diff --git a/adapter/gear/gear.go b/adapter/gear/gear.go new file mode 100644 index 0000000000000000000000000000000000000000..2aacd2c01a525c8b01fa0809ea8917444f50ff08 --- /dev/null +++ b/adapter/gear/gear.go @@ -0,0 +1,206 @@ +/*** +# File Name: ../../adapter/gear/gear.go +# Author: eavesmy +# Email: eavesmy@gmail.com +# Created Time: 2021年06月03日 星期四 19时05分06秒 +***/ + +package gear + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/teambition/gear" +) + +// Gear structure value is a Gin GoAdmin adapter. +type Gear struct { + adapter.BaseAdapter + ctx *gear.Context + app *gear.App + router *gear.Router +} + +func init() { + engine.Register(new(Gear)) +} + +// User implements the method Adapter.User. +func (gears *Gear) User(ctx interface{}) (models.UserModel, bool) { + return gears.GetUser(ctx, gears) +} + +// Use implements the method Adapter.Use. +func (gears *Gear) Use(app interface{}, plugs []plugins.Plugin) error { + return gears.GetUse(app, plugs, gears) +} + +// Content implements the method Adapter.Content. +func (gears *Gear) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + gears.GetContent(ctx, getPanelFn, gears, btns, fn) +} + +type HandlerFunc func(ctx *gear.Context) (types.Panel, error) + +func Content(handler HandlerFunc) gear.Middleware { + return func(ctx *gear.Context) error { + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(*gear.Context)) + }) + return nil + } +} + +// SetApp implements the method Adapter.SetApp. +func (gears *Gear) SetApp(app interface{}) error { + gears.app = app.(*gear.App) + gears.router = gear.NewRouter() + var ( + eng *gear.App + ok bool + ) + if eng, ok = app.(*gear.App); !ok { + return errors.New("beego adapter SetApp: wrong parameter") + } + gears.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (gears *Gear) AddHandler(method, path string, handlers context.Handlers) { + + if gears.router == nil { + gears.router = gear.NewRouter() + } + + gears.router.Handle(strings.ToUpper(method), path, func(c *gear.Context) error { + + ctx := context.NewContext(c.Req) + + newPath := path + + reg1 := regexp.MustCompile(":(.*?)/") + reg2 := regexp.MustCompile(":(.*?)$") + + params := reg1.FindAllString(newPath, -1) + newPath = reg1.ReplaceAllString(newPath, "") + params = append(params, reg2.FindAllString(newPath, -1)...) + + for _, param := range params { + p := utils.ReplaceAll(param, ":", "", "/", "") + + if c.Req.URL.RawQuery == "" { + c.Req.URL.RawQuery += p + "=" + c.Param(p) + } else { + c.Req.URL.RawQuery += "&" + p + "=" + c.Param(p) + } + } + + ctx.SetHandlers(handlers).Next() + + for key, head := range ctx.Response.Header { + c.Res.Header().Add(key, head[0]) + } + + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + + return c.End(ctx.Response.StatusCode, buf.Bytes()) + } + + c.Status(ctx.Response.StatusCode) + return nil + }) + + gears.app.UseHandler(gears.router) +} + +// Name implements the method Adapter.Name. +func (*Gear) Name() string { + return "gear" +} + +// SetContext implements the method Adapter.SetContext. +func (*Gear) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx *gear.Context + ok bool + ) + + if ctx, ok = contextInterface.(*gear.Context); !ok { + panic("gear adapter SetContext: wrong parameter") + } + + return &Gear{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (gears *Gear) Redirect() { + gears.ctx.Redirect(config.Url(config.GetLoginUrl())) +} + +// SetContentType implements the method Adapter.SetContentType. +func (gears *Gear) SetContentType() { + gears.ctx.Res.Header().Set("Content-Type", gears.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (gears *Gear) Write(body []byte) { + gears.ctx.End(http.StatusOK, body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (gears *Gear) GetCookie() (string, error) { + return gears.ctx.Cookies.Get(gears.CookieKey()) +} + +// Lang implements the method Adapter.Lang. +func (gears *Gear) Lang() string { + return gears.ctx.Req.URL.Query().Get("__ga_lang") +} + +// Path implements the method Adapter.Path. +func (gears *Gear) Path() string { + return gears.ctx.Req.URL.Path +} + +// Method implements the method Adapter.Method. +func (gears *Gear) Method() string { + return gears.ctx.Req.Method +} + +// FormParam implements the method Adapter.FormParam. +func (gears *Gear) FormParam() url.Values { + _ = gears.ctx.Req.ParseMultipartForm(32 << 20) + return gears.ctx.Req.PostForm +} + +// IsPjax implements the method Adapter.IsPjax. +func (gears *Gear) IsPjax() bool { + return gears.ctx.Req.Header.Get(constant.PjaxHeader) == "true" +} + +// Query implements the method Adapter.Query. +func (gears *Gear) Query() url.Values { + return gears.ctx.Req.URL.Query() +} + +// Request implements the method Adapter.Request. +func (gears *Gear) Request() *http.Request { + return gears.ctx.Req +} \ No newline at end of file diff --git a/adapter/gf/gf.go b/adapter/gf/gf.go new file mode 100644 index 0000000000000000000000000000000000000000..824a45f6f116da53eda26956959077d6f865c0ab --- /dev/null +++ b/adapter/gf/gf.go @@ -0,0 +1,186 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package gf + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/gogf/gf/net/ghttp" +) + +// Gf structure value is a Gf GoAdmin adapter. +type Gf struct { + adapter.BaseAdapter + ctx *ghttp.Request + app *ghttp.Server +} + +func init() { + engine.Register(new(Gf)) +} + +// User implements the method Adapter.User. +func (gf *Gf) User(ctx interface{}) (models.UserModel, bool) { + return gf.GetUser(ctx, gf) +} + +// Use implements the method Adapter.Use. +func (gf *Gf) Use(app interface{}, plugs []plugins.Plugin) error { + return gf.GetUse(app, plugs, gf) +} + +// Content implements the method Adapter.Content. +func (gf *Gf) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + gf.GetContent(ctx, getPanelFn, gf, btns, fn) +} + +type HandlerFunc func(ctx *ghttp.Request) (types.Panel, error) + +func Content(handler HandlerFunc) ghttp.HandlerFunc { + return func(ctx *ghttp.Request) { + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(*ghttp.Request)) + }) + } +} + +// SetApp implements the method Adapter.SetApp. +func (gf *Gf) SetApp(app interface{}) error { + var ( + eng *ghttp.Server + ok bool + ) + if eng, ok = app.(*ghttp.Server); !ok { + return errors.New("gf adapter SetApp: wrong parameter") + } + gf.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (gf *Gf) AddHandler(method, path string, handlers context.Handlers) { + gf.app.BindHandler(strings.ToUpper(method)+":"+path, func(c *ghttp.Request) { + ctx := context.NewContext(c.Request) + + newPath := path + + reg1 := regexp.MustCompile(":(.*?)/") + reg2 := regexp.MustCompile(":(.*?)$") + + params := reg1.FindAllString(newPath, -1) + newPath = reg1.ReplaceAllString(newPath, "") + params = append(params, reg2.FindAllString(newPath, -1)...) + + for _, param := range params { + p := utils.ReplaceAll(param, ":", "", "/", "") + + if c.Request.URL.RawQuery == "" { + c.Request.URL.RawQuery += p + "=" + c.GetRequestString(p) + } else { + c.Request.URL.RawQuery += "&" + p + "=" + c.GetRequestString(p) + } + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.Response.Header().Add(key, head[0]) + } + + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + c.Response.WriteStatus(ctx.Response.StatusCode, buf.Bytes()) + } else { + c.Response.WriteStatus(ctx.Response.StatusCode) + } + }) +} + +// Name implements the method Adapter.Name. +func (*Gf) Name() string { + return "gf" +} + +// SetContext implements the method Adapter.SetContext. +func (*Gf) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx *ghttp.Request + ok bool + ) + + if ctx, ok = contextInterface.(*ghttp.Request); !ok { + panic("gf adapter SetContext: wrong parameter") + } + return &Gf{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (gf *Gf) Redirect() { + gf.ctx.Response.RedirectTo(config.Url(config.GetLoginUrl())) +} + +// SetContentType implements the method Adapter.SetContentType. +func (gf *Gf) SetContentType() { + gf.ctx.Response.Header().Add("Content-Type", gf.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (gf *Gf) Write(body []byte) { + gf.ctx.Response.WriteStatus(http.StatusOK, body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (gf *Gf) GetCookie() (string, error) { + return gf.ctx.Cookie.Get(gf.CookieKey()), nil +} + +// Lang implements the method Adapter.Lang. +func (gf *Gf) Lang() string { + return gf.ctx.Request.URL.Query().Get("__ga_lang") +} + +// Path implements the method Adapter.Path. +func (gf *Gf) Path() string { + return gf.ctx.URL.Path +} + +// Method implements the method Adapter.Method. +func (gf *Gf) Method() string { + return gf.ctx.Method +} + +// FormParam implements the method Adapter.FormParam. +func (gf *Gf) FormParam() url.Values { + return gf.ctx.Form +} + +// IsPjax implements the method Adapter.IsPjax. +func (gf *Gf) IsPjax() bool { + return gf.ctx.Header.Get(constant.PjaxHeader) == "true" +} + +// Query implements the method Adapter.Query. +func (gf *Gf) Query() url.Values { + return gf.ctx.Request.URL.Query() +} + +// Request implements the method Adapter.Request. +func (gf *Gf) Request() *http.Request { + return gf.ctx.Request +} \ No newline at end of file diff --git a/adapter/gf2/gf2.go b/adapter/gf2/gf2.go new file mode 100644 index 0000000000000000000000000000000000000000..721f6bc950076ac49307cb24fbee99f7aeecf3f6 --- /dev/null +++ b/adapter/gf2/gf2.go @@ -0,0 +1,153 @@ +package gf2 + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/constant" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/gogf/gf/v2/net/ghttp" +) + +type GF2 struct { + adapter.BaseAdapter + ctx *ghttp.Request + app *ghttp.Server +} + +func init() { + engine.Register(new(GF2)) +} + +func (*GF2) Name() string { + return "gf2" +} + +func (gf2 *GF2) Use(app interface{}, plugins []plugins.Plugin) error { + return gf2.GetUse(app, plugins, gf2) +} + +func (gf2 *GF2) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + gf2.GetContent(ctx, getPanelFn, gf2, btns, fn) +} + +func (gf2 *GF2) User(ctx interface{}) (models.UserModel, bool) { + return gf2.GetUser(ctx, gf2) +} + +func (gf2 *GF2) AddHandler(method, path string, handlers context.Handlers) { + gf2.app.BindHandler(strings.ToUpper(method)+":"+path, func(c *ghttp.Request) { + ctx := context.NewContext(c.Request) + + newPath := path + + reg1 := regexp.MustCompile(":(.*?)/") + reg2 := regexp.MustCompile(":(.*?)$") + + params := reg1.FindAllString(newPath, -1) + newPath = reg1.ReplaceAllString(newPath, "") + params = append(params, reg2.FindAllString(newPath, -1)...) + + for _, param := range params { + p := utils.ReplaceAll(param, ":", "", "/", "") + + if c.Request.URL.RawQuery == "" { + c.Request.URL.RawQuery += p + "=" + c.GetRequest(p).String() + } else { + c.Request.URL.RawQuery += "&" + p + "=" + c.GetRequest(p).String() + } + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.Response.Header().Add(key, head[0]) + } + + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + c.Response.WriteStatus(ctx.Response.StatusCode, buf.Bytes()) + } else { + c.Response.WriteStatus(ctx.Response.StatusCode) + } + }) +} + +func (gf2 *GF2) SetApp(app interface{}) error { + var ( + eng *ghttp.Server + ok bool + ) + if eng, ok = app.(*ghttp.Server); !ok { + return errors.New("gf2 adapter SetApp: wrong parameter") + } + gf2.app = eng + return nil +} + +func (*GF2) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx *ghttp.Request + ok bool + ) + if ctx, ok = contextInterface.(*ghttp.Request); !ok { + panic("gf2 adapter SetContext: wrong parameter") + } + return &GF2{ctx: ctx} +} + +func (gf2 *GF2) GetCookie() (string, error) { + return gf2.ctx.Cookie.Get(gf2.CookieKey()).String(), nil +} + +func (gf2 *GF2) Lang() string { + return gf2.ctx.Request.URL.Query().Get("__ga_lang") +} + +func (gf2 *GF2) Path() string { + return gf2.ctx.URL.Path +} + +func (gf2 *GF2) Method() string { + return gf2.ctx.Method +} + +func (gf2 *GF2) FormParam() url.Values { + return gf2.ctx.Form +} + +func (gf2 *GF2) Query() url.Values { + return gf2.ctx.Request.URL.Query() +} + +func (gf2 *GF2) IsPjax() bool { + return gf2.ctx.Header.Get(constant.PjaxHeader) == "true" +} + +func (gf2 *GF2) Redirect() { + gf2.ctx.Response.RedirectTo(config.Url(config.GetLoginUrl())) +} + +func (gf2 *GF2) SetContentType() { + gf2.ctx.Response.Header().Add("Content-Type", gf2.HTMLContentType()) +} + +func (gf2 *GF2) Write(body []byte) { + gf2.ctx.Response.WriteStatus(http.StatusOK, body) +} + +// Request implements the method Adapter.Request. +func (gf2 *GF2) Request() *http.Request { + return gf2.ctx.Request +} \ No newline at end of file diff --git a/adapter/gin/gin.go b/adapter/gin/gin.go new file mode 100644 index 0000000000000000000000000000000000000000..3f26c4f51c7d5d80d286349c0e288ef2f1d095b6 --- /dev/null +++ b/adapter/gin/gin.go @@ -0,0 +1,173 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package gin + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/gin-gonic/gin" +) + +// Gin structure value is a Gin GoAdmin adapter. +type Gin struct { + adapter.BaseAdapter + ctx *gin.Context + app *gin.Engine +} + +func init() { + engine.Register(new(Gin)) +} + +// User implements the method Adapter.User. +func (gins *Gin) User(ctx interface{}) (models.UserModel, bool) { + return gins.GetUser(ctx, gins) +} + +// Use implements the method Adapter.Use. +func (gins *Gin) Use(app interface{}, plugs []plugins.Plugin) error { + return gins.GetUse(app, plugs, gins) +} + +// Content implements the method Adapter.Content. +func (gins *Gin) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + gins.GetContent(ctx, getPanelFn, gins, btns, fn) +} + +type HandlerFunc func(ctx *gin.Context) (types.Panel, error) + +func Content(handler HandlerFunc) gin.HandlerFunc { + return func(ctx *gin.Context) { + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(*gin.Context)) + }) + } +} + +// SetApp implements the method Adapter.SetApp. +func (gins *Gin) SetApp(app interface{}) error { + var ( + eng *gin.Engine + ok bool + ) + if eng, ok = app.(*gin.Engine); !ok { + return errors.New("gin adapter SetApp: wrong parameter") + } + gins.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (gins *Gin) AddHandler(method, path string, handlers context.Handlers) { + gins.app.Handle(strings.ToUpper(method), path, func(c *gin.Context) { + ctx := context.NewContext(c.Request) + + for _, param := range c.Params { + if c.Request.URL.RawQuery == "" { + c.Request.URL.RawQuery += strings.ReplaceAll(param.Key, ":", "") + "=" + param.Value + } else { + c.Request.URL.RawQuery += "&" + strings.ReplaceAll(param.Key, ":", "") + "=" + param.Value + } + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.Header(key, head[0]) + } + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + c.String(ctx.Response.StatusCode, buf.String()) + } else { + c.Status(ctx.Response.StatusCode) + } + }) +} + +// Name implements the method Adapter.Name. +func (*Gin) Name() string { + return "gin" +} + +// SetContext implements the method Adapter.SetContext. +func (*Gin) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx *gin.Context + ok bool + ) + + if ctx, ok = contextInterface.(*gin.Context); !ok { + panic("gin adapter SetContext: wrong parameter") + } + + return &Gin{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (gins *Gin) Redirect() { + gins.ctx.Redirect(http.StatusFound, config.Url(config.GetLoginUrl())) + gins.ctx.Abort() +} + +// SetContentType implements the method Adapter.SetContentType. +func (*Gin) SetContentType() {} + +// Write implements the method Adapter.Write. +func (gins *Gin) Write(body []byte) { + gins.ctx.Data(http.StatusOK, gins.HTMLContentType(), body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (gins *Gin) GetCookie() (string, error) { + return gins.ctx.Cookie(gins.CookieKey()) +} + +// Lang implements the method Adapter.Lang. +func (gins *Gin) Lang() string { + return gins.ctx.Request.URL.Query().Get("__ga_lang") +} + +// Path implements the method Adapter.Path. +func (gins *Gin) Path() string { + return gins.ctx.Request.URL.Path +} + +// Method implements the method Adapter.Method. +func (gins *Gin) Method() string { + return gins.ctx.Request.Method +} + +// FormParam implements the method Adapter.FormParam. +func (gins *Gin) FormParam() url.Values { + _ = gins.ctx.Request.ParseMultipartForm(32 << 20) + return gins.ctx.Request.PostForm +} + +// IsPjax implements the method Adapter.IsPjax. +func (gins *Gin) IsPjax() bool { + return gins.ctx.Request.Header.Get(constant.PjaxHeader) == "true" +} + +// Query implements the method Adapter.Query. +func (gins *Gin) Query() url.Values { + return gins.ctx.Request.URL.Query() +} + +// Request implements the method Adapter.Request. +func (gins *Gin) Request() *http.Request { + return gins.ctx.Request +} \ No newline at end of file diff --git a/adapter/gofiber/gofiber.go b/adapter/gofiber/gofiber.go new file mode 100644 index 0000000000000000000000000000000000000000..d215216b5e4ae12f71ce9c704f82212bea63562f --- /dev/null +++ b/adapter/gofiber/gofiber.go @@ -0,0 +1,230 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package gofiber + +import ( + "errors" + "io" + "net/http" + "net/url" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/gofiber/fiber/v2" + "github.com/valyala/fasthttp" +) + +// Fasthttp structure value is a Fasthttp GoAdmin adapter. +type Gofiber struct { + adapter.BaseAdapter + ctx *fiber.Ctx + app *fiber.App +} + +func init() { + engine.Register(new(Gofiber)) +} + +// User implements the method Adapter.User. +func (gof *Gofiber) User(ctx interface{}) (models.UserModel, bool) { + return gof.GetUser(ctx, gof) +} + +// Use implements the method Adapter.Use. +func (gof *Gofiber) Use(app interface{}, plugs []plugins.Plugin) error { + return gof.GetUse(app, plugs, gof) +} + +// Content implements the method Adapter.Content. +func (gof *Gofiber) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + gof.GetContent(ctx, getPanelFn, gof, btns, fn) +} + +// SetApp implements the method Adapter.SetApp. +func (gof *Gofiber) SetApp(app interface{}) error { + var ( + eng *fiber.App + ok bool + ) + if eng, ok = app.(*fiber.App); !ok { + return errors.New("fiber.App adapter SetApp: wrong parameter") + } + + gof.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (gof *Gofiber) AddHandler(method, path string, handlers context.Handlers) { + + gof.app.Add(strings.ToUpper(method), path, func(c *fiber.Ctx) error { + + httpreq := convertCtx(c.Context()) + ctx := context.NewContext(httpreq) + + for _, key := range c.Route().Params { + if httpreq.URL.RawQuery == "" { + httpreq.URL.RawQuery += strings.ReplaceAll(key, ":", "") + "=" + c.Params(key) + } else { + httpreq.URL.RawQuery += "&" + strings.ReplaceAll(key, ":", "") + "=" + c.Params(key) + } + + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.Set(key, head[0]) + + } + + return c.Status(ctx.Response.StatusCode).SendStream(ctx.Response.Body) + + }) + +} + +func convertCtx(ctx *fasthttp.RequestCtx) *http.Request { + var r http.Request + + body := ctx.PostBody() + r.Method = string(ctx.Method()) + r.Proto = "HTTP/1.1" + r.ProtoMajor = 1 + r.ProtoMinor = 1 + r.RequestURI = string(ctx.RequestURI()) + r.ContentLength = int64(len(body)) + r.Host = string(ctx.Host()) + r.RemoteAddr = ctx.RemoteAddr().String() + + hdr := make(http.Header) + ctx.Request.Header.VisitAll(func(k, v []byte) { + sk := string(k) + sv := string(v) + switch sk { + case "Transfer-Encoding": + r.TransferEncoding = append(r.TransferEncoding, sv) + default: + hdr.Set(sk, sv) + } + }) + r.Header = hdr + r.Body = &netHTTPBody{body} + rURL, err := url.ParseRequestURI(r.RequestURI) + if err != nil { + ctx.Logger().Printf("cannot parse requestURI %q: %s", r.RequestURI, err) + ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError) + return &r + } + r.URL = rURL + return &r +} + +type netHTTPBody struct { + b []byte +} + +func (r *netHTTPBody) Read(p []byte) (int, error) { + if len(r.b) == 0 { + return 0, io.EOF + } + n := copy(p, r.b) + r.b = r.b[n:] + return n, nil +} + +func (r *netHTTPBody) Close() error { + r.b = r.b[:0] + return nil +} + +// Name implements the method Adapter.Name. +func (*Gofiber) Name() string { + return "gofiber" +} + +// SetContext implements the method Adapter.SetContext. +func (*Gofiber) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx *fiber.Ctx + ok bool + ) + if ctx, ok = contextInterface.(*fiber.Ctx); !ok { + panic("gofiber adapter SetContext: wrong parameter") + } + return &Gofiber{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (gof *Gofiber) Redirect() { + _ = gof.ctx.Redirect(config.Url(config.GetLoginUrl()), http.StatusFound) +} + +// SetContentType implements the method Adapter.SetContentType. +func (gof *Gofiber) SetContentType() { + gof.ctx.Response().Header.Set("Content-Type", gof.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (gof *Gofiber) Write(body []byte) { + _, _ = gof.ctx.Write(body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (gof *Gofiber) GetCookie() (string, error) { + return string(gof.ctx.Cookies(gof.CookieKey())), nil +} + +// Lang implements the method Adapter.Lang. +func (gof *Gofiber) Lang() string { + return string(gof.ctx.Request().URI().QueryArgs().Peek("__ga_lang")) +} + +// Path implements the method Adapter.Path. +func (gof *Gofiber) Path() string { + return string(gof.ctx.Path()) +} + +// Method implements the method Adapter.Method. +func (gof *Gofiber) Method() string { + return string(gof.ctx.Method()) +} + +// FormParam implements the method Adapter.FormParam. +func (gof *Gofiber) FormParam() url.Values { + f, _ := gof.ctx.MultipartForm() + if f != nil { + return f.Value + } + return url.Values{} +} + +// IsPjax implements the method Adapter.IsPjax. +func (gof *Gofiber) IsPjax() bool { + return string(gof.ctx.Request().Header.Peek(constant.PjaxHeader)) == "true" +} + +// Query implements the method Adapter.Query. +func (gof *Gofiber) Query() url.Values { + queryStr := gof.ctx.Context().QueryArgs().QueryString() + queryObj, err := url.Parse(string(queryStr)) + + if err != nil { + return url.Values{} + } + + return queryObj.Query() +} + +// Request implements the method Adapter.Request. +func (gof *Gofiber) Request() *http.Request { + return convertCtx(gof.ctx.Context()) +} \ No newline at end of file diff --git a/adapter/gorilla/gorilla.go b/adapter/gorilla/gorilla.go new file mode 100644 index 0000000000000000000000000000000000000000..a72d330976ce6df0ddf5243cc6991e5c6338416a --- /dev/null +++ b/adapter/gorilla/gorilla.go @@ -0,0 +1,207 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package gorilla + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/gorilla/mux" +) + +// Gorilla structure value is a Gorilla GoAdmin adapter. +type Gorilla struct { + adapter.BaseAdapter + ctx Context + app *mux.Router +} + +func init() { + engine.Register(new(Gorilla)) +} + +// User implements the method Adapter.User. +func (g *Gorilla) User(ctx interface{}) (models.UserModel, bool) { + return g.GetUser(ctx, g) +} + +// Use implements the method Adapter.Use. +func (g *Gorilla) Use(app interface{}, plugs []plugins.Plugin) error { + return g.GetUse(app, plugs, g) +} + +// Content implements the method Adapter.Content. +func (g *Gorilla) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + g.GetContent(ctx, getPanelFn, g, btns, fn) +} + +type HandlerFunc func(ctx Context) (types.Panel, error) + +func Content(handler HandlerFunc) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + ctx := Context{ + Request: request, + Response: writer, + } + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(Context)) + }) + } +} + +// SetApp implements the method Adapter.SetApp. +func (g *Gorilla) SetApp(app interface{}) error { + var ( + eng *mux.Router + ok bool + ) + if eng, ok = app.(*mux.Router); !ok { + return errors.New("gorilla adapter SetApp: wrong parameter") + } + g.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (g *Gorilla) AddHandler(method, path string, handlers context.Handlers) { + + reg1 := regexp.MustCompile(":(.*?)/") + reg2 := regexp.MustCompile(":(.*?)$") + + u := path + u = reg1.ReplaceAllString(u, "{$1}/") + u = reg2.ReplaceAllString(u, "{$1}") + + g.app.HandleFunc(u, func(w http.ResponseWriter, r *http.Request) { + ctx := context.NewContext(r) + + params := mux.Vars(r) + + for key, param := range params { + if r.URL.RawQuery == "" { + r.URL.RawQuery += strings.ReplaceAll(key, ":", "") + "=" + param + } else { + r.URL.RawQuery += "&" + strings.ReplaceAll(key, ":", "") + "=" + param + } + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + w.Header().Add(key, head[0]) + } + + if ctx.Response.Body == nil { + w.WriteHeader(ctx.Response.StatusCode) + return + } + + w.WriteHeader(ctx.Response.StatusCode) + + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + + _, err := w.Write(buf.Bytes()) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + }).Methods(strings.ToUpper(method)) +} + +// Context wraps the Request and Response object of Gorilla. +type Context struct { + Request *http.Request + Response http.ResponseWriter +} + +// Name implements the method Adapter.Name. +func (*Gorilla) Name() string { + return "gorilla" +} + +// SetContext implements the method Adapter.SetContext. +func (*Gorilla) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx Context + ok bool + ) + if ctx, ok = contextInterface.(Context); !ok { + panic("gorilla adapter SetContext: wrong parameter") + } + + return &Gorilla{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (g *Gorilla) Redirect() { + http.Redirect(g.ctx.Response, g.ctx.Request, config.Url(config.GetLoginUrl()), http.StatusFound) +} + +// SetContentType implements the method Adapter.SetContentType. +func (g *Gorilla) SetContentType() { + g.ctx.Response.Header().Set("Content-Type", g.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (g *Gorilla) Write(body []byte) { + _, _ = g.ctx.Response.Write(body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (g *Gorilla) GetCookie() (string, error) { + cookie, err := g.ctx.Request.Cookie(g.CookieKey()) + if err != nil { + return "", err + } + return cookie.Value, err +} + +// Lang implements the method Adapter.Lang. +func (g *Gorilla) Lang() string { + return g.ctx.Request.URL.Query().Get("__ga_lang") +} + +// Path implements the method Adapter.Path. +func (g *Gorilla) Path() string { + return g.ctx.Request.RequestURI +} + +// Method implements the method Adapter.Method. +func (g *Gorilla) Method() string { + return g.ctx.Request.Method +} + +// FormParam implements the method Adapter.FormParam. +func (g *Gorilla) FormParam() url.Values { + _ = g.ctx.Request.ParseMultipartForm(32 << 20) + return g.ctx.Request.PostForm +} + +// IsPjax implements the method Adapter.IsPjax. +func (g *Gorilla) IsPjax() bool { + return g.ctx.Request.Header.Get(constant.PjaxHeader) == "true" +} + +// Query implements the method Adapter.Query. +func (g *Gorilla) Query() url.Values { + return g.ctx.Request.URL.Query() +} + +// Request implements the method Adapter.Request. +func (g *Gorilla) Request() *http.Request { + return g.ctx.Request +} \ No newline at end of file diff --git a/adapter/iris/iris.go b/adapter/iris/iris.go new file mode 100644 index 0000000000000000000000000000000000000000..3acc9da543906c8edc5339fba07ec210bf31554a --- /dev/null +++ b/adapter/iris/iris.go @@ -0,0 +1,177 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package iris + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "strings" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/kataras/iris/v12" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" +) + +// Iris structure value is an Iris GoAdmin adapter. +type Iris struct { + adapter.BaseAdapter + ctx iris.Context + app *iris.Application +} + +func init() { + engine.Register(new(Iris)) +} + +// User implements the method Adapter.User. +func (is *Iris) User(ctx interface{}) (models.UserModel, bool) { + return is.GetUser(ctx, is) +} + +// Use implements the method Adapter.Use. +func (is *Iris) Use(app interface{}, plugs []plugins.Plugin) error { + return is.GetUse(app, plugs, is) +} + +// Content implements the method Adapter.Content. +func (is *Iris) Content(ctx interface{}, getPanelFn types.GetPanelFn, fn context.NodeProcessor, btns ...types.Button) { + is.GetContent(ctx, getPanelFn, is, btns, fn) +} + +type HandlerFunc func(ctx iris.Context) (types.Panel, error) + +func Content(handler HandlerFunc) iris.Handler { + return func(ctx iris.Context) { + engine.Content(ctx, func(ctx interface{}) (types.Panel, error) { + return handler(ctx.(iris.Context)) + }) + } +} + +// SetApp implements the method Adapter.SetApp. +func (is *Iris) SetApp(app interface{}) error { + var ( + eng *iris.Application + ok bool + ) + if eng, ok = app.(*iris.Application); !ok { + return errors.New("iris adapter SetApp: wrong parameter") + } + is.app = eng + return nil +} + +// AddHandler implements the method Adapter.AddHandler. +func (is *Iris) AddHandler(method, path string, handlers context.Handlers) { + is.app.Handle(strings.ToUpper(method), path, func(c iris.Context) { + ctx := context.NewContext(c.Request()) + + var params = map[string]string{} + c.Params().Visit(func(key string, value string) { + params[key] = value + }) + + for key, value := range params { + if c.Request().URL.RawQuery == "" { + c.Request().URL.RawQuery += strings.ReplaceAll(key, ":", "") + "=" + value + } else { + c.Request().URL.RawQuery += "&" + strings.ReplaceAll(key, ":", "") + "=" + value + } + } + + ctx.SetHandlers(handlers).Next() + for key, head := range ctx.Response.Header { + c.Header(key, head[0]) + } + c.StatusCode(ctx.Response.StatusCode) + if ctx.Response.Body != nil { + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(ctx.Response.Body) + _, _ = c.WriteString(buf.String()) + } + }) +} + +// Name implements the method Adapter.Name. +func (*Iris) Name() string { + return "iris" +} + +// SetContext implements the method Adapter.SetContext. +func (*Iris) SetContext(contextInterface interface{}) adapter.WebFrameWork { + var ( + ctx iris.Context + ok bool + ) + if ctx, ok = contextInterface.(iris.Context); !ok { + panic("iris adapter SetContext: wrong parameter") + } + + return &Iris{ctx: ctx} +} + +// Redirect implements the method Adapter.Redirect. +func (is *Iris) Redirect() { + is.ctx.Redirect(config.Url(config.GetLoginUrl()), http.StatusFound) +} + +// SetContentType implements the method Adapter.SetContentType. +func (is *Iris) SetContentType() { + is.ctx.Header("Content-Type", is.HTMLContentType()) +} + +// Write implements the method Adapter.Write. +func (is *Iris) Write(body []byte) { + _, _ = is.ctx.Write(body) +} + +// GetCookie implements the method Adapter.GetCookie. +func (is *Iris) GetCookie() (string, error) { + return is.ctx.GetCookie(is.CookieKey()), nil +} + +// Lang implements the method Adapter.Lang. +func (is *Iris) Lang() string { + return is.ctx.Request().URL.Query().Get("__ga_lang") +} + +// Path implements the method Adapter.Path. +func (is *Iris) Path() string { + return is.ctx.Path() +} + +// Method implements the method Adapter.Method. +func (is *Iris) Method() string { + return is.ctx.Method() +} + +// FormParam implements the method Adapter.FormParam. +func (is *Iris) FormParam() url.Values { + return is.ctx.FormValues() +} + +// IsPjax implements the method Adapter.IsPjax. +func (is *Iris) IsPjax() bool { + return is.ctx.GetHeader(constant.PjaxHeader) == "true" +} + +// Query implements the method Adapter.Query. +func (is *Iris) Query() url.Values { + return is.ctx.Request().URL.Query() +} + +// Request implements the method Adapter.Request. +func (is *Iris) Request() *http.Request { + return is.ctx.Request() +} \ No newline at end of file diff --git a/context/context.go b/context/context.go new file mode 100644 index 0000000000000000000000000000000000000000..4f46b60aa22caf8c32e9d67875e3ea3ec62b3a69 --- /dev/null +++ b/context/context.go @@ -0,0 +1,781 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package context + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "net" + "net/http" + "net/url" + "os" + "path" + "strings" + "time" + + "github.com/GoAdminGroup/go-admin/modules/constant" +) + +const abortIndex int8 = math.MaxInt8 / 2 + +// Context is the simplify version of web framework context. +// But it is important which will be used in plugins to custom +// the request and response. And adapter will help to transform +// the Context to the web framework`s context. It has three attributes. +// Request and response are belongs to net/http package. UserValue +// is the custom key-value store of context. +type Context struct { + Request *http.Request + Response *http.Response + UserValue map[string]interface{} + index int8 + handlers Handlers +} + +// Path is used in the matching of request and response. Url stores the +// raw register url. RegUrl contains the wildcard which on behalf of +// the route params. +type Path struct { + URL string + Method string +} + +type RouterMap map[string]Router + +func (r RouterMap) Get(name string) Router { + return r[name] +} + +type Router struct { + Methods []string + Patten string +} + +func (r Router) Method() string { + return r.Methods[0] +} + +func (r Router) GetURL(value ...string) string { + u := r.Patten + for i := 0; i < len(value); i += 2 { + u = strings.ReplaceAll(u, ":__"+value[i], value[i+1]) + } + return u +} + +type NodeProcessor func(...Node) + +type Node struct { + Path string + Method string + Handlers []Handler + Value map[string]interface{} +} + +// SetUserValue set the value of user context. +func (ctx *Context) SetUserValue(key string, value interface{}) { + ctx.UserValue[key] = value +} + +// GetUserValue get the value of key. +func (ctx *Context) GetUserValue(key string) interface{} { + return ctx.UserValue[key] +} + +// Path return the url path. +func (ctx *Context) Path() string { + return ctx.Request.URL.Path +} + +// Abort abort the context. +func (ctx *Context) Abort() { + ctx.index = abortIndex +} + +// Next should be used only inside middleware. +func (ctx *Context) Next() { + ctx.index++ + for s := int8(len(ctx.handlers)); ctx.index < s; ctx.index++ { + ctx.handlers[ctx.index](ctx) + } +} + +// SetHandlers set the handlers of Context. +func (ctx *Context) SetHandlers(handlers Handlers) *Context { + ctx.handlers = handlers + return ctx +} + +// Method return the request method. +func (ctx *Context) Method() string { + return ctx.Request.Method +} + +// NewContext used in adapter which return a Context with request +// and slice of UserValue and a default Response. +func NewContext(req *http.Request) *Context { + + return &Context{ + Request: req, + UserValue: make(map[string]interface{}), + Response: &http.Response{ + StatusCode: http.StatusOK, + Header: make(http.Header), + }, + index: -1, + } +} + +const ( + HeaderContentType = "Content-Type" + + HeaderLastModified = "Last-Modified" + HeaderIfModifiedSince = "If-Modified-Since" + HeaderCacheControl = "Cache-Control" + HeaderETag = "ETag" + + HeaderContentDisposition = "Content-Disposition" + HeaderContentLength = "Content-Length" + HeaderContentEncoding = "Content-Encoding" + + GzipHeaderValue = "gzip" + HeaderAcceptEncoding = "Accept-Encoding" + HeaderVary = "Vary" + + ThemeKey = "__ga_theme" +) + +func (ctx *Context) BindJSON(data interface{}) error { + if ctx.Request.Body != nil { + b, err := io.ReadAll(ctx.Request.Body) + if err == nil { + return json.Unmarshal(b, data) + } + return err + } + return errors.New("empty request body") +} + +func (ctx *Context) MustBindJSON(data interface{}) { + if ctx.Request.Body != nil { + b, err := io.ReadAll(ctx.Request.Body) + if err != nil { + panic(err) + } + err = json.Unmarshal(b, data) + if err != nil { + panic(err) + } + } + panic("empty request body") +} + +// Write save the given status code, headers and body string into the response. +func (ctx *Context) Write(code int, header map[string]string, Body string) { + ctx.Response.StatusCode = code + for key, head := range header { + ctx.AddHeader(key, head) + } + ctx.Response.Body = io.NopCloser(strings.NewReader(Body)) +} + +// JSON serializes the given struct as JSON into the response body. +// It also sets the Content-Type as "application/json". +func (ctx *Context) JSON(code int, Body map[string]interface{}) { + ctx.Response.StatusCode = code + ctx.SetContentType("application/json") + BodyStr, err := json.Marshal(Body) + if err != nil { + panic(err) + } + ctx.Response.Body = io.NopCloser(bytes.NewReader(BodyStr)) +} + +// DataWithHeaders save the given status code, headers and body data into the response. +func (ctx *Context) DataWithHeaders(code int, header map[string]string, data []byte) { + ctx.Response.StatusCode = code + for key, head := range header { + ctx.AddHeader(key, head) + } + ctx.Response.Body = io.NopCloser(bytes.NewBuffer(data)) +} + +// Data writes some data into the body stream and updates the HTTP code. +func (ctx *Context) Data(code int, contentType string, data []byte) { + ctx.Response.StatusCode = code + ctx.SetContentType(contentType) + ctx.Response.Body = io.NopCloser(bytes.NewBuffer(data)) +} + +// Redirect add redirect url to header. +func (ctx *Context) Redirect(path string) { + ctx.Response.StatusCode = http.StatusFound + ctx.SetContentType("text/html; charset=utf-8") + ctx.AddHeader("Location", path) +} + +// HTML output html response. +func (ctx *Context) HTML(code int, body string) { + ctx.SetContentType("text/html; charset=utf-8") + ctx.SetStatusCode(code) + ctx.WriteString(body) +} + +// HTMLByte output html response. +func (ctx *Context) HTMLByte(code int, body []byte) { + ctx.SetContentType("text/html; charset=utf-8") + ctx.SetStatusCode(code) + ctx.Response.Body = io.NopCloser(bytes.NewBuffer(body)) +} + +// WriteString save the given body string into the response. +func (ctx *Context) WriteString(body string) { + ctx.Response.Body = io.NopCloser(strings.NewReader(body)) +} + +// SetStatusCode save the given status code into the response. +func (ctx *Context) SetStatusCode(code int) { + ctx.Response.StatusCode = code +} + +// SetContentType save the given content type header into the response header. +func (ctx *Context) SetContentType(contentType string) { + ctx.AddHeader(HeaderContentType, contentType) +} + +func (ctx *Context) SetLastModified(modtime time.Time) { + if !IsZeroTime(modtime) { + ctx.AddHeader(HeaderLastModified, modtime.UTC().Format(http.TimeFormat)) // or modtime.UTC()? + } +} + +var unixEpochTime = time.Unix(0, 0) + +// IsZeroTime reports whether t is obviously unspecified (either zero or Unix()=0). +func IsZeroTime(t time.Time) bool { + return t.IsZero() || t.Equal(unixEpochTime) +} + +// ParseTime parses a time header (such as the Date: header), +// trying each forth formats +// that are allowed by HTTP/1.1: +// time.RFC850, and time.ANSIC. +var ParseTime = func(text string) (t time.Time, err error) { + t, err = time.Parse(http.TimeFormat, text) + if err != nil { + return http.ParseTime(text) + } + + return +} + +func (ctx *Context) WriteNotModified() { + // RFC 7232 section 4.1: + // a sender SHOULD NOT generate representation metadata other than the + // above listed fields unless said metadata exists for the purpose of + // guiding cache updates (e.g.," Last-Modified" might be useful if the + // response does not have an ETag field). + delete(ctx.Response.Header, HeaderContentType) + delete(ctx.Response.Header, HeaderContentLength) + if ctx.Headers(HeaderETag) != "" { + delete(ctx.Response.Header, HeaderLastModified) + } + ctx.SetStatusCode(http.StatusNotModified) +} + +func (ctx *Context) CheckIfModifiedSince(modtime time.Time) (bool, error) { + if method := ctx.Method(); method != http.MethodGet && method != http.MethodHead { + return false, errors.New("skip: method") + } + ims := ctx.Headers(HeaderIfModifiedSince) + if ims == "" || IsZeroTime(modtime) { + return false, errors.New("skip: zero time") + } + t, err := ParseTime(ims) + if err != nil { + return false, errors.New("skip: " + err.Error()) + } + // sub-second precision, so + // use mtime < t+1s instead of mtime <= t to check for unmodified. + if modtime.UTC().Before(t.Add(1 * time.Second)) { + return false, nil + } + return true, nil +} + +// LocalIP return the request client ip. +func (ctx *Context) LocalIP() string { + xForwardedFor := ctx.Request.Header.Get("X-Forwarded-For") + ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0]) + if ip != "" { + return ip + } + + ip = strings.TrimSpace(ctx.Request.Header.Get("X-Real-Ip")) + if ip != "" { + return ip + } + + if ip, _, err := net.SplitHostPort(strings.TrimSpace(ctx.Request.RemoteAddr)); err == nil { + return ip + } + + return "127.0.0.1" +} + +// SetCookie save the given cookie obj into the response Set-Cookie header. +func (ctx *Context) SetCookie(cookie *http.Cookie) { + if v := cookie.String(); v != "" { + ctx.AddHeader("Set-Cookie", v) + } +} + +// Query get the query parameter of url. +func (ctx *Context) Query(key string) string { + return ctx.Request.URL.Query().Get(key) +} + +// QueryAll get the query parameters of url. +func (ctx *Context) QueryAll(key string) []string { + return ctx.Request.URL.Query()[key] +} + +// QueryDefault get the query parameter of url. If it is empty, return the default. +func (ctx *Context) QueryDefault(key, def string) string { + value := ctx.Query(key) + if value == "" { + return def + } + return value +} + +// Lang get the query parameter of url with given key __ga_lang. +func (ctx *Context) Lang() string { + return ctx.Query("__ga_lang") +} + +// Theme get the request theme with given key __ga_theme. +func (ctx *Context) Theme() string { + queryTheme := ctx.Query(ThemeKey) + if queryTheme != "" { + return queryTheme + } + cookieTheme := ctx.Cookie(ThemeKey) + if cookieTheme != "" { + return cookieTheme + } + return ctx.RefererQuery(ThemeKey) +} + +// Headers get the value of request headers key. +func (ctx *Context) Headers(key string) string { + return ctx.Request.Header.Get(key) +} + +// Referer get the url string of request header Referer. +func (ctx *Context) Referer() string { + return ctx.Headers("Referer") +} + +// RefererURL get the url.URL object of request header Referer. +func (ctx *Context) RefererURL() *url.URL { + ref := ctx.Headers("Referer") + if ref == "" { + return nil + } + u, err := url.Parse(ref) + if err != nil { + return nil + } + return u +} + +// RefererQuery retrieve the value of given key from url.URL object of request header Referer. +func (ctx *Context) RefererQuery(key string) string { + if u := ctx.RefererURL(); u != nil { + return u.Query().Get(key) + } + return "" +} + +// FormValue get the value of request form key. +func (ctx *Context) FormValue(key string) string { + return ctx.Request.FormValue(key) +} + +// PostForm get the values of request form. +func (ctx *Context) PostForm() url.Values { + _ = ctx.Request.ParseMultipartForm(32 << 20) + return ctx.Request.PostForm +} + +func (ctx *Context) WantHTML() bool { + return ctx.Method() == "GET" && strings.Contains(ctx.Headers("Accept"), "html") +} + +func (ctx *Context) WantJSON() bool { + return strings.Contains(ctx.Headers("Accept"), "json") +} + +// AddHeader adds the key, value pair to the header. +func (ctx *Context) AddHeader(key, value string) { + ctx.Response.Header.Add(key, value) +} + +// PjaxUrl add pjax url header. +func (ctx *Context) PjaxUrl(url string) { + ctx.Response.Header.Add(constant.PjaxUrlHeader, url) +} + +// IsPjax check request is pjax or not. +func (ctx *Context) IsPjax() bool { + return ctx.Headers(constant.PjaxHeader) == "true" +} + +// IsIframe check request is iframe or not. +func (ctx *Context) IsIframe() bool { + return ctx.Query(constant.IframeKey) == "true" || ctx.Headers(constant.IframeKey) == "true" +} + +// SetHeader set the key, value pair to the header. +func (ctx *Context) SetHeader(key, value string) { + ctx.Response.Header.Set(key, value) +} + +func (ctx *Context) GetContentType() string { + return ctx.Request.Header.Get("Content-Type") +} + +func (ctx *Context) Cookie(name string) string { + for _, ck := range ctx.Request.Cookies() { + if ck.Name == name { + return ck.Value + } + } + return "" +} + +// User return the current login user. +func (ctx *Context) User() interface{} { + return ctx.UserValue["user"] +} + +// ServeContent serves content, headers are autoset +// receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string) +// +// You can define your own "Content-Type" header also, after this function call +// Doesn't implements resuming (by range), use ctx.SendFile instead +func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error { + if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil { + ctx.WriteNotModified() + return nil + } + + if ctx.GetContentType() == "" { + ctx.SetContentType(filename) + } + + buf, _ := io.ReadAll(content) + ctx.Response.Body = io.NopCloser(bytes.NewBuffer(buf)) + return nil +} + +// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename) +func (ctx *Context) ServeFile(filename string, gzipCompression bool) error { + f, err := os.Open(filename) + if err != nil { + return fmt.Errorf("%d", http.StatusNotFound) + } + defer func() { + _ = f.Close() + }() + fi, _ := f.Stat() + if fi.IsDir() { + return ctx.ServeFile(path.Join(filename, "index.html"), gzipCompression) + } + + return ctx.ServeContent(f, fi.Name(), fi.ModTime(), gzipCompression) +} + +type HandlerMap map[Path]Handlers + +// App is the key struct of the package. App as a member of plugin +// entity contains the request and the corresponding handler. Prefix +// is the url prefix and MiddlewareList is for control flow. +type App struct { + Requests []Path + Handlers HandlerMap + Middlewares Handlers + Prefix string + + Routers RouterMap + routeIndex int + routeANY bool +} + +// NewApp return an empty app. +func NewApp() *App { + return &App{ + Requests: make([]Path, 0), + Handlers: make(HandlerMap), + Prefix: "/", + Middlewares: make([]Handler, 0), + routeIndex: -1, + Routers: make(RouterMap), + } +} + +// Handler defines the handler used by the middleware as return value. +type Handler func(ctx *Context) + +// Handlers is the array of Handler +type Handlers []Handler + +// AppendReqAndResp stores the request info and handle into app. +// support the route parameter. The route parameter will be recognized as +// wildcard store into the RegUrl of Path struct. For example: +// +// /user/:id => /user/(.*) +// /user/:id/info => /user/(.*?)/info +// +// The RegUrl will be used to recognize the incoming path and find +// the handler. +func (app *App) AppendReqAndResp(url, method string, handler []Handler) { + + app.Requests = append(app.Requests, Path{ + URL: join(app.Prefix, url), + Method: method, + }) + app.routeIndex++ + + app.Handlers[Path{ + URL: join(app.Prefix, url), + Method: method, + }] = append(app.Middlewares, handler...) +} + +// Find is public helper method for findPath of tree. +func (app *App) Find(url, method string) []Handler { + app.routeANY = false + return app.Handlers[Path{URL: url, Method: method}] +} + +// POST is a shortcut for app.AppendReqAndResp(url, "post", handler). +func (app *App) POST(url string, handler ...Handler) *App { + app.routeANY = false + app.AppendReqAndResp(url, "post", handler) + return app +} + +// GET is a shortcut for app.AppendReqAndResp(url, "get", handler). +func (app *App) GET(url string, handler ...Handler) *App { + app.routeANY = false + app.AppendReqAndResp(url, "get", handler) + return app +} + +// DELETE is a shortcut for app.AppendReqAndResp(url, "delete", handler). +func (app *App) DELETE(url string, handler ...Handler) *App { + app.routeANY = false + app.AppendReqAndResp(url, "delete", handler) + return app +} + +// PUT is a shortcut for app.AppendReqAndResp(url, "put", handler). +func (app *App) PUT(url string, handler ...Handler) *App { + app.routeANY = false + app.AppendReqAndResp(url, "put", handler) + return app +} + +// OPTIONS is a shortcut for app.AppendReqAndResp(url, "options", handler). +func (app *App) OPTIONS(url string, handler ...Handler) *App { + app.routeANY = false + app.AppendReqAndResp(url, "options", handler) + return app +} + +// HEAD is a shortcut for app.AppendReqAndResp(url, "head", handler). +func (app *App) HEAD(url string, handler ...Handler) *App { + app.routeANY = false + app.AppendReqAndResp(url, "head", handler) + return app +} + +// ANY registers a route that matches all the HTTP methods. +// GET, POST, PUT, HEAD, OPTIONS, DELETE. +func (app *App) ANY(url string, handler ...Handler) *App { + app.routeANY = true + app.AppendReqAndResp(url, "post", handler) + app.AppendReqAndResp(url, "get", handler) + app.AppendReqAndResp(url, "delete", handler) + app.AppendReqAndResp(url, "put", handler) + app.AppendReqAndResp(url, "options", handler) + app.AppendReqAndResp(url, "head", handler) + return app +} + +func (app *App) Name(name string) { + if app.routeANY { + app.Routers[name] = Router{ + Methods: []string{"POST", "GET", "DELETE", "PUT", "OPTIONS", "HEAD"}, + Patten: app.Requests[app.routeIndex].URL, + } + } else { + app.Routers[name] = Router{ + Methods: []string{app.Requests[app.routeIndex].Method}, + Patten: app.Requests[app.routeIndex].URL, + } + } +} + +// Group add middlewares and prefix for App. +func (app *App) Group(prefix string, middleware ...Handler) *RouterGroup { + return &RouterGroup{ + app: app, + Middlewares: append(app.Middlewares, middleware...), + Prefix: slash(prefix), + } +} + +// RouterGroup is a group of routes. +type RouterGroup struct { + app *App + Middlewares Handlers + Prefix string +} + +// AppendReqAndResp stores the request info and handle into app. +// support the route parameter. The route parameter will be recognized as +// wildcard store into the RegUrl of Path struct. For example: +// +// /user/:id => /user/(.*) +// /user/:id/info => /user/(.*?)/info +// +// The RegUrl will be used to recognize the incoming path and find +// the handler. +func (g *RouterGroup) AppendReqAndResp(url, method string, handler []Handler) { + + g.app.Requests = append(g.app.Requests, Path{ + URL: join(g.Prefix, url), + Method: method, + }) + g.app.routeIndex++ + + var h = make([]Handler, len(g.Middlewares)) + copy(h, g.Middlewares) + + g.app.Handlers[Path{ + URL: join(g.Prefix, url), + Method: method, + }] = append(h, handler...) +} + +// POST is a shortcut for app.AppendReqAndResp(url, "post", handler). +func (g *RouterGroup) POST(url string, handler ...Handler) *RouterGroup { + g.app.routeANY = false + g.AppendReqAndResp(url, "post", handler) + return g +} + +// GET is a shortcut for app.AppendReqAndResp(url, "get", handler). +func (g *RouterGroup) GET(url string, handler ...Handler) *RouterGroup { + g.app.routeANY = false + g.AppendReqAndResp(url, "get", handler) + return g +} + +// DELETE is a shortcut for app.AppendReqAndResp(url, "delete", handler). +func (g *RouterGroup) DELETE(url string, handler ...Handler) *RouterGroup { + g.app.routeANY = false + g.AppendReqAndResp(url, "delete", handler) + return g +} + +// PUT is a shortcut for app.AppendReqAndResp(url, "put", handler). +func (g *RouterGroup) PUT(url string, handler ...Handler) *RouterGroup { + g.app.routeANY = false + g.AppendReqAndResp(url, "put", handler) + return g +} + +// OPTIONS is a shortcut for app.AppendReqAndResp(url, "options", handler). +func (g *RouterGroup) OPTIONS(url string, handler ...Handler) *RouterGroup { + g.app.routeANY = false + g.AppendReqAndResp(url, "options", handler) + return g +} + +// HEAD is a shortcut for app.AppendReqAndResp(url, "head", handler). +func (g *RouterGroup) HEAD(url string, handler ...Handler) *RouterGroup { + g.app.routeANY = false + g.AppendReqAndResp(url, "head", handler) + return g +} + +// ANY registers a route that matches all the HTTP methods. +// GET, POST, PUT, HEAD, OPTIONS, DELETE. +func (g *RouterGroup) ANY(url string, handler ...Handler) *RouterGroup { + g.app.routeANY = true + g.AppendReqAndResp(url, "post", handler) + g.AppendReqAndResp(url, "get", handler) + g.AppendReqAndResp(url, "delete", handler) + g.AppendReqAndResp(url, "put", handler) + g.AppendReqAndResp(url, "options", handler) + g.AppendReqAndResp(url, "head", handler) + return g +} + +func (g *RouterGroup) Name(name string) { + g.app.Name(name) +} + +// Group add middlewares and prefix for RouterGroup. +func (g *RouterGroup) Group(prefix string, middleware ...Handler) *RouterGroup { + return &RouterGroup{ + app: g.app, + Middlewares: append(g.Middlewares, middleware...), + Prefix: join(slash(g.Prefix), slash(prefix)), + } +} + +// slash fix the path which has wrong format problem. +// +// "" => "/" +// "abc/" => "/abc" +// "/abc/" => "/abc" +// "/abc" => "/abc" +// "/" => "/" +func slash(prefix string) string { + prefix = strings.TrimSpace(prefix) + if prefix == "" || prefix == "/" { + return "/" + } + if prefix[0] != '/' { + if prefix[len(prefix)-1] == '/' { + return "/" + prefix[:len(prefix)-1] + } + return "/" + prefix + } + if prefix[len(prefix)-1] == '/' { + return prefix[:len(prefix)-1] + } + return prefix +} + +// join join the path. +func join(prefix, suffix string) string { + if prefix == "/" { + return suffix + } + if suffix == "/" { + return prefix + } + return prefix + suffix +} diff --git a/context/context_test.go b/context/context_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2635a9e093f5d06f85de0734ad00e93684bd4582 --- /dev/null +++ b/context/context_test.go @@ -0,0 +1,72 @@ +package context + +import ( + "fmt" + "testing" + + "github.com/magiconair/properties/assert" +) + +func TestSlash(t *testing.T) { + assert.Equal(t, "/abc", slash("/abc")) + assert.Equal(t, "/", slash("")) + assert.Equal(t, "/abc", slash("abc/")) + assert.Equal(t, "/", slash("/")) + assert.Equal(t, "/abc", slash("/abc/")) + assert.Equal(t, "/", slash("//")) +} + +func TestJoin(t *testing.T) { + assert.Equal(t, "/abc/abc", join(slash("/abc"), slash("/abc"))) + assert.Equal(t, "/", join(slash("/"), slash("/"))) + assert.Equal(t, "/abc", join(slash("/"), slash("/abc"))) + assert.Equal(t, "/abc", join(slash("abc/"), slash("/"))) + assert.Equal(t, "/abc", join(slash("/abc/"), slash("/"))) +} + +func TestTree(t *testing.T) { + tree := tree() + tree.addPath(stringToArr("/adm"), "GET", []Handler{func(ctx *Context) { fmt.Println(1) }}) + tree.addPath(stringToArr("/admi"), "GET", []Handler{func(ctx *Context) { fmt.Println(1) }}) + tree.addPath(stringToArr("/admin"), "GET", []Handler{func(ctx *Context) { fmt.Println(1) }}) + tree.addPath(stringToArr("/admin/menu/new"), "POST", []Handler{func(ctx *Context) { fmt.Println(1) }}) + tree.addPath(stringToArr("/admin/menu/new"), "GET", []Handler{func(ctx *Context) { fmt.Println(1) }}) + tree.addPath(stringToArr("/admin/info/:__prefix"), "GET", []Handler{ + func(ctx *Context) { fmt.Println("auth") }, + func(ctx *Context) { fmt.Println("init") }, + func(ctx *Context) { fmt.Println("info") }, + }) + tree.addPath(stringToArr("/admin/info/:__prefix/detail"), "GET", []Handler{ + func(ctx *Context) { fmt.Println("auth") }, + func(ctx *Context) { fmt.Println("detail") }, + }) + + fmt.Println("/admin/menu/new", "GET") + h := tree.findPath(stringToArr("/admin/menu/new"), "GET") + assert.Equal(t, h != nil, true) + printHandler(h) + fmt.Println("/admin/menu/new", "POST") + h = tree.findPath(stringToArr("/admin/menu/new"), "POST") + assert.Equal(t, h != nil, true) + printHandler(h) + fmt.Println("/admin/me/new", "POST") + h = tree.findPath(stringToArr("/admin/me/new"), "POST") + assert.Equal(t, h == nil, true) + printHandler(h) + fmt.Println("/admin/info/user", "GET") + h = tree.findPath(stringToArr("/admin/info/user"), "GET") + assert.Equal(t, h != nil, true) + printHandler(h) + fmt.Println("/admin/info/user/detail", "GET") + h = tree.findPath(stringToArr("/admin/info/user/detail"), "GET") + assert.Equal(t, h != nil, true) + printHandler(h) + fmt.Println("=========== printChildren ===========") + tree.printChildren() +} + +func printHandler(h []Handler) { + for _, value := range h { + value(&Context{}) + } +} diff --git a/context/trie.go b/context/trie.go new file mode 100644 index 0000000000000000000000000000000000000000..328c33ddcfda3719abab574a0b12b589dd9e01e2 --- /dev/null +++ b/context/trie.go @@ -0,0 +1,134 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package context + +import "fmt" + +type node struct { + children []*node + value string + method []string + handle [][]Handler +} + +func tree() *node { + return &node{ + children: make([]*node, 0), + value: "/", + handle: nil, + } +} + +func (n *node) hasMethod(method string) int { + for k, m := range n.method { + if m == method { + return k + } + } + return -1 +} + +func (n *node) addMethodAndHandler(method string, handler []Handler) { + n.method = append(n.method, method) + n.handle = append(n.handle, handler) +} + +func (n *node) addChild(child *node) { + n.children = append(n.children, child) +} + +func (n *node) addContent(value string) *node { + var child = n.search(value) + if child == nil { + child = &node{ + children: make([]*node, 0), + value: value, + } + n.addChild(child) + } + return child +} + +func (n *node) search(value string) *node { + for _, child := range n.children { + if child.value == value || child.value == "*" { + return child + } + } + return nil +} + +func (n *node) addPath(paths []string, method string, handler []Handler) { + child := n + for i := 0; i < len(paths); i++ { + child = child.addContent(paths[i]) + } + child.addMethodAndHandler(method, handler) +} + +func (n *node) findPath(paths []string, method string) []Handler { + child := n + for i := 0; i < len(paths); i++ { + child = child.search(paths[i]) + if child == nil { + return nil + } + } + + methodIndex := child.hasMethod(method) + if methodIndex == -1 { + return nil + } + + return child.handle[methodIndex] +} + +func (n *node) print() { + fmt.Println(n) +} + +func (n *node) printChildren() { + n.print() + for _, child := range n.children { + child.printChildren() + } +} + +func stringToArr(path string) []string { + var ( + paths = make([]string, 0) + start = 0 + end int + isWildcard = false + ) + for i := 0; i < len(path); i++ { + if i == 0 && path[0] == '/' { + start = 1 + continue + } + if path[i] == ':' { + isWildcard = true + } + if i == len(path)-1 { + end = i + 1 + if isWildcard { + paths = append(paths, "*") + } else { + paths = append(paths, path[start:end]) + } + } + if path[i] == '/' { + end = i + if isWildcard { + paths = append(paths, "*") + } else { + paths = append(paths, path[start:end]) + } + start = i + 1 + isWildcard = false + } + } + return paths +} diff --git a/data/admin.db b/data/admin.db new file mode 100644 index 0000000000000000000000000000000000000000..79d4a9a0d75836cf3aab5304129b2b5c3d3729f4 Binary files /dev/null and b/data/admin.db differ diff --git a/data/admin.mssql b/data/admin.mssql new file mode 100644 index 0000000000000000000000000000000000000000..6aa44d846ab52fd7780f512dc07bcb9f16520579 --- /dev/null +++ b/data/admin.mssql @@ -0,0 +1,207 @@ + + +CREATE TABLE [goadmin_menu] ( + [id] int identity(1,1) , + [parent_id] int NOT NULL DEFAULT 0, + [type] tinyint NOT NULL DEFAULT 0, + [order] int NOT NULL DEFAULT '0', + [title] varchar(50) NOT NULL, + [icon] varchar(50) NOT NULL, + [uri] varchar(3000) NOT NULL DEFAULT '', + [header] varchar(150) DEFAULT NULL, + [uuid] varchar(150) DEFAULT NULL, + [plugin_name] varchar(150) NOT NULL DEFAULT '', + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([id]), +) +set IDENTITY_INSERT [goadmin_menu] ON +INSERT INTO[goadmin_menu] ([id],[parent_id],[type],[order],[title],[icon],[uri],[header],[created_at],[updated_at]) +VALUES + (1,0,1,2,'Admin','fa-tasks','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,1,1,2,'Users','fa-users','/info/manager',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (3,1,1,3,'Roles','fa-user','/info/roles',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (4,1,1,4,'Permission','fa-ban','/info/permission',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (5,1,1,5,'Menu','fa-bars','/menu',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (6,1,1,6,'Operation log','fa-history','/info/op',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (7,0,1,1,'Dashboard','fa-bar-chart','/',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'); +set IDENTITY_INSERT [goadmin_menu] OFF + + + + +CREATE TABLE[goadmin_operation_log] ( + [id] int identity(1,1) , + [user_id] int NOT NULL, + [path] varchar(255) NOT NULL, + [method] varchar(10) NOT NULL, + [ip] varchar(15) NOT NULL, + [input] text NOT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([id]), +) + + +CREATE TABLE[goadmin_site] ( + [id] int identity(1,1) , + [key] varchar(100) NOT NULL, + [value] text NOT NULL, + [state] tinyint NOT NULL DEFAULT 0, + [description] varchar(3000) DEFAULT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([id]), +) + + +CREATE TABLE[goadmin_permissions] ( + [id] int identity(1,1) , + [name] varchar(50) NOT NULL, + [slug] varchar(50) NOT NULL, + [http_method] varchar(255) DEFAULT NULL, + [http_path] text NOT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([id]), +) + +set IDENTITY_INSERT [goadmin_permissions] ON + +INSERT INTO[goadmin_permissions] ([id],[name],[slug],[http_method],[http_path],[created_at],[updated_at]) +VALUES + (1,'All permission','*','','*','2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,'Dashboard','dashboard','GET,PUT,POST,DELETE','/','2019-09-10 00:00:00','2019-09-10 00:00:00'); + +set IDENTITY_INSERT [goadmin_permissions] OFF + + + + +CREATE TABLE[goadmin_role_menu] ( + [role_id] int NOT NULL, + [menu_id] int NOT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY([role_id],[menu_id]) +) + + + +INSERT INTO[goadmin_role_menu] ([role_id],[menu_id],[created_at],[updated_at]) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (1,7,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,7,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (1,8,'2019-09-11 10:20:55','2019-09-11 10:20:55'), + (2,8,'2019-09-11 10:20:55','2019-09-11 10:20:55'); + + + + + +CREATE TABLE[goadmin_role_permissions] ( + [role_id] int NOT NULL, + [permission_id] int NOT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([role_id],[permission_id]) +) + + +INSERT INTO[goadmin_role_permissions] ([role_id],[permission_id],[created_at],[updated_at]) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (1,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + + + + +CREATE TABLE[goadmin_role_users] ( + [role_id] int NOT NULL, + [user_id] int NOT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([role_id],[user_id]) +) + + +INSERT INTO[goadmin_role_users] ([role_id],[user_id],[created_at],[updated_at]) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + + + + +CREATE TABLE[goadmin_roles] ( + [id] int identity(1,1) , + [name] varchar(50) NOT NULL UNIQUE, + [slug] varchar(50) NOT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([id]), +) + +set IDENTITY_INSERT [goadmin_roles] ON + +INSERT INTO[goadmin_roles] ([id],[name],[slug],[created_at],[updated_at]) +VALUES + (1,'Administrator','administrator','2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,'Operator','operator','2019-09-10 00:00:00','2019-09-10 00:00:00'); + +set IDENTITY_INSERT [goadmin_roles] OFF + + +CREATE TABLE[goadmin_session] ( + [id] int identity(1,1) , + [sid] varchar(50) DEFAULT '', + [values] varchar(3000) DEFAULT '', + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([id]) +) + + + + +CREATE TABLE[goadmin_user_permissions] ( + [user_id] int NOT NULL, + [permission_id] int NOT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([user_id],[permission_id]) +) + + +INSERT INTO[goadmin_user_permissions] ([user_id],[permission_id],[created_at],[updated_at]) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + + + + + +CREATE TABLE[goadmin_users] ( + [id] int identity(1,1) , + [username] varchar(100) NOT NULL UNIQUE, + [password] varchar(100) NOT NULL DEFAULT '', + [name] varchar(100) NOT NULL, + [avatar] varchar(255) DEFAULT NULL, + [remember_token] varchar(100) DEFAULT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([id]), +) + +set IDENTITY_INSERT [goadmin_users] ON + +INSERT INTO[goadmin_users] ([id],[username],[password],[name],[avatar],[remember_token],[created_at],[updated_at]) +VALUES + (1,'admin','$2a$10$U3F/NSaf2kaVbyXTBp7ppOn0jZFyRqXRnYXB.AMioCjXl3Ciaj4oy','admin','','tlNcBVK9AvfYH7WEnwB1RKvocJu8FfRy4um3DJtwdHuJy0dwFsLOgAc0xUfh','2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,'operator','$2a$10$rVqkOzHjN2MdlEprRflb1eGP0oZXuSrbJLOmJagFsCd81YZm0bsh.','Operator','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'); +set IDENTITY_INSERT [goadmin_users] OFF + + diff --git a/data/admin.pgsql b/data/admin.pgsql new file mode 100644 index 0000000000000000000000000000000000000000..83e28d024dea28d30c034867a5d040c5edcaff9e --- /dev/null +++ b/data/admin.pgsql @@ -0,0 +1,574 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 9.5.14 +-- Dumped by pg_dump version 10.5 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + + +-- +-- Name: goadmin_menu_myid_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.goadmin_menu_myid_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + MAXVALUE 99999999 + CACHE 1; + + +ALTER TABLE public.goadmin_menu_myid_seq OWNER TO postgres; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: goadmin_menu; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_menu ( + id integer DEFAULT nextval('public.goadmin_menu_myid_seq'::regclass) NOT NULL, + parent_id integer DEFAULT 0 NOT NULL, + type integer DEFAULT 0, + "order" integer DEFAULT 0 NOT NULL, + title character varying(50) NOT NULL, + header character varying(100), + plugin_name character varying(100) NOT NULL, + icon character varying(50) NOT NULL, + uri character varying(3000) NOT NULL, + uuid character varying(100), + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_menu OWNER TO postgres; + +-- +-- Name: goadmin_operation_log_myid_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.goadmin_operation_log_myid_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + MAXVALUE 99999999 + CACHE 1; + + +ALTER TABLE public.goadmin_operation_log_myid_seq OWNER TO postgres; + +-- +-- Name: goadmin_operation_log; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_operation_log ( + id integer DEFAULT nextval('public.goadmin_operation_log_myid_seq'::regclass) NOT NULL, + user_id integer NOT NULL, + path character varying(255) NOT NULL, + method character varying(10) NOT NULL, + ip character varying(15) NOT NULL, + input text NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_operation_log OWNER TO postgres; + +-- +-- Name: goadmin_site_myid_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.goadmin_site_myid_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + MAXVALUE 99999999 + CACHE 1; + + +ALTER TABLE public.goadmin_site_myid_seq OWNER TO postgres; + +-- +-- Name: goadmin_site; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_site ( + id integer DEFAULT nextval('public.goadmin_site_myid_seq'::regclass) NOT NULL, + key character varying(100) NOT NULL, + value text NOT NULL, + type integer DEFAULT 0, + description character varying(3000), + state integer DEFAULT 0, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_site OWNER TO postgres; + +-- +-- Name: goadmin_permissions_myid_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.goadmin_permissions_myid_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + MAXVALUE 99999999 + CACHE 1; + + +ALTER TABLE public.goadmin_permissions_myid_seq OWNER TO postgres; + +-- +-- Name: goadmin_permissions; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_permissions ( + id integer DEFAULT nextval('public.goadmin_permissions_myid_seq'::regclass) NOT NULL, + name character varying(50) NOT NULL, + slug character varying(50) NOT NULL, + http_method character varying(255), + http_path text NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_permissions OWNER TO postgres; + +-- +-- Name: goadmin_role_menu; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_role_menu ( + role_id integer NOT NULL, + menu_id integer NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_role_menu OWNER TO postgres; + +-- +-- Name: goadmin_role_permissions; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_role_permissions ( + role_id integer NOT NULL, + permission_id integer NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_role_permissions OWNER TO postgres; + +-- +-- Name: goadmin_role_users; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_role_users ( + role_id integer NOT NULL, + user_id integer NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_role_users OWNER TO postgres; + +-- +-- Name: goadmin_roles_myid_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.goadmin_roles_myid_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + MAXVALUE 99999999 + CACHE 1; + + +ALTER TABLE public.goadmin_roles_myid_seq OWNER TO postgres; + +-- +-- Name: goadmin_roles; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_roles ( + id integer DEFAULT nextval('public.goadmin_roles_myid_seq'::regclass) NOT NULL, + name character varying NOT NULL, + slug character varying NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_roles OWNER TO postgres; + +-- +-- Name: goadmin_session_myid_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.goadmin_session_myid_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + MAXVALUE 99999999 + CACHE 1; + + +ALTER TABLE public.goadmin_session_myid_seq OWNER TO postgres; + +-- +-- Name: goadmin_session; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_session ( + id integer DEFAULT nextval('public.goadmin_session_myid_seq'::regclass) NOT NULL, + sid character varying(50) NOT NULL, + "values" character varying(3000) NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_session OWNER TO postgres; + +-- +-- Name: goadmin_user_permissions; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_user_permissions ( + user_id integer NOT NULL, + permission_id integer NOT NULL, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_user_permissions OWNER TO postgres; + +-- +-- Name: goadmin_users_myid_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.goadmin_users_myid_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + MAXVALUE 99999999 + CACHE 1; + + +ALTER TABLE public.goadmin_users_myid_seq OWNER TO postgres; + +-- +-- Name: goadmin_users; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_users ( + id integer DEFAULT nextval('public.goadmin_users_myid_seq'::regclass) NOT NULL, + username character varying(100) NOT NULL, + password character varying(100) NOT NULL, + name character varying(100) NOT NULL, + avatar character varying(255), + remember_token character varying(100), + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_users OWNER TO postgres; + +-- +-- Data for Name: goadmin_menu; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_menu (id, parent_id, type, "order", title, plugin_name, header, icon, uri, created_at, updated_at) FROM stdin; +1 0 1 2 Admin \N fa-tasks 2019-09-10 00:00:00 2019-09-10 00:00:00 +2 1 1 2 Users \N fa-users /info/manager 2019-09-10 00:00:00 2019-09-10 00:00:00 +3 1 1 3 Roles \N fa-user /info/roles 2019-09-10 00:00:00 2019-09-10 00:00:00 +4 1 1 4 Permission \N fa-ban /info/permission 2019-09-10 00:00:00 2019-09-10 00:00:00 +5 1 1 5 Menu \N fa-bars /menu 2019-09-10 00:00:00 2019-09-10 00:00:00 +6 1 1 6 Operation log \N fa-history /info/op 2019-09-10 00:00:00 2019-09-10 00:00:00 +7 0 1 1 Dashboard \N fa-bar-chart / 2019-09-10 00:00:00 2019-09-10 00:00:00 +\. + + +-- +-- Data for Name: goadmin_operation_log; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_operation_log (id, user_id, path, method, ip, input, created_at, updated_at) FROM stdin; +\. + + +-- +-- Data for Name: goadmin_site; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_site (id, key, value, description, state, created_at, updated_at) FROM stdin; +\. + + +-- +-- Data for Name: goadmin_permissions; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_permissions (id, name, slug, http_method, http_path, created_at, updated_at) FROM stdin; +1 All permission * * 2019-09-10 00:00:00 2019-09-10 00:00:00 +2 Dashboard dashboard GET,PUT,POST,DELETE / 2019-09-10 00:00:00 2019-09-10 00:00:00 +\. + + +-- +-- Data for Name: goadmin_role_menu; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_role_menu (role_id, menu_id, created_at, updated_at) FROM stdin; +1 1 2019-09-10 00:00:00 2019-09-10 00:00:00 +1 7 2019-09-10 00:00:00 2019-09-10 00:00:00 +2 7 2019-09-10 00:00:00 2019-09-10 00:00:00 +\. + + +-- +-- Data for Name: goadmin_role_permissions; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) FROM stdin; +1 1 2019-09-10 00:00:00 2019-09-10 00:00:00 +1 2 2019-09-10 00:00:00 2019-09-10 00:00:00 +2 2 2019-09-10 00:00:00 2019-09-10 00:00:00 +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +0 3 \N \N +\. + + +-- +-- Data for Name: goadmin_role_users; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_role_users (role_id, user_id, created_at, updated_at) FROM stdin; +1 1 2019-09-10 00:00:00 2019-09-10 00:00:00 +2 2 2019-09-10 00:00:00 2019-09-10 00:00:00 +\. + + +-- +-- Data for Name: goadmin_roles; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_roles (id, name, slug, created_at, updated_at) FROM stdin; +1 Administrator administrator 2019-09-10 00:00:00 2019-09-10 00:00:00 +2 Operator operator 2019-09-10 00:00:00 2019-09-10 00:00:00 +\. + + +-- +-- Data for Name: goadmin_session; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_session (id, sid, "values", created_at, updated_at) FROM stdin; +\. + + +-- +-- Data for Name: goadmin_user_permissions; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) FROM stdin; +1 1 2019-09-10 00:00:00 2019-09-10 00:00:00 +2 2 2019-09-10 00:00:00 2019-09-10 00:00:00 +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +0 1 \N \N +\. + + +-- +-- Data for Name: goadmin_users; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_users (id, username, password, name, avatar, remember_token, created_at, updated_at) FROM stdin; +1 admin $2a$10$OxWYJJGTP2gi00l2x06QuOWqw5VR47MQCJ0vNKnbMYfrutij10Hwe admin tlNcBVK9AvfYH7WEnwB1RKvocJu8FfRy4um3DJtwdHuJy0dwFsLOgAc0xUfh 2019-09-10 00:00:00 2019-09-10 00:00:00 +2 operator $2a$10$rVqkOzHjN2MdlEprRflb1eGP0oZXuSrbJLOmJagFsCd81YZm0bsh. Operator \N 2019-09-10 00:00:00 2019-09-10 00:00:00 +\. + + +-- +-- Name: goadmin_menu_myid_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.goadmin_menu_myid_seq', 7, true); + + +-- +-- Name: goadmin_operation_log_myid_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.goadmin_operation_log_myid_seq', 1, true); + + +-- +-- Name: goadmin_permissions_myid_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.goadmin_permissions_myid_seq', 2, true); + + +-- +-- Name: goadmin_roles_myid_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.goadmin_roles_myid_seq', 2, true); + + +-- +-- Name: goadmin_site_myid_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.goadmin_site_myid_seq', 1, true); + + +-- +-- Name: goadmin_session_myid_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.goadmin_session_myid_seq', 1, true); + + +-- +-- Name: goadmin_users_myid_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.goadmin_users_myid_seq', 2, true); + + +-- +-- Name: goadmin_menu goadmin_menu_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.goadmin_menu + ADD CONSTRAINT goadmin_menu_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_operation_log goadmin_operation_log_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.goadmin_operation_log + ADD CONSTRAINT goadmin_operation_log_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_permissions goadmin_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.goadmin_permissions + ADD CONSTRAINT goadmin_permissions_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_roles goadmin_roles_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.goadmin_roles + ADD CONSTRAINT goadmin_roles_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_site goadmin_site_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.goadmin_site + ADD CONSTRAINT goadmin_site_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_session goadmin_session_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.goadmin_session + ADD CONSTRAINT goadmin_session_pkey PRIMARY KEY (id); + + +-- +-- Name: goadmin_users goadmin_users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.goadmin_users + ADD CONSTRAINT goadmin_users_pkey PRIMARY KEY (id); + + +-- +-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres +-- + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +REVOKE ALL ON SCHEMA public FROM postgres; +GRANT ALL ON SCHEMA public TO postgres; +GRANT ALL ON SCHEMA public TO PUBLIC; + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/data/admin.sql b/data/admin.sql new file mode 100644 index 0000000000000000000000000000000000000000..24c0b5677490bfda22bf6b31cab6905093eed904 --- /dev/null +++ b/data/admin.sql @@ -0,0 +1,309 @@ +# ************************************************************ +# Sequel Pro SQL dump +# Version 4468 +# +# http://www.sequelpro.com/ +# https://github.com/sequelpro/sequelpro +# +# Host: 127.0.0.1 (MySQL 5.7.19) +# Database: godmin +# Generation Time: 2019-09-12 04:16:47 +0000 +# ************************************************************ + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +# Dump of table goadmin_menu +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_menu`; + +CREATE TABLE `goadmin_menu` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `parent_id` int(11) unsigned NOT NULL DEFAULT '0', + `type` tinyint(4) unsigned NOT NULL DEFAULT '0', + `order` int(11) unsigned NOT NULL DEFAULT '0', + `title` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `icon` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `uri` varchar(3000) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `header` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `plugin_name` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `uuid` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_menu` WRITE; +/*!40000 ALTER TABLE `goadmin_menu` DISABLE KEYS */; + +INSERT INTO `goadmin_menu` (`id`, `parent_id`, `type`, `order`, `title`, `icon`, `uri`, `plugin_name`, `header`, `created_at`, `updated_at`) +VALUES + (1,0,1,2,'Admin','fa-tasks','','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,1,1,2,'Users','fa-users','/info/manager','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (3,1,1,3,'Roles','fa-user','/info/roles','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (4,1,1,4,'Permission','fa-ban','/info/permission','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (5,1,1,5,'Menu','fa-bars','/menu','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (6,1,1,6,'Operation log','fa-history','/info/op','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (7,0,1,1,'Dashboard','fa-bar-chart','/','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_menu` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_operation_log +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_operation_log`; + +CREATE TABLE `goadmin_operation_log` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(11) unsigned NOT NULL, + `path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `method` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL, + `ip` varchar(15) COLLATE utf8mb4_unicode_ci NOT NULL, + `input` text COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `admin_operation_log_user_id_index` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +# Dump of table goadmin_site +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_site`; + +CREATE TABLE `goadmin_site` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `value` longtext COLLATE utf8mb4_unicode_ci, + `description` varchar(3000) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `state` tinyint(3) unsigned NOT NULL DEFAULT '0', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +# Dump of table goadmin_permissions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_permissions`; + +CREATE TABLE `goadmin_permissions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `slug` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `http_method` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `http_path` text COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `admin_permissions_name_unique` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_permissions` WRITE; +/*!40000 ALTER TABLE `goadmin_permissions` DISABLE KEYS */; + +INSERT INTO `goadmin_permissions` (`id`, `name`, `slug`, `http_method`, `http_path`, `created_at`, `updated_at`) +VALUES + (1,'All permission','*','','*','2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,'Dashboard','dashboard','GET,PUT,POST,DELETE','/','2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_permissions` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_role_menu +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_role_menu`; + +CREATE TABLE `goadmin_role_menu` ( + `role_id` int(11) unsigned NOT NULL, + `menu_id` int(11) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + KEY `admin_role_menu_role_id_menu_id_index` (`role_id`,`menu_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_role_menu` WRITE; +/*!40000 ALTER TABLE `goadmin_role_menu` DISABLE KEYS */; + +INSERT INTO `goadmin_role_menu` (`role_id`, `menu_id`, `created_at`, `updated_at`) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (1,7,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,7,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (1,8,'2019-09-11 10:20:55','2019-09-11 10:20:55'), + (2,8,'2019-09-11 10:20:55','2019-09-11 10:20:55'); + +/*!40000 ALTER TABLE `goadmin_role_menu` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_role_permissions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_role_permissions`; + +CREATE TABLE `goadmin_role_permissions` ( + `role_id` int(11) unsigned NOT NULL, + `permission_id` int(11) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `admin_role_permissions` (`role_id`,`permission_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_role_permissions` WRITE; +/*!40000 ALTER TABLE `goadmin_role_permissions` DISABLE KEYS */; + +INSERT INTO `goadmin_role_permissions` (`role_id`, `permission_id`, `created_at`, `updated_at`) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (1,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_role_permissions` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_role_users +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_role_users`; + +CREATE TABLE `goadmin_role_users` ( + `role_id` int(11) unsigned NOT NULL, + `user_id` int(11) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `admin_user_roles` (`role_id`,`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_role_users` WRITE; +/*!40000 ALTER TABLE `goadmin_role_users` DISABLE KEYS */; + +INSERT INTO `goadmin_role_users` (`role_id`, `user_id`, `created_at`, `updated_at`) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_role_users` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_roles +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_roles`; + +CREATE TABLE `goadmin_roles` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `slug` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `admin_roles_name_unique` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_roles` WRITE; +/*!40000 ALTER TABLE `goadmin_roles` DISABLE KEYS */; + +INSERT INTO `goadmin_roles` (`id`, `name`, `slug`, `created_at`, `updated_at`) +VALUES + (1,'Administrator','administrator','2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,'Operator','operator','2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_roles` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_session +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_session`; + +CREATE TABLE `goadmin_session` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `sid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `values` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + + + +# Dump of table goadmin_user_permissions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_user_permissions`; + +CREATE TABLE `goadmin_user_permissions` ( + `user_id` int(11) unsigned NOT NULL, + `permission_id` int(11) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `admin_user_permissions` (`user_id`,`permission_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_user_permissions` WRITE; +/*!40000 ALTER TABLE `goadmin_user_permissions` DISABLE KEYS */; + +INSERT INTO `goadmin_user_permissions` (`user_id`, `permission_id`, `created_at`, `updated_at`) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_user_permissions` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_users +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_users`; + +CREATE TABLE `goadmin_users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `password` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `avatar` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `admin_users_username_unique` (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_users` WRITE; +/*!40000 ALTER TABLE `goadmin_users` DISABLE KEYS */; + +INSERT INTO `goadmin_users` (`id`, `username`, `password`, `name`, `avatar`, `remember_token`, `created_at`, `updated_at`) +VALUES + (1,'admin','$2a$10$U3F/NSaf2kaVbyXTBp7ppOn0jZFyRqXRnYXB.AMioCjXl3Ciaj4oy','admin','','tlNcBVK9AvfYH7WEnwB1RKvocJu8FfRy4um3DJtwdHuJy0dwFsLOgAc0xUfh','2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,'operator','$2a$10$rVqkOzHjN2MdlEprRflb1eGP0oZXuSrbJLOmJagFsCd81YZm0bsh.','Operator','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_users` ENABLE KEYS */; +UNLOCK TABLES; + + + +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/data/migrations/admin_2020_04_14_100427_ms.sql b/data/migrations/admin_2020_04_14_100427_ms.sql new file mode 100644 index 0000000000000000000000000000000000000000..76320fdb89df2200c577807a183afe3ddc6a345f --- /dev/null +++ b/data/migrations/admin_2020_04_14_100427_ms.sql @@ -0,0 +1,10 @@ +CREATE TABLE[goadmin_site] ( + [id] int identity(1,1) , + [key] varchar(100) NOT NULL, + [value] text NOT NULL, + [state] tinyint NOT NULL DEFAULT 0, + [description] varchar(3000) NOT NULL, + [created_at] datetime NULL DEFAULT GETDATE(), + [updated_at] datetime NULL DEFAULT GETDATE(), + PRIMARY KEY ([id]), +) diff --git a/data/migrations/admin_2020_04_14_100427_mysql.sql b/data/migrations/admin_2020_04_14_100427_mysql.sql new file mode 100644 index 0000000000000000000000000000000000000000..2e116e038384368d5f0c6f29604bc96fc1c2e8c1 --- /dev/null +++ b/data/migrations/admin_2020_04_14_100427_mysql.sql @@ -0,0 +1,10 @@ +CREATE TABLE `goadmin_site` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `value` longtext COLLATE utf8mb4_unicode_ci, + `description` varchar(3000) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `state` tinyint(3) unsigned NOT NULL DEFAULT '0', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; \ No newline at end of file diff --git a/data/migrations/admin_2020_04_14_100427_postgres.sql b/data/migrations/admin_2020_04_14_100427_postgres.sql new file mode 100644 index 0000000000000000000000000000000000000000..b94d875f04ba7665d8873e97ecc8e94b02c90ab6 --- /dev/null +++ b/data/migrations/admin_2020_04_14_100427_postgres.sql @@ -0,0 +1,72 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 9.5.14 +-- Dumped by pg_dump version 10.5 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'EUC_CN'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; +SET row_security = off; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: goadmin_site_myid_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.goadmin_site_myid_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + MAXVALUE 99999999 + CACHE 1; + +ALTER TABLE public.goadmin_site_myid_seq OWNER TO postgres; + +-- +-- Name: goadmin_site; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.goadmin_site ( + id integer DEFAULT nextval('public.goadmin_site_myid_seq'::regclass) NOT NULL, + key character varying(100) NOT NULL, + value text NOT NULL, + type integer DEFAULT 0, + description character varying(3000), + state integer DEFAULT 0, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.goadmin_site OWNER TO postgres; + +-- +-- Data for Name: goadmin_site; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) FROM stdin; +\. + + +-- +-- Name: goadmin_site goadmin_site_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.goadmin_site + ADD CONSTRAINT goadmin_site_pkey PRIMARY KEY (id); + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/data/migrations/admin_2020_04_14_100427_sqlite.sql b/data/migrations/admin_2020_04_14_100427_sqlite.sql new file mode 100644 index 0000000000000000000000000000000000000000..50a33174fa0243420abb5f84494e5de9b70afc08 --- /dev/null +++ b/data/migrations/admin_2020_04_14_100427_sqlite.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS "goadmin_site" ( +`id` integer PRIMARY KEY autoincrement, +`key` CHAR(100) COLLATE NOCASE NOT NULL, +`value` text COLLATE NOCASE NOT NULL, +`state` INT NOT NULL DEFAULT '0', +`description` CHAR(3000) COLLATE NOCASE, +`created_at` TIMESTAMP default CURRENT_TIMESTAMP, +`updated_at` TIMESTAMP default CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/data/migrations/admin_2020_08_04_092427_ms.sql b/data/migrations/admin_2020_08_04_092427_ms.sql new file mode 100644 index 0000000000000000000000000000000000000000..9f474db3119f9ce91a9cc640b6cc0dc6033edcb9 --- /dev/null +++ b/data/migrations/admin_2020_08_04_092427_ms.sql @@ -0,0 +1,3 @@ +ALTER TABLE goadmin_menu +ADD plugin_name varchar(150) NOT NULL DEFAULT '', +ADD uuid varchar(150) NOT NULL DEFAULT ''; \ No newline at end of file diff --git a/data/migrations/admin_2020_08_04_092427_mysql.sql b/data/migrations/admin_2020_08_04_092427_mysql.sql new file mode 100644 index 0000000000000000000000000000000000000000..ffb5733efa8b8cb4e44a9586a751937dc63c2d0c --- /dev/null +++ b/data/migrations/admin_2020_08_04_092427_mysql.sql @@ -0,0 +1,3 @@ +ALTER TABLE goadmin_menu +ADD COLUMN `uuid` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', +ADD COLUMN `plugin_name` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''; \ No newline at end of file diff --git a/data/migrations/admin_2020_08_04_092427_postgres.sql b/data/migrations/admin_2020_08_04_092427_postgres.sql new file mode 100644 index 0000000000000000000000000000000000000000..fd468f06541613e1814c734a4f83ccb1fcc3670e --- /dev/null +++ b/data/migrations/admin_2020_08_04_092427_postgres.sql @@ -0,0 +1,3 @@ +ALTER TABLE goadmin_menu +ADD COLUMN plugin_name character varying(150) NOT NULL DEFAULT '', +ADD COLUMN uuid character varying(150) NOT NULL DEFAULT ''; \ No newline at end of file diff --git a/data/migrations/admin_2020_08_04_092427_sqlite.sql b/data/migrations/admin_2020_08_04_092427_sqlite.sql new file mode 100644 index 0000000000000000000000000000000000000000..858c757540d981c5b3712516a1839010e154d2fd --- /dev/null +++ b/data/migrations/admin_2020_08_04_092427_sqlite.sql @@ -0,0 +1,3 @@ +ALTER TABLE goadmin_menu +ADD COLUMN `uuid` varchar(150) NOT NULL DEFAULT '', +ADD COLUMN `plugin_name` varchar(150) NOT NULL DEFAULT ''; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..dd98f243bdbb8fd2677dec605efcf0af16a0884c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +version: "3.3" +services: + mysql: + image: mysql:5.6 + container_name: mysql + ports: + - "3306:3306" + volumes: + - mysql:/data + networks: + - goadmin + environment: + - MYSQL_ROOT_PASSWORD=goadmin + postgres: + image: postgres:latest + container_name: postgres + ports: + - "5432:5432" + volumes: + - postgres:/data + networks: + - goadmin + environment: + - POSTGRES_PASSWORD=goadmin + goadmin: + image: josingcjx/goadmin:1.1 + tty: true + container_name: goadmin + volumes: + - .:/home/goadmin + networks: + - goadmin + command: + - /bin/bash + portainer: + image: portainer/portainer:latest + container_name: portainer + restart: always + ports: + - "9000:9000" + networks: + - goadmin + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - portainer:/data +networks: + goadmin: {} +volumes: + portainer: {} + postgres: {} + mysql: {} diff --git a/engine/engine.go b/engine/engine.go new file mode 100644 index 0000000000000000000000000000000000000000..be90896e4119a488e644b5eed5caaf695f97640b --- /dev/null +++ b/engine/engine.go @@ -0,0 +1,793 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package engine + +import ( + "bytes" + "encoding/json" + errors2 "errors" + "fmt" + template2 "html/template" + "net/http" + "runtime/debug" + "strings" + "sync" + + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/template/icon" + "github.com/GoAdminGroup/go-admin/template/types/action" + + "github.com/GoAdminGroup/go-admin/adapter" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/menu" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/modules/system" + "github.com/GoAdminGroup/go-admin/modules/ui" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" +) + +// Engine is the core component of goAdmin. It has two attributes. +// PluginList is an array of plugin. Adapter is the adapter of +// web framework context and goAdmin context. The relationship of adapter and +// plugin is that the adapter use the plugin which contains routers and +// controller methods to inject into the framework entity and make it work. +type Engine struct { + PluginList plugins.Plugins + Adapter adapter.WebFrameWork + Services service.List + NavButtons *types.Buttons + config *config.Config + announceLock sync.Once +} + +// Default return the default engine instance. +func Default() *Engine { + engine = &Engine{ + Adapter: defaultAdapter, + Services: service.GetServices(), + NavButtons: new(types.Buttons), + } + return engine +} + +// Use enable the adapter. +func (eng *Engine) Use(router interface{}) error { + if eng.Adapter == nil { + emptyAdapterPanic() + } + + eng.Services.Add(auth.InitCSRFTokenSrv(eng.DefaultConnection())) + eng.initSiteSetting() + eng.initJumpNavButtons() + eng.initPlugins() + + printInitMsg(language.Get("initialize success")) + + return eng.Adapter.Use(router, eng.PluginList) +} + +// AddPlugins add the plugins +func (eng *Engine) AddPlugins(plugs ...plugins.Plugin) *Engine { + + if len(plugs) == 0 { + return eng + } + + for _, plug := range plugs { + eng.PluginList = eng.PluginList.Add(plug) + } + + return eng +} + +// AddPluginList add the plugins +func (eng *Engine) AddPluginList(plugs plugins.Plugins) *Engine { + + if len(plugs) == 0 { + return eng + } + + for _, plug := range plugs { + eng.PluginList = eng.PluginList.Add(plug) + } + + return eng +} + +// FindPluginByName find the register plugin by given name. +func (eng *Engine) FindPluginByName(name string) (plugins.Plugin, bool) { + for _, plug := range eng.PluginList { + if plug.Name() == name { + return plug, true + } + } + return nil, false +} + +// AddAuthService customize the auth logic with given callback function. +func (eng *Engine) AddAuthService(processor auth.Processor) *Engine { + eng.Services.Add("auth", auth.NewService(processor)) + return eng +} + +// ============================ +// Config APIs +// ============================ + +func (eng *Engine) announce() *Engine { + if eng.config.Debug { + eng.announceLock.Do(func() { + fmt.Printf(language.Get("goadmin is now running. \nrunning in \"debug\" mode. switch to \"release\" mode in production.\n\n")) + }) + } + return eng +} + +// AddConfig set the global config. +func (eng *Engine) AddConfig(cfg *config.Config) *Engine { + return eng.setConfig(cfg).announce().initDatabase() +} + +// setConfig set the config of engine. +func (eng *Engine) setConfig(cfg *config.Config) *Engine { + eng.config = config.Initialize(cfg) + sysCheck, themeCheck := template.CheckRequirements() + if !sysCheck { + logger.Panicf(language.Get("wrong goadmin version, theme %s required goadmin version are %s"), + eng.config.Theme, strings.Join(template.Default().GetRequirements(), ",")) + } + if !themeCheck { + logger.Panicf(language.Get("wrong theme version, goadmin %s required version of theme %s is %s"), + system.Version(), eng.config.Theme, strings.Join(system.RequireThemeVersion()[eng.config.Theme], ",")) + } + return eng +} + +// AddConfigFromJSON set the global config from json file. +func (eng *Engine) AddConfigFromJSON(path string) *Engine { + cfg := config.ReadFromJson(path) + return eng.setConfig(&cfg).announce().initDatabase() +} + +// AddConfigFromYAML set the global config from yaml file. +func (eng *Engine) AddConfigFromYAML(path string) *Engine { + cfg := config.ReadFromYaml(path) + return eng.setConfig(&cfg).announce().initDatabase() +} + +// AddConfigFromINI set the global config from ini file. +func (eng *Engine) AddConfigFromINI(path string) *Engine { + cfg := config.ReadFromINI(path) + return eng.setConfig(&cfg).announce().initDatabase() +} + +// InitDatabase initialize all database connection. +func (eng *Engine) initDatabase() *Engine { + printInitMsg(language.Get("initialize database connections")) + for driver, databaseCfg := range eng.config.Databases.GroupByDriver() { + eng.Services.Add(driver, db.GetConnectionByDriver(driver).InitDB(databaseCfg)) + } + if defaultAdapter == nil { + emptyAdapterPanic() + } + defaultConnection := db.GetConnection(eng.Services) + defaultAdapter.SetConnection(defaultConnection) + eng.Adapter.SetConnection(defaultConnection) + return eng +} + +// AddAdapter add the adapter of engine. +func (eng *Engine) AddAdapter(ada adapter.WebFrameWork) *Engine { + eng.Adapter = ada + defaultAdapter = ada + return eng +} + +// defaultAdapter is the default adapter of engine. +var defaultAdapter adapter.WebFrameWork + +var engine *Engine + +// navButtons is the default buttons in the navigation bar. +var navButtons = new(types.Buttons) + +func emptyAdapterPanic() { + logger.Panic(language.Get("adapter is nil, import the default adapter or use addadapter method add the adapter")) +} + +// Register set default adapter of engine. +func Register(ada adapter.WebFrameWork) { + if ada == nil { + emptyAdapterPanic() + } + defaultAdapter = ada +} + +// User call the User method of defaultAdapter. +func User(ctx interface{}) (models.UserModel, bool) { + return defaultAdapter.User(ctx) +} + +// User call the User method of engine adapter. +func (eng *Engine) User(ctx interface{}) (models.UserModel, bool) { + return eng.Adapter.User(ctx) +} + +// ============================ +// DB Connection APIs +// ============================ + +// DB return the db connection of given driver. +func (eng *Engine) DB(driver string) db.Connection { + return db.GetConnectionFromService(eng.Services.Get(driver)) +} + +// DefaultConnection return the default db connection. +func (eng *Engine) DefaultConnection() db.Connection { + return eng.DB(eng.config.Databases.GetDefault().Driver) +} + +// MysqlConnection return the mysql db connection of given driver. +func (eng *Engine) MysqlConnection() db.Connection { + return db.GetConnectionFromService(eng.Services.Get(db.DriverMysql)) +} + +// MssqlConnection return the mssql db connection of given driver. +func (eng *Engine) MssqlConnection() db.Connection { + return db.GetConnectionFromService(eng.Services.Get(db.DriverMssql)) +} + +// PostgresqlConnection return the postgresql db connection of given driver. +func (eng *Engine) PostgresqlConnection() db.Connection { + return db.GetConnectionFromService(eng.Services.Get(db.DriverPostgresql)) +} + +// SqliteConnection return the sqlite db connection of given driver. +func (eng *Engine) SqliteConnection() db.Connection { + return db.GetConnectionFromService(eng.Services.Get(db.DriverSqlite)) +} + +// OceanBaseConnection return the OceanBase db connection of given driver. +func (eng *Engine) OceanBaseConnection() db.Connection { + return db.GetConnectionFromService(eng.Services.Get(db.DriverOceanBase)) +} + +type ConnectionSetter func(db.Connection) + +// ResolveConnection resolve the specified driver connection. +func (eng *Engine) ResolveConnection(setter ConnectionSetter, driver string) *Engine { + setter(eng.DB(driver)) + return eng +} + +// ResolveMysqlConnection resolve the mysql connection. +func (eng *Engine) ResolveMysqlConnection(setter ConnectionSetter) *Engine { + eng.ResolveConnection(setter, db.DriverMysql) + return eng +} + +// ResolveMssqlConnection resolve the mssql connection. +func (eng *Engine) ResolveMssqlConnection(setter ConnectionSetter) *Engine { + eng.ResolveConnection(setter, db.DriverMssql) + return eng +} + +// ResolveSqliteConnection resolve the sqlite connection. +func (eng *Engine) ResolveSqliteConnection(setter ConnectionSetter) *Engine { + eng.ResolveConnection(setter, db.DriverSqlite) + return eng +} + +// ResolvePostgresqlConnection resolve the postgres connection. +func (eng *Engine) ResolvePostgresqlConnection(setter ConnectionSetter) *Engine { + eng.ResolveConnection(setter, db.DriverPostgresql) + return eng +} + +type Setter func(*Engine) + +// Clone copy a new Engine. +func (eng *Engine) Clone(e *Engine) *Engine { + e = eng + return eng +} + +// ClonedBySetter copy a new Engine by a setter callback function. +func (eng *Engine) ClonedBySetter(setter Setter) *Engine { + setter(eng) + return eng +} + +func (eng *Engine) deferHandler(conn db.Connection) context.Handler { + return func(ctx *context.Context) { + defer func(ctx *context.Context) { + if user, ok := ctx.UserValue["user"].(models.UserModel); ok { + var input []byte + form := ctx.Request.MultipartForm + if form != nil { + input, _ = json.Marshal((*form).Value) + } + + models.OperationLog().SetConn(conn).New(user.Id, ctx.Path(), ctx.Method(), ctx.LocalIP(), string(input)) + } + + if err := recover(); err != nil { + logger.Error(err) + logger.Error(string(debug.Stack())) + + var ( + errMsg string + ok bool + e error + ) + + if errMsg, ok = err.(string); !ok { + if e, ok = err.(error); ok { + errMsg = e.Error() + } + } + + if errMsg == "" { + errMsg = "system error" + } + + if ctx.WantJSON() { + response.Error(ctx, errMsg) + return + } + + eng.errorPanelHTML(ctx, new(bytes.Buffer), errors2.New(errMsg)) + } + }(ctx) + ctx.Next() + } +} + +// wrapWithAuthMiddleware wrap a auth middleware to the given handler. +func (eng *Engine) wrapWithAuthMiddleware(handler context.Handler) context.Handlers { + conn := db.GetConnection(eng.Services) + return []context.Handler{eng.deferHandler(conn), response.OffLineHandler, auth.Middleware(conn), handler} +} + +// wrapWithAuthMiddleware wrap a auth middleware to the given handler. +func (eng *Engine) wrap(handler context.Handler) context.Handlers { + conn := db.GetConnection(eng.Services) + return []context.Handler{eng.deferHandler(conn), response.OffLineHandler, handler} +} + +// ============================ +// Initialize methods +// ============================ + +// AddNavButtons add the nav buttons. +func (eng *Engine) AddNavButtons(title template2.HTML, icon string, action types.Action) *Engine { + btn := types.GetNavButton(title, icon, action) + *eng.NavButtons = append(*eng.NavButtons, btn) + return eng +} + +// AddNavButtonsRaw add the nav buttons. +func (eng *Engine) AddNavButtonsRaw(btns ...types.Button) *Engine { + *eng.NavButtons = append(*eng.NavButtons, btns...) + return eng +} + +type navJumpButtonParam struct { + Exist bool + Icon string + BtnName string + URL string + Title string + TitleScore string +} + +func (eng *Engine) addJumpNavButton(param navJumpButtonParam) *Engine { + if param.Exist { + *eng.NavButtons = (*eng.NavButtons).AddNavButton(param.Icon, param.BtnName, + action.JumpInNewTab(config.Url(param.URL), + language.GetWithScope(param.Title, param.TitleScore))) + } + return eng +} + +func printInitMsg(msg string) { + logger.Info(msg) +} + +func (eng *Engine) initJumpNavButtons() { + printInitMsg(language.Get("initialize navigation buttons")) + for _, param := range eng.initNavJumpButtonParams() { + eng.addJumpNavButton(param) + } + navButtons = eng.NavButtons + eng.Services.Add(ui.ServiceKey, ui.NewService(eng.NavButtons)) +} + +func (eng *Engine) initPlugins() { + + printInitMsg(language.Get("initialize plugins")) + + eng.AddPlugins(admin.NewAdmin()).AddPluginList(plugins.Get()) + + var plugGenerators = make(table.GeneratorList) + + for i := range eng.PluginList { + if eng.PluginList[i].Name() != "admin" { + printInitMsg("--> " + eng.PluginList[i].Name()) + eng.PluginList[i].InitPlugin(eng.Services) + if !eng.PluginList[i].GetInfo().SkipInstallation { + eng.AddGenerator("plugin_"+eng.PluginList[i].Name(), eng.PluginList[i].GetSettingPage()) + } + plugGenerators = plugGenerators.Combine(eng.PluginList[i].GetGenerators()) + } + } + adm := eng.AdminPlugin().AddGenerators(plugGenerators) + adm.InitPlugin(eng.Services) + plugins.Add(adm) +} + +func (eng *Engine) initNavJumpButtonParams() []navJumpButtonParam { + return []navJumpButtonParam{ + { + Exist: !eng.config.HideConfigCenterEntrance, + Icon: icon.Gear, + BtnName: types.NavBtnSiteName, + URL: "/info/site/edit", + Title: "site setting", + TitleScore: "config", + }, { + Exist: !eng.config.HideToolEntrance && eng.config.IsNotProductionEnvironment(), + Icon: icon.Wrench, + BtnName: types.NavBtnToolName, + URL: "/info/generate/new", + Title: "code generate tool", + TitleScore: "tool", + }, { + Exist: !eng.config.HideAppInfoEntrance, + Icon: icon.Info, + BtnName: types.NavBtnInfoName, + URL: "/application/info", + Title: "site info", + TitleScore: "system", + }, { + Exist: !eng.config.HidePluginEntrance, + Icon: icon.Th, + BtnName: types.NavBtnPlugName, + URL: "/plugins", + Title: "plugins", + TitleScore: "plugin", + }, + } +} + +func (eng *Engine) initSiteSetting() { + + printInitMsg(language.Get("initialize configuration")) + + err := eng.config.Update(models.Site(). + SetConn(eng.DefaultConnection()). + Init(eng.config.ToMap()). + AllToMap()) + if err != nil { + logger.Panic(err) + } + eng.Services.Add("config", config.SrvWithConfig(eng.config)) + + errors.Init() +} + +// ============================ +// HTML Content Render APIs +// ============================ + +// Content call the Content method of engine adapter. +// If adapter is nil, it will panic. +func (eng *Engine) Content(ctx interface{}, panel types.GetPanelFn) { + if eng.Adapter == nil { + emptyAdapterPanic() + } + eng.Adapter.Content(ctx, panel, eng.AdminPlugin().GetAddOperationFn(), *eng.NavButtons...) +} + +// Content call the Content method of defaultAdapter. +// If defaultAdapter is nil, it will panic. +func Content(ctx interface{}, panel types.GetPanelFn) { + if defaultAdapter == nil { + emptyAdapterPanic() + } + defaultAdapter.Content(ctx, panel, engine.AdminPlugin().GetAddOperationFn(), *navButtons...) +} + +// Data inject the route and corresponding handler to the web framework. +func (eng *Engine) Data(method, url string, handler context.Handler, noAuth ...bool) { + if len(noAuth) > 0 && noAuth[0] { + eng.Adapter.AddHandler(method, url, eng.wrap(handler)) + } else { + eng.Adapter.AddHandler(method, url, eng.wrapWithAuthMiddleware(handler)) + } +} + +// HTML inject the route and corresponding handler wrapped by the given function to the web framework. +func (eng *Engine) HTML(method, url string, fn types.GetPanelInfoFn, noAuth ...bool) { + + var handler = func(ctx *context.Context) { + panel, err := fn(ctx) + if err != nil { + panel = template.WarningPanel(ctx, err.Error()) + } + + eng.AdminPlugin().GetAddOperationFn()(panel.Callbacks...) + + var ( + tmpl, tmplName = template.Default(ctx).GetTemplate(ctx.IsPjax()) + + user = auth.Auth(ctx) + buf = new(bytes.Buffer) + ) + + hasError := tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(ctx, &types.NewPageParam{ + User: user, + Menu: menu.GetGlobalMenu(user, eng.Adapter.GetConnection(), ctx.Lang()).SetActiveClass(config.URLRemovePrefix(ctx.Path())), + Panel: panel.GetContent(eng.config.IsProductionEnvironment()), + Assets: template.GetComponentAssetImportHTML(ctx), + Buttons: eng.NavButtons.CheckPermission(user), + TmplHeadHTML: template.Default(ctx).GetHeadHTML(), + TmplFootJS: template.Default(ctx).GetFootJS(), + Iframe: ctx.IsIframe(), + })) + + if hasError != nil { + logger.Error(fmt.Sprintf("error: %s adapter content, ", eng.Adapter.Name()), hasError) + } + + ctx.HTMLByte(http.StatusOK, buf.Bytes()) + } + + if len(noAuth) > 0 && noAuth[0] { + eng.Adapter.AddHandler(method, url, eng.wrap(handler)) + } else { + eng.Adapter.AddHandler(method, url, eng.wrapWithAuthMiddleware(handler)) + } +} + +// HTMLFile inject the route and corresponding handler which returns the panel content of given html file path +// to the web framework. +func (eng *Engine) HTMLFile(method, url, path string, data map[string]interface{}, noAuth ...bool) { + + var handler = func(ctx *context.Context) { + + cbuf := new(bytes.Buffer) + + t, err := template2.ParseFiles(path) + if err != nil { + eng.errorPanelHTML(ctx, cbuf, err) + return + } else if err := t.Execute(cbuf, data); err != nil { + eng.errorPanelHTML(ctx, cbuf, err) + return + } + + var ( + tmpl, tmplName = template.Default(ctx).GetTemplate(ctx.IsPjax()) + + user = auth.Auth(ctx) + buf = new(bytes.Buffer) + ) + + hasError := tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(ctx, &types.NewPageParam{ + User: user, + Menu: menu.GetGlobalMenu(user, eng.Adapter.GetConnection(), ctx.Lang()).SetActiveClass(eng.config.URLRemovePrefix(ctx.Path())), + Panel: types.Panel{ + Content: template.HTML(cbuf.String()), + }, + Assets: template.GetComponentAssetImportHTML(ctx), + Buttons: eng.NavButtons.CheckPermission(user), + TmplHeadHTML: template.Default(ctx).GetHeadHTML(), + TmplFootJS: template.Default(ctx).GetFootJS(), + Iframe: ctx.IsIframe(), + })) + + if hasError != nil { + logger.Error(fmt.Sprintf("error: %s adapter content, ", eng.Adapter.Name()), hasError) + } + + ctx.HTMLByte(http.StatusOK, buf.Bytes()) + } + + if len(noAuth) > 0 && noAuth[0] { + eng.Adapter.AddHandler(method, url, eng.wrap(handler)) + } else { + eng.Adapter.AddHandler(method, url, eng.wrapWithAuthMiddleware(handler)) + } +} + +// HTMLFiles inject the route and corresponding handler which returns the panel content of given html files path +// to the web framework. +func (eng *Engine) HTMLFiles(method, url string, data map[string]interface{}, files ...string) { + eng.Adapter.AddHandler(method, url, eng.wrapWithAuthMiddleware(eng.htmlFilesHandler(data, files...))) +} + +// HTMLFilesNoAuth inject the route and corresponding handler which returns the panel content of given html files path +// to the web framework without auth check. +func (eng *Engine) HTMLFilesNoAuth(method, url string, data map[string]interface{}, files ...string) { + eng.Adapter.AddHandler(method, url, eng.wrap(eng.htmlFilesHandler(data, files...))) +} + +// HTMLFiles inject the route and corresponding handler which returns the panel content of given html files path +// to the web framework. +func (eng *Engine) htmlFilesHandler(data map[string]interface{}, files ...string) context.Handler { + return func(ctx *context.Context) { + + cbuf := new(bytes.Buffer) + + t, err := template2.ParseFiles(files...) + if err != nil { + eng.errorPanelHTML(ctx, cbuf, err) + return + } else if err := t.Execute(cbuf, data); err != nil { + eng.errorPanelHTML(ctx, cbuf, err) + return + } + + var ( + tmpl, tmplName = template.Default(ctx).GetTemplate(ctx.IsPjax()) + + user = auth.Auth(ctx) + buf = new(bytes.Buffer) + ) + + hasError := tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(ctx, &types.NewPageParam{ + User: user, + Menu: menu.GetGlobalMenu(user, eng.Adapter.GetConnection(), ctx.Lang()).SetActiveClass(eng.config.URLRemovePrefix(ctx.Path())), + Panel: types.Panel{ + Content: template.HTML(cbuf.String()), + }, + Assets: template.GetComponentAssetImportHTML(ctx), + Buttons: eng.NavButtons.CheckPermission(user), + TmplHeadHTML: template.Default(ctx).GetHeadHTML(), + TmplFootJS: template.Default(ctx).GetFootJS(), + Iframe: ctx.IsIframe(), + })) + + if hasError != nil { + logger.Error(fmt.Sprintf("error: %s adapter content, ", eng.Adapter.Name()), hasError) + } + + ctx.HTMLByte(http.StatusOK, buf.Bytes()) + } +} + +// errorPanelHTML add an error panel html to context response. +func (eng *Engine) errorPanelHTML(ctx *context.Context, buf *bytes.Buffer, err error) { + + user := auth.Auth(ctx) + tmpl, tmplName := template.Default(ctx).GetTemplate(ctx.IsPjax()) + + hasError := tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(ctx, &types.NewPageParam{ + User: user, + Menu: menu.GetGlobalMenu(user, eng.Adapter.GetConnection(), ctx.Lang()).SetActiveClass(eng.config.URLRemovePrefix(ctx.Path())), + Panel: template.WarningPanel(ctx, err.Error()).GetContent(eng.config.IsProductionEnvironment()), + Assets: template.GetComponentAssetImportHTML(ctx), + Buttons: (*eng.NavButtons).CheckPermission(user), + TmplHeadHTML: template.Default(ctx).GetHeadHTML(), + TmplFootJS: template.Default(ctx).GetFootJS(), + Iframe: ctx.IsIframe(), + })) + + if hasError != nil { + logger.Error(fmt.Sprintf("error: %s adapter content, ", eng.Adapter.Name()), hasError) + } + + ctx.HTMLByte(http.StatusOK, buf.Bytes()) +} + +// ============================ +// Admin Plugin APIs +// ============================ + +// AddGenerators add the admin generators. +func (eng *Engine) AddGenerators(list ...table.GeneratorList) *Engine { + plug, exist := eng.FindPluginByName("admin") + if exist { + plug.(*admin.Admin).AddGenerators(list...) + return eng + } + eng.PluginList = append(eng.PluginList, admin.NewAdmin(list...)) + return eng +} + +// AdminPlugin get the admin plugin. if not exist, create one. +func (eng *Engine) AdminPlugin() *admin.Admin { + plug, exist := eng.FindPluginByName("admin") + if exist { + return plug.(*admin.Admin) + } + adm := admin.NewAdmin() + eng.PluginList = append(eng.PluginList, adm) + return adm +} + +// SetCaptcha set the captcha config. +func (eng *Engine) SetCaptcha(captcha map[string]string) *Engine { + eng.AdminPlugin().SetCaptcha(captcha) + return eng +} + +// SetCaptchaDriver set the captcha config with driver. +func (eng *Engine) SetCaptchaDriver(driver string) *Engine { + eng.AdminPlugin().SetCaptcha(map[string]string{"driver": driver}) + return eng +} + +// AddGenerator add table model generator. +func (eng *Engine) AddGenerator(key string, g table.Generator) *Engine { + eng.AdminPlugin().AddGenerator(key, g) + return eng +} + +// AddGlobalDisplayProcessFn call types.AddGlobalDisplayProcessFn. +func (eng *Engine) AddGlobalDisplayProcessFn(f types.FieldFilterFn) *Engine { + types.AddGlobalDisplayProcessFn(f) + return eng +} + +// AddDisplayFilterLimit call types.AddDisplayFilterLimit. +func (eng *Engine) AddDisplayFilterLimit(limit int) *Engine { + types.AddLimit(limit) + return eng +} + +// AddDisplayFilterTrimSpace call types.AddDisplayFilterTrimSpace. +func (eng *Engine) AddDisplayFilterTrimSpace() *Engine { + types.AddTrimSpace() + return eng +} + +// AddDisplayFilterSubstr call types.AddDisplayFilterSubstr. +func (eng *Engine) AddDisplayFilterSubstr(start int, end int) *Engine { + types.AddSubstr(start, end) + return eng +} + +// AddDisplayFilterToTitle call types.AddDisplayFilterToTitle. +func (eng *Engine) AddDisplayFilterToTitle() *Engine { + types.AddToTitle() + return eng +} + +// AddDisplayFilterToUpper call types.AddDisplayFilterToUpper. +func (eng *Engine) AddDisplayFilterToUpper() *Engine { + types.AddToUpper() + return eng +} + +// AddDisplayFilterToLower call types.AddDisplayFilterToLower. +func (eng *Engine) AddDisplayFilterToLower() *Engine { + types.AddToUpper() + return eng +} + +// AddDisplayFilterXssFilter call types.AddDisplayFilterXssFilter. +func (eng *Engine) AddDisplayFilterXssFilter() *Engine { + types.AddXssFilter() + return eng +} + +// AddDisplayFilterXssJsFilter call types.AddDisplayFilterXssJsFilter. +func (eng *Engine) AddDisplayFilterXssJsFilter() *Engine { + types.AddXssJsFilter() + return eng +} diff --git a/examples/beego/main.go b/examples/beego/main.go new file mode 100644 index 0000000000000000000000000000000000000000..88b7a023e8e488e9a7afb00c3e170c4640f03810 --- /dev/null +++ b/examples/beego/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/beego" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/themes/adminlte" + "github.com/astaxie/beego" +) + +func main() { + app := beego.NewApp() + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + }, + }, + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + UrlPrefix: "admin", + IndexUrl: "/", + Debug: true, + Language: language.CN, + ColorScheme: adminlte.ColorschemeSkinBlack, + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // eng.AddConfigFromJSON("../datamodel/config.json") + + beego.SetStaticPath("/uploads", "uploads") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(app); err != nil { + panic(err) + } + + // you can custom your pages like: + + eng.HTML("GET", "/admin", datamodel.GetContent) + + beego.BConfig.Listen.HTTPAddr = "127.0.0.1" + beego.BConfig.Listen.HTTPPort = 9087 + go app.Run() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} diff --git a/examples/beego2/main.go b/examples/beego2/main.go new file mode 100644 index 0000000000000000000000000000000000000000..179bfa6e998e7c9e408395d5561406084011173f --- /dev/null +++ b/examples/beego2/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/beego2" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/themes/adminlte" + "github.com/beego/beego/v2/server/web" +) + +func main() { + app := web.NewHttpSever() + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "123456", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + }, + }, + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + UrlPrefix: "admin", + IndexUrl: "/", + Debug: true, + Language: language.CN, + ColorScheme: adminlte.ColorschemeSkinBlack, + } + + template.AddComp(chartjs.NewChart()) + + examplePlugin := example.NewExample() + + web.SetStaticPath("/uploads", "uploads") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(app); err != nil { + panic(err) + } + + eng.HTML("GET", "/admin", datamodel.GetContent) + + app.Cfg.Listen.HTTPSAddr = "127.0.0.1" + app.Cfg.Listen.HTTPPort = 9087 + go app.Run("") + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} diff --git a/examples/buffalo/main.go b/examples/buffalo/main.go new file mode 100644 index 0000000000000000000000000000000000000000..509e62ca55988482f752f8a435b500fdcf650c05 --- /dev/null +++ b/examples/buffalo/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "log" + "net/http" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/buffalo" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/themes/adminlte" + "github.com/gobuffalo/buffalo" +) + +func main() { + bu := buffalo.New(buffalo.Options{ + Env: "test", + Addr: "127.0.0.1:9033", + }) + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + }, + }, + UrlPrefix: "admin", + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + Language: language.EN, + IndexUrl: "/", + Debug: true, + ColorScheme: adminlte.ColorschemeSkinBlack, + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // eng.AddConfigFromJSON("../datamodel/config.json") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(bu); err != nil { + panic(err) + } + + bu.ServeFiles("/uploads", http.Dir("./uploads")) + + // you can custom your pages like: + + eng.HTML("GET", "/admin", datamodel.GetContent) + + go func() { + _ = bu.Serve() + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} diff --git a/examples/chi/main.go b/examples/chi/main.go new file mode 100644 index 0000000000000000000000000000000000000000..12a26268f1ca18648424ae5012c299d2e332a47b --- /dev/null +++ b/examples/chi/main.go @@ -0,0 +1,127 @@ +package main + +import ( + "log" + "net/http" + "os" + "os/signal" + "path/filepath" + "strings" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/chi" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/themes/adminlte" + "github.com/go-chi/chi" +) + +func main() { + r := chi.NewRouter() + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + }, + }, + UrlPrefix: "admin", + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + Language: language.EN, + IndexUrl: "/", + Debug: true, + ColorScheme: adminlte.ColorschemeSkinBlack, + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // eng.AddConfigFromJSON("../datamodel/config.json") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(r); err != nil { + panic(err) + } + + workDir, _ := os.Getwd() + filesDir := filepath.Join(workDir, "uploads") + FileServer(r, "/uploads", http.Dir(filesDir)) + + // you can custom your pages like: + + eng.HTML("GET", "/admin", datamodel.GetContent) + + go func() { + _ = http.ListenAndServe(":3333", r) + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} + +// FileServer conveniently sets up a http.FileServer handler to serve +// static files from a http.FileSystem. +func FileServer(r chi.Router, path string, root http.FileSystem) { + if strings.ContainsAny(path, "{}*") { + panic("FileServer does not permit URL parameters.") + } + + fs := http.StripPrefix(path, http.FileServer(root)) + + if path != "/" && path[len(path)-1] != '/' { + r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP) + path += "/" + } + path += "*" + + r.Get(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fs.ServeHTTP(w, r) + })) +} diff --git a/examples/datamodel/authors.go b/examples/datamodel/authors.go new file mode 100644 index 0000000000000000000000000000000000000000..5446a645ae0fa7df6bc2ed3d3fc05f68f505c2b6 --- /dev/null +++ b/examples/datamodel/authors.go @@ -0,0 +1,39 @@ +package datamodel + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +// GetAuthorsTable return the model of table author. +func GetAuthorsTable(ctx *context.Context) (authorsTable table.Table) { + + authorsTable = table.NewDefaultTable(ctx, table.DefaultConfig()) + + // connect your custom connection + // authorsTable = table.NewDefaultTable(ctx, table.DefaultConfigWithDriverAndConnection("mysql", "admin")) + + info := authorsTable.GetInfo() + info.AddField("ID", "id", db.Int).FieldSortable() + info.AddField("First Name", "first_name", db.Varchar) + info.AddField("Last Name", "last_name", db.Varchar) + info.AddField("Email", "email", db.Varchar) + info.AddField("Birthdate", "birthdate", db.Date) + info.AddField("Added", "added", db.Timestamp) + + info.SetTable("authors").SetTitle("Authors").SetDescription("Authors") + + formList := authorsTable.GetForm() + formList.AddField("ID", "id", db.Int, form.Default).FieldDisplayButCanNotEditWhenUpdate().FieldDisableWhenCreate() + formList.AddField("First Name", "first_name", db.Varchar, form.Text) + formList.AddField("Last Name", "last_name", db.Varchar, form.Text) + formList.AddField("Email", "email", db.Varchar, form.Text) + formList.AddField("Birthdate", "birthdate", db.Date, form.Text) + formList.AddField("Added", "added", db.Timestamp, form.Text) + + formList.SetTable("authors").SetTitle("Authors").SetDescription("Authors") + + return +} diff --git a/examples/datamodel/bootstrap.go b/examples/datamodel/bootstrap.go new file mode 100644 index 0000000000000000000000000000000000000000..025a443dfaa67333965ca495d48b93bf9db30f99 --- /dev/null +++ b/examples/datamodel/bootstrap.go @@ -0,0 +1 @@ +package datamodel diff --git a/examples/datamodel/config.json b/examples/datamodel/config.json new file mode 100644 index 0000000000000000000000000000000000000000..9ea474321e1139d19adfcaee6bf5ad8b9b4c57b1 --- /dev/null +++ b/examples/datamodel/config.json @@ -0,0 +1,24 @@ +{ + "database": { + "default": { + "host": "127.0.0.1", + "port": "3306", + "user": "root", + "pwd": "root", + "name": "godmin", + "max_idle_con": 50, + "max_open_con": 150, + "driver": "mysql" + } + }, + "domain": "localhost", + "prefix": "admin", + "store": { + "path": "./uploads", + "prefix": "uploads" + }, + "language": "cn", + "index": "/", + "debug": true, + "color_scheme": "skin-black" +} \ No newline at end of file diff --git a/examples/datamodel/content.go b/examples/datamodel/content.go new file mode 100644 index 0000000000000000000000000000000000000000..7e0970c62ae142cf635a236bdf5f7ceee2cb2d8f --- /dev/null +++ b/examples/datamodel/content.go @@ -0,0 +1,396 @@ +package datamodel + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/context" + tmpl "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/go-admin/template/icon" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/themes/adminlte/components/chart_legend" + "github.com/GoAdminGroup/themes/adminlte/components/description" + "github.com/GoAdminGroup/themes/adminlte/components/infobox" + "github.com/GoAdminGroup/themes/adminlte/components/productlist" + "github.com/GoAdminGroup/themes/adminlte/components/progress_group" + "github.com/GoAdminGroup/themes/adminlte/components/smallbox" +) + +// GetContent return the content of index page. +func GetContent(ctx *context.Context) (types.Panel, error) { + + components := tmpl.Default(ctx) + colComp := components.Col() + + /************************** + * Info Box + /**************************/ + + infobox1 := infobox.New(). + SetText("CPU TRAFFIC"). + SetColor("aqua"). + SetNumber("100"). + SetIcon("ion-ios-gear-outline"). + GetContent() + + infobox2 := infobox.New(). + SetText("Likes"). + SetColor("red"). + SetNumber("1030.00$"). + SetIcon(icon.GooglePlus). + GetContent() + + infobox3 := infobox.New(). + SetText("Sales"). + SetColor("green"). + SetNumber("760"). + SetIcon("ion-ios-cart-outline"). + GetContent() + + infobox4 := infobox.New(). + SetText("New Members"). + SetColor("yellow"). + SetNumber("2,349"). + SetIcon("ion-ios-people-outline"). // svg is ok + GetContent() + + var size = types.Size(6, 3, 0).XS(12) + infoboxCol1 := colComp.SetSize(size).SetContent(infobox1).GetContent() + infoboxCol2 := colComp.SetSize(size).SetContent(infobox2).GetContent() + infoboxCol3 := colComp.SetSize(size).SetContent(infobox3).GetContent() + infoboxCol4 := colComp.SetSize(size).SetContent(infobox4).GetContent() + row1 := components.Row().SetContent(infoboxCol1 + infoboxCol2 + infoboxCol3 + infoboxCol4).GetContent() + + /************************** + * Box + /**************************/ + + table := components.Table().SetInfoList([]map[string]types.InfoItem{ + { + "Order ID": {Content: "OR9842"}, + "Item": {Content: "Call of Duty IV"}, + "Status": {Content: "shipped"}, + "Popularity": {Content: "90%"}, + }, { + "Order ID": {Content: "OR9842"}, + "Item": {Content: "Call of Duty IV"}, + "Status": {Content: "shipped"}, + "Popularity": {Content: "90%"}, + }, { + "Order ID": {Content: "OR9842"}, + "Item": {Content: "Call of Duty IV"}, + "Status": {Content: "shipped"}, + "Popularity": {Content: "90%"}, + }, { + "Order ID": {Content: "OR9842"}, + "Item": {Content: "Call of Duty IV"}, + "Status": {Content: "shipped"}, + "Popularity": {Content: "90%"}, + }, + }).SetThead(types.Thead{ + {Head: "Order ID"}, + {Head: "Item"}, + {Head: "Status"}, + {Head: "Popularity"}, + }).GetContent() + + boxInfo := components.Box(). + WithHeadBorder(). + SetHeader("Latest Orders"). + SetHeadColor("#f7f7f7"). + SetBody(table). + SetFooter(``). + GetContent() + + tableCol := colComp.SetSize(types.SizeMD(8)).SetContent(row1 + boxInfo).GetContent() + + /************************** + * Product List + /**************************/ + + productList := productlist.New().SetData([]map[string]string{ + { + "img": "http://adminlte.io/themes/AdminLTE/dist/img/default-50x50.gif", + "title": "GoAdmin", + "has_tabel": "true", + "labeltype": "warning", + "label": "free", + "description": `a framework help you build the dataviz system`, + }, { + "img": "http://adminlte.io/themes/AdminLTE/dist/img/default-50x50.gif", + "title": "GoAdmin", + "has_tabel": "true", + "labeltype": "warning", + "label": "free", + "description": `a framework help you build the dataviz system`, + }, { + "img": "http://adminlte.io/themes/AdminLTE/dist/img/default-50x50.gif", + "title": "GoAdmin", + "has_tabel": "true", + "labeltype": "warning", + "label": "free", + "description": `a framework help you build the dataviz system`, + }, { + "img": "http://adminlte.io/themes/AdminLTE/dist/img/default-50x50.gif", + "title": "GoAdmin", + "has_tabel": "true", + "labeltype": "warning", + "label": "free", + "description": `a framework help you build the dataviz system`, + }, + }).GetContent() + + boxWarning := components.Box().SetTheme("warning").WithHeadBorder().SetHeader("Recently Added Products"). + SetBody(productList). + SetFooter(`View All Products`). + GetContent() + + newsCol := colComp.SetSize(types.SizeMD(4)).SetContent(boxWarning).GetContent() + + row5 := components.Row().SetContent(tableCol + newsCol).GetContent() + + /************************** + * Box + /**************************/ + + line := chartjs.Line() + + lineChart := line. + SetID("salechart"). + SetHeight(180). + SetTitle("Sales: 1 Jan, 2019 - 30 Jul, 2019"). + SetLabels([]string{"January", "February", "March", "April", "May", "June", "July"}). + AddDataSet("Electronics"). + DSData([]float64{65, 59, 80, 81, 56, 55, 40}). + DSFill(false). + DSBorderColor("rgb(210, 214, 222)"). + DSLineTension(0.1). + AddDataSet("Digital Goods"). + DSData([]float64{28, 48, 40, 19, 86, 27, 90}). + DSFill(false). + DSBorderColor("rgba(60,141,188,1)"). + DSLineTension(0.1). + GetContent() + + title := `

Goal Completion

` + progressGroup := progress_group.New(). + SetTitle("Add Products to Cart"). + SetColor("#76b2d4"). + SetDenominator(200). + SetMolecular(160). + SetPercent(80). + GetContent() + + progressGroup1 := progress_group.New(). + SetTitle("Complete Purchase"). + SetColor("#f17c6e"). + SetDenominator(400). + SetMolecular(310). + SetPercent(80). + GetContent() + + progressGroup2 := progress_group.New(). + SetTitle("Visit Premium Page"). + SetColor("#ace0ae"). + SetDenominator(800). + SetMolecular(490). + SetPercent(80). + GetContent() + + progressGroup3 := progress_group.New(). + SetTitle("Send Inquiries"). + SetColor("#fdd698"). + SetDenominator(500). + SetMolecular(250). + SetPercent(50). + GetContent() + + boxInternalCol1 := colComp.SetContent(lineChart).SetSize(types.SizeMD(8)).GetContent() + boxInternalCol2 := colComp. + SetContent(template.HTML(title) + progressGroup + progressGroup1 + progressGroup2 + progressGroup3). + SetSize(types.SizeMD(4)). + GetContent() + + boxInternalRow := components.Row().SetContent(boxInternalCol1 + boxInternalCol2).GetContent() + + description1 := description.New(). + SetPercent("17"). + SetNumber("¥140,100"). + SetTitle("TOTAL REVENUE"). + SetArrow("up"). + SetColor("green"). + SetBorder("right"). + GetContent() + + description2 := description.New(). + SetPercent("2"). + SetNumber("440,560"). + SetTitle("TOTAL REVENUE"). + SetArrow("down"). + SetColor("red"). + SetBorder("right"). + GetContent() + + description3 := description.New(). + SetPercent("12"). + SetNumber("¥140,050"). + SetTitle("TOTAL REVENUE"). + SetArrow("up"). + SetColor("green"). + SetBorder("right"). + GetContent() + + description4 := description.New(). + SetPercent("1"). + SetNumber("30943"). + SetTitle("TOTAL REVENUE"). + SetArrow("up"). + SetColor("green"). + GetContent() + + size2 := types.SizeXS(6).SM(3) + boxInternalCol3 := colComp.SetContent(description1).SetSize(size2).GetContent() + boxInternalCol4 := colComp.SetContent(description2).SetSize(size2).GetContent() + boxInternalCol5 := colComp.SetContent(description3).SetSize(size2).GetContent() + boxInternalCol6 := colComp.SetContent(description4).SetSize(size2).GetContent() + + boxInternalRow2 := components.Row().SetContent(boxInternalCol3 + boxInternalCol4 + boxInternalCol5 + boxInternalCol6).GetContent() + + box := components.Box().WithHeadBorder().SetHeader("Monthly Recap Report"). + SetBody(boxInternalRow). + SetFooter(boxInternalRow2). + GetContent() + + boxcol := colComp.SetContent(box).SetSize(types.SizeMD(12)).GetContent() + row2 := components.Row().SetContent(boxcol).GetContent() + + /************************** + * Small Box + /**************************/ + + smallbox1 := smallbox.New().SetColor("blue").SetIcon("ion-ios-gear-outline").SetUrl("/").SetTitle("new users").SetValue("345¥").GetContent() + smallbox2 := smallbox.New().SetColor("yellow").SetIcon("ion-ios-cart-outline").SetUrl("/").SetTitle("new users").SetValue("80%").GetContent() + smallbox3 := smallbox.New().SetColor("red").SetIcon("fa-user").SetUrl("/").SetTitle("new users").SetValue("645¥").GetContent() + smallbox4 := smallbox.New().SetColor("green").SetIcon("ion-ios-cart-outline").SetUrl("/").SetTitle("new users").SetValue("889¥").GetContent() + + col1 := colComp.SetSize(size).SetContent(smallbox1).GetContent() + col2 := colComp.SetSize(size).SetContent(smallbox2).GetContent() + col3 := colComp.SetSize(size).SetContent(smallbox3).GetContent() + col4 := colComp.SetSize(size).SetContent(smallbox4).GetContent() + + row3 := components.Row().SetContent(col1 + col2 + col3 + col4).GetContent() + + /************************** + * Pie Chart + /**************************/ + + pie := chartjs.Pie(). + SetHeight(170). + SetLabels([]string{"Navigator", "Opera", "Safari", "FireFox", "IE", "Chrome"}). + SetID("pieChart"). + AddDataSet("Chrome"). + DSData([]float64{100, 300, 600, 400, 500, 700}). + DSBackgroundColor([]chartjs.Color{ + "rgb(255, 205, 86)", "rgb(54, 162, 235)", "rgb(255, 99, 132)", "rgb(255, 205, 86)", "rgb(54, 162, 235)", "rgb(255, 99, 132)", + }). + GetContent() + + legend := chart_legend.New().SetData([]map[string]string{ + { + "label": " Chrome", + "color": "red", + }, { + "label": " IE", + "color": "Green", + }, { + "label": " FireFox", + "color": "yellow", + }, { + "label": " Sarafri", + "color": "blue", + }, { + "label": " Opera", + "color": "light-blue", + }, { + "label": " Navigator", + "color": "gray", + }, + }).GetContent() + + boxDanger := components.Box().SetTheme("danger").WithHeadBorder().SetHeader("Browser Usage"). + SetBody(components.Row(). + SetContent(colComp.SetSize(types.SizeMD(8)). + SetContent(pie). + GetContent() + colComp.SetSize(types.SizeMD(4)). + SetContent(legend). + GetContent()).GetContent()). + SetFooter(`

View All Users

`). + GetContent() + + tabs := components.Tabs().SetData([]map[string]template.HTML{ + { + "title": "tabs1", + "content": template.HTML(`How to use: + +

Exactly like the original bootstrap tabs except you should use + the custom wrapper .nav-tabs-custom to achieve this style.

+ A wonderful serenity has taken possession of my entire soul, + like these sweet mornings of spring which I enjoy with my whole heart. + I am alone, and feel the charm of existence in this spot, + which was created for the bliss of souls like mine. I am so happy, + my dear friend, so absorbed in the exquisite sense of mere tranquil existence, + that I neglect my talents. I should be incapable of drawing a single stroke + at the present moment; and yet I feel that I never was a greater artist than now.`), + }, { + "title": "tabs2", + "content": template.HTML(` + The European languages are members of the same family. Their separate existence is a myth. + For science, music, sport, etc, Europe uses the same vocabulary. The languages only differ + in their grammar, their pronunciation and their most common words. Everyone realizes why a + new common language would be desirable: one could refuse to pay expensive translators. To + achieve this, it would be necessary to have uniform grammar, pronunciation and more common + words. If several languages coalesce, the grammar of the resulting language is more simple + and regular than that of the individual languages. + `), + }, { + "title": "tabs3", + "content": template.HTML(` + Lorem Ipsum is simply dummy text of the printing and typesetting industry. + Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, + when an unknown printer took a galley of type and scrambled it to make a type specimen book. + It has survived not only five centuries, but also the leap into electronic typesetting, + remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset + sheets containing Lorem Ipsum passages, and more recently with desktop publishing software + like Aldus PageMaker including versions of Lorem Ipsum. + `), + }, + }).GetContent() + + buttonTest := `` + popupForm := `
+
+ + +
+
+ + +
+
` + popup := components.Popup().SetID("exampleModal"). + SetFooter("Save Change"). + SetTitle("this is a popup"). + SetBody(template.HTML(popupForm)). + GetContent() + + col5 := colComp.SetSize(types.SizeMD(8)).SetContent(tabs + template.HTML(buttonTest)).GetContent() + col6 := colComp.SetSize(types.SizeMD(4)).SetContent(boxDanger + popup).GetContent() + + row4 := components.Row().SetContent(col5 + col6).GetContent() + + return types.Panel{ + Content: row3 + row2 + row5 + row4, + Title: "Dashboard", + Description: "dashboard example", + }, nil +} diff --git a/examples/datamodel/goadmin_super_users.go b/examples/datamodel/goadmin_super_users.go new file mode 100644 index 0000000000000000000000000000000000000000..9fc9bbfe27cafc5751e78c03e5e3306d17985b19 --- /dev/null +++ b/examples/datamodel/goadmin_super_users.go @@ -0,0 +1,40 @@ +package datamodel + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +func GetGoadminSuperUsersTable(ctx *context.Context) table.Table { + + goadminSuperUsers := table.NewDefaultTable(ctx, table.DefaultConfigWithDriver("mysql")) + + info := goadminSuperUsers.GetInfo().HideFilterArea() + + info.AddField("Id", "id", db.Int).FieldFilterable() + info.AddField("Username", "username", db.Varchar) + info.AddField("Password", "password", db.Varchar) + info.AddField("Name", "name", db.Varchar) + info.AddField("Avatar", "avatar", db.Varchar) + info.AddField("Remember_token", "remember_token", db.Varchar) + info.AddField("Created_at", "created_at", db.Timestamp) + info.AddField("Updated_at", "updated_at", db.Timestamp) + + info.SetTable("goadmin_super_users").SetTitle("GoadminSuperUsers").SetDescription("GoadminSuperUsers") + + formList := goadminSuperUsers.GetForm() + formList.AddField("Id", "id", db.Int, form.Default) + formList.AddField("Username", "username", db.Varchar, form.Text) + formList.AddField("Password", "password", db.Varchar, form.Password) + formList.AddField("Name", "name", db.Varchar, form.Text) + formList.AddField("Avatar", "avatar", db.Varchar, form.Text) + formList.AddField("Remember_token", "remember_token", db.Varchar, form.Text) + formList.AddField("Created_at", "created_at", db.Timestamp, form.Datetime) + formList.AddField("Updated_at", "updated_at", db.Timestamp, form.Datetime) + + formList.SetTable("goadmin_super_users").SetTitle("GoadminSuperUsers").SetDescription("GoadminSuperUsers") + + return goadminSuperUsers +} diff --git a/examples/datamodel/mysql_types.go b/examples/datamodel/mysql_types.go new file mode 100644 index 0000000000000000000000000000000000000000..5a5d2d8da1f334469d374518db1fa565840374f2 --- /dev/null +++ b/examples/datamodel/mysql_types.go @@ -0,0 +1,115 @@ +package datamodel + +import ( + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +// GetAllTypesTable return the model of table types. +func GetAllTypesTable() table.Table { + + allTypesTable := table.NewDefaultTable(nil, table.DefaultConfigWithDriver("mysql")) + + info := allTypesTable.GetInfo() + + info.AddField("Id", "id", db.Int) + info.AddField("Type_1", "type_1", db.Tinyint) + info.AddField("Type_2", "type_2", db.Smallint) + info.AddField("Type_3", "type_3", db.Mediumint) + info.AddField("Type_4", "type_4", db.Bigint) + info.AddField("Type_5", "type_5", db.Float) + info.AddField("Type_6", "type_6", db.Double) + info.AddField("Type_7", "type_7", db.Double) + info.AddField("Type_8", "type_8", db.Double) + info.AddField("Type_9", "type_9", db.Decimal) + info.AddField("Type_10", "type_10", db.Bit) + info.AddField("Type_11", "type_11", db.Tinyint) + info.AddField("Type_12", "type_12", db.Tinyint) + info.AddField("Type_13", "type_13", db.Decimal) + info.AddField("Type_14", "type_14", db.Decimal) + info.AddField("Type_15", "type_15", db.Decimal) + info.AddField("Type_16", "type_16", db.Char) + info.AddField("Type_17", "type_17", db.Varchar) + info.AddField("Type_18", "type_18", db.Tinytext) + info.AddField("Type_19", "type_19", db.Text) + info.AddField("Type_20", "type_20", db.Mediumtext) + info.AddField("Type_21", "type_21", db.Longtext) + info.AddField("Type_22", "type_22", db.Tinyblob) + info.AddField("Type_23", "type_23", db.Mediumblob) + info.AddField("Type_24", "type_24", db.Blob) + info.AddField("Type_25", "type_25", db.Longblob) + info.AddField("Type_26", "type_26", db.Binary) + info.AddField("Type_27", "type_27", db.Varbinary) + info.AddField("Type_28", "type_28", db.Enum) + info.AddField("Type_29", "type_29", db.Set) + info.AddField("Type_30", "type_30", db.Date) + info.AddField("Type_31", "type_31", db.Datetime) + info.AddField("Type_32", "type_32", db.Timestamp) + info.AddField("Type_33", "type_33", db.Time) + info.AddField("Type_34", "type_34", db.Year) + info.AddField("Type_35", "type_35", db.Geometry) + info.AddField("Type_36", "type_36", db.Point) + info.AddField("Type_39", "type_39", db.Multilinestring) + info.AddField("Type_41", "type_41", db.Multipolygon) + info.AddField("Type_37", "type_37", db.Linestring) + info.AddField("Type_38", "type_38", db.Polygon) + info.AddField("Type_40", "type_40", db.Multipoint) + info.AddField("Type_42", "type_42", db.Geometrycollection) + info.AddField("Type_50", "type_50", db.Double) + info.AddField("Type_51", "type_51", db.JSON) + + info.SetTable("all_types").SetTitle("All_types").SetDescription("All_types") + + formList := allTypesTable.GetForm() + + formList.AddField("Id", "id", db.Int, form.Default) + formList.AddField("Type_1", "type_1", db.Tinyint, form.Number) + formList.AddField("Type_2", "type_2", db.Smallint, form.Number) + formList.AddField("Type_3", "type_3", db.Mediumint, form.Number) + formList.AddField("Type_4", "type_4", db.Bigint, form.Number) + formList.AddField("Type_5", "type_5", db.Float, form.Text) + formList.AddField("Type_6", "type_6", db.Double, form.Text) + formList.AddField("Type_7", "type_7", db.Double, form.Text) + formList.AddField("Type_8", "type_8", db.Double, form.Text) + formList.AddField("Type_9", "type_9", db.Decimal, form.Text) + formList.AddField("Type_10", "type_10", db.Bit, form.Text) + formList.AddField("Type_11", "type_11", db.Tinyint, form.Number) + formList.AddField("Type_12", "type_12", db.Tinyint, form.Number) + formList.AddField("Type_13", "type_13", db.Decimal, form.Text) + formList.AddField("Type_14", "type_14", db.Decimal, form.Text) + formList.AddField("Type_15", "type_15", db.Decimal, form.Text) + formList.AddField("Type_16", "type_16", db.Char, form.Text) + formList.AddField("Type_17", "type_17", db.Varchar, form.Text) + formList.AddField("Type_18", "type_18", db.Tinytext, form.RichText) + formList.AddField("Type_19", "type_19", db.Text, form.RichText) + formList.AddField("Type_20", "type_20", db.Mediumtext, form.RichText) + formList.AddField("Type_21", "type_21", db.Longtext, form.RichText) + formList.AddField("Type_22", "type_22", db.Tinyblob, form.Text) + formList.AddField("Type_23", "type_23", db.Mediumblob, form.Text) + formList.AddField("Type_24", "type_24", db.Blob, form.Text) + formList.AddField("Type_25", "type_25", db.Longblob, form.Text) + formList.AddField("Type_26", "type_26", db.Binary, form.Text) + formList.AddField("Type_27", "type_27", db.Varbinary, form.Text) + formList.AddField("Type_28", "type_28", db.Enum, form.Text) + formList.AddField("Type_29", "type_29", db.Set, form.Text) + formList.AddField("Type_30", "type_30", db.Date, form.Datetime) + formList.AddField("Type_31", "type_31", db.Datetime, form.Datetime) + formList.AddField("Type_32", "type_32", db.Timestamp, form.Datetime) + formList.AddField("Type_33", "type_33", db.Time, form.Datetime) + formList.AddField("Type_34", "type_34", db.Year, form.Datetime) + formList.AddField("Type_35", "type_35", db.Geometry, form.Text) + formList.AddField("Type_36", "type_36", db.Point, form.Text) + formList.AddField("Type_39", "type_39", db.Multilinestring, form.Text) + formList.AddField("Type_41", "type_41", db.Multipolygon, form.Text) + formList.AddField("Type_37", "type_37", db.Linestring, form.Text) + formList.AddField("Type_38", "type_38", db.Polygon, form.Text) + formList.AddField("Type_40", "type_40", db.Multipoint, form.Text) + formList.AddField("Type_42", "type_42", db.Geometrycollection, form.Text) + formList.AddField("Type_50", "type_50", db.Double, form.Text) + formList.AddField("Type_51", "type_51", db.JSON, form.Text) + + formList.SetTable("all_types").SetTitle("All_types").SetDescription("All_types") + + return allTypesTable +} diff --git a/examples/datamodel/posts.go b/examples/datamodel/posts.go new file mode 100644 index 0000000000000000000000000000000000000000..2152f12d99f339ad4357d3ea1753bdb690f52988 --- /dev/null +++ b/examples/datamodel/posts.go @@ -0,0 +1,70 @@ +package datamodel + +import ( + template2 "html/template" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/form" + editType "github.com/GoAdminGroup/go-admin/template/types/table" +) + +// GetPostsTable return the model of table posts. +func GetPostsTable(ctx *context.Context) (postsTable table.Table) { + + postsTable = table.NewDefaultTable(ctx, table.DefaultConfig().SetExportable(true)) + + info := postsTable.GetInfo() + info.AddField("ID", "id", db.Int).FieldSortable() + info.AddField("Title", "title", db.Varchar) + info.AddField("AuthorID", "author_id", db.Varchar).FieldDisplay(func(value types.FieldModel) interface{} { + return template.Default(ctx). + Link(). + SetURL("/admin/info/authors/detail?__goadmin_detail_pk=" + value.Value). + SetContent(template2.HTML(value.Value)). + OpenInNewTab(). + SetTabTitle("Author Detail"). + GetContent() + }) + info.AddField("AuthorName", "name", db.Varchar).FieldDisplay(func(value types.FieldModel) interface{} { + first, _ := value.Row["authors_goadmin_join_first_name"].(string) + last, _ := value.Row["authors_goadmin_join_last_name"].(string) + return first + " " + last + }) + info.AddField("AuthorFirstName", "first_name", db.Varchar).FieldJoin(types.Join{ + Field: "author_id", + JoinField: "id", + Table: "authors", + }).FieldHide() + info.AddField("AuthorLastName", "last_name", db.Varchar).FieldJoin(types.Join{ + Field: "author_id", + JoinField: "id", + Table: "authors", + }).FieldHide() + info.AddField("Description", "description", db.Varchar) + info.AddField("Content", "content", db.Varchar).FieldEditAble(editType.Textarea) + info.AddField("Date", "date", db.Varchar) + info.AddField("AuthorCreatedAt", "created_at", db.Varchar).FieldJoin(types.Join{ + Field: "author_id", + JoinField: "id", + Table: "authors", + }) + info.AddField("Created_at", "created_at", db.Varchar).FieldFilterable() + + info.SetTable("posts").SetTitle("Posts").SetDescription("Posts") + + formList := postsTable.GetForm() + formList.AddField("ID", "id", db.Int, form.Default).FieldDisplayButCanNotEditWhenUpdate().FieldDisableWhenCreate() + formList.AddField("Title", "title", db.Varchar, form.Text) + formList.AddField("Description", "description", db.Varchar, form.Text) + formList.AddField("Content", "content", db.Varchar, form.RichText).FieldEnableFileUpload() + formList.AddField("Date", "date", db.Varchar, form.Datetime) + formList.EnableAjax("Request Success", "Request Failed") + + formList.SetTable("posts").SetTitle("Posts").SetDescription("Posts") + + return +} diff --git a/examples/datamodel/tables.go b/examples/datamodel/tables.go new file mode 100644 index 0000000000000000000000000000000000000000..601bdbee8c2998ff22f58baf209e4157af551604 --- /dev/null +++ b/examples/datamodel/tables.go @@ -0,0 +1,20 @@ +package datamodel + +import "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + +// Generators is a map of table models. +// +// The key of generators is the prefix of table info url. +// The corresponding value is the Form and TableName data. +// +// http://{{config.Domain}}:{{Port}}/{{config.Prefix}}/info/{{key}} +// +// example: +// +// "posts" => http://localhost:9033/admin/info/posts +// "authors" => http://localhost:9033/admin/info/authors +// +var Generators = map[string]table.Generator{ + "posts": GetPostsTable, + "authors": GetAuthorsTable, +} diff --git a/examples/datamodel/user.go b/examples/datamodel/user.go new file mode 100644 index 0000000000000000000000000000000000000000..a8f471fbbf7b8c8fe0193fc2a5029fb4cc932c57 --- /dev/null +++ b/examples/datamodel/user.go @@ -0,0 +1,210 @@ +package datamodel + +import ( + "fmt" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/db" + form2 "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/icon" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/action" + "github.com/GoAdminGroup/go-admin/template/types/form" + selection "github.com/GoAdminGroup/go-admin/template/types/form/select" + editType "github.com/GoAdminGroup/go-admin/template/types/table" +) + +// GetUserTable return the model of table user. +func GetUserTable(ctx *context.Context) (userTable table.Table) { + + userTable = table.NewDefaultTable(ctx, table.Config{ + Driver: db.DriverMysql, + CanAdd: true, + Editable: true, + Deletable: true, + Exportable: true, + Connection: table.DefaultConnectionName, + PrimaryKey: table.PrimaryKey{ + Type: db.Int, + Name: table.DefaultPrimaryKeyName, + }, + }) + + info := userTable.GetInfo().SetFilterFormLayout(form.LayoutThreeCol) + info.AddField("ID", "id", db.Int).FieldSortable() + info.AddField("Name", "name", db.Varchar).FieldEditAble(editType.Text). + FieldFilterable(types.FilterType{Operator: types.FilterOperatorLike}) + info.AddField("Gender", "gender", db.Tinyint).FieldDisplay(func(model types.FieldModel) interface{} { + if model.Value == "0" { + return "men" + } + if model.Value == "1" { + return "women" + } + return "unknown" + }).FieldEditAble(editType.Switch).FieldEditOptions(types.FieldOptions{ + {Value: "0", Text: "👦"}, + {Value: "1", Text: "👧"}, + }).FieldFilterable(types.FilterType{FormType: form.SelectSingle}).FieldFilterOptions(types.FieldOptions{ + {Value: "0", Text: "men"}, + {Value: "1", Text: "women"}, + }) + info.AddColumn("Personality", func(value types.FieldModel) interface{} { + return "handsome" + }) + info.AddColumnButtons(ctx, "see more", types.GetColumnButton("see more", icon.Info, + action.PopUp("/see/more/example", "see more", func(ctx *context.Context) (success bool, msg string, data interface{}) { + return true, "ok", "

Detail

balabala

" + }))) + info.AddField("Phone", "phone", db.Varchar).FieldFilterable() + info.AddField("City", "city", db.Varchar).FieldFilterable() + info.AddField("Avatar", "avatar", db.Varchar).FieldDisplay(func(value types.FieldModel) interface{} { + return template.Default(ctx).Image(). + SetSrc(`//quick.go-admin.cn/demo/assets/dist/img/gopher_avatar.png`). + SetHeight("120").SetWidth("120").WithModal().GetContent() + }) + info.AddField("CreatedAt", "created_at", db.Timestamp). + FieldFilterable(types.FilterType{FormType: form.DatetimeRange}) + info.AddField("UpdatedAt", "updated_at", db.Timestamp).FieldEditAble(editType.Datetime) + + info.AddActionButton(ctx, "google", action.Jump("https://google.com")) + info.AddActionButton(ctx, "Audit", action.Ajax("/admin/audit", + func(ctx *context.Context) (success bool, msg string, data interface{}) { + fmt.Println("PostForm", ctx.PostForm()) + return true, "success", "" + })) + info.AddActionButton(ctx, "Preview", action.PopUp("/admin/preview", "Preview", + func(ctx *context.Context) (success bool, msg string, data interface{}) { + return true, "", "

hello world

" + })) + info.AddButton(ctx, "jump", icon.User, action.JumpInNewTab("/admin/info/authors", "authors")) + info.AddButton(ctx, "popup", icon.Terminal, action.PopUp("/admin/popup", "Popup Example", + func(ctx *context.Context) (success bool, msg string, data interface{}) { + return true, "", "

hello world

" + })) + + info.AddButton(ctx, "iframe", icon.Tv, action.PopUpWithIframe("/admin/iframe", "Iframe Example", + action.IframeData{Src: "/admin/info/authors"}, "900px", "560px")) + info.AddButton(ctx, "form", icon.Folder, action.PopUpWithForm(action.PopUpData{ + Id: "/admin/popup/form", + Title: "Popup Form Example", + Width: "900px", + Height: "430px", + }, func(panel *types.FormPanel) *types.FormPanel { + panel.AddField("Name", "name", db.Varchar, form.Text) + panel.AddField("Age", "age", db.Int, form.Number) + panel.AddField("HomePage", "homepage", db.Varchar, form.Url).FieldDefault("http://google.com") + panel.AddField("Email", "email", db.Varchar, form.Email).FieldDefault("xxxx@xxx.com") + panel.AddField("Birthday", "birthday", db.Varchar, form.Date).FieldDefault("2010-09-03 18:09:05") + panel.AddField("Time", "time", db.Varchar, form.Datetime).FieldDefault("2010-09-05") + panel.EnableAjax("Request Success", "Request Failed") + return panel + }, "/admin/popup/form")) + + info.AddButton(ctx, "ajax", icon.Android, action.Ajax("/admin/ajax", + func(ctx *context.Context) (success bool, msg string, data interface{}) { + return true, "success", "" + })) + info.AddSelectBox(ctx, "gender", types.FieldOptions{ + {Value: "0", Text: "men"}, + {Value: "1", Text: "women"}, + }, action.FieldFilter("gender")) + + info.SetTable("users").SetTitle("Users").SetDescription("Users") + + formList := userTable.GetForm() + formList.AddField("ID", "id", db.Int, form.Default).FieldDisplayButCanNotEditWhenUpdate().FieldDisableWhenCreate() + formList.AddField("Ip", "ip", db.Varchar, form.Text) + formList.AddField("Name", "name", db.Varchar, form.Text) + formList.AddField("Gender", "gender", db.Tinyint, form.Radio). + FieldOptions(types.FieldOptions{ + {Text: "men", Value: "0"}, + {Text: "women", Value: "1"}, + }) + formList.AddField("Country", "country", db.Tinyint, form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: "China", Value: "0"}, + {Text: "America", Value: "1"}, + {Text: "England", Value: "2"}, + {Text: "Canada", Value: "3"}, + }).FieldDefault("0").FieldOnChooseAjax("city", "/choose/country", + func(ctx *context.Context) (bool, string, interface{}) { + country := ctx.FormValue("value") + var data = make(selection.Options, 0) + switch country { + case "0": + data = selection.Options{ + {Text: "Beijing", ID: "beijing"}, + {Text: "ShangHai", ID: "shangHai"}, + {Text: "GuangZhou", ID: "guangZhou"}, + {Text: "ShenZhen", ID: "shenZhen"}, + } + case "1": + data = selection.Options{ + {Text: "Los Angeles", ID: "los angeles"}, + {Text: "Washington, dc", ID: "washington, dc"}, + {Text: "New York", ID: "new york"}, + {Text: "Las Vegas", ID: "las vegas"}, + } + case "2": + data = selection.Options{ + {Text: "London", ID: "london"}, + {Text: "Cambridge", ID: "cambridge"}, + {Text: "Manchester", ID: "manchester"}, + {Text: "Liverpool", ID: "liverpool"}, + } + case "3": + data = selection.Options{ + {Text: "Vancouver", ID: "vancouver"}, + {Text: "Toronto", ID: "toronto"}, + } + default: + data = selection.Options{ + {Text: "Beijing", ID: "beijing"}, + {Text: "ShangHai", ID: "shangHai"}, + {Text: "GuangZhou", ID: "guangZhou"}, + {Text: "ShenZhen", ID: "shenZhen"}, + } + } + return true, "ok", data + }, "", `'phone':$(".phone").val(),`) + formList.AddField("Phone", "phone", db.Varchar, form.Custom). + FieldCustomContent(` + +`) + formList.AddField("City", "city", db.Varchar, form.SelectSingle). + FieldOptionInitFn(func(val types.FieldModel) types.FieldOptions { + return types.FieldOptions{ + {Value: val.Value, Text: val.Value, Selected: true}, + } + }).FieldOptions(types.FieldOptions{ + {Text: "Beijing", Value: "beijing"}, + {Text: "ShangHai", Value: "shanghai"}, + {Text: "GuangZhou", Value: "guangzhou"}, + {Text: "ShenZhen", Value: "shenzhen"}, + }) + formList.AddField("Custom Field", "role", db.Varchar, form.Text). + FieldPostFilterFn(func(value types.PostFieldModel) interface{} { + fmt.Println("user custom field", value) + return "" + }) + + formList.AddField("UpdatedAt", "updated_at", db.Timestamp, form.Default).FieldDisableWhenCreate() + formList.AddField("CreatedAt", "created_at", db.Timestamp, form.Datetime).FieldDisableWhenCreate() + + userTable.GetForm().SetTabGroups(types. + NewTabGroups("id", "ip", "name", "gender", "country", "city"). + AddGroup("phone", "role", "created_at", "updated_at")). + SetTabHeaders("profile1", "profile2") + + formList.SetTable("users").SetTitle("Users").SetDescription("Users") + + formList.SetPostHook(func(values form2.Values) error { + fmt.Println("userTable.GetForm().PostHook", values) + return nil + }) + + return +} diff --git a/examples/echo/main.go b/examples/echo/main.go new file mode 100644 index 0000000000000000000000000000000000000000..b5b70280e268193e390587fea0b3b0e0c4b9ccbd --- /dev/null +++ b/examples/echo/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/echo" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + _ "github.com/GoAdminGroup/themes/adminlte" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/labstack/echo/v4" +) + +func main() { + e := echo.New() + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + }, + }, + UrlPrefix: "admin", + IndexUrl: "/", + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + Debug: true, + Language: language.CN, + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // eng.AddConfigFromJSON("../datamodel/config.json") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(e); err != nil { + panic(err) + } + + e.Static("/uploads", "./uploads") + + // you can custom your pages like: + + eng.HTML("GET", "/admin", datamodel.GetContent) + + // Start server + go e.Logger.Fatal(e.Start(":1323")) + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} diff --git a/examples/fasthttp/main.go b/examples/fasthttp/main.go new file mode 100644 index 0000000000000000000000000000000000000000..200476405d2347036ee6522ac7ae2b1ef9a6a186 --- /dev/null +++ b/examples/fasthttp/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/fasthttp" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + _ "github.com/GoAdminGroup/themes/adminlte" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/buaazp/fasthttprouter" + "github.com/valyala/fasthttp" +) + +func main() { + router := fasthttprouter.New() + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + }, + }, + UrlPrefix: "admin", + IndexUrl: "/", + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + Debug: true, + Language: language.CN, + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // eng.AddConfigFromJSON("../datamodel/config.json") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(router); err != nil { + panic(err) + } + + router.ServeFiles("/uploads/*filepath", "./uploads") + + eng.HTML("GET", "/admin", datamodel.GetContent) + + go func() { + _ = fasthttp.ListenAndServe(":8897", router.Handler) + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} diff --git a/examples/gear/main.go b/examples/gear/main.go new file mode 100644 index 0000000000000000000000000000000000000000..eb9be4b315e2e0c27d9d52459c1a674945052a15 --- /dev/null +++ b/examples/gear/main.go @@ -0,0 +1,110 @@ +package main + +import ( + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/gear" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + _ "github.com/GoAdminGroup/themes/sword" + "github.com/teambition/gear" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/themes/adminlte" +) + +func main() { + + app := gear.New() + + e := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + + //Driver: config.DriverSqlite, + //File: "../datamodel/admin.db", + }, + }, + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + UrlPrefix: "admin", + Language: language.CN, + IndexUrl: "/", + Debug: true, + AccessAssetsLogOff: true, + Animation: config.PageAnimation{ + Type: "fadeInUp", + }, + ColorScheme: adminlte.ColorschemeSkinBlack, + BootstrapFilePath: "./../datamodel/bootstrap.go", + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // e.AddConfigFromJSON("../datamodel/config.json") + + if err := e.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddDisplayFilterXssJsFilter(). + AddPlugins(examplePlugin). + Use(app); err != nil { + panic(err) + } + + // customize your pages + + e.HTML("GET", "/admin", datamodel.GetContent) + + go func() { + app.Start(":8099") + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + e.MysqlConnection().Close() +} diff --git a/examples/gf/main.go b/examples/gf/main.go new file mode 100644 index 0000000000000000000000000000000000000000..df7ee20b6b2b3a18408d97ca4de163bb0700c895 --- /dev/null +++ b/examples/gf/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/gf" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/themes/adminlte" + "github.com/gogf/gf/frame/g" +) + +func main() { + s := g.Server() + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + + //Driver: config.DriverSqlite, + //File: "../datamodel/admin.db", + }, + }, + UrlPrefix: "admin", + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + Language: language.CN, + IndexUrl: "/", + Debug: true, + ColorScheme: adminlte.ColorschemeSkinBlack, + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // eng.AddConfigFromJSON("../datamodel/config.json") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(s); err != nil { + panic(err) + } + + s.AddStaticPath("/uploads", "./uploads") + + // customize your pages + + eng.HTML("GET", "/admin", datamodel.GetContent) + + s.SetPort(9033) + go s.Run() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} diff --git a/examples/gf2/main.go b/examples/gf2/main.go new file mode 100644 index 0000000000000000000000000000000000000000..e55f4236817f5b3131608fb61b9cdfa5a991fe1d --- /dev/null +++ b/examples/gf2/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/gf2" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/themes/adminlte" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + s := g.Server() + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "123456", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + + //Driver: config.DriverSqlite, + //File: "../datamodel/admin.db", + }, + }, + UrlPrefix: "admin", + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + Language: language.CN, + IndexUrl: "/", + Debug: true, + ColorScheme: adminlte.ColorschemeSkinBlack, + } + + template.AddComp(chartjs.NewChart()) + + examplePlugin := example.NewExample() + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssFilter(). + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(s); err != nil { + panic(err) + } + + s.AddStaticPath("/uploads", "./uploads") + + eng.HTML("GET", "/admin", datamodel.GetContent) + + s.SetPort(9033) + go s.Run() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() + +} diff --git a/examples/gin/main.go b/examples/gin/main.go new file mode 100644 index 0000000000000000000000000000000000000000..49512e5411cda0d8c458538dc7f3d6885a70b082 --- /dev/null +++ b/examples/gin/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "io/ioutil" + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/gin" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + _ "github.com/GoAdminGroup/themes/sword" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/themes/adminlte" + "github.com/gin-gonic/gin" +) + +func main() { + gin.SetMode(gin.ReleaseMode) + gin.DefaultWriter = ioutil.Discard + + r := gin.New() + + e := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + + //Driver: config.DriverSqlite, + //File: "../datamodel/admin.db", + }, + }, + UrlPrefix: "admin", + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + Language: language.CN, + IndexUrl: "/", + Debug: true, + AccessAssetsLogOff: true, + Animation: config.PageAnimation{ + Type: "fadeInUp", + }, + ColorScheme: adminlte.ColorschemeSkinBlack, + BootstrapFilePath: "./../datamodel/bootstrap.go", + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // e.AddConfigFromJSON("../datamodel/config.json") + + if err := e.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddDisplayFilterXssJsFilter(). + AddPlugins(examplePlugin). + Use(r); err != nil { + panic(err) + } + + r.Static("/uploads", "./uploads") + + // customize your pages + + e.HTML("GET", "/admin", datamodel.GetContent) + + go func() { + _ = r.Run(":9033") + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + e.MysqlConnection().Close() +} diff --git a/examples/gofiber/main.go b/examples/gofiber/main.go new file mode 100644 index 0000000000000000000000000000000000000000..c603b9994ee7a8077d1c8df0eaa89b5a09c4a539 --- /dev/null +++ b/examples/gofiber/main.go @@ -0,0 +1,99 @@ +package main + +import ( + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/gofiber" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + _ "github.com/GoAdminGroup/themes/adminlte" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + + "github.com/gofiber/fiber/v2" +) + +func main() { + + app := fiber.New(fiber.Config{ + ServerHeader: "Fiber", + }) + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + }, + }, + UrlPrefix: "admin", + IndexUrl: "/", + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + Debug: true, + Language: language.CN, + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // eng.AddConfigFromJSON("../datamodel/config.json") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(app); err != nil { + panic(err) + } + + eng.HTML("GET", "/admin", datamodel.GetContent) + + _ = app.Listen(":8897") + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} diff --git a/examples/gorilla/main.go b/examples/gorilla/main.go new file mode 100644 index 0000000000000000000000000000000000000000..f7359fd5012cdecaa244c0a17a28e39354886c54 --- /dev/null +++ b/examples/gorilla/main.go @@ -0,0 +1,99 @@ +package main + +import ( + "log" + "net/http" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/gorilla" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + _ "github.com/GoAdminGroup/themes/adminlte" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/gorilla/mux" +) + +func main() { + app := mux.NewRouter() + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + }, + }, + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + UrlPrefix: "admin", + IndexUrl: "/", + Debug: true, + Language: language.EN, + } + + // customize a plugin + + examplePlugin := example.NewExample() + + template.AddComp(chartjs.NewChart()) + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // eng.AddConfigFromJSON("../datamodel/config.json") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(app); err != nil { + panic(err) + } + + app.PathPrefix("/uploads/").Handler(http.StripPrefix("/uploads/", http.FileServer(http.Dir("./uploads")))) + + eng.HTML("GET", "/admin", datamodel.GetContent) + + go func() { + _ = http.ListenAndServe(":9033", app) + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} diff --git a/examples/iris/main.go b/examples/iris/main.go new file mode 100644 index 0000000000000000000000000000000000000000..d1a82d40a45e3e8a3774a68ddb08b9a44ee83e2b --- /dev/null +++ b/examples/iris/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "log" + "os" + "os/signal" + "time" + + _ "github.com/GoAdminGroup/go-admin/adapter/iris" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + _ "github.com/GoAdminGroup/themes/adminlte" + + "github.com/GoAdminGroup/go-admin/engine" + "github.com/GoAdminGroup/go-admin/examples/datamodel" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/example" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/kataras/iris/v12" +) + +func main() { + app := iris.Default() + + eng := engine.Default() + + cfg := config.Config{ + Env: config.EnvLocal, + Databases: config.DatabaseList{ + "default": { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pwd: "root", + Name: "godmin", + MaxIdleConns: 50, + MaxOpenConns: 150, + ConnMaxLifetime: time.Hour, + Driver: config.DriverMysql, + }, + }, + UrlPrefix: "admin", + Store: config.Store{ + Path: "./uploads", + Prefix: "uploads", + }, + IndexUrl: "/", + Debug: true, + Language: language.CN, + } + + template.AddComp(chartjs.NewChart()) + + // customize a plugin + + examplePlugin := example.NewExample() + + // load from golang.Plugin + // + // examplePlugin := plugins.LoadFromPlugin("../datamodel/example.so") + + // customize the login page + // example: https://github.com/GoAdminGroup/demo.go-admin.cn/blob/master/main.go#L39 + // + // template.AddComp("login", datamodel.LoginPage) + + // load config from json file + // + // eng.AddConfigFromJSON("../datamodel/config.json") + + if err := eng.AddConfig(&cfg). + AddGenerators(datamodel.Generators). + AddDisplayFilterXssJsFilter(). + // add generator, first parameter is the url prefix of table when visit. + // example: + // + // "user" => http://localhost:9033/admin/info/user + // + AddGenerator("user", datamodel.GetUserTable). + AddPlugins(examplePlugin). + Use(app); err != nil { + panic(err) + } + + app.HandleDir("/uploads", "./uploads", iris.DirOptions{ + IndexName: "/index.html", + ShowList: false, + }) + + // you can custom your pages like: + + eng.HTML("GET", "/admin", datamodel.GetContent) + + go func() { + _ = app.Run(iris.Addr(":8099")) + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + log.Print("closing database connection") + eng.MysqlConnection().Close() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..fa568aa9f0338554968ba60933cca32a6e822ed2 --- /dev/null +++ b/go.mod @@ -0,0 +1,177 @@ +module github.com/GoAdminGroup/go-admin + +go 1.21.5 + +require ( + github.com/360EntSecGroup-Skylar/excelize v1.4.1 + github.com/GoAdminGroup/html v0.0.1 + github.com/GoAdminGroup/themes v0.0.48 + github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e + github.com/agiledragon/gomonkey v2.0.2+incompatible + github.com/astaxie/beego v1.12.1 + github.com/beego/beego/v2 v2.2.0 + github.com/buaazp/fasthttprouter v0.1.1 + github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e + github.com/gavv/httpexpect v2.0.0+incompatible + github.com/gin-gonic/gin v1.3.0 + github.com/go-chi/chi v1.5.5 + github.com/go-sql-driver/mysql v1.8.1 + github.com/gobuffalo/buffalo v1.1.0 + github.com/gofiber/fiber/v2 v2.52.4 + github.com/gogf/gf v1.16.9 + github.com/gogf/gf/v2 v2.6.4 + github.com/google/uuid v1.6.0 + github.com/gorilla/mux v1.8.0 + github.com/kataras/iris/v12 v12.2.10 + github.com/labstack/echo/v4 v4.11.4 + github.com/lib/pq v1.10.5 + github.com/magiconair/properties v1.8.6 + github.com/mattn/go-sqlite3 v2.0.3+incompatible + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b + github.com/sclevine/agouti v3.0.0+incompatible + github.com/stretchr/testify v1.9.0 + github.com/teambition/gear v1.27.3 + github.com/valyala/fasthttp v1.52.0 + go.uber.org/zap v1.19.1 + golang.org/x/crypto v0.20.0 + golang.org/x/text v0.14.0 + gopkg.in/ini.v1 v1.67.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gopkg.in/yaml.v2 v2.4.0 + xorm.io/xorm v1.0.2 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect + github.com/CloudyKit/jet/v6 v6.2.0 // indirect + github.com/Joker/jade v1.1.3 // indirect + github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect + github.com/ajg/form v1.5.1 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/flosch/pongo2/v4 v4.0.2 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-http-utils/cookie v1.3.1 // indirect + github.com/go-http-utils/negotiator v1.0.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gobuffalo/envy v1.10.2 // indirect + github.com/gobuffalo/events v1.4.3 // indirect + github.com/gobuffalo/flect v1.0.0 // indirect + github.com/gobuffalo/github_flavored_markdown v1.1.3 // indirect + github.com/gobuffalo/grift v1.5.2 // indirect + github.com/gobuffalo/helpers v0.6.7 // indirect + github.com/gobuffalo/logger v1.0.7 // indirect + github.com/gobuffalo/meta v0.3.3 // indirect + github.com/gobuffalo/nulls v0.4.2 // indirect + github.com/gobuffalo/plush/v4 v4.1.18 // indirect + github.com/gobuffalo/refresh v1.13.3 // indirect + github.com/gobuffalo/tags/v3 v3.1.4 // indirect + github.com/gobuffalo/validate/v3 v3.3.3 // indirect + github.com/gofrs/uuid v4.2.0+incompatible // indirect + github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 // indirect + github.com/gomodule/redigo v2.0.0+incompatible // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/gorilla/css v1.0.0 // indirect + github.com/gorilla/handlers v1.5.1 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.2.1 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/grokify/html-strip-tags-go v0.0.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/imkira/go-interpol v1.1.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/iris-contrib/schema v0.0.6 // indirect + github.com/joho/godotenv v1.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kataras/blocks v0.0.8 // indirect + github.com/kataras/golog v0.1.11 // indirect + github.com/kataras/pio v0.0.13 // indirect + github.com/kataras/sitemap v0.0.6 // indirect + github.com/kataras/tunnel v0.0.4 // indirect + github.com/klauspost/compress v1.17.6 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/mailgun/raymond/v2 v2.0.48 // indirect + github.com/mailru/easyjson v0.7.7 // 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.15 // indirect + github.com/microcosm-cc/bluemonday v1.0.26 // indirect + github.com/mitchellh/go-homedir v1.1.0 // 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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/monoculum/formam v3.5.5+incompatible // indirect + github.com/moul/http2curl v1.0.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/schollz/closestmatch v2.1.0+incompatible // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/smartystreets/goconvey v1.8.1 // indirect + github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect + github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect + github.com/spf13/cobra v1.6.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/syndtr/goleveldb v1.0.0 // indirect + github.com/tdewolff/minify/v2 v2.20.14 // indirect + github.com/tdewolff/parse/v2 v2.7.8 // indirect + github.com/teambition/trie-mux v1.5.2 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/yosssi/ace v0.0.5 // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yudai/pp v2.0.1+incompatible // indirect + go.opentelemetry.io/otel v1.14.0 // indirect + go.opentelemetry.io/otel/sdk v1.14.0 // indirect + go.opentelemetry.io/otel/trace v1.14.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.7.0 // indirect + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/go-playground/validator.v8 v8.18.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + xorm.io/builder v0.3.7 // indirect +) + +replace github.com/ugorji/go => github.com/ugorji/go v1.2.11 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..59ce26eb4cd31c86de9006acfde4eb0faa2bfe9e --- /dev/null +++ b/go.sum @@ -0,0 +1,651 @@ +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.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= +gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= +github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/GoAdminGroup/html v0.0.1 h1:SdWNWl4OKPsvDk2GDp5ZKD6ceWoN8n4Pj6cUYxavUd0= +github.com/GoAdminGroup/html v0.0.1/go.mod h1:A1laTJaOx8sQ64p2dE8IqtstDeCNBHEazrEp7hR5VvM= +github.com/GoAdminGroup/themes v0.0.48 h1:OveEEoFBCBTU5kNicqnvs0e/pL6uZKNQU1RAP9kmNFA= +github.com/GoAdminGroup/themes v0.0.48/go.mod h1:w/5P0WCmM8iv7DYE5scIT8AODYMoo6zj/bVlzAbgOaU= +github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4= +github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= +github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw= +github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/astaxie/beego v1.12.1 h1:dfpuoxpzLVgclveAXe4PyNKqkzgm5zF4tgF2B3kkM2I= +github.com/astaxie/beego v1.12.1/go.mod h1:kPBWpSANNbSdIqOc8SUL9h+1oyBMZhROeYsXQDbidWQ= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/beego/beego/v2 v2.2.0 h1:x2yCNL9x74vqAXRdFBw5HCzB8AwownALpBWEOitivow= +github.com/beego/beego/v2 v2.2.0/go.mod h1:kqiwel3TqpZHYtI08GWnleCtBc0LqtawsmfFDxY9POY= +github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= +github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/buaazp/fasthttprouter v0.1.1 h1:4oAnN0C3xZjylvZJdP35cxfclyn4TYkW6Y+DSvS+h8Q= +github.com/buaazp/fasthttprouter v0.1.1/go.mod h1:h/Ap5oRVLeItGKTVBb+heQPks+HdIUtGmI4H5WCYijM= +github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4= +github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/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/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= +github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= +github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= +github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e h1:LzwWXEScfcTu7vUZNlDDWDARoSGEtvlDKK2BYHowNeE= +github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0= +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-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/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= +github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= +github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= +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.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +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/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +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.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= +github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= +github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= +github.com/go-http-utils/cookie v1.3.1 h1:GCdTeqVV5vDcjP7LrgYpH8pbt3dOYKS+Wrs7Jo3/k/w= +github.com/go-http-utils/cookie v1.3.1/go.mod h1:ATl4rfG3bEemjiVa+8WIfgNcBUWdYBTasfXKjJ3Avt8= +github.com/go-http-utils/negotiator v1.0.0 h1:Qp1zofD6Nw7KXApXa3pAjehP06Js0ILguEBCnHhZeVA= +github.com/go-http-utils/negotiator v1.0.0/go.mod h1:mTQe1sH0XhdFkeDiWpCY3QSk7Apo5jwOlIwLWJbJe2c= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.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/gobuffalo/buffalo v1.1.0 h1:6y1fUC47QWevaM1ImukJFHNgxiRIT+Y74VcP4ZQaz80= +github.com/gobuffalo/buffalo v1.1.0/go.mod h1:lLsx9Y8bFYu9uvQyIEB3M0QA908ChHUPjwOGumZWARU= +github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= +github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8= +github.com/gobuffalo/events v1.4.3 h1:JYDq7NbozP10zaN9Ijfem6Ozox2KacU2fU38RyquXM8= +github.com/gobuffalo/events v1.4.3/go.mod h1:2BwfpV5X63t8xkUcVqIv4IbyAobJazRSVu1F1pgf3rc= +github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= +github.com/gobuffalo/flect v1.0.0 h1:eBFmskjXZgAOagiTXJH25Nt5sdFwNRcb8DKZsIsAUQI= +github.com/gobuffalo/flect v1.0.0/go.mod h1:l9V6xSb4BlXwsxEMj3FVEub2nkdQjWhPvD8XTTlHPQc= +github.com/gobuffalo/github_flavored_markdown v1.1.3 h1:rSMPtx9ePkFB22vJ+dH+m/EUBS8doQ3S8LeEXcdwZHk= +github.com/gobuffalo/github_flavored_markdown v1.1.3/go.mod h1:IzgO5xS6hqkDmUh91BW/+Qxo/qYnvfzoz3A7uLkg77I= +github.com/gobuffalo/grift v1.5.2 h1:mC0vHRs+nXz+JhkH3sv+rVnnTQRDXrUrOXOPYpgPjpo= +github.com/gobuffalo/grift v1.5.2/go.mod h1:Uf/3T2AR1Vv+t84EPmxCjqQ8oyJwXs0FAoLMFUn/JVs= +github.com/gobuffalo/helpers v0.6.7 h1:C9CedoRSfgWg2ZoIkVXgjI5kgmSpL34Z3qdnzpfNVd8= +github.com/gobuffalo/helpers v0.6.7/go.mod h1:j0u1iC1VqlCaJEEVkZN8Ia3TEzfj/zoXANqyJExTMTA= +github.com/gobuffalo/here v0.6.7/go.mod h1:vuCfanjqckTuRlqAitJz6QC4ABNnS27wLb816UhsPcc= +github.com/gobuffalo/httptest v1.5.2 h1:GpGy520SfY1QEmyPvaqmznTpG4gEQqQ82HtHqyNEreM= +github.com/gobuffalo/httptest v1.5.2/go.mod h1:FA23yjsWLGj92mVV74Qtc8eqluc11VqcWr8/C1vxt4g= +github.com/gobuffalo/logger v1.0.7 h1:LTLwWelETXDYyqF/ASf0nxaIcdEOIJNxRokPcfI/xbU= +github.com/gobuffalo/logger v1.0.7/go.mod h1:u40u6Bq3VVvaMcy5sRBclD8SXhBYPS0Qk95ubt+1xJM= +github.com/gobuffalo/meta v0.3.3 h1:GwPWdbdnp4JrKASvMLa03OtmzISq7z/nE7T6aMqzoYM= +github.com/gobuffalo/meta v0.3.3/go.mod h1:o4B099IUFUfK4555Guqxz1zHAqyuUQ/KtHXi8WvVeFE= +github.com/gobuffalo/nulls v0.4.2 h1:GAqBR29R3oPY+WCC7JL9KKk9erchaNuV6unsOSZGQkw= +github.com/gobuffalo/nulls v0.4.2/go.mod h1:EElw2zmBYafU2R9W4Ii1ByIj177wA/pc0JdjtD0EsH8= +github.com/gobuffalo/plush/v4 v4.1.18 h1:bnPjdMTEUQHqj9TNX2Ck3mxEXYZa+0nrFMNM07kpX9g= +github.com/gobuffalo/plush/v4 v4.1.18/go.mod h1:xi2tJIhFI4UdzIL8sxZtzGYOd2xbBpcFbLZlIPGGZhU= +github.com/gobuffalo/refresh v1.13.3 h1:HYQlI6RiqWUf2yzCXvUHAYqm9M9/teVnox+mjzo/9rQ= +github.com/gobuffalo/refresh v1.13.3/go.mod h1:NkzgLKZGk5suOvgvOD0/VALog0fH29Ib7fwym9JmRxA= +github.com/gobuffalo/tags/v3 v3.1.4 h1:X/ydLLPhgXV4h04Hp2xlbI2oc5MDaa7eub6zw8oHjsM= +github.com/gobuffalo/tags/v3 v3.1.4/go.mod h1:ArRNo3ErlHO8BtdA0REaZxijuWnWzF6PUXngmMXd2I0= +github.com/gobuffalo/validate/v3 v3.3.3 h1:o7wkIGSvZBYBd6ChQoLxkz2y1pfmhbI4jNJYh6PuNJ4= +github.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= +github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogf/gf v1.16.9 h1:Q803UmmRo59+Ws08sMVFOcd8oNpkSWL9vS33hlo/Cyk= +github.com/gogf/gf v1.16.9/go.mod h1:8Q/kw05nlVRp+4vv7XASBsMe9L1tsVKiGoeP2AHnlkk= +github.com/gogf/gf/v2 v2.6.4 h1:w7HXdH9mcTsn/aE13CkaDbRArmAL1KS3FuQqDi6u74Y= +github.com/gogf/gf/v2 v2.6.4/go.mod h1:x2XONYcI4hRQ/4gMNbWHmZrNzSEIg20s2NULbzom5k0= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/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/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k= +github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.2/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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +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/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +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 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0= +github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go= +github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE= +github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +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/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM= +github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg= +github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20= +github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A= +github.com/kataras/iris/v12 v12.2.10 h1:rEJVM7qMoyhv8wpgkA1yGxibFcONE0jkJ70LFLibTAA= +github.com/kataras/iris/v12 v12.2.10/go.mod h1:z4+E+kLMqZ7U4WtDsYfFnG7BjMTXLkdzMAXLVMLnMNs= +github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM= +github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM= +github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= +github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +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.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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/lib/pq v1.0.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.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= +github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/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.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.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monoculum/formam v3.5.5+incompatible h1:iPl5csfEN96G2N2mGu8V/ZB62XLf9ySTpC8KRH6qXec= +github.com/monoculum/formam v3.5.5+incompatible/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= +github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef h1:NKxTG6GVGbfMXc2mIk+KphcH6hagbVXhcFkbTgYleTI= +github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sclevine/agouti v3.0.0+incompatible h1:8IBJS6PWz3uTlMP3YBIR5f+KAldcGuOeFkFbUWfBgK4= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik= +github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= +github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= +github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= +github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +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/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= +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.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.2.3-0.20181224173747-660f15d67dbb/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.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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tdewolff/minify/v2 v2.20.14 h1:sktSuVixRwk0ryQjqvKBu/uYS+MWmkwEFMEWtFZ+TdE= +github.com/tdewolff/minify/v2 v2.20.14/go.mod h1:qnIJbnG2dSzk7LIa/UUwgN2OjS8ir6RRlqc0T/1q2xY= +github.com/tdewolff/parse/v2 v2.7.8 h1:1cnVqa8L63xFkc2vfRsZTM6Qy35nJpTvQ2Uvdv3vbvs= +github.com/tdewolff/parse/v2 v2.7.8/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +github.com/teambition/gear v1.27.3 h1:iWUOJYdBwxU+SZP5aZ2ZYR5FnRGmdgrMbbSpOCZo0go= +github.com/teambition/gear v1.27.3/go.mod h1:d3Nmr6rRPnH5lYSK33W9IDhsaxp/8n14vRrUZu9dP9c= +github.com/teambition/trie-mux v1.5.2 h1:ALTagFwKZXkn1vfSRlODlmoZg+NMeWAm4dyBPQI6a8w= +github.com/teambition/trie-mux v1.5.2/go.mod h1:0Woh4KOHSN9bkJ66eWmLs8ltrEKw+fnZbFaHFfbMrtc= +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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= +github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= +go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= +go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= +go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= +go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= +go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= +go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= +go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +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/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +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-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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-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-20190125091013-d26f9f9a57f3/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-20190327091125-710a502c58a2/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-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-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +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/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-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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210615035016-665e8c7367d1/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-20211019181941-9d821ace8654/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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +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.3/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.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +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= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +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.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +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-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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-20200902074654-038fdea0a05b/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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +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.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +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/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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/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= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +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= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= +xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= +xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +xorm.io/xorm v1.0.2 h1:kZlCh9rqd1AzGwWitcrEEqHE1h1eaZE/ujU5/2tWEtg= +xorm.io/xorm v1.0.2/go.mod h1:o4vnEsQ5V2F1/WK6w4XTwmiWJeGj82tqjAnHe44wVHY= diff --git a/modules/auth/auth.go b/modules/auth/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..0e2b9f82bdfd96d82b16db41f0a52702e8d0276f --- /dev/null +++ b/modules/auth/auth.go @@ -0,0 +1,197 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package auth + +import ( + "log" + "sync" + + "github.com/GoAdminGroup/go-admin/modules/db/dialect" + "github.com/GoAdminGroup/go-admin/modules/logger" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "golang.org/x/crypto/bcrypt" +) + +// Auth get the user model from Context. +func Auth(ctx *context.Context) models.UserModel { + return ctx.User().(models.UserModel) +} + +// Check check the password and username and return the user model. +func Check(password string, username string, conn db.Connection) (user models.UserModel, ok bool) { + + user = models.User().SetConn(conn).FindByUserName(username) + + if user.IsEmpty() { + ok = false + } else { + if comparePassword(password, user.Password) { + ok = true + user = user.WithRoles().WithPermissions().WithMenus() + user.UpdatePwd(EncodePassword([]byte(password))) + } else { + ok = false + } + } + return +} + +func comparePassword(comPwd, pwdHash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(pwdHash), []byte(comPwd)) + return err == nil +} + +// EncodePassword encode the password. +func EncodePassword(pwd []byte) string { + hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.DefaultCost) + if err != nil { + return "" + } + return string(hash) +} + +// SetCookie set the cookie. +func SetCookie(ctx *context.Context, user models.UserModel, conn db.Connection) error { + ses, err := InitSession(ctx, conn) + + if err != nil { + return err + } + + return ses.Add("user_id", user.Id) +} + +// DelCookie delete the cookie from Context. +func DelCookie(ctx *context.Context, conn db.Connection) error { + ses, err := InitSession(ctx, conn) + + if err != nil { + return err + } + + return ses.Clear() +} + +type TokenService struct { + tokens CSRFToken + lock sync.Mutex + conn db.Connection +} + +func (s *TokenService) Name() string { + return TokenServiceKey +} + +func InitCSRFTokenSrv(conn db.Connection) (string, service.Service) { + list, err := db.WithDriver(conn).Table("goadmin_session"). + Where("values", "=", "__csrf_token__"). + All() + if db.CheckError(err, db.QUERY) { + logger.Error("csrf token query from database error: ", err) + } + tokens := make(CSRFToken, len(list)) + for i := 0; i < len(list); i++ { + tokens[i] = list[i]["sid"].(string) + } + return TokenServiceKey, &TokenService{ + tokens: tokens, + conn: conn, + } +} + +const ( + TokenServiceKey = "token_csrf_helper" + ServiceKey = "auth" +) + +func GetTokenService(s interface{}) *TokenService { + if srv, ok := s.(*TokenService); ok { + return srv + } + log.Panicf("wrong service: %+v", s) + return nil +} + +// AddToken add the token to the CSRFToken. +func (s *TokenService) AddToken() string { + s.lock.Lock() + defer s.lock.Unlock() + tokenStr := modules.Uuid() + s.tokens = append(s.tokens, tokenStr) + _, err := db.WithDriver(s.conn).Table("goadmin_session").Insert(dialect.H{ + "sid": tokenStr, + "values": "__csrf_token__", + }) + if db.CheckError(err, db.INSERT) { + logger.Error("csrf token insert into database error: ", err) + } + return tokenStr +} + +// CheckToken check the given token with tokens in the CSRFToken, if exist +// return true. +func (s *TokenService) CheckToken(toCheckToken string) (ok bool) { + defer func() { + if ok { + err := db.WithDriver(s.conn).Table("goadmin_session"). + Where("sid", "=", toCheckToken). + Where("values", "=", "__csrf_token__"). + Delete() + if db.CheckError(err, db.DELETE) { + logger.Error("csrf token delete from database error: ", err) + } + } + }() + + for i := 0; i < len(s.tokens); i++ { + if (s.tokens)[i] == toCheckToken { + s.tokens = append((s.tokens)[:i], (s.tokens)[i+1:]...) + ok = true + return + } + } + + item, err := db.WithDriver(s.conn).Table("goadmin_session"). + Where("sid", "=", toCheckToken). + Where("values", "=", "__csrf_token__"). + First() + if item != nil && err == nil { + ok = true + return + } + return +} + +// CSRFToken is type of a csrf token list. +type CSRFToken []string + +type Processor func(ctx *context.Context) (model models.UserModel, exist bool, msg string) + +type Service struct { + P Processor +} + +func (s *Service) Name() string { + return "auth" +} + +func GetService(s interface{}) *Service { + if srv, ok := s.(*Service); ok { + return srv + } + log.Panicf("wrong service: %+v", s) + return nil +} + +func NewService(processor Processor) *Service { + return &Service{ + P: processor, + } +} diff --git a/modules/auth/auth_test.go b/modules/auth/auth_test.go new file mode 100644 index 0000000000000000000000000000000000000000..06f2c125b92ee8662b345ce4fdacfff975ccbd01 --- /dev/null +++ b/modules/auth/auth_test.go @@ -0,0 +1,12 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncodePassword(t *testing.T) { + pwd := EncodePassword([]byte("123456")) + assert.Equal(t, comparePassword("123456", pwd), true) +} diff --git a/modules/auth/middleware.go b/modules/auth/middleware.go new file mode 100644 index 0000000000000000000000000000000000000000..62428f9f16953ddf1c25c22086c5e972d1ba1e02 --- /dev/null +++ b/modules/auth/middleware.go @@ -0,0 +1,237 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package auth + +import ( + "net/http" + "net/url" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/constant" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/page" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + template2 "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" +) + +// Invoker contains the callback functions which are used +// in the route middleware. +type Invoker struct { + prefix string + authFailCallback MiddlewareCallback + permissionDenyCallback MiddlewareCallback + conn db.Connection +} + +// Middleware is the default auth middleware of plugins. +func Middleware(conn db.Connection) context.Handler { + return DefaultInvoker(conn).Middleware() +} + +// DefaultInvoker return a default Invoker. +func DefaultInvoker(conn db.Connection) *Invoker { + return &Invoker{ + prefix: config.Prefix(), + authFailCallback: func(ctx *context.Context) { + if ctx.Request.URL.Path == config.Url(config.GetLoginUrl()) { + return + } + if ctx.Request.URL.Path == config.Url("/logout") { + ctx.Write(302, map[string]string{ + "Location": config.Url(config.GetLoginUrl()), + }, ``) + return + } + param := "" + if ref := ctx.Referer(); ref != "" { + param = "?ref=" + url.QueryEscape(ref) + } + + u := config.Url(config.GetLoginUrl() + param) + _, err := ctx.Request.Cookie(DefaultCookieKey) + referer := ctx.Referer() + + if (ctx.Headers(constant.PjaxHeader) == "" && ctx.Method() != "GET") || + err != nil || + referer == "" { + ctx.Write(302, map[string]string{ + "Location": u, + }, ``) + } else { + msg := language.Get("login overdue, please login again") + ctx.HTML(http.StatusOK, ``) + } + }, + permissionDenyCallback: func(rawCtx *context.Context) { + if rawCtx.Headers(constant.PjaxHeader) == "" && rawCtx.Method() != "GET" { + rawCtx.JSON(http.StatusForbidden, map[string]interface{}{ + "code": http.StatusForbidden, + "msg": language.Get(errors.PermissionDenied), + }) + } else { + page.SetPageContent(rawCtx, Auth(rawCtx), func(ctx interface{}) (types.Panel, error) { + return template2.WarningPanel(rawCtx, errors.PermissionDenied, template2.NoPermission403Page), nil + }, conn) + } + }, + conn: conn, + } +} + +// SetPrefix return the default Invoker with the given prefix. +func SetPrefix(prefix string, conn db.Connection) *Invoker { + i := DefaultInvoker(conn) + i.prefix = prefix + return i +} + +// SetAuthFailCallback set the authFailCallback of Invoker. +func (invoker *Invoker) SetAuthFailCallback(callback MiddlewareCallback) *Invoker { + invoker.authFailCallback = callback + return invoker +} + +// SetPermissionDenyCallback set the permissionDenyCallback of Invoker. +func (invoker *Invoker) SetPermissionDenyCallback(callback MiddlewareCallback) *Invoker { + invoker.permissionDenyCallback = callback + return invoker +} + +// MiddlewareCallback is type of callback function. +type MiddlewareCallback func(ctx *context.Context) + +// Middleware get the auth middleware from Invoker. +func (invoker *Invoker) Middleware() context.Handler { + return func(ctx *context.Context) { + user, authOk, permissionOk := Filter(ctx, invoker.conn) + + if authOk && permissionOk { + ctx.SetUserValue("user", user) + ctx.Next() + return + } + + if !authOk { + invoker.authFailCallback(ctx) + ctx.Abort() + return + } + + if !permissionOk { + ctx.SetUserValue("user", user) + invoker.permissionDenyCallback(ctx) + ctx.Abort() + return + } + } +} + +// Filter retrieve the user model from Context and check the permission +// at the same time. +func Filter(ctx *context.Context, conn db.Connection) (models.UserModel, bool, bool) { + var ( + id float64 + ok bool + + user = models.User() + ses, err = InitSession(ctx, conn) + ) + + if err != nil { + logger.ErrorCtx(ctx, "retrieve auth user failed %+v", err) + return user, false, false + } + + if id, ok = ses.Get("user_id").(float64); !ok { + return user, false, false + } + + user, ok = GetCurUserByID(int64(id), conn) + + if !ok { + return user, false, false + } + + return user, true, CheckPermissions(user, ctx.Request.URL.String(), ctx.Method(), ctx.PostForm()) +} + +const defaultUserIDSesKey = "user_id" + +// GetUserID return the user id from the session. +func GetUserID(sesKey string, conn db.Connection) int64 { + id, err := GetSessionByKey(sesKey, defaultUserIDSesKey, conn) + if err != nil { + logger.Error("retrieve auth user failed", err) + return -1 + } + if idFloat64, ok := id.(float64); ok { + return int64(idFloat64) + } + return -1 +} + +// GetCurUser return the user model. +func GetCurUser(sesKey string, conn db.Connection) (user models.UserModel, ok bool) { + + if sesKey == "" { + ok = false + return + } + + id := GetUserID(sesKey, conn) + if id == -1 { + ok = false + return + } + return GetCurUserByID(id, conn) +} + +// GetCurUserByID return the user model of given user id. +func GetCurUserByID(id int64, conn db.Connection) (user models.UserModel, ok bool) { + + user = models.User().SetConn(conn).Find(id) + + if user.IsEmpty() { + ok = false + return + } + + if user.Avatar == "" || config.GetStore().Prefix == "" { + user.Avatar = "" + } else { + user.Avatar = config.GetStore().URL(user.Avatar) + } + + user = user.WithRoles().WithPermissions().WithMenus() + + ok = user.HasMenu() + + return +} + +// CheckPermissions check the permission of the user. +func CheckPermissions(user models.UserModel, path, method string, param url.Values) bool { + return user.CheckPermissionByUrlMethod(path, method, param) +} diff --git a/modules/auth/middleware_test.go b/modules/auth/middleware_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b2e3237a913b7230d5160fe1c266ebddbe3fe813 --- /dev/null +++ b/modules/auth/middleware_test.go @@ -0,0 +1,83 @@ +package auth + +import ( + "net/url" + "testing" + + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/stretchr/testify/assert" +) + +func TestCheckPermissions(t *testing.T) { + + config.Initialize(&config.Config{ + UrlPrefix: "admin", + }) + + user := models.UserModel{ + Permissions: []models.PermissionModel{ + { + Name: "/", + Slug: "/", + HttpMethod: []string{"GET"}, + HttpPath: []string{"/"}, + }, { + Name: "/info/user", + Slug: "/", + HttpMethod: []string{"GET"}, + HttpPath: []string{"/info/user"}, + }, { + Name: "/info/user/edit", + Slug: "/", + HttpMethod: []string{"GET"}, + HttpPath: []string{"/info/user/edit"}, + }, { + Name: "/info/normal_manager?id=2", + Slug: "/", + HttpMethod: []string{"GET"}, + HttpPath: []string{"/info/normal_manager?id=2"}, + }, { + Name: "/info/normal_manager/edit?id=2", + Slug: "/", + HttpMethod: []string{"GET"}, + HttpPath: []string{"/info/normal_manager/edit?id=2"}, + }, { + Name: "/info/user_list?user_type=10", + Slug: "/", + HttpMethod: []string{"GET"}, + HttpPath: []string{"/info/user_list?user_type=10"}, + }, { + Name: "/info/user_list?user_type=20", + Slug: "/", + HttpMethod: []string{"GET"}, + HttpPath: []string{"/info/user_list?user_type=20"}, + }, { + Name: "/delete/user", + Slug: "/", + HttpMethod: []string{"POST"}, + HttpPath: []string{"/delete/user"}, + }, + }, + } + + param := make(url.Values) + + assert.Equal(t, CheckPermissions(user, "/admin/", "GET", param), true) + assert.Equal(t, CheckPermissions(user, "/admin", "GET", param), true) + assert.Equal(t, CheckPermissions(user, "/", "GET", param), false) + assert.Equal(t, CheckPermissions(user, "/admin", "POST", param), false) + assert.Equal(t, CheckPermissions(user, "/admin/info/users", "GET", param), false) + assert.Equal(t, CheckPermissions(user, "/admin/info/user", "GET", param), true) + assert.Equal(t, CheckPermissions(user, "/admin/info/user", "get", param), true) + assert.Equal(t, CheckPermissions(user, "/admin/info/normal_manager/edit?__goadmin_edit_pk=2&__columns=id,roles,created_at,updated_at", "get", param), true) + assert.Equal(t, CheckPermissions(user, "/admin/info/normal_manager/edit?__goadmin_edit_pk=2", "get", param), true) + assert.Equal(t, CheckPermissions(user, "/admin/info/normal_manager/edit?__goadmin_edit_pk=3&__columns=id,roles,created_at,updated_at", "get", param), false) + assert.Equal(t, CheckPermissions(user, "/admin/info/normal_manager/edit?__columns=id,roles,created_at,updated_at&id=3", "get", param), false) + assert.Equal(t, CheckPermissions(user, "/admin/info/user", "post", param), false) + assert.Equal(t, CheckPermissions(user, "/admin/info/user/edit?id=3", "get", param), true) + assert.Equal(t, CheckPermissions(user, "/admin/logout?j=asdf", "post", param), true) + assert.Equal(t, CheckPermissions(user, "/admin/info/user_list?user_type=20", "get", param), true) + assert.Equal(t, CheckPermissions(user, "/admin/info/user_list?__goadmin_edit_pk=3&user_type=20", "get", param), true) + assert.Equal(t, CheckPermissions(user, "/admin/delete/user", "post", param), true) +} diff --git a/modules/auth/session.go b/modules/auth/session.go new file mode 100644 index 0000000000000000000000000000000000000000..ecf9e34b9597a9aa74dc8b71722c2b2e64224481 --- /dev/null +++ b/modules/auth/session.go @@ -0,0 +1,238 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package auth + +import ( + "encoding/json" + "net/http" + "strconv" + "time" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/db/dialect" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" +) + +const DefaultCookieKey = "go_admin_session" + +// NewDBDriver return the default PersistenceDriver. +func newDBDriver(conn db.Connection) *DBDriver { + return &DBDriver{ + conn: conn, + tableName: "goadmin_session", + } +} + +// PersistenceDriver is a driver of storing and getting the session info. +type PersistenceDriver interface { + Load(string) (map[string]interface{}, error) + Update(sid string, values map[string]interface{}) error +} + +// GetSessionByKey get the session value by key. +func GetSessionByKey(sesKey, key string, conn db.Connection) (interface{}, error) { + m, err := newDBDriver(conn).Load(sesKey) + return m[key], err +} + +// Session contains info of session. +type Session struct { + Expires time.Duration + Cookie string + Values map[string]interface{} + Driver PersistenceDriver + Sid string + Context *context.Context +} + +// Config wraps the Session info. +type Config struct { + Expires time.Duration + Cookie string +} + +// UpdateConfig update the Expires and Cookie of Session. +func (ses *Session) UpdateConfig(config Config) { + ses.Expires = config.Expires + ses.Cookie = config.Cookie +} + +// Get get the session value. +func (ses *Session) Get(key string) interface{} { + return ses.Values[key] +} + +// Add add the session value of key. +func (ses *Session) Add(key string, value interface{}) error { + ses.Values[key] = value + if err := ses.Driver.Update(ses.Sid, ses.Values); err != nil { + return err + } + cookie := http.Cookie{ + Name: ses.Cookie, + Value: ses.Sid, + MaxAge: config.GetSessionLifeTime(), + Expires: time.Now().Add(ses.Expires), + HttpOnly: true, + Path: "/", + } + if config.GetDomain() != "" { + cookie.Domain = config.GetDomain() + } + ses.Context.SetCookie(&cookie) + return nil +} + +// Clear clear a Session. +func (ses *Session) Clear() error { + ses.Values = map[string]interface{}{} + return ses.Driver.Update(ses.Sid, ses.Values) +} + +// UseDriver set the driver of the Session. +func (ses *Session) UseDriver(driver PersistenceDriver) { + ses.Driver = driver +} + +// StartCtx return a Session from the given Context. +func (ses *Session) StartCtx(ctx *context.Context) (*Session, error) { + if cookie, err := ctx.Request.Cookie(ses.Cookie); err == nil && cookie.Value != "" { + ses.Sid = cookie.Value + valueFromDriver, err := ses.Driver.Load(cookie.Value) + if err != nil { + return nil, err + } + if len(valueFromDriver) > 0 { + ses.Values = valueFromDriver + } + } else { + ses.Sid = modules.Uuid() + } + ses.Context = ctx + return ses, nil +} + +// InitSession return the default Session. +func InitSession(ctx *context.Context, conn db.Connection) (*Session, error) { + + sessions := new(Session) + sessions.UpdateConfig(Config{ + Expires: time.Second * time.Duration(config.GetSessionLifeTime()), + Cookie: DefaultCookieKey, + }) + + sessions.UseDriver(newDBDriver(conn)) + sessions.Values = make(map[string]interface{}) + + return sessions.StartCtx(ctx) +} + +// DBDriver is a driver which uses database as a persistence tool. +type DBDriver struct { + conn db.Connection + tableName string +} + +// Load implements the PersistenceDriver.Load. +func (driver *DBDriver) Load(sid string) (map[string]interface{}, error) { + sesModel, err := driver.table().Where("sid", "=", sid).First() + + if db.CheckError(err, db.QUERY) { + return nil, err + } + + if sesModel == nil { + return map[string]interface{}{}, nil + } + + var values map[string]interface{} + err = json.Unmarshal([]byte(sesModel["values"].(string)), &values) + return values, err +} + +func (driver *DBDriver) deleteOverdueSession() { + + defer func() { + if err := recover(); err != nil { + logger.Error(err) + panic(err) + } + }() + + var ( + duration = strconv.Itoa(config.GetSessionLifeTime() + 1000) + driverName = config.GetDatabases().GetDefault().Driver + raw = `` + ) + + if db.DriverPostgresql == driverName { + raw = `extract(epoch from now()) - ` + duration + ` > extract(epoch from created_at)` + } else if db.DriverMysql == driverName { + raw = `unix_timestamp(created_at) < unix_timestamp() - ` + duration + } else if db.DriverSqlite == driverName { + raw = `strftime('%s', created_at) < strftime('%s', 'now') - ` + duration + } else if db.DriverMssql == driverName { + raw = `DATEDIFF(second, [created_at], GETDATE()) > ` + duration + } else if db.DriverOceanBase == driverName { + raw = `unix_timestamp(created_at) < unix_timestamp() - ` + duration + } + + if raw != "" { + _ = driver.table().WhereRaw(raw).Delete() + } +} + +// Update implements the PersistenceDriver.Update. +func (driver *DBDriver) Update(sid string, values map[string]interface{}) error { + + go driver.deleteOverdueSession() + + if sid != "" { + if len(values) == 0 { + err := driver.table().Where("sid", "=", sid).Delete() + if db.CheckError(err, db.DELETE) { + return err + } + } + valuesByte, err := json.Marshal(values) + if err != nil { + return err + } + sesValue := string(valuesByte) + sesModel, _ := driver.table().Where("sid", "=", sid).First() + if sesModel == nil { + if !config.GetNoLimitLoginIP() { + err = driver.table().Where("values", "=", sesValue).Delete() + if db.CheckError(err, db.DELETE) { + return err + } + } + _, err := driver.table().Insert(dialect.H{ + "values": sesValue, + "sid": sid, + }) + if db.CheckError(err, db.INSERT) { + return err + } + } else { + _, err := driver.table(). + Where("sid", "=", sid). + Update(dialect.H{ + "values": sesValue, + }) + if db.CheckError(err, db.UPDATE) { + return err + } + } + } + return nil +} + +func (driver *DBDriver) table() *db.SQL { + return db.Table(driver.tableName).WithDriver(driver.conn) +} diff --git a/modules/collection/collection.go b/modules/collection/collection.go new file mode 100644 index 0000000000000000000000000000000000000000..39ffc91e7d832b373d096b98174be47434d3e426 --- /dev/null +++ b/modules/collection/collection.go @@ -0,0 +1,71 @@ +package collection + +type Collection []map[string]interface{} + +// Where filters the collection by a given key / value pair. +func (c Collection) Where(key string, values ...interface{}) Collection { + var d = make([]map[string]interface{}, 0) + if len(values) < 1 { + for _, value := range c { + if isTrue(value[key]) { + d = append(d, value) + } + } + } else if len(values) < 2 { + for _, value := range c { + if value[key] == values[0] { + d = append(d, value) + } + } + } else if values[0].(string) == "=" { + for _, value := range c { + if value[key] == values[1] { + d = append(d, value) + } + } + } + return d +} + +func (c Collection) Length() int { + return len(c) +} + +func (c Collection) FirstGet(key string) interface{} { + return c[0][key] +} + +func isTrue(a interface{}) bool { + switch a := a.(type) { + case uint: + return a != uint(0) + case uint8: + return a != uint8(0) + case uint16: + return a != uint16(0) + case uint32: + return a != uint32(0) + case uint64: + return a != uint64(0) + case int: + return a != int(0) + case int8: + return a != int8(0) + case int16: + return a != int16(0) + case int32: + return a != int32(0) + case int64: + return a != int64(0) + case float32: + return a != float32(0) + case float64: + return a != float64(0) + case string: + return a != "" + case bool: + return a + default: + return false + } +} diff --git a/modules/collection/collection_test.go b/modules/collection/collection_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3f643bb80887fe3c1611def64a9e0e19aac15ca8 --- /dev/null +++ b/modules/collection/collection_test.go @@ -0,0 +1 @@ +package collection diff --git a/modules/config/config.go b/modules/config/config.go new file mode 100644 index 0000000000000000000000000000000000000000..aeae74ffd63f486aebd909317a63be0447db0c21 --- /dev/null +++ b/modules/config/config.go @@ -0,0 +1,1316 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package config + +import ( + "encoding/json" + "fmt" + "html/template" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "gopkg.in/ini.v1" + "gopkg.in/yaml.v2" +) + +// Database is a type of database connection config. +// +// Because a little difference of different database driver. +// The Config has multiple options but may not be used. +// Such as the sqlite driver only use the File option which +// can be ignored when the driver is mysql. +// +// If the Dsn is configured, when driver is mysql/postgresql/ +// mssql, the other configurations will be ignored, except for +// MaxIdleConns and MaxOpenConns. +type Database struct { + Host string `json:"host,omitempty" yaml:"host,omitempty" ini:"host,omitempty"` + Port string `json:"port,omitempty" yaml:"port,omitempty" ini:"port,omitempty"` + User string `json:"user,omitempty" yaml:"user,omitempty" ini:"user,omitempty"` + Pwd string `json:"pwd,omitempty" yaml:"pwd,omitempty" ini:"pwd,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty" ini:"name,omitempty"` + Driver string `json:"driver,omitempty" yaml:"driver,omitempty" ini:"driver,omitempty"` + DriverMode string `json:"driver_mode,omitempty" yaml:"driver_mode,omitempty" ini:"driver_mode,omitempty"` + File string `json:"file,omitempty" yaml:"file,omitempty" ini:"file,omitempty"` + Dsn string `json:"dsn,omitempty" yaml:"dsn,omitempty" ini:"dsn,omitempty"` + + MaxIdleConns int `json:"max_idle_con,omitempty" yaml:"max_idle_con,omitempty" ini:"max_idle_con,omitempty"` + MaxOpenConns int `json:"max_open_con,omitempty" yaml:"max_open_con,omitempty" ini:"max_open_con,omitempty"` + ConnMaxLifetime time.Duration `json:"conn_max_life_time,omitempty" yaml:"conn_max_life_time,omitempty" ini:"conn_max_life_time,omitempty"` + ConnMaxIdleTime time.Duration `json:"conn_max_idle_time,omitempty" yaml:"conn_max_idle_time,omitempty" ini:"conn_max_idle_time,omitempty"` + + Params map[string]string `json:"params,omitempty" yaml:"params,omitempty" ini:"params,omitempty"` +} + +func (d Database) GetDSN() string { + if d.Dsn != "" { + return d.Dsn + } + + if d.Driver == DriverMysql { + return d.User + ":" + d.Pwd + "@tcp(" + d.Host + ":" + d.Port + ")/" + + d.Name + d.ParamStr() + } + if d.Driver == DriverPostgresql { + return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s"+d.ParamStr(), + d.Host, d.Port, d.User, d.Pwd, d.Name) + } + if d.Driver == DriverMssql { + return fmt.Sprintf("user id=%s;password=%s;server=%s;port=%s;database=%s;"+d.ParamStr(), + d.User, d.Pwd, d.Host, d.Port, d.Name) + } + if d.Driver == DriverSqlite { + return d.File + d.ParamStr() + } + if d.Driver == DriverOceanBase { + return d.User + ":" + d.Pwd + "@tcp(" + d.Host + ":" + d.Port + ")/" + + d.Name + d.ParamStr() + } + return "" +} + +func (d Database) ParamStr() string { + p := "" + if d.Params == nil { + d.Params = make(map[string]string) + } + if d.Driver == DriverMysql || d.Driver == DriverSqlite || d.Driver == DriverOceanBase { + if d.Driver == DriverMysql || d.Driver == DriverOceanBase { + if _, ok := d.Params["charset"]; !ok { + d.Params["charset"] = "utf8mb4" + } + } + if len(d.Params) > 0 { + p = "?" + for k, v := range d.Params { + p += k + "=" + v + "&" + } + p = p[:len(p)-1] + } + } + if d.Driver == DriverMssql { + if _, ok := d.Params["encrypt"]; !ok { + d.Params["encrypt"] = "disable" + } + for k, v := range d.Params { + p += k + "=" + v + ";" + } + p = p[:len(p)-1] + } + if d.Driver == DriverPostgresql { + if _, ok := d.Params["sslmode"]; !ok { + d.Params["sslmode"] = "disable" + } + p = " " + for k, v := range d.Params { + p += k + "=" + v + " " + } + p = p[:len(p)-1] + } + return p +} + +// DatabaseList is a map of Database. +type DatabaseList map[string]Database + +// GetDefault get the default Database. +func (d DatabaseList) GetDefault() Database { + return d["default"] +} + +// Add add a Database to the DatabaseList. +func (d DatabaseList) Add(key string, db Database) { + d[key] = db +} + +// GroupByDriver group the Databases with the drivers. +func (d DatabaseList) GroupByDriver() map[string]DatabaseList { + drivers := make(map[string]DatabaseList) + for key, item := range d { + if driverList, ok := drivers[item.Driver]; ok { + driverList.Add(key, item) + } else { + drivers[item.Driver] = make(DatabaseList) + drivers[item.Driver].Add(key, item) + } + } + return drivers +} + +func (d DatabaseList) JSON() string { + return utils.JSON(d) +} + +func (d DatabaseList) Copy() DatabaseList { + var c = make(DatabaseList) + for k, v := range d { + c[k] = v + } + return c +} + +func (d DatabaseList) Connections() []string { + conns := make([]string, len(d)) + count := 0 + for key := range d { + conns[count] = key + count++ + } + return conns +} + +func GetDatabaseListFromJSON(m string) DatabaseList { + var d = make(DatabaseList) + if m == "" { + panic("wrong config") + } + _ = json.Unmarshal([]byte(m), &d) + return d +} + +const ( + // EnvTest is a const value of test environment. + EnvTest = "test" + // EnvLocal is a const value of local environment. + EnvLocal = "local" + // EnvProd is a const value of production environment. + EnvProd = "prod" + + // DriverMysql is a const value of mysql driver. + DriverMysql = "mysql" + // DriverSqlite is a const value of sqlite driver. + DriverSqlite = "sqlite" + // DriverPostgresql is a const value of postgresql driver. + DriverPostgresql = "postgresql" + // DriverMssql is a const value of mssql driver. + DriverMssql = "mssql" + // DriverOceanBase is a const value of mysql driver. + DriverOceanBase = "oceanbase" +) + +// Store is the file store config. Path is the local store path. +// and prefix is the url prefix used to visit it. +type Store struct { + Path string `json:"path,omitempty" yaml:"path,omitempty" ini:"path,omitempty"` + Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty" ini:"prefix,omitempty"` +} + +func (s Store) URL(suffix string) string { + if len(suffix) > 4 && suffix[:4] == "http" { + return suffix + } + if s.Prefix == "" { + if suffix[0] == '/' { + return suffix + } + return "/" + suffix + } + if s.Prefix[0] == '/' { + if suffix[0] == '/' { + return s.Prefix + suffix + } + return s.Prefix + "/" + suffix + } + if suffix[0] == '/' { + if len(s.Prefix) > 4 && s.Prefix[:4] == "http" { + return s.Prefix + suffix + } + return "/" + s.Prefix + suffix + } + if len(s.Prefix) > 4 && s.Prefix[:4] == "http" { + return s.Prefix + "/" + suffix + } + return "/" + s.Prefix + "/" + suffix +} + +func (s Store) JSON() string { + if s.Path == "" && s.Prefix == "" { + return "" + } + return utils.JSON(s) +} + +func GetStoreFromJSON(m string) Store { + var s Store + if m == "" { + return s + } + _ = json.Unmarshal([]byte(m), &s) + return s +} + +// Config type is the global config of goAdmin. It will be +// initialized in the engine. +type Config struct { + // An map supports multi database connection. The first + // element of Databases is the default connection. See the + // file connection.go. + Databases DatabaseList `json:"database,omitempty" yaml:"database,omitempty" ini:"database,omitempty"` + + // The application unique ID. Once generated, don't modify. + AppID string `json:"app_id,omitempty" yaml:"app_id,omitempty" ini:"app_id,omitempty"` + + // The cookie domain used in the auth modules. see + // the session.go. + Domain string `json:"domain,omitempty" yaml:"domain,omitempty" ini:"domain,omitempty"` + + // Used to set as the localize language which show in the + // interface. + Language string `json:"language,omitempty" yaml:"language,omitempty" ini:"language,omitempty"` + + // The global url prefix. + UrlPrefix string `json:"prefix,omitempty" yaml:"prefix,omitempty" ini:"prefix,omitempty"` + + // The theme name of template. + Theme string `json:"theme,omitempty" yaml:"theme,omitempty" ini:"theme,omitempty"` + + // The path where files will be stored into. + Store Store `json:"store,omitempty" yaml:"store,omitempty" ini:"store,omitempty"` + + // The title of web page. + Title string `json:"title,omitempty" yaml:"title,omitempty" ini:"title,omitempty"` + + // Logo is the top text in the sidebar. + Logo template.HTML `json:"logo,omitempty" yaml:"logo,omitempty" ini:"logo,omitempty"` + + // Mini-logo is the top text in the sidebar when folding. + MiniLogo template.HTML `json:"mini_logo,omitempty" yaml:"mini_logo,omitempty" ini:"mini_logo,omitempty"` + + // The url redirect to after login. + IndexUrl string `json:"index,omitempty" yaml:"index,omitempty" ini:"index,omitempty"` + + // Login page URL + LoginUrl string `json:"login_url,omitempty" yaml:"login_url,omitempty" ini:"login_url,omitempty"` + + // Debug mode + Debug bool `json:"debug,omitempty" yaml:"debug,omitempty" ini:"debug,omitempty"` + + // Env is the environment,which maybe local,test,prod. + Env string `json:"env,omitempty" yaml:"env,omitempty" ini:"env,omitempty"` + + // Info log path. + InfoLogPath string `json:"info_log,omitempty" yaml:"info_log,omitempty" ini:"info_log,omitempty"` + + // Error log path. + ErrorLogPath string `json:"error_log,omitempty" yaml:"error_log,omitempty" ini:"error_log,omitempty"` + + // Access log path. + AccessLogPath string `json:"access_log,omitempty" yaml:"access_log,omitempty" ini:"access_log,omitempty"` + + // Access assets log off + AccessAssetsLogOff bool `json:"access_assets_log_off,omitempty" yaml:"access_assets_log_off,omitempty" ini:"access_assets_log_off,omitempty"` + + // Sql operator record log switch. + SqlLog bool `json:"sql_log,omitempty" yaml:"sql_log,omitempty" ini:"sql_log,omitempty"` + + AccessLogOff bool `json:"access_log_off,omitempty" yaml:"access_log_off,omitempty" ini:"access_log_off,omitempty"` + InfoLogOff bool `json:"info_log_off,omitempty" yaml:"info_log_off,omitempty" ini:"info_log_off,omitempty"` + ErrorLogOff bool `json:"error_log_off,omitempty" yaml:"error_log_off,omitempty" ini:"error_log_off,omitempty"` + + Logger Logger `json:"logger,omitempty" yaml:"logger,omitempty" ini:"logger,omitempty"` + + // Color scheme. + ColorScheme string `json:"color_scheme,omitempty" yaml:"color_scheme,omitempty" ini:"color_scheme,omitempty"` + + // Session valid time duration,units are seconds. Default 7200. + SessionLifeTime int `json:"session_life_time,omitempty" yaml:"session_life_time,omitempty" ini:"session_life_time,omitempty"` + + // Assets visit link. + AssetUrl string `json:"asset_url,omitempty" yaml:"asset_url,omitempty" ini:"asset_url,omitempty"` + + // File upload engine,default "local" + FileUploadEngine FileUploadEngine `json:"file_upload_engine,omitempty" yaml:"file_upload_engine,omitempty" ini:"file_upload_engine,omitempty"` + + // Custom html in the tag head. + CustomHeadHtml template.HTML `json:"custom_head_html,omitempty" yaml:"custom_head_html,omitempty" ini:"custom_head_html,omitempty"` + + // Custom html after body. + CustomFootHtml template.HTML `json:"custom_foot_html,omitempty" yaml:"custom_foot_html,omitempty" ini:"custom_foot_html,omitempty"` + + // Footer Info html + FooterInfo template.HTML `json:"footer_info,omitempty" yaml:"footer_info,omitempty" ini:"footer_info,omitempty"` + + // Login page title + LoginTitle string `json:"login_title,omitempty" yaml:"login_title,omitempty" ini:"login_title,omitempty"` + + // Login page logo + LoginLogo template.HTML `json:"login_logo,omitempty" yaml:"login_logo,omitempty" ini:"login_logo,omitempty"` + + // Auth user table + AuthUserTable string `json:"auth_user_table,omitempty" yaml:"auth_user_table,omitempty" ini:"auth_user_table,omitempty"` + + // Extra config info + Extra ExtraInfo `json:"extra,omitempty" yaml:"extra,omitempty" ini:"extra,omitempty"` + + // Page animation + Animation PageAnimation `json:"animation,omitempty" yaml:"animation,omitempty" ini:"animation,omitempty"` + + // Limit login with different IPs + NoLimitLoginIP bool `json:"no_limit_login_ip,omitempty" yaml:"no_limit_login_ip,omitempty" ini:"no_limit_login_ip,omitempty"` + + // When site off is true, website will be closed + SiteOff bool `json:"site_off,omitempty" yaml:"site_off,omitempty" ini:"site_off,omitempty"` + + // Hide config center entrance flag + HideConfigCenterEntrance bool `json:"hide_config_center_entrance,omitempty" yaml:"hide_config_center_entrance,omitempty" ini:"hide_config_center_entrance,omitempty"` + + // Prohibit config modification + ProhibitConfigModification bool `json:"prohibit_config_modification,omitempty" yaml:"prohibit_config_modification,omitempty" ini:"prohibit_config_modification,omitempty"` + + // Hide app info entrance flag + HideAppInfoEntrance bool `json:"hide_app_info_entrance,omitempty" yaml:"hide_app_info_entrance,omitempty" ini:"hide_app_info_entrance,omitempty"` + + // Hide tool entrance flag + HideToolEntrance bool `json:"hide_tool_entrance,omitempty" yaml:"hide_tool_entrance,omitempty" ini:"hide_tool_entrance,omitempty"` + + HidePluginEntrance bool `json:"hide_plugin_entrance,omitempty" yaml:"hide_plugin_entrance,omitempty" ini:"hide_plugin_entrance,omitempty"` + + Custom404HTML template.HTML `json:"custom_404_html,omitempty" yaml:"custom_404_html,omitempty" ini:"custom_404_html,omitempty"` + + Custom403HTML template.HTML `json:"custom_403_html,omitempty" yaml:"custom_403_html,omitempty" ini:"custom_403_html,omitempty"` + + Custom500HTML template.HTML `json:"custom_500_html,omitempty" yaml:"custom_500_html,omitempty" ini:"custom_500_html,omitempty"` + + // Update Process Function + UpdateProcessFn UpdateConfigProcessFn `json:"-" yaml:"-" ini:"-"` + + // Favicon string `json:"favicon,omitempty" yaml:"favicon,omitempty" ini:"favicon,omitempty"` + + // Is open admin plugin json api + OpenAdminApi bool `json:"open_admin_api,omitempty" yaml:"open_admin_api,omitempty" ini:"open_admin_api,omitempty"` + + HideVisitorUserCenterEntrance bool `json:"hide_visitor_user_center_entrance,omitempty" yaml:"hide_visitor_user_center_entrance,omitempty" ini:"hide_visitor_user_center_entrance,omitempty"` + + ExcludeThemeComponents []string `json:"exclude_theme_components,omitempty" yaml:"exclude_theme_components,omitempty" ini:"exclude_theme_components,omitempty"` + + BootstrapFilePath string `json:"bootstrap_file_path,omitempty" yaml:"bootstrap_file_path,omitempty" ini:"bootstrap_file_path,omitempty"` + + GoModFilePath string `json:"go_mod_file_path,omitempty" yaml:"go_mod_file_path,omitempty" ini:"go_mod_file_path,omitempty"` + + AllowDelOperationLog bool `json:"allow_del_operation_log,omitempty" yaml:"allow_del_operation_log,omitempty" ini:"allow_del_operation_log,omitempty"` + + OperationLogOff bool `json:"operation_log_off,omitempty" yaml:"operation_log_off,omitempty" ini:"operation_log_off,omitempty"` + + AssetRootPath string `json:"asset_root_path,omitempty" yaml:"asset_root_path,omitempty" ini:"asset_root_path,omitempty"` + + URLFormat URLFormat `json:"url_format,omitempty" yaml:"url_format,omitempty" ini:"url_format,omitempty"` + + prefix string `json:"-" yaml:"-" ini:"-"` + lock sync.RWMutex `json:"-" yaml:"-" ini:"-"` +} + +type Logger struct { + Encoder EncoderCfg `json:"encoder,omitempty" yaml:"encoder,omitempty" ini:"encoder,omitempty"` + Rotate RotateCfg `json:"rotate,omitempty" yaml:"rotate,omitempty" ini:"rotate,omitempty"` + Level int8 `json:"level,omitempty" yaml:"level,omitempty" ini:"level,omitempty"` +} + +type EncoderCfg struct { + TimeKey string `json:"time_key,omitempty" yaml:"time_key,omitempty" ini:"time_key,omitempty"` + LevelKey string `json:"level_key,omitempty" yaml:"level_key,omitempty" ini:"level_key,omitempty"` + NameKey string `json:"name_key,omitempty" yaml:"name_key,omitempty" ini:"name_key,omitempty"` + CallerKey string `json:"caller_key,omitempty" yaml:"caller_key,omitempty" ini:"caller_key,omitempty"` + MessageKey string `json:"message_key,omitempty" yaml:"message_key,omitempty" ini:"message_key,omitempty"` + StacktraceKey string `json:"stacktrace_key,omitempty" yaml:"stacktrace_key,omitempty" ini:"stacktrace_key,omitempty"` + Level string `json:"level,omitempty" yaml:"level,omitempty" ini:"level,omitempty"` + Time string `json:"time,omitempty" yaml:"time,omitempty" ini:"time,omitempty"` + Duration string `json:"duration,omitempty" yaml:"duration,omitempty" ini:"duration,omitempty"` + Caller string `json:"caller,omitempty" yaml:"caller,omitempty" ini:"caller,omitempty"` + Encoding string `json:"encoding,omitempty" yaml:"encoding,omitempty" ini:"encoding,omitempty"` +} + +type RotateCfg struct { + MaxSize int `json:"max_size,omitempty" yaml:"max_size,omitempty" ini:"max_size,omitempty"` + MaxBackups int `json:"max_backups,omitempty" yaml:"max_backups,omitempty" ini:"max_backups,omitempty"` + MaxAge int `json:"max_age,omitempty" yaml:"max_age,omitempty" ini:"max_age,omitempty"` + Compress bool `json:"compress,omitempty" yaml:"compress,omitempty" ini:"compress,omitempty"` +} + +type URLFormat struct { + Info string `json:"info,omitempty" yaml:"info,omitempty" ini:"info,omitempty"` + Detail string `json:"detail,omitempty" yaml:"detail,omitempty" ini:"detail,omitempty"` + Create string `json:"create,omitempty" yaml:"create,omitempty" ini:"create,omitempty"` + Delete string `json:"delete,omitempty" yaml:"delete,omitempty" ini:"delete,omitempty"` + Export string `json:"export,omitempty" yaml:"export,omitempty" ini:"export,omitempty"` + Edit string `json:"edit,omitempty" yaml:"edit,omitempty" ini:"edit,omitempty"` + ShowEdit string `json:"show_edit,omitempty" yaml:"show_edit,omitempty" ini:"show_edit,omitempty"` + ShowCreate string `json:"show_create,omitempty" yaml:"show_create,omitempty" ini:"show_create,omitempty"` + Update string `json:"update,omitempty" yaml:"update,omitempty" ini:"update,omitempty"` +} + +func (f URLFormat) SetDefault() URLFormat { + f.Detail = utils.SetDefault(f.Detail, "", "/info/:__prefix/detail") + f.ShowEdit = utils.SetDefault(f.ShowEdit, "", "/info/:__prefix/edit") + f.ShowCreate = utils.SetDefault(f.ShowCreate, "", "/info/:__prefix/new") + f.Edit = utils.SetDefault(f.Edit, "", "/edit/:__prefix") + f.Create = utils.SetDefault(f.Create, "", "/new/:__prefix") + f.Delete = utils.SetDefault(f.Delete, "", "/delete/:__prefix") + f.Export = utils.SetDefault(f.Export, "", "/export/:__prefix") + f.Info = utils.SetDefault(f.Info, "", "/info/:__prefix") + f.Update = utils.SetDefault(f.Update, "", "/update/:__prefix") + return f +} + +type ExtraInfo map[string]interface{} + +type UpdateConfigProcessFn func(values form.Values) (form.Values, error) + +// see more: https://daneden.github.io/animate.css/ +type PageAnimation struct { + Type string `json:"type,omitempty" yaml:"type,omitempty" ini:"type,omitempty"` + Duration float32 `json:"duration,omitempty" yaml:"duration,omitempty" ini:"duration,omitempty"` + Delay float32 `json:"delay,omitempty" yaml:"delay,omitempty" ini:"delay,omitempty"` +} + +func (p PageAnimation) JSON() string { + if p.Type == "" { + return "" + } + return utils.JSON(p) +} + +// FileUploadEngine is a file upload engine. +type FileUploadEngine struct { + Name string `json:"name,omitempty" yaml:"name,omitempty" ini:"name,omitempty"` + Config map[string]interface{} `json:"config,omitempty" yaml:"config,omitempty" ini:"config,omitempty"` +} + +func (f FileUploadEngine) JSON() string { + if f.Name == "" { + return "" + } + if len(f.Config) == 0 { + f.Config = nil + } + return utils.JSON(f) +} + +func GetFileUploadEngineFromJSON(m string) FileUploadEngine { + var f FileUploadEngine + if m == "" { + return f + } + _ = json.Unmarshal([]byte(m), &f) + return f +} + +// GetIndexURL get the index url with prefix. +func (c *Config) GetIndexURL() string { + index := c.Index() + if index == "/" { + return c.Prefix() + } + + return c.Prefix() + index +} + +// Url get url with the given suffix. +func (c *Config) Url(suffix string) string { + if c.prefix == "/" { + return suffix + } + if suffix == "/" { + return c.prefix + } + return c.prefix + suffix +} + +// IsTestEnvironment check the environment if it is test. +func (c *Config) IsTestEnvironment() bool { + return c.Env == EnvTest +} + +// IsLocalEnvironment check the environment if it is local. +func (c *Config) IsLocalEnvironment() bool { + return c.Env == EnvLocal +} + +// IsProductionEnvironment check the environment if it is production. +func (c *Config) IsProductionEnvironment() bool { + return c.Env == EnvProd +} + +// IsNotProductionEnvironment check the environment if it is not production. +func (c *Config) IsNotProductionEnvironment() bool { + return c.Env != EnvProd +} + +func (c *Config) IsAllowConfigModification() bool { + return !c.ProhibitConfigModification +} + +// URLRemovePrefix remove prefix from the given url. +func (c *Config) URLRemovePrefix(url string) string { + if url == c.prefix { + return "/" + } + if c.prefix == "/" { + return url + } + return strings.Replace(url, c.prefix, "", 1) +} + +// Index return the index url without prefix. +func (c *Config) Index() string { + if c.IndexUrl == "" { + return "/" + } + if c.IndexUrl[0] != '/' { + return "/" + c.IndexUrl + } + return c.IndexUrl +} + +// Prefix return the prefix. +func (c *Config) Prefix() string { + return c.prefix +} + +// AssertPrefix return the prefix of assert. +func (c *Config) AssertPrefix() string { + if c.prefix == "/" { + return "" + } + return c.prefix +} + +func (c *Config) AddUpdateProcessFn(fn UpdateConfigProcessFn) *Config { + c.UpdateProcessFn = fn + return c +} + +// PrefixFixSlash return the prefix fix the slash error. +func (c *Config) PrefixFixSlash() string { + if c.UrlPrefix == "/" { + return "" + } + if c.UrlPrefix != "" && c.UrlPrefix[0] != '/' { + return "/" + c.UrlPrefix + } + return c.UrlPrefix +} + +func (c *Config) Copy() *Config { + + c.lock.RLock() + defer c.lock.RUnlock() + + var ( + newCfg = new(Config) + srcType = reflect.TypeOf(c).Elem() + srcVal = reflect.ValueOf(c).Elem() + distType = reflect.TypeOf(newCfg).Elem() + distVal = reflect.ValueOf(newCfg).Elem() + ) + + for i := 0; i < distType.NumField(); i++ { + v := distVal.Field(i) + if distType.Field(i).Type.String() == "config.DatabaseList" { + newCfg.Databases = c.Databases.Copy() + } else if v.CanInterface() { + for j := 0; j < srcType.NumField(); j++ { + if distType.Field(i).Name == srcType.Field(j).Name { + v.Set(reflect.ValueOf(srcVal.Field(i).Interface())) + break + } + } + } + } + + newCfg.prefix = c.prefix + + return newCfg +} + +func (c *Config) ToMap() map[string]string { + c.lock.RLock() + defer c.lock.RUnlock() + + var ( + m = make(map[string]string) + rType = reflect.TypeOf(c).Elem() + rVal = reflect.ValueOf(c).Elem() + ) + + for i := 0; i < rType.NumField(); i++ { + v := rVal.Field(i) + if !v.CanInterface() { + continue + } + t := rType.Field(i) + keyName := t.Tag.Get("json") + if keyName == "-" { + continue + } + keyName = keyName[:len(keyName)-10] + switch t.Type.Kind() { + case reflect.Bool: + m[keyName] = strconv.FormatBool(v.Bool()) + case reflect.String: + if keyName == "prefix" { + keyName = "url_prefix" + } else if keyName == "index" { + keyName = "index_url" + } else if keyName == "info_log" || keyName == "error_log" || keyName == "access_log" { + keyName += "_path" + } + m[keyName] = v.String() + case reflect.Int: + m[keyName] = fmt.Sprintf("%d", v.Int()) + case reflect.Struct: + switch t.Type.String() { + case "config.PageAnimation": + m["animation_type"] = c.Animation.Type + m["animation_duration"] = fmt.Sprintf("%.2f", c.Animation.Duration) + m["animation_delay"] = fmt.Sprintf("%.2f", c.Animation.Delay) + case "config.Logger": + m["logger_rotate_max_size"] = strconv.Itoa(c.Logger.Rotate.MaxSize) + m["logger_rotate_max_backups"] = strconv.Itoa(c.Logger.Rotate.MaxBackups) + m["logger_rotate_max_age"] = strconv.Itoa(c.Logger.Rotate.MaxAge) + m["logger_rotate_compress"] = strconv.FormatBool(c.Logger.Rotate.Compress) + + m["logger_encoder_time_key"] = c.Logger.Encoder.TimeKey + m["logger_encoder_level_key"] = c.Logger.Encoder.LevelKey + m["logger_encoder_name_key"] = c.Logger.Encoder.NameKey + m["logger_encoder_caller_key"] = c.Logger.Encoder.CallerKey + m["logger_encoder_message_key"] = c.Logger.Encoder.MessageKey + m["logger_encoder_stacktrace_key"] = c.Logger.Encoder.StacktraceKey + m["logger_encoder_level"] = c.Logger.Encoder.Level + m["logger_encoder_time"] = c.Logger.Encoder.Time + m["logger_encoder_duration"] = c.Logger.Encoder.Duration + m["logger_encoder_caller"] = c.Logger.Encoder.Caller + m["logger_encoder_encoding"] = c.Logger.Encoder.Encoding + m["logger_level"] = strconv.Itoa(int(c.Logger.Level)) + case "config.DatabaseList": + m["databases"] = utils.JSON(v.Interface()) + case "config.FileUploadEngine": + m["file_upload_engine"] = c.FileUploadEngine.JSON() + } + case reflect.Map: + if t.Type.String() == "config.ExtraInfo" { + if len(c.Extra) == 0 { + m["extra"] = "" + } else { + m["extra"] = utils.JSON(c.Extra) + } + } + default: + m[keyName] = utils.JSON(v.Interface()) + } + } + return m +} + +func (c *Config) Update(m map[string]string) error { + c.lock.Lock() + defer c.lock.Unlock() + rType := reflect.TypeOf(c).Elem() + rVal := reflect.ValueOf(c).Elem() + for i := 0; i < rType.NumField(); i++ { + v := rVal.Field(i) + if !v.CanInterface() { + continue + } + t := rType.Field(i) + keyName := t.Tag.Get("json") + if keyName == "-" { + continue + } + keyName = keyName[:len(keyName)-10] + switch t.Type.Kind() { + case reflect.Bool: + if mv, ok := m[keyName]; ok { + v.Set(reflect.ValueOf(utils.ParseBool(mv))) + } + case reflect.String: + if t.Type.String() == "template.HTML" { + if mv, ok := m[keyName]; ok { + v.Set(reflect.ValueOf(template.HTML(mv))) + } + continue + } + if keyName == "prefix" { + keyName = "url_prefix" + } else if keyName == "index" { + keyName = "index_url" + } else if keyName == "info_log" || keyName == "error_log" || keyName == "access_log" { + keyName += "_path" + } + if mv, ok := m[keyName]; ok { + if keyName == "info_log" || keyName == "error_log" || keyName == "access_log" { + v.Set(reflect.ValueOf(utils.SetDefault(mv, v.String(), v.String()))) + } else if keyName == "app_id" { + v.Set(reflect.ValueOf(utils.SetDefault(mv, "", v.String()))) + } else if keyName == "color_scheme" { + if m["theme"] == "adminlte" { + v.Set(reflect.ValueOf(mv)) + } + } else { + v.Set(reflect.ValueOf(mv)) + } + } + case reflect.Int: + ses, _ := strconv.Atoi(m[keyName]) + if ses != 0 { + v.Set(reflect.ValueOf(ses)) + } + case reflect.Struct: + switch t.Type.String() { + case "config.PageAnimation": + c.Animation.Type = m["animation_type"] + c.Animation.Duration = utils.ParseFloat32(m["animation_duration"]) + c.Animation.Delay = utils.ParseFloat32(m["animation_delay"]) + case "config.Logger": + c.Logger.Rotate.MaxSize, _ = strconv.Atoi(m["logger_rotate_max_size"]) + c.Logger.Rotate.MaxBackups, _ = strconv.Atoi(m["logger_rotate_max_backups"]) + c.Logger.Rotate.MaxAge, _ = strconv.Atoi(m["logger_rotate_max_age"]) + c.Logger.Rotate.Compress = utils.ParseBool(m["logger_rotate_compress"]) + + c.Logger.Encoder.Encoding = m["logger_encoder_encoding"] + loggerLevel, _ := strconv.Atoi(m["logger_level"]) + c.Logger.Level = int8(loggerLevel) + + if c.Logger.Encoder.Encoding == "json" { + c.Logger.Encoder.TimeKey = m["logger_encoder_time_key"] + c.Logger.Encoder.LevelKey = m["logger_encoder_level_key"] + c.Logger.Encoder.NameKey = m["logger_encoder_name_key"] + c.Logger.Encoder.CallerKey = m["logger_encoder_caller_key"] + c.Logger.Encoder.MessageKey = m["logger_encoder_message_key"] + c.Logger.Encoder.StacktraceKey = m["logger_encoder_stacktrace_key"] + c.Logger.Encoder.Level = m["logger_encoder_level"] + c.Logger.Encoder.Time = m["logger_encoder_time"] + c.Logger.Encoder.Duration = m["logger_encoder_duration"] + c.Logger.Encoder.Caller = m["logger_encoder_caller"] + } + + initLogger(c) + case "config.FileUploadEngine": + c.FileUploadEngine = GetFileUploadEngineFromJSON(m["file_upload_engine"]) + } + case reflect.Map: + if t.Type.String() == "config.ExtraInfo" && m["extra"] != "" { + var extra = make(map[string]interface{}) + _ = json.Unmarshal([]byte(m["extra"]), &extra) + c.Extra = extra + } + } + } + return nil +} + +// eraseSens erase sensitive info. +func (c *Config) EraseSens() *Config { + for key := range c.Databases { + c.Databases[key] = Database{ + Driver: c.Databases[key].Driver, + } + } + return c +} + +var ( + _global = new(Config) + count uint32 + initializeLock sync.Mutex +) + +// ReadFromJson read the Config from a JSON file. +func ReadFromJson(path string) Config { + jsonByte, err := os.ReadFile(path) + + if err != nil { + panic(err) + } + + var cfg Config + + err = json.Unmarshal(jsonByte, &cfg) + + if err != nil { + panic(err) + } + + return cfg +} + +// ReadFromYaml read the Config from a YAML file. +func ReadFromYaml(path string) Config { + jsonByte, err := os.ReadFile(path) + + if err != nil { + panic(err) + } + + var cfg Config + + err = yaml.Unmarshal(jsonByte, &cfg) + + if err != nil { + panic(err) + } + + return cfg +} + +// ReadFromINI read the Config from a INI file. +func ReadFromINI(path string) Config { + iniCfg, err := ini.Load(path) + + if err != nil { + panic(err) + } + + var cfg = Config{ + Databases: make(DatabaseList), + } + + err = iniCfg.MapTo(&cfg) + + if err != nil { + panic(err) + } + + for _, child := range iniCfg.ChildSections("database") { + var d Database + err = child.MapTo(&d) + if err != nil { + panic(err) + } + cfg.Databases[child.Name()[9:]] = d + } + + return cfg +} + +func SetDefault(cfg *Config) *Config { + cfg.Title = utils.SetDefault(cfg.Title, "", "GoAdmin") + cfg.LoginTitle = utils.SetDefault(cfg.LoginTitle, "", "GoAdmin") + cfg.Logo = template.HTML(utils.SetDefault(string(cfg.Logo), "", "GoAdmin")) + cfg.MiniLogo = template.HTML(utils.SetDefault(string(cfg.MiniLogo), "", "GA")) + cfg.Theme = utils.SetDefault(cfg.Theme, "", "adminlte") + cfg.IndexUrl = utils.SetDefault(cfg.IndexUrl, "", "/info/manager") + cfg.LoginUrl = utils.SetDefault(cfg.LoginUrl, "", "/login") + cfg.AuthUserTable = utils.SetDefault(cfg.AuthUserTable, "", "goadmin_users") + cfg.ColorScheme = utils.SetDefault(cfg.ColorScheme, "", "skin-black") + cfg.AssetRootPath = utils.SetDefault(cfg.AssetRootPath, "", "./public/") + cfg.AssetRootPath = filepath.ToSlash(cfg.AssetRootPath) + cfg.FileUploadEngine.Name = utils.SetDefault(cfg.FileUploadEngine.Name, "", "local") + cfg.Env = utils.SetDefault(cfg.Env, "", EnvProd) + if cfg.SessionLifeTime == 0 { + // default two hours + cfg.SessionLifeTime = 7200 + } + cfg.AppID = utils.Uuid(12) + if cfg.UrlPrefix == "" { + cfg.prefix = "/" + } else if cfg.UrlPrefix[0] != '/' { + cfg.prefix = "/" + cfg.UrlPrefix + } else { + cfg.prefix = cfg.UrlPrefix + } + cfg.URLFormat = cfg.URLFormat.SetDefault() + return cfg +} + +// Initialize initialize the config. +func Initialize(cfg *Config) *Config { + + initializeLock.Lock() + defer initializeLock.Unlock() + + if atomic.LoadUint32(&count) != 0 { + panic("can not initialize config twice") + } + atomic.StoreUint32(&count, 1) + + initLogger(SetDefault(cfg)) + + _global = cfg + + return _global +} + +func initLogger(cfg *Config) { + logger.InitWithConfig(logger.Config{ + InfoLogOff: cfg.InfoLogOff, + ErrorLogOff: cfg.ErrorLogOff, + AccessLogOff: cfg.AccessLogOff, + SqlLogOpen: cfg.SqlLog, + InfoLogPath: cfg.InfoLogPath, + ErrorLogPath: cfg.ErrorLogPath, + AccessLogPath: cfg.AccessLogPath, + AccessAssetsLogOff: cfg.AccessAssetsLogOff, + Rotate: logger.RotateCfg{ + MaxSize: cfg.Logger.Rotate.MaxSize, + MaxBackups: cfg.Logger.Rotate.MaxBackups, + MaxAge: cfg.Logger.Rotate.MaxAge, + Compress: cfg.Logger.Rotate.Compress, + }, + Encode: logger.EncoderCfg{ + TimeKey: cfg.Logger.Encoder.TimeKey, + LevelKey: cfg.Logger.Encoder.LevelKey, + NameKey: cfg.Logger.Encoder.NameKey, + CallerKey: cfg.Logger.Encoder.CallerKey, + MessageKey: cfg.Logger.Encoder.MessageKey, + StacktraceKey: cfg.Logger.Encoder.StacktraceKey, + Level: cfg.Logger.Encoder.Level, + Time: cfg.Logger.Encoder.Time, + Duration: cfg.Logger.Encoder.Duration, + Caller: cfg.Logger.Encoder.Caller, + Encoding: cfg.Logger.Encoder.Encoding, + }, + Debug: cfg.Debug, + Level: cfg.Logger.Level, + }) +} + +// AssertPrefix return the prefix of assert. +func AssertPrefix() string { + return _global.AssertPrefix() +} + +// GetIndexURL get the index url with prefix. +func GetIndexURL() string { + return _global.GetIndexURL() +} + +// IsProductionEnvironment check the environment if it is production. +func IsProductionEnvironment() bool { + return _global.IsProductionEnvironment() +} + +// IsNotProductionEnvironment check the environment if it is not production. +func IsNotProductionEnvironment() bool { + return _global.IsNotProductionEnvironment() +} + +// URLRemovePrefix remove prefix from the given url. +func URLRemovePrefix(url string) string { + return _global.URLRemovePrefix(url) +} + +func Url(suffix string) string { + return _global.Url(suffix) +} + +func GetURLFormats() URLFormat { + return _global.URLFormat +} + +// Prefix return the prefix. +func Prefix() string { + return _global.prefix +} + +// PrefixFixSlash return the prefix fix the slash error. +func PrefixFixSlash() string { + return _global.PrefixFixSlash() +} + +// Get gets the config. +func Get() *Config { + _global.lock.RLock() + defer _global.lock.RUnlock() + + return _global.Copy().EraseSens() +} + +// Getter methods +// ============================ + +func GetDatabases() DatabaseList { + var list = make(DatabaseList, len(_global.Databases)) + for key := range _global.Databases { + list[key] = Database{ + Driver: _global.Databases[key].Driver, + DriverMode: _global.Databases[key].DriverMode, + } + } + return list +} + +func GetDomain() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Domain +} + +func GetLanguage() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Language +} + +func GetAppID() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.AppID +} + +func GetUrlPrefix() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.UrlPrefix +} + +func GetOpenAdminApi() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.OpenAdminApi +} + +func GetAllowDelOperationLog() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.AllowDelOperationLog +} + +func GetOperationLogOff() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.OperationLogOff +} + +func GetCustom500HTML() template.HTML { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Custom500HTML +} + +func GetCustom404HTML() template.HTML { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Custom404HTML +} + +func GetCustom403HTML() template.HTML { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Custom403HTML +} + +func GetTheme() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Theme +} + +func GetStore() Store { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Store +} + +func GetTitle() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Title +} + +func GetAssetRootPath() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.AssetRootPath +} + +func GetLogo() template.HTML { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Logo +} + +func GetSiteOff() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.SiteOff +} + +func GetMiniLogo() template.HTML { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.MiniLogo +} + +func GetIndexUrl() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.IndexUrl +} + +func GetLoginUrl() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.LoginUrl +} + +func GetDebug() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Debug +} + +func GetEnv() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Env +} + +func GetInfoLogPath() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.InfoLogPath +} + +func GetErrorLogPath() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.ErrorLogPath +} + +func GetAccessLogPath() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.AccessLogPath +} + +func GetSqlLog() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.SqlLog +} + +func GetAccessLogOff() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.AccessLogOff +} +func GetInfoLogOff() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.InfoLogOff +} +func GetErrorLogOff() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.ErrorLogOff +} + +func GetColorScheme() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.ColorScheme +} + +func GetSessionLifeTime() int { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.SessionLifeTime +} + +func GetAssetUrl() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.AssetUrl +} + +func GetFileUploadEngine() FileUploadEngine { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.FileUploadEngine +} + +func GetCustomHeadHtml() template.HTML { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.CustomHeadHtml +} + +func GetCustomFootHtml() template.HTML { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.CustomFootHtml +} + +func GetFooterInfo() template.HTML { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.FooterInfo +} + +func GetLoginTitle() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.LoginTitle +} + +func GetLoginLogo() template.HTML { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.LoginLogo +} + +func GetAuthUserTable() string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.AuthUserTable +} + +func GetExtra() map[string]interface{} { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Extra +} + +func GetAnimation() PageAnimation { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.Animation +} + +func GetNoLimitLoginIP() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.NoLimitLoginIP +} + +func GetHideVisitorUserCenterEntrance() bool { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.HideVisitorUserCenterEntrance +} + +func GetExcludeThemeComponents() []string { + _global.lock.RLock() + defer _global.lock.RUnlock() + return _global.ExcludeThemeComponents +} + +type Service struct { + C *Config +} + +func (s *Service) Name() string { + return "config" +} + +func SrvWithConfig(c *Config) *Service { + return &Service{c} +} + +func GetService(s interface{}) *Config { + if srv, ok := s.(*Service); ok { + return srv.C + } + panic("wrong service") +} diff --git a/modules/config/config.ini b/modules/config/config.ini new file mode 100644 index 0000000000000000000000000000000000000000..672ddb8a71329445d3b957c739720206e3846379 --- /dev/null +++ b/modules/config/config.ini @@ -0,0 +1,23 @@ +domain = localhost +prefix = admin +language = en +index = / +debug = true +open_admin_api = true +color_scheme = skin-black + +[database.default] + +host = 127.0.0.1 +port = 5432 +user = postgres +pwd = root +name = go-admin-test +max_idle_con = 50 +max_open_con = 150 +driver = postgresql + +[store] + +path = ./uploads +prefix = uploads diff --git a/modules/config/config.yaml b/modules/config/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f1db308cd56e1d8fae4d718817ac1eb8ab605527 --- /dev/null +++ b/modules/config/config.yaml @@ -0,0 +1,21 @@ +--- +database: + default: + host: 127.0.0.1 + port: '1433' + user: sa + pwd: Aa123456 + name: goadmin + max_idle_con: 50 + max_open_con: 150 + driver: mssql +domain: localhost +prefix: admin +store: + path: "./uploads" + prefix: uploads +language: en +index: "/" +debug: true +open_admin_api: true +color_scheme: skin-black \ No newline at end of file diff --git a/modules/config/config_test.go b/modules/config/config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d644e225fbb2f0b9385d362710571b2cecb0ca02 --- /dev/null +++ b/modules/config/config_test.go @@ -0,0 +1,359 @@ +package config + +import ( + "fmt" + "reflect" + "testing" + + "github.com/GoAdminGroup/go-admin/modules/utils" + + "github.com/stretchr/testify/assert" +) + +func TestConfig_GetIndexUrl(t *testing.T) { + Initialize(&Config{ + UrlPrefix: "admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().GetIndexURL(), "/admin") + + testSetCfg(&Config{ + UrlPrefix: "/admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().GetIndexURL(), "/admin") + + testSetCfg(&Config{ + UrlPrefix: "/admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().GetIndexURL(), "/admin") +} + +func TestConfig_Index(t *testing.T) { + testSetCfg(&Config{ + UrlPrefix: "admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().Index(), "/") +} + +func TestConfig_Prefix(t *testing.T) { + testSetCfg(&Config{ + UrlPrefix: "admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().Prefix(), "/admin") + + testSetCfg(&Config{ + UrlPrefix: "/admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().Prefix(), "/admin") +} + +func TestConfig_Url(t *testing.T) { + testSetCfg(&Config{ + UrlPrefix: "admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().Url("/info/user"), "/admin/info/user") + + testSetCfg(&Config{ + UrlPrefix: "/admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().Url("/info/user"), "/admin/info/user") + assert.Equal(t, Get().Url("/info/user") != "/admin/info/user/", true) +} + +func TestConfig_UrlRemovePrefix(t *testing.T) { + + testSetCfg(&Config{ + UrlPrefix: "/admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().URLRemovePrefix("/admin/info/user"), "/info/user") +} + +func TestConfig_PrefixFixSlash(t *testing.T) { + + testSetCfg(&Config{ + UrlPrefix: "/admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().PrefixFixSlash(), "/admin") + + testSetCfg(&Config{ + UrlPrefix: "admin", + IndexUrl: "/", + }) + + assert.Equal(t, Get().PrefixFixSlash(), "/admin") +} + +func TestSet(t *testing.T) { + testSetCfg(&Config{Theme: "abc"}) + testSetCfg(&Config{Theme: "bcd"}) + assert.Equal(t, Get().Theme, "bcd") +} + +func TestStore_URL(t *testing.T) { + testSetCfg(&Config{ + Store: Store{ + Prefix: "/file", + Path: "./uploads", + }, + }) + + assert.Equal(t, Get().Store.URL("/xxxxxx.png"), "/file/xxxxxx.png") + + testSetCfg(&Config{ + Store: Store{ + Prefix: "http://xxxxx.com/xxxx/file", + Path: "./uploads", + }, + }) + + assert.Equal(t, Get().Store.URL("/xxxxxx.png"), "http://xxxxx.com/xxxx/file/xxxxxx.png") + + testSetCfg(&Config{ + Store: Store{ + Prefix: "/file", + Path: "./uploads", + }, + }) + + assert.Equal(t, Get().Store.URL("http://xxxxx.com/xxxx/file/xxxx.png"), "http://xxxxx.com/xxxx/file/xxxx.png") +} + +func TestDatabase_ParamStr(t *testing.T) { + cfg := Database{ + Driver: DriverMysql, + Params: map[string]string{ + "parseTime": "true", + }, + } + assert.Equal(t, cfg.ParamStr(), "?charset=utf8mb4&parseTime=true") +} + +func TestReadFromYaml(t *testing.T) { + cfg := ReadFromYaml("./config.yaml") + assert.Equal(t, cfg.Databases.GetDefault().Driver, "mssql") + assert.Equal(t, cfg.Domain, "localhost") + assert.Equal(t, cfg.UrlPrefix, "admin") + assert.Equal(t, cfg.Store.Path, "./uploads") + assert.Equal(t, cfg.IndexUrl, "/") + assert.Equal(t, cfg.Debug, true) + assert.Equal(t, cfg.OpenAdminApi, true) + assert.Equal(t, cfg.ColorScheme, "skin-black") +} + +func TestReadFromINI(t *testing.T) { + cfg := ReadFromINI("./config.ini") + assert.Equal(t, cfg.Databases.GetDefault().Driver, "postgresql") + assert.Equal(t, cfg.Domain, "localhost") + assert.Equal(t, cfg.UrlPrefix, "admin") + assert.Equal(t, cfg.Store.Path, "./uploads") + assert.Equal(t, cfg.IndexUrl, "/") + assert.Equal(t, cfg.Debug, true) + assert.Equal(t, cfg.OpenAdminApi, true) + assert.Equal(t, cfg.ColorScheme, "skin-black") +} + +func testSetCfg(cfg *Config) { + count = 0 + Initialize(cfg) +} + +func TestUpdate(t *testing.T) { + m := map[string]string{ + "access_assets_log_off": `true`, + "access_log_off": `false`, + "access_log_path": "", + "allow_del_operation_log": `false`, + "animation": `{"type":"fadeInUp","duration":0,"delay":0}`, + "animation_delay": `0.00`, + "animation_duration": `0`, + "animation_type": `fadeInUp`, + "app_id": `70rv3KwjwjXE`, + "asset_root_path": `./public/`, + "asset_url": "", + "auth_user_table": `goadmin_users`, + "bootstrap_file_path": `./../datamodel/bootstrap.go`, + "color_scheme": `skin-black`, + "custom_403_html": "", + "custom_404_html": "", + "custom_500_html": "", + "custom_foot_html": "", + "custom_head_html": ` `, + "databases": `{"default":{"host":"127.0.0.1","port":"3306","user":"root","pwd":"root","name":"godmin","max_idle_con":50,"max_open_con":150,"driver":"mysql","file":"","dsn":""}}`, + "debug": `true`, + "domain": "", + "env": `test`, + "error_log_off": `false`, + "error_log_path": "", + "exclude_theme_components": `null`, + "extra": "", + "file_upload_engine": `{"name":"local","config":null}`, + "footer_info": "", + "go_mod_file_path": "", + "hide_app_info_entrance": `false`, + "hide_config_center_entrance": `false`, + "hide_plugin_entrance": `false`, + "hide_tool_entrance": `false`, + "hide_visitor_user_center_entrance": `false`, + "index_url": `/`, + "info_log_off": `false`, + "info_log_path": "", + "language": `zh`, + "logger_encoder_caller": `full`, + "logger_encoder_caller_key": `caller`, + "logger_encoder_duration": `string`, + "logger_encoder_encoding": `console`, + "logger_encoder_level": `capitalColor`, + "logger_encoder_level_key": `level`, + "logger_encoder_message_key": `msg`, + "logger_encoder_name_key": `logger`, + "logger_encoder_stacktrace_key": `stacktrace`, + "logger_encoder_time": `iso8601`, + "logger_encoder_time_key": `ts`, + "logger_level": `0`, + "logger_rotate_compress": `false`, + "logger_rotate_max_age": `30`, + "logger_rotate_max_backups": `5`, + "logger_rotate_max_size": `10`, + "login_logo": "", + "login_title": `GoAdmin`, + "login_url": `/login`, + "logo": `GoAdmin`, + "mini_logo": `GA`, + "no_limit_login_ip": `false`, + "open_admin_api": `false`, + "operation_log_off": `false`, + "plugin_file_path": `/go/src/github.com/GoAdminGroup/go-admin/examples/gin/plugins.go`, + "session_life_time": `7200`, + "site_off": `false`, + "sql_log": `true`, + "store": `{"path":"./uploads","prefix":"uploads"}`, + "theme": `sword`, + "title": `GoAdmin`, + "url_prefix": `admin`, + } + c := &Config{} + c2 := &Config{} + if c.Update(m) == nil { + if c.Language != c2.Language || + c.Domain != c2.Domain || + c.Theme != c2.Theme || + c.Title != c2.Title || + c.Logo != c2.Logo || + c.MiniLogo != c2.MiniLogo || + c.Debug != c2.Debug || + c.SiteOff != c2.SiteOff || + c.AccessLogOff != c2.AccessLogOff || + c.InfoLogOff != c2.InfoLogOff || + c.ErrorLogOff != c2.ErrorLogOff || + c.AccessAssetsLogOff != c2.AccessAssetsLogOff || + c.InfoLogPath != c2.InfoLogPath || + c.ErrorLogPath != c2.ErrorLogPath || + c.AccessLogPath != c2.AccessLogPath || + c.SqlLog != c2.SqlLog || + c.Logger.Rotate.MaxSize != c2.Logger.Rotate.MaxSize || + c.Logger.Rotate.MaxBackups != c2.Logger.Rotate.MaxBackups || + c.Logger.Rotate.MaxAge != c2.Logger.Rotate.MaxAge || + c.Logger.Rotate.Compress != c2.Logger.Rotate.Compress || + c.Logger.Encoder.Encoding != c2.Logger.Encoder.Encoding || + c.Logger.Level != c2.Logger.Level || + c.Logger.Encoder.TimeKey != c2.Logger.Encoder.TimeKey || + c.Logger.Encoder.LevelKey != c2.Logger.Encoder.LevelKey || + c.Logger.Encoder.NameKey != c2.Logger.Encoder.NameKey || + c.Logger.Encoder.CallerKey != c2.Logger.Encoder.CallerKey || + c.Logger.Encoder.MessageKey != c2.Logger.Encoder.MessageKey || + c.Logger.Encoder.StacktraceKey != c2.Logger.Encoder.StacktraceKey || + c.Logger.Encoder.Level != c2.Logger.Encoder.Level || + c.Logger.Encoder.Time != c2.Logger.Encoder.Time || + c.Logger.Encoder.Duration != c2.Logger.Encoder.Duration || + c.Logger.Encoder.Caller != c2.Logger.Encoder.Caller || + c.ColorScheme != c2.ColorScheme || + c.SessionLifeTime != c2.SessionLifeTime || + c.CustomHeadHtml != c2.CustomHeadHtml || + c.CustomFootHtml != c2.CustomFootHtml || + c.Custom404HTML != c2.Custom404HTML || + c.Custom403HTML != c2.Custom403HTML || + c.Custom500HTML != c2.Custom500HTML || + c.BootstrapFilePath != c2.BootstrapFilePath || + c.GoModFilePath != c2.GoModFilePath || + c.FooterInfo != c2.FooterInfo || + c.LoginTitle != c2.LoginTitle || + c.AssetUrl != c2.AssetUrl || + c.LoginLogo != c2.LoginLogo || + c.NoLimitLoginIP != c2.NoLimitLoginIP || + c.AllowDelOperationLog != c2.AllowDelOperationLog || + c.OperationLogOff != c2.OperationLogOff || + c.HideConfigCenterEntrance != c2.HideConfigCenterEntrance || + c.HideAppInfoEntrance != c2.HideAppInfoEntrance || + c.HideToolEntrance != c2.HideToolEntrance || + c.HidePluginEntrance != c2.HidePluginEntrance || + c.FileUploadEngine.Name != c2.FileUploadEngine.Name || + c.Animation.Type != c2.Animation.Type || + c.Animation.Duration != c2.Animation.Duration || + c.Animation.Delay != c2.Animation.Delay || + !reflect.DeepEqual(c.Extra, c2.Extra) { + panic("c.Extra") + } + } +} + +func TestToMap(t *testing.T) { + c := &Config{ + UrlPrefix: "/admin", + IndexUrl: "/", + MiniLogo: "", + Animation: PageAnimation{ + Type: "12313213", + }, + SessionLifeTime: 40, + ExcludeThemeComponents: []string{"asdfas", "sadfasf"}, + } + m := c.ToMap() + fmt.Println(m) + fmt.Println(m["prefix"], m["animation_type"], m["mini_logo"]) + + arr := []string{ + "language", "databases", "domain", "url_prefix", "theme", "store", "title", "logo", "mini_logo", "index_url", + "site_off", "login_url", "debug", "env", "open_admin_api", "hide_visitor_user_center_entrance", + "info_log_path", "error_log_path", "access_log_path", "sql_log", "access_log_off", "info_log_off", "error_log_off", + "access_assets_log_off", + "logger_rotate_max_size", "logger_rotate_max_backups", "logger_rotate_max_age", "logger_rotate_compress", + "logger_encoder_time_key", "logger_encoder_level_key", "logger_encoder_name_key", "logger_encoder_caller_key", + "logger_encoder_message_key", "logger_encoder_stacktrace_key", "logger_encoder_level", "logger_encoder_time", + "logger_encoder_duration", "logger_encoder_caller", "logger_encoder_encoding", "logger_level", + "color_scheme", "session_life_time", "asset_url", "file_upload_engine", "custom_head_html", "custom_foot_html", + "custom_404_html", "custom_403_html", "custom_500_html", "bootstrap_file_path", "go_mod_file_path", "footer_info", + "app_id", "login_title", "login_logo", "auth_user_table", "exclude_theme_components", + "extra", + "animation_type", "animation_duration", "animation_delay", + "no_limit_login_ip", "allow_del_operation_log", "operation_log_off", + "hide_config_center_entrance", "hide_app_info_entrance", "hide_tool_entrance", "hide_plugin_entrance", + "asset_root_path", + } + + for key := range m { + if !utils.InArray(arr, key) { + panic(key) + } + } + + fmt.Println(len(arr), len(m)) +} diff --git a/modules/constant/constant.go b/modules/constant/constant.go new file mode 100644 index 0000000000000000000000000000000000000000..bac4506d163a3efbe3914716bc6ce019dd84e821 --- /dev/null +++ b/modules/constant/constant.go @@ -0,0 +1,17 @@ +package constant + +const ( + // PjaxHeader is default pjax http header key. + PjaxHeader = "X-PJAX" + + // PjaxUrlHeader is default pjax url http header key. + PjaxUrlHeader = "X-PJAX-Url" + + // Title is default title of the project. + Title = "GoAdmin" + + ContextNodeNeedAuth = "need_auth" + + IframeKey = "__goadmin_iframe" + IframeIDKey = "__goadmin_iframe_id" +) diff --git a/modules/db/base.go b/modules/db/base.go new file mode 100644 index 0000000000000000000000000000000000000000..2f8caf67ee3a6687d09051e11f5b6e1058dd3cfc --- /dev/null +++ b/modules/db/base.go @@ -0,0 +1,54 @@ +package db + +import ( + "database/sql" + "errors" + "sync" + + "github.com/GoAdminGroup/go-admin/modules/config" + "xorm.io/xorm" +) + +// Base is a common Connection. +type Base struct { + DbList map[string]*sql.DB + Once sync.Once + Configs config.DatabaseList +} + +// Close implements the method Connection.Close. +func (db *Base) Close() []error { + errs := make([]error, 0) + for _, d := range db.DbList { + errs = append(errs, d.Close()) + } + return errs +} + +// GetDB implements the method Connection.GetDB. +func (db *Base) GetDB(key string) *sql.DB { + return db.DbList[key] +} + +func (db *Base) CreateDB(name string, beans ...interface{}) error { + cfg := db.GetConfig(name) + if cfg.Driver == "" { + return errors.New("wrong connection name") + } + engine, err := xorm.NewEngine(cfg.Driver, cfg.GetDSN()) + if err != nil { + return err + } + defer func() { + _ = engine.Close() + }() + err = engine.Sync(beans...) + if err != nil { + return err + } + return nil +} + +func (db *Base) GetConfig(name string) config.Database { + return db.Configs[name] +} diff --git a/modules/db/connection.go b/modules/db/connection.go new file mode 100644 index 0000000000000000000000000000000000000000..fd77bb064d48d9f2aa9c8bd14e8f4d96a6def171 --- /dev/null +++ b/modules/db/connection.go @@ -0,0 +1,173 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package db + +import ( + "database/sql" + "fmt" + "strings" + + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/service" +) + +const ( + // DriverMysql is a const value of mysql driver. + DriverMysql = "mysql" + // DriverSqlite is a const value of sqlite driver. + DriverSqlite = "sqlite" + // DriverPostgresql is a const value of postgresql driver. + DriverPostgresql = "postgresql" + // DriverMssql is a const value of mssql driver. + DriverMssql = "mssql" + // DriverOceanBase is a const value of oceanbase driver. + DriverOceanBase = "oceanbase" +) + +// Connection is a connection handler of database. +type Connection interface { + // Query is the query method of sql. + Query(query string, args ...interface{}) ([]map[string]interface{}, error) + + // Exec is the exec method of sql. + Exec(query string, args ...interface{}) (sql.Result, error) + + // QueryWithConnection is the query method with given connection of sql. + QueryWithConnection(conn, query string, args ...interface{}) ([]map[string]interface{}, error) + QueryWithTx(tx *sql.Tx, query string, args ...interface{}) ([]map[string]interface{}, error) + QueryWith(tx *sql.Tx, conn, query string, args ...interface{}) ([]map[string]interface{}, error) + + // ExecWithConnection is the exec method with given connection of sql. + ExecWithConnection(conn, query string, args ...interface{}) (sql.Result, error) + ExecWithTx(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) + ExecWith(tx *sql.Tx, conn, query string, args ...interface{}) (sql.Result, error) + + BeginTxWithReadUncommitted() *sql.Tx + BeginTxWithReadCommitted() *sql.Tx + BeginTxWithRepeatableRead() *sql.Tx + BeginTx() *sql.Tx + BeginTxWithLevel(level sql.IsolationLevel) *sql.Tx + + BeginTxWithReadUncommittedAndConnection(conn string) *sql.Tx + BeginTxWithReadCommittedAndConnection(conn string) *sql.Tx + BeginTxWithRepeatableReadAndConnection(conn string) *sql.Tx + BeginTxAndConnection(conn string) *sql.Tx + BeginTxWithLevelAndConnection(conn string, level sql.IsolationLevel) *sql.Tx + + // InitDB initialize the database connections. + InitDB(cfg map[string]config.Database) Connection + + // GetName get the connection name. + Name() string + + Close() []error + + GetDelimiter() string + GetDelimiter2() string + GetDelimiters() []string + + GetDB(key string) *sql.DB + + GetConfig(name string) config.Database + + CreateDB(name string, beans ...interface{}) error +} + +// GetConnectionByDriver return the Connection by given driver name. +func GetConnectionByDriver(driver string) Connection { + switch driver { + case "mysql": + return GetMysqlDB() + case "mssql": + return GetMssqlDB() + case "sqlite": + return GetSqliteDB() + case "postgresql": + return GetPostgresqlDB() + case "oceanbase": + return GetOceanBaseDB() + default: + panic("driver not found!") + } +} + +func GetConnectionFromService(srv interface{}) Connection { + if v, ok := srv.(Connection); ok { + return v + } + panic("wrong service") +} + +func GetConnection(srvs service.List) Connection { + if v, ok := srvs.Get(config.GetDatabases().GetDefault().Driver).(Connection); ok { + return v + } + panic("wrong service") +} + +func GetAggregationExpression(driver, field, headField, delimiter string) string { + switch driver { + case "postgresql": + return fmt.Sprintf("string_agg(%s::character varying, '%s') as %s", field, delimiter, headField) + case "mysql": + return fmt.Sprintf("group_concat(%s separator '%s') as %s", field, delimiter, headField) + case "sqlite": + return fmt.Sprintf("group_concat(%s, '%s') as %s", field, delimiter, headField) + case "mssql": + return fmt.Sprintf("string_agg(%s, '%s') as [%s]", field, delimiter, headField) + case "oceanbase": + return fmt.Sprintf("group_concat(%s separator '%s') as %s", field, delimiter, headField) + + default: + panic("wrong driver") + } +} + +const ( + INSERT = 0 + DELETE = 1 + UPDATE = 2 + QUERY = 3 +) + +var ignoreErrors = [...][]string{ + // insert + { + "LastInsertId is not supported", + "There is no generated identity value", + "LastInsertId is not supported by this driver", + }, + // delete + { + "no affect", + }, + // update + { + "LastInsertId is not supported", + "There is no generated identity value", + "no affect", + "LastInsertId is not supported by this driver", + }, + // query + { + "LastInsertId is not supported", + "There is no generated identity value", + "no affect", + "out of index", + "LastInsertId is not supported by this driver", + }, +} + +func CheckError(err error, t int) bool { + if err == nil { + return false + } + for _, msg := range ignoreErrors[t] { + if strings.Contains(err.Error(), msg) { + return false + } + } + return true +} diff --git a/modules/db/converter.go b/modules/db/converter.go new file mode 100644 index 0000000000000000000000000000000000000000..1eb5c758882ef10cc69a30a03ef0b345873ae6f8 --- /dev/null +++ b/modules/db/converter.go @@ -0,0 +1,85 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package db + +import ( + "database/sql" +) + +// SetColVarType set the column type. +func SetColVarType(colVar *[]interface{}, i int, typeName string) { + dt := DT(typeName) + switch { + case Contains(dt, BoolTypeList): + var s sql.NullBool + (*colVar)[i] = &s + case Contains(dt, IntTypeList): + var s sql.NullInt64 + (*colVar)[i] = &s + case Contains(dt, FloatTypeList): + var s sql.NullFloat64 + (*colVar)[i] = &s + case Contains(dt, UintTypeList): + var s []uint8 + (*colVar)[i] = &s + case Contains(dt, StringTypeList): + var s sql.NullString + (*colVar)[i] = &s + default: + var s interface{} + (*colVar)[i] = &s + } +} + +// SetResultValue set the result value. +func SetResultValue(result *map[string]interface{}, index string, colVar interface{}, typeName string) { + dt := DT(typeName) + switch { + case Contains(dt, BoolTypeList): + temp := *(colVar.(*sql.NullBool)) + if temp.Valid { + (*result)[index] = temp.Bool + } else { + (*result)[index] = nil + } + case Contains(dt, IntTypeList): + temp := *(colVar.(*sql.NullInt64)) + if temp.Valid { + (*result)[index] = temp.Int64 + } else { + (*result)[index] = nil + } + case Contains(dt, FloatTypeList): + temp := *(colVar.(*sql.NullFloat64)) + if temp.Valid { + (*result)[index] = temp.Float64 + } else { + (*result)[index] = nil + } + case Contains(dt, UintTypeList): + (*result)[index] = *(colVar.(*[]uint8)) + case Contains(dt, StringTypeList): + temp := *(colVar.(*sql.NullString)) + if temp.Valid { + (*result)[index] = temp.String + } else { + (*result)[index] = nil + } + default: + if colVar2, ok := colVar.(*interface{}); ok { + if colVar, ok = (*colVar2).(int64); ok { + (*result)[index] = colVar + } else if colVar, ok = (*colVar2).(string); ok { + (*result)[index] = colVar + } else if colVar, ok = (*colVar2).(float64); ok { + (*result)[index] = colVar + } else if colVar, ok = (*colVar2).([]uint8); ok { + (*result)[index] = colVar + } else { + (*result)[index] = colVar + } + } + } +} diff --git a/modules/db/dialect/common.go b/modules/db/dialect/common.go new file mode 100644 index 0000000000000000000000000000000000000000..a115d6bd5b620435bd48dd4575f6dbbcb1884db2 --- /dev/null +++ b/modules/db/dialect/common.go @@ -0,0 +1,66 @@ +package dialect + +import "fmt" + +type commonDialect struct { + delimiter string + delimiter2 string +} + +func (c commonDialect) Insert(comp *SQLComponent) string { + comp.prepareInsert(c.delimiter, c.delimiter2) + return comp.Statement +} + +func (c commonDialect) Delete(comp *SQLComponent) string { + comp.Statement = "delete from " + c.WrapTableName(comp) + comp.getWheres(c.delimiter, c.delimiter2) + return comp.Statement +} + +func (c commonDialect) Update(comp *SQLComponent) string { + comp.prepareUpdate(c.delimiter, c.delimiter2) + return comp.Statement +} + +func (c commonDialect) Count(comp *SQLComponent) string { + comp.prepareUpdate(c.delimiter, c.delimiter2) + return comp.Statement +} + +func (c commonDialect) Select(comp *SQLComponent) string { + comp.Statement = "select " + comp.getFields(c.delimiter, c.delimiter2) + " from " + c.WrapTableName(comp) + comp.getJoins(c.delimiter, c.delimiter2) + + comp.getWheres(c.delimiter, c.delimiter2) + comp.getGroupBy() + comp.getOrderBy() + comp.getLimit() + comp.getOffset() + return comp.Statement +} + +func (c commonDialect) ShowColumns(table string) string { + return fmt.Sprintf("select * from information_schema.columns where table_name = '%s'", table) +} + +func (c commonDialect) GetName() string { + return "common" +} + +func (c commonDialect) WrapTableName(comp *SQLComponent) string { + return c.delimiter + comp.TableName + c.delimiter2 +} + +func (c commonDialect) ShowTables() string { + return "show tables" +} + +func (c commonDialect) ShowColumnsWithComment(schema, table string) string { + return "" +} + +func (c commonDialect) GetDelimiter() string { + return c.delimiter +} + +func (c commonDialect) GetDelimiter2() string { + return c.delimiter2 +} + +func (c commonDialect) GetDelimiters() []string { + return []string{c.delimiter, c.delimiter2} +} diff --git a/modules/db/dialect/dialect.go b/modules/db/dialect/dialect.go new file mode 100644 index 0000000000000000000000000000000000000000..c0b5e804e091c0f25f0aeff4e8f4d22829c525dc --- /dev/null +++ b/modules/db/dialect/dialect.go @@ -0,0 +1,285 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package dialect + +import ( + "strings" + + "github.com/GoAdminGroup/go-admin/modules/config" +) + +// Dialect is methods set of different driver. +type Dialect interface { + // GetName get dialect's name + GetName() string + + // ShowColumns show columns of specified table + ShowColumns(table string) string + + // ShowColumnsWithComment show columns with coment of specified table + ShowColumnsWithComment(schema, table string) string + + // ShowTables show tables of database + ShowTables() string + + // Insert + Insert(comp *SQLComponent) string + + // Delete + Delete(comp *SQLComponent) string + + // Update + Update(comp *SQLComponent) string + + // Select + Select(comp *SQLComponent) string + + // GetDelimiter return the delimiter of Dialect. + GetDelimiter() string +} + +// GetDialect return the default Dialect. +func GetDialect() Dialect { + return GetDialectByDriver(config.GetDatabases().GetDefault().Driver) +} + +// GetDialectByDriver return the Dialect of given driver. +func GetDialectByDriver(driver string) Dialect { + switch driver { + case "mysql": + return mysql{ + commonDialect: commonDialect{delimiter: "`", delimiter2: "`"}, + } + case "mssql": + return mssql{ + commonDialect: commonDialect{delimiter: "[", delimiter2: "]"}, + } + case "postgresql": + return postgresql{ + commonDialect: commonDialect{delimiter: `"`, delimiter2: `"`}, + } + case "sqlite": + return sqlite{ + commonDialect: commonDialect{delimiter: "`", delimiter2: "`"}, + } + case "oceanbase": + return oceanbase{ + commonDialect: commonDialect{delimiter: "`", delimiter2: "`"}, + } + default: + return commonDialect{delimiter: "`", delimiter2: "`"} + } +} + +// H is a shorthand of map. +type H map[string]interface{} + +// SQLComponent is a sql components set. +type SQLComponent struct { + Fields []string + Functions []string + TableName string + Wheres []Where + Leftjoins []Join + Args []interface{} + Order string + Offset string + Limit string + WhereRaws string + UpdateRaws []RawUpdate + Group string + Statement string + Values H +} + +// Where contains the operation and field. +type Where struct { + Operation string + Field string + Qmark string +} + +// Join contains the table and field and operation. +type Join struct { + Table string + FieldA string + Operation string + FieldB string +} + +// RawUpdate contains the expression and arguments. +type RawUpdate struct { + Expression string + Args []interface{} +} + +// ******************************* +// internal help function +// ******************************* + +func (sql *SQLComponent) getLimit() string { + if sql.Limit == "" { + return "" + } + return " limit " + sql.Limit + " " +} + +func (sql *SQLComponent) getOffset() string { + if sql.Offset == "" { + return "" + } + return " offset " + sql.Offset + " " +} + +func (sql *SQLComponent) getOrderBy() string { + if sql.Order == "" { + return "" + } + return " order by " + sql.Order + " " +} + +func (sql *SQLComponent) getGroupBy() string { + if sql.Group == "" { + return "" + } + return " group by " + sql.Group + " " +} + +func (sql *SQLComponent) getJoins(delimiter, delimiter2 string) string { + if len(sql.Leftjoins) == 0 { + return "" + } + joins := "" + for _, join := range sql.Leftjoins { + joins += " left join " + wrap(delimiter, delimiter2, join.Table) + " on " + + sql.processLeftJoinField(join.FieldA, delimiter, delimiter2) + " " + join.Operation + " " + + sql.processLeftJoinField(join.FieldB, delimiter, delimiter2) + " " + } + return joins +} + +func (sql *SQLComponent) processLeftJoinField(field, delimiter, delimiter2 string) string { + arr := strings.Split(field, ".") + if len(arr) > 0 { + return delimiter + arr[0] + delimiter2 + "." + delimiter + arr[1] + delimiter2 + } + return field +} + +func (sql *SQLComponent) getFields(delimiter, delimiter2 string) string { + if len(sql.Fields) == 0 { + return "*" + } + fields := "" + if len(sql.Leftjoins) == 0 { + for k, field := range sql.Fields { + if sql.Functions[k] != "" { + fields += sql.Functions[k] + "(" + wrap(delimiter, delimiter2, field) + ")," + } else { + fields += wrap(delimiter, delimiter2, field) + "," + } + } + } else { + for _, field := range sql.Fields { + arr := strings.Split(field, ".") + if len(arr) > 1 { + fields += wrap(delimiter, delimiter2, arr[0]) + "." + wrap(delimiter, delimiter2, arr[1]) + "," + } else { + fields += wrap(delimiter, delimiter2, field) + "," + } + } + } + return fields[:len(fields)-1] +} + +func wrap(delimiter, delimiter2, field string) string { + if field == "*" { + return "*" + } + return delimiter + field + delimiter2 +} + +func (sql *SQLComponent) getWheres(delimiter, delimiter2 string) string { + if len(sql.Wheres) == 0 { + if sql.WhereRaws != "" { + return " where " + sql.WhereRaws + } + return "" + } + wheres := " where " + var arr []string + for _, where := range sql.Wheres { + arr = strings.Split(where.Field, ".") + if len(arr) > 1 { + wheres += arr[0] + "." + wrap(delimiter, delimiter2, arr[1]) + " " + where.Operation + " " + where.Qmark + " and " + } else { + wheres += wrap(delimiter, delimiter2, where.Field) + " " + where.Operation + " " + where.Qmark + " and " + } + } + + if sql.WhereRaws != "" { + return wheres + sql.WhereRaws + } + return wheres[:len(wheres)-5] +} + +func (sql *SQLComponent) prepareUpdate(delimiter, delimiter2 string) { + fields := "" + args := make([]interface{}, 0) + + if len(sql.Values) != 0 { + + for key, value := range sql.Values { + fields += wrap(delimiter, delimiter2, key) + " = ?, " + args = append(args, value) + } + + if len(sql.UpdateRaws) == 0 { + fields = fields[:len(fields)-2] + } else { + for i := 0; i < len(sql.UpdateRaws); i++ { + if i == len(sql.UpdateRaws)-1 { + fields += sql.UpdateRaws[i].Expression + " " + } else { + fields += sql.UpdateRaws[i].Expression + "," + } + args = append(args, sql.UpdateRaws[i].Args...) + } + } + + sql.Args = append(args, sql.Args...) + } else { + if len(sql.UpdateRaws) == 0 { + panic("prepareUpdate: wrong parameter") + } else { + for i := 0; i < len(sql.UpdateRaws); i++ { + if i == len(sql.UpdateRaws)-1 { + fields += sql.UpdateRaws[i].Expression + " " + } else { + fields += sql.UpdateRaws[i].Expression + "," + } + args = append(args, sql.UpdateRaws[i].Args...) + } + } + sql.Args = append(args, sql.Args...) + } + + sql.Statement = "update " + delimiter + sql.TableName + delimiter2 + " set " + fields + sql.getWheres(delimiter, delimiter2) +} + +func (sql *SQLComponent) prepareInsert(delimiter, delimiter2 string) { + fields := " (" + quesMark := "(" + + for key, value := range sql.Values { + fields += wrap(delimiter, delimiter2, key) + "," + quesMark += "?," + sql.Args = append(sql.Args, value) + } + fields = fields[:len(fields)-1] + ")" + quesMark = quesMark[:len(quesMark)-1] + ")" + + sql.Statement = "insert into " + delimiter + sql.TableName + delimiter2 + fields + " values " + quesMark +} diff --git a/modules/db/dialect/mssql.go b/modules/db/dialect/mssql.go new file mode 100644 index 0000000000000000000000000000000000000000..635d826be709ea125914ad060773fe9ab0d75e4e --- /dev/null +++ b/modules/db/dialect/mssql.go @@ -0,0 +1,27 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package dialect + +import "fmt" + +type mssql struct { + commonDialect +} + +func (mssql) GetName() string { + return "mssql" +} + +func (mssql) ShowColumnsWithComment(schema, table string) string { + return fmt.Sprintf("select column_name, data_type from information_schema.columns where table_name = '%s'", table) +} + +func (mssql) ShowColumns(table string) string { + return fmt.Sprintf("select column_name, data_type from information_schema.columns where table_name = '%s'", table) +} + +func (mssql) ShowTables() string { + return "select * from information_schema.TABLES" +} diff --git a/modules/db/dialect/mysql.go b/modules/db/dialect/mysql.go new file mode 100644 index 0000000000000000000000000000000000000000..2270f7f6a72e714254dd2ffef3d7eda81aff6127 --- /dev/null +++ b/modules/db/dialect/mysql.go @@ -0,0 +1,31 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package dialect + +type mysql struct { + commonDialect +} + +func (mysql) GetName() string { + return "mysql" +} + +func (mysql) ShowColumnsWithComment(schema, table string) string { + return `SELECT + COLUMN_NAME Field, DATA_TYPE Type, IS_NULLABLE 'Null', COLUMN_KEY 'Key', COLUMN_DEFAULT 'Default', EXTRA Extra, COLUMN_COMMENT Comment + FROM information_schema.COLUMNS + WHERE + table_name = '` + table + `' + AND + table_schema = '` + schema + `'` +} + +func (mysql) ShowColumns(table string) string { + return "show columns in " + table +} + +func (mysql) ShowTables() string { + return "show tables" +} diff --git a/modules/db/dialect/oceanbase.go b/modules/db/dialect/oceanbase.go new file mode 100644 index 0000000000000000000000000000000000000000..b95d833bf313059cf23d56b93d797f80c1ebc5a0 --- /dev/null +++ b/modules/db/dialect/oceanbase.go @@ -0,0 +1,21 @@ +package dialect + +type oceanbase struct { + commonDialect +} + +func (oceanbase) GetName() string { + return "oceanbase" +} + +func (oceanbase) ShowColumnsWithComment(schema, table string) string { + return "show columns in " + table +} + +func (oceanbase) ShowColumns(table string) string { + return "show columns in " + table +} + +func (oceanbase) ShowTables() string { + return "show tables" +} diff --git a/modules/db/dialect/postgresql.go b/modules/db/dialect/postgresql.go new file mode 100644 index 0000000000000000000000000000000000000000..0015e4acadc264bc510ffe6bf86ba9e28ae08f85 --- /dev/null +++ b/modules/db/dialect/postgresql.go @@ -0,0 +1,40 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package dialect + +import ( + "fmt" + "strings" +) + +type postgresql struct { + commonDialect +} + +func (postgresql) GetName() string { + return "postgresql" +} + +func (postgresql) ShowColumnsWithComment(schema, table string) string { + tableArr := strings.Split(table, "\".\"") + if len(tableArr) > 1 { + return fmt.Sprintf("select * from information_schema.columns where table_name = '%s' and table_schema = '%s'", tableArr[1], tableArr[0]) + } else { + return fmt.Sprintf("select * from information_schema.columns where table_name = '%s'", table) + } +} + +func (postgresql) ShowTables() string { + return "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname != 'pg_catalog' AND schemaname != 'information_schema';" +} + +func (postgresql) ShowColumns(table string) string { + tableArr := strings.Split(table, "\".\"") + if len(tableArr) > 1 { + return fmt.Sprintf("select * from information_schema.columns where table_name = '%s' and table_schema = '%s'", tableArr[1], tableArr[0]) + } else { + return fmt.Sprintf("select * from information_schema.columns where table_name = '%s'", table) + } +} diff --git a/modules/db/dialect/sqlite.go b/modules/db/dialect/sqlite.go new file mode 100644 index 0000000000000000000000000000000000000000..27c5329e14b81e8338177945af84aaa7c2823c73 --- /dev/null +++ b/modules/db/dialect/sqlite.go @@ -0,0 +1,25 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package dialect + +type sqlite struct { + commonDialect +} + +func (sqlite) GetName() string { + return "sqlite" +} + +func (sqlite) ShowColumnsWithComment(schema, table string) string { + return "PRAGMA table_info(" + table + ");" +} + +func (sqlite) ShowColumns(table string) string { + return "PRAGMA table_info(" + table + ");" +} + +func (sqlite) ShowTables() string { + return "SELECT name as tablename FROM sqlite_master WHERE type ='table'" +} diff --git a/modules/db/drivers/mssql/mssql.go b/modules/db/drivers/mssql/mssql.go new file mode 100644 index 0000000000000000000000000000000000000000..12946c441e522f8ea0a409d782d9c894f1ac6031 --- /dev/null +++ b/modules/db/drivers/mssql/mssql.go @@ -0,0 +1,3 @@ +package mssql + +import _ "github.com/denisenkom/go-mssqldb" // Import the mssql driver. diff --git a/modules/db/drivers/mysql/mysql.go b/modules/db/drivers/mysql/mysql.go new file mode 100644 index 0000000000000000000000000000000000000000..95de0ab8be04994fed627ab1a9a2f466cadd079a --- /dev/null +++ b/modules/db/drivers/mysql/mysql.go @@ -0,0 +1,3 @@ +package mysql + +import _ "github.com/go-sql-driver/mysql" // Import the mysql driver. diff --git a/modules/db/drivers/oceanbase/oceanbase.go b/modules/db/drivers/oceanbase/oceanbase.go new file mode 100644 index 0000000000000000000000000000000000000000..36aa64b158a559162eb5b7e02b26f3af59a7f25a --- /dev/null +++ b/modules/db/drivers/oceanbase/oceanbase.go @@ -0,0 +1,4 @@ +package oceanbase + +//oceanbase-ce can use mysql driver +import _ "github.com/go-sql-driver/mysql" // Import the mysql driver. diff --git a/modules/db/drivers/postgres/postgres.go b/modules/db/drivers/postgres/postgres.go new file mode 100644 index 0000000000000000000000000000000000000000..2b4860a471268afc85b57868078c88612a6451a8 --- /dev/null +++ b/modules/db/drivers/postgres/postgres.go @@ -0,0 +1,3 @@ +package postgres + +import _ "github.com/lib/pq" // Import the postgresql driver. diff --git a/modules/db/drivers/sqlite/sqlite.go b/modules/db/drivers/sqlite/sqlite.go new file mode 100644 index 0000000000000000000000000000000000000000..c6373fd912ac0e8f9c93ba48d142c1352e63b735 --- /dev/null +++ b/modules/db/drivers/sqlite/sqlite.go @@ -0,0 +1,3 @@ +package sqlite + +import _ "github.com/mattn/go-sqlite3" // Import the sqlite driver. diff --git a/modules/db/mssql.go b/modules/db/mssql.go new file mode 100644 index 0000000000000000000000000000000000000000..a416ce453c8f77e5f81d114b42b445504867ebc0 --- /dev/null +++ b/modules/db/mssql.go @@ -0,0 +1,343 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package db + +import ( + "database/sql" + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/GoAdminGroup/go-admin/modules/config" +) + +// Mssql is a Connection of mssql. +type Mssql struct { + Base +} + +// GetMssqlDB return the global mssql connection. +func GetMssqlDB() *Mssql { + return &Mssql{ + Base: Base{ + DbList: make(map[string]*sql.DB), + }, + } +} + +// GetDelimiter implements the method Connection.GetDelimiter. +func (db *Mssql) GetDelimiter() string { + return "[" +} + +// GetDelimiter2 implements the method Connection.GetDelimiter2. +func (db *Mssql) GetDelimiter2() string { + return "]" +} + +// GetDelimiters implements the method Connection.GetDelimiters. +func (db *Mssql) GetDelimiters() []string { + return []string{"[", "]"} +} + +// Name implements the method Connection.Name. +func (db *Mssql) Name() string { + return "mssql" +} + +// TODO: 整理优化 + +func replaceStringFunc(pattern, src string, rpl func(s string) string) (string, error) { + + r, err := regexp.Compile(pattern) + if err != nil { + return "", err + } + + bytes := r.ReplaceAllFunc([]byte(src), func(bytes []byte) []byte { + return []byte(rpl(string(bytes))) + }) + + return string(bytes), nil +} + +func replace(pattern string, replace, src []byte) ([]byte, error) { + + r, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + + return r.ReplaceAll(src, replace), nil +} + +func replaceString(pattern, rep, src string) (string, error) { + r, e := replace(pattern, []byte(rep), []byte(src)) + return string(r), e +} + +func matchAllString(pattern string, src string) ([][]string, error) { + r, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + return r.FindAllStringSubmatch(src, -1), nil +} + +func isMatch(pattern string, src []byte) bool { + r, err := regexp.Compile(pattern) + if err != nil { + return false + } + return r.Match(src) +} + +func isMatchString(pattern string, src string) bool { + return isMatch(pattern, []byte(src)) +} + +func matchString(pattern string, src string) ([]string, error) { + r, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + return r.FindStringSubmatch(src), nil +} + +// 从Gf框架复制 +// 在执行sql之前对sql进行进一步处理 +func (db *Mssql) handleSqlBeforeExec(query string) string { + index := 0 + str, _ := replaceStringFunc("\\?", query, func(s string) string { + index++ + return fmt.Sprintf("@p%d", index) + }) + + str, _ = replaceString("\"", "", str) + + return db.parseSql(str) +} + +//将MYSQL的SQL语法转换为MSSQL的语法 +//1.由于mssql不支持limit写法所以需要对mysql中的limit用法做转换 +func (db *Mssql) parseSql(sql string) string { + //下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理,如有LIMIT则将LIMIT的关键字也匹配出 + patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))` + if !isMatchString(patten, sql) { + //fmt.Println("not matched..") + return sql + } + + res, err := matchAllString(patten, sql) + if err != nil { + //fmt.Println("MatchString error.", err) + return "" + } + + index := 0 + keyword := strings.TrimSpace(res[index][0]) + keyword = strings.ToUpper(keyword) + + index++ + switch keyword { + case "SELECT": + //不含LIMIT关键字则不处理 + if len(res) < 2 || (!strings.HasPrefix(res[index][0], "LIMIT") && !strings.HasPrefix(res[index][0], "limit")) { + break + } + + //不含LIMIT则不处理 + if !isMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) { + break + } + + //判断SQL中是否含有order by + selectStr := "" + orderbyStr := "" + haveOrderby := isMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql) + if haveOrderby { + //取order by 前面的字符串 + queryExpr, _ := matchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql) + + if len(queryExpr) != 4 || !strings.EqualFold(queryExpr[1], "SELECT") || !strings.EqualFold(queryExpr[3], "ORDER BY") { + break + } + selectStr = queryExpr[2] + + //取order by表达式的值 + orderbyExpr, _ := matchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql) + if len(orderbyExpr) != 4 || !strings.EqualFold(orderbyExpr[1], "ORDER BY") || !strings.EqualFold(orderbyExpr[3], "LIMIT") { + break + } + orderbyStr = orderbyExpr[2] + } else { + queryExpr, _ := matchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) + if len(queryExpr) != 4 || !strings.EqualFold(queryExpr[1], "SELECT") || !strings.EqualFold(queryExpr[3], "LIMIT") { + break + } + selectStr = queryExpr[2] + } + + //取limit后面的取值范围 + first, limit := 0, 0 + for i := 1; i < len(res[index]); i++ { + if strings.TrimSpace(res[index][i]) == "" { + continue + } + + if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") { + first, _ = strconv.Atoi(res[index][i+1]) + limit, _ = strconv.Atoi(res[index][i+2]) + break + } + } + + if haveOrderby { + sql = fmt.Sprintf("SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d", orderbyStr, selectStr, first, limit) + } else { + if first == 0 { + first = limit + } else { + first = limit - first + } + sql = fmt.Sprintf("SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ", first, limit, selectStr) + } + default: + } + return sql +} + +// QueryWithConnection implements the method Connection.QueryWithConnection. +func (db *Mssql) QueryWithConnection(con string, query string, args ...interface{}) ([]map[string]interface{}, error) { + query = db.handleSqlBeforeExec(query) + return CommonQuery(db.DbList[con], query, args...) +} + +// ExecWithConnection implements the method Connection.ExecWithConnection. +func (db *Mssql) ExecWithConnection(con string, query string, args ...interface{}) (sql.Result, error) { + query = db.handleSqlBeforeExec(query) + return CommonExec(db.DbList[con], query, args...) +} + +// Query implements the method Connection.Query. +func (db *Mssql) Query(query string, args ...interface{}) ([]map[string]interface{}, error) { + query = db.handleSqlBeforeExec(query) + return CommonQuery(db.DbList["default"], query, args...) +} + +// Exec implements the method Connection.Exec. +func (db *Mssql) Exec(query string, args ...interface{}) (sql.Result, error) { + query = db.handleSqlBeforeExec(query) + return CommonExec(db.DbList["default"], query, args...) +} + +func (db *Mssql) QueryWith(tx *sql.Tx, conn, query string, args ...interface{}) ([]map[string]interface{}, error) { + if tx != nil { + return db.QueryWithTx(tx, query, args...) + } + return db.QueryWithConnection(conn, query, args...) +} + +func (db *Mssql) ExecWith(tx *sql.Tx, conn, query string, args ...interface{}) (sql.Result, error) { + if tx != nil { + return db.ExecWithTx(tx, query, args...) + } + return db.ExecWithConnection(conn, query, args...) +} + +// InitDB implements the method Connection.InitDB. +func (db *Mssql) InitDB(cfgs map[string]config.Database) Connection { + db.Configs = cfgs + db.Once.Do(func() { + for conn, cfg := range cfgs { + + sqlDB, err := sql.Open("sqlserver", cfg.GetDSN()) + + if sqlDB == nil { + panic("invalid connection") + } + + if err != nil { + _ = sqlDB.Close() + panic(err.Error()) + } + + sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) + sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) + sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime) + sqlDB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime) + + db.DbList[conn] = sqlDB + + if err := sqlDB.Ping(); err != nil { + panic(err) + } + } + }) + return db +} + +// BeginTxWithReadUncommitted starts a transaction with level LevelReadUncommitted. +func (db *Mssql) BeginTxWithReadUncommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommitted starts a transaction with level LevelReadCommitted. +func (db *Mssql) BeginTxWithReadCommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableRead starts a transaction with level LevelRepeatableRead. +func (db *Mssql) BeginTxWithRepeatableRead() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelRepeatableRead) +} + +// BeginTx starts a transaction with level LevelDefault. +func (db *Mssql) BeginTx() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelDefault) +} + +// BeginTxWithLevel starts a transaction with given transaction isolation level. +func (db *Mssql) BeginTxWithLevel(level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], level) +} + +// BeginTxWithReadUncommittedAndConnection starts a transaction with level LevelReadUncommitted and connection. +func (db *Mssql) BeginTxWithReadUncommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommittedAndConnection starts a transaction with level LevelReadCommitted and connection. +func (db *Mssql) BeginTxWithReadCommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableReadAndConnection starts a transaction with level LevelRepeatableRead and connection. +func (db *Mssql) BeginTxWithRepeatableReadAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelRepeatableRead) +} + +// BeginTxAndConnection starts a transaction with level LevelDefault and connection. +func (db *Mssql) BeginTxAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelDefault) +} + +// BeginTxWithLevelAndConnection starts a transaction with given transaction isolation level and connection. +func (db *Mssql) BeginTxWithLevelAndConnection(conn string, level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], level) +} + +// QueryWithTx is query method within the transaction. +func (db *Mssql) QueryWithTx(tx *sql.Tx, query string, args ...interface{}) ([]map[string]interface{}, error) { + query = db.handleSqlBeforeExec(query) + return CommonQueryWithTx(tx, query, args...) +} + +// ExecWithTx is exec method within the transaction. +func (db *Mssql) ExecWithTx(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) { + query = db.handleSqlBeforeExec(query) + return CommonExecWithTx(tx, query, args...) +} diff --git a/modules/db/mysql.go b/modules/db/mysql.go new file mode 100644 index 0000000000000000000000000000000000000000..57847a34a62a69b4132bf212c22e760f7604d582 --- /dev/null +++ b/modules/db/mysql.go @@ -0,0 +1,175 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package db + +import ( + "database/sql" + + "github.com/GoAdminGroup/go-admin/modules/config" +) + +// SQLTx is an in-progress database transaction. +type SQLTx struct { + Tx *sql.Tx +} + +// Mysql is a Connection of mysql. +type Mysql struct { + Base +} + +// GetMysqlDB return the global mysql connection. +func GetMysqlDB() *Mysql { + return &Mysql{ + Base: Base{ + DbList: make(map[string]*sql.DB), + }, + } +} + +// Name implements the method Connection.Name. +func (db *Mysql) Name() string { + return "mysql" +} + +// GetDelimiter implements the method Connection.GetDelimiter. +func (db *Mysql) GetDelimiter() string { + return "`" +} + +// GetDelimiter2 implements the method Connection.GetDelimiter2. +func (db *Mysql) GetDelimiter2() string { + return "`" +} + +// GetDelimiters implements the method Connection.GetDelimiters. +func (db *Mysql) GetDelimiters() []string { + return []string{"`", "`"} +} + +// InitDB implements the method Connection.InitDB. +func (db *Mysql) InitDB(cfgs map[string]config.Database) Connection { + db.Configs = cfgs + db.Once.Do(func() { + for conn, cfg := range cfgs { + + sqlDB, err := sql.Open("mysql", cfg.GetDSN()) + + if err != nil { + if sqlDB != nil { + _ = sqlDB.Close() + } + panic(err) + } + + // Largest set up the database connection reduce time wait + sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) + sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) + sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime) + sqlDB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime) + + db.DbList[conn] = sqlDB + + if err := sqlDB.Ping(); err != nil { + panic(err) + } + } + }) + return db +} + +// QueryWithConnection implements the method Connection.QueryWithConnection. +func (db *Mysql) QueryWithConnection(con string, query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQuery(db.DbList[con], query, args...) +} + +// ExecWithConnection implements the method Connection.ExecWithConnection. +func (db *Mysql) ExecWithConnection(con string, query string, args ...interface{}) (sql.Result, error) { + return CommonExec(db.DbList[con], query, args...) +} + +// Query implements the method Connection.Query. +func (db *Mysql) Query(query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQuery(db.DbList["default"], query, args...) +} + +// Exec implements the method Connection.Exec. +func (db *Mysql) Exec(query string, args ...interface{}) (sql.Result, error) { + return CommonExec(db.DbList["default"], query, args...) +} + +// QueryWithTx is query method within the transaction. +func (db *Mysql) QueryWithTx(tx *sql.Tx, query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQueryWithTx(tx, query, args...) +} + +// ExecWithTx is exec method within the transaction. +func (db *Mysql) ExecWithTx(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) { + return CommonExecWithTx(tx, query, args...) +} + +func (db *Mysql) QueryWith(tx *sql.Tx, conn, query string, args ...interface{}) ([]map[string]interface{}, error) { + if tx != nil { + return db.QueryWithTx(tx, query, args...) + } + return db.QueryWithConnection(conn, query, args...) +} + +func (db *Mysql) ExecWith(tx *sql.Tx, conn, query string, args ...interface{}) (sql.Result, error) { + if tx != nil { + return db.ExecWithTx(tx, query, args...) + } + return db.ExecWithConnection(conn, query, args...) +} + +// BeginTxWithReadUncommitted starts a transaction with level LevelReadUncommitted. +func (db *Mysql) BeginTxWithReadUncommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommitted starts a transaction with level LevelReadCommitted. +func (db *Mysql) BeginTxWithReadCommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableRead starts a transaction with level LevelRepeatableRead. +func (db *Mysql) BeginTxWithRepeatableRead() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelRepeatableRead) +} + +// BeginTx starts a transaction with level LevelDefault. +func (db *Mysql) BeginTx() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelDefault) +} + +// BeginTxWithLevel starts a transaction with given transaction isolation level. +func (db *Mysql) BeginTxWithLevel(level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], level) +} + +// BeginTxWithReadUncommittedAndConnection starts a transaction with level LevelReadUncommitted and connection. +func (db *Mysql) BeginTxWithReadUncommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommittedAndConnection starts a transaction with level LevelReadCommitted and connection. +func (db *Mysql) BeginTxWithReadCommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableReadAndConnection starts a transaction with level LevelRepeatableRead and connection. +func (db *Mysql) BeginTxWithRepeatableReadAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelRepeatableRead) +} + +// BeginTxAndConnection starts a transaction with level LevelDefault and connection. +func (db *Mysql) BeginTxAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelDefault) +} + +// BeginTxWithLevelAndConnection starts a transaction with given transaction isolation level and connection. +func (db *Mysql) BeginTxWithLevelAndConnection(conn string, level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], level) +} diff --git a/modules/db/oceanbase.go b/modules/db/oceanbase.go new file mode 100644 index 0000000000000000000000000000000000000000..2312b1b15aef27c43dc327c31eda6235a0732902 --- /dev/null +++ b/modules/db/oceanbase.go @@ -0,0 +1,165 @@ +package db + +import ( + "database/sql" + "github.com/GoAdminGroup/go-admin/modules/config" +) + +// OceanBase is a Connection of OceanBase. +type OceanBase struct { + Base +} + +// GetOceanBaseDB return the global OceanBase connection. +func GetOceanBaseDB() *OceanBase { + return &OceanBase{ + Base: Base{ + DbList: make(map[string]*sql.DB), + }, + } +} + +// Name implements the method Connection.Name. +func (db *OceanBase) Name() string { + return "OceanBase" +} + +// GetDelimiter implements the method Connection.GetDelimiter. +func (db *OceanBase) GetDelimiter() string { + return "`" +} + +// GetDelimiter2 implements the method Connection.GetDelimiter2. +func (db *OceanBase) GetDelimiter2() string { + return "`" +} + +// GetDelimiters implements the method Connection.GetDelimiters. +func (db *OceanBase) GetDelimiters() []string { + return []string{"`", "`"} +} + +// InitDB implements the method Connection.InitDB. +func (db *OceanBase) InitDB(cfgs map[string]config.Database) Connection { + db.Configs = cfgs + db.Once.Do(func() { + for conn, cfg := range cfgs { + + sqlDB, err := sql.Open("mysql", cfg.GetDSN()) + + if err != nil { + if sqlDB != nil { + _ = sqlDB.Close() + } + panic(err) + } + + // Largest set up the database connection reduce time wait + sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) + sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) + sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime) + sqlDB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime) + + db.DbList[conn] = sqlDB + + if err := sqlDB.Ping(); err != nil { + panic(err) + } + } + }) + return db +} + +// QueryWithConnection implements the method Connection.QueryWithConnection. +func (db *OceanBase) QueryWithConnection(con string, query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQuery(db.DbList[con], query, args...) +} + +// ExecWithConnection implements the method Connection.ExecWithConnection. +func (db *OceanBase) ExecWithConnection(con string, query string, args ...interface{}) (sql.Result, error) { + return CommonExec(db.DbList[con], query, args...) +} + +// Query implements the method Connection.Query. +func (db *OceanBase) Query(query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQuery(db.DbList["default"], query, args...) +} + +// Exec implements the method Connection.Exec. +func (db *OceanBase) Exec(query string, args ...interface{}) (sql.Result, error) { + return CommonExec(db.DbList["default"], query, args...) +} + +// QueryWithTx is query method within the transaction. +func (db *OceanBase) QueryWithTx(tx *sql.Tx, query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQueryWithTx(tx, query, args...) +} + +// ExecWithTx is exec method within the transaction. +func (db *OceanBase) ExecWithTx(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) { + return CommonExecWithTx(tx, query, args...) +} + +func (db *OceanBase) QueryWith(tx *sql.Tx, conn, query string, args ...interface{}) ([]map[string]interface{}, error) { + if tx != nil { + return db.QueryWithTx(tx, query, args...) + } + return db.QueryWithConnection(conn, query, args...) +} + +func (db *OceanBase) ExecWith(tx *sql.Tx, conn, query string, args ...interface{}) (sql.Result, error) { + if tx != nil { + return db.ExecWithTx(tx, query, args...) + } + return db.ExecWithConnection(conn, query, args...) +} + +// BeginTxWithReadUncommitted starts a transaction with level LevelReadUncommitted. +func (db *OceanBase) BeginTxWithReadUncommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommitted starts a transaction with level LevelReadCommitted. +func (db *OceanBase) BeginTxWithReadCommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableRead starts a transaction with level LevelRepeatableRead. +func (db *OceanBase) BeginTxWithRepeatableRead() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelRepeatableRead) +} + +// BeginTx starts a transaction with level LevelDefault. +func (db *OceanBase) BeginTx() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelDefault) +} + +// BeginTxWithLevel starts a transaction with given transaction isolation level. +func (db *OceanBase) BeginTxWithLevel(level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], level) +} + +// BeginTxWithReadUncommittedAndConnection starts a transaction with level LevelReadUncommitted and connection. +func (db *OceanBase) BeginTxWithReadUncommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommittedAndConnection starts a transaction with level LevelReadCommitted and connection. +func (db *OceanBase) BeginTxWithReadCommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableReadAndConnection starts a transaction with level LevelRepeatableRead and connection. +func (db *OceanBase) BeginTxWithRepeatableReadAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelRepeatableRead) +} + +// BeginTxAndConnection starts a transaction with level LevelDefault and connection. +func (db *OceanBase) BeginTxAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelDefault) +} + +// BeginTxWithLevelAndConnection starts a transaction with given transaction isolation level and connection. +func (db *OceanBase) BeginTxWithLevelAndConnection(conn string, level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], level) +} diff --git a/modules/db/performer.go b/modules/db/performer.go new file mode 100644 index 0000000000000000000000000000000000000000..da70f4d88d0dc09bb7fce4543200054f260388ff --- /dev/null +++ b/modules/db/performer.go @@ -0,0 +1,146 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package db + +import ( + "context" + "database/sql" + "regexp" + "strings" +) + +// CommonQuery is a common method of query. +func CommonQuery(db *sql.DB, query string, args ...interface{}) ([]map[string]interface{}, error) { + + rs, err := db.Query(query, args...) + + if err != nil { + panic(err) + } + + defer func() { + if rs != nil { + _ = rs.Close() + } + }() + + col, colErr := rs.Columns() + + if colErr != nil { + return nil, colErr + } + + typeVal, err := rs.ColumnTypes() + if err != nil { + return nil, err + } + + // TODO: regular expressions for sqlite, use the dialect module + // tell the drive to reduce the performance loss + results := make([]map[string]interface{}, 0) + + r, _ := regexp.Compile(`\\((.*)\\)`) + for rs.Next() { + var colVar = make([]interface{}, len(col)) + for i := 0; i < len(col); i++ { + typeName := strings.ToUpper(r.ReplaceAllString(typeVal[i].DatabaseTypeName(), "")) + SetColVarType(&colVar, i, typeName) + } + result := make(map[string]interface{}) + if scanErr := rs.Scan(colVar...); scanErr != nil { + return nil, scanErr + } + for j := 0; j < len(col); j++ { + typeName := strings.ToUpper(r.ReplaceAllString(typeVal[j].DatabaseTypeName(), "")) + SetResultValue(&result, col[j], colVar[j], typeName) + } + results = append(results, result) + } + if err := rs.Err(); err != nil { + return nil, err + } + return results, nil +} + +// CommonExec is a common method of exec. +func CommonExec(db *sql.DB, query string, args ...interface{}) (sql.Result, error) { + + rs, err := db.Exec(query, args...) + if err != nil { + return nil, err + } + return rs, nil +} + +// CommonQueryWithTx is a common method of query. +func CommonQueryWithTx(tx *sql.Tx, query string, args ...interface{}) ([]map[string]interface{}, error) { + + rs, err := tx.Query(query, args...) + + if err != nil { + panic(err) + } + + defer func() { + if rs != nil { + _ = rs.Close() + } + }() + + col, colErr := rs.Columns() + + if colErr != nil { + return nil, colErr + } + + typeVal, err := rs.ColumnTypes() + if err != nil { + return nil, err + } + + // TODO: regular expressions for sqlite, use the dialect module + // tell the drive to reduce the performance loss + results := make([]map[string]interface{}, 0) + + r, _ := regexp.Compile(`\\((.*)\\)`) + for rs.Next() { + var colVar = make([]interface{}, len(col)) + for i := 0; i < len(col); i++ { + typeName := strings.ToUpper(r.ReplaceAllString(typeVal[i].DatabaseTypeName(), "")) + SetColVarType(&colVar, i, typeName) + } + result := make(map[string]interface{}) + if scanErr := rs.Scan(colVar...); scanErr != nil { + return nil, scanErr + } + for j := 0; j < len(col); j++ { + typeName := strings.ToUpper(r.ReplaceAllString(typeVal[j].DatabaseTypeName(), "")) + SetResultValue(&result, col[j], colVar[j], typeName) + } + results = append(results, result) + } + if err := rs.Err(); err != nil { + return nil, err + } + return results, nil +} + +// CommonExecWithTx is a common method of exec. +func CommonExecWithTx(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) { + rs, err := tx.Exec(query, args...) + if err != nil { + return nil, err + } + return rs, nil +} + +// CommonBeginTxWithLevel starts a transaction with given transaction isolation level and db connection. +func CommonBeginTxWithLevel(db *sql.DB, level sql.IsolationLevel) *sql.Tx { + tx, err := db.BeginTx(context.Background(), &sql.TxOptions{Isolation: level}) + if err != nil { + panic(err) + } + return tx +} diff --git a/modules/db/postgresql.go b/modules/db/postgresql.go new file mode 100644 index 0000000000000000000000000000000000000000..328fc1755980d95579c9601c9577b1eaa41f0334 --- /dev/null +++ b/modules/db/postgresql.go @@ -0,0 +1,179 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package db + +import ( + "database/sql" + "strconv" + "strings" + + "github.com/GoAdminGroup/go-admin/modules/config" +) + +// Postgresql is a Connection of postgresql. +type Postgresql struct { + Base +} + +// GetPostgresqlDB return the global postgresql connection. +func GetPostgresqlDB() *Postgresql { + return &Postgresql{ + Base: Base{ + DbList: make(map[string]*sql.DB), + }, + } +} + +// Name implements the method Connection.Name. +func (db *Postgresql) Name() string { + return "postgresql" +} + +// GetDelimiter implements the method Connection.GetDelimiter. +func (db *Postgresql) GetDelimiter() string { + return `"` +} + +// GetDelimiter2 implements the method Connection.GetDelimiter2. +func (db *Postgresql) GetDelimiter2() string { + return `"` +} + +// GetDelimiters implements the method Connection.GetDelimiters. +func (db *Postgresql) GetDelimiters() []string { + return []string{`"`, `"`} +} + +// QueryWithConnection implements the method Connection.QueryWithConnection. +func (db *Postgresql) QueryWithConnection(con string, query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQuery(db.DbList[con], filterQuery(query), args...) +} + +// ExecWithConnection implements the method Connection.ExecWithConnection. +func (db *Postgresql) ExecWithConnection(con string, query string, args ...interface{}) (sql.Result, error) { + return CommonExec(db.DbList[con], filterQuery(query), args...) +} + +// Query implements the method Connection.Query. +func (db *Postgresql) Query(query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQuery(db.DbList["default"], filterQuery(query), args...) +} + +// Exec implements the method Connection.Exec. +func (db *Postgresql) Exec(query string, args ...interface{}) (sql.Result, error) { + return CommonExec(db.DbList["default"], filterQuery(query), args...) +} + +func (db *Postgresql) QueryWith(tx *sql.Tx, conn, query string, args ...interface{}) ([]map[string]interface{}, error) { + if tx != nil { + return db.QueryWithTx(tx, query, args...) + } + return db.QueryWithConnection(conn, query, args...) +} + +func (db *Postgresql) ExecWith(tx *sql.Tx, conn, query string, args ...interface{}) (sql.Result, error) { + if tx != nil { + return db.ExecWithTx(tx, query, args...) + } + return db.ExecWithConnection(conn, query, args...) +} + +func filterQuery(query string) string { + queCount := strings.Count(query, "?") + for i := 1; i < queCount+1; i++ { + query = strings.Replace(query, "?", "$"+strconv.Itoa(i), 1) + } + query = strings.ReplaceAll(query, "`", "") + // TODO: add " to the keyword + return strings.ReplaceAll(query, "by order ", `by "order" `) +} + +// InitDB implements the method Connection.InitDB. +func (db *Postgresql) InitDB(cfgList map[string]config.Database) Connection { + db.Configs = cfgList + db.Once.Do(func() { + for conn, cfg := range cfgList { + sqlDB, err := sql.Open("postgres", cfg.GetDSN()) + if err != nil { + if sqlDB != nil { + _ = sqlDB.Close() + } + panic(err) + } + + sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) + sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) + sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime) + sqlDB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime) + + db.DbList[conn] = sqlDB + + if err := sqlDB.Ping(); err != nil { + panic(err) + } + } + }) + return db +} + +// BeginTxWithReadUncommitted starts a transaction with level LevelReadUncommitted. +func (db *Postgresql) BeginTxWithReadUncommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommitted starts a transaction with level LevelReadCommitted. +func (db *Postgresql) BeginTxWithReadCommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableRead starts a transaction with level LevelRepeatableRead. +func (db *Postgresql) BeginTxWithRepeatableRead() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelRepeatableRead) +} + +// BeginTx starts a transaction with level LevelDefault. +func (db *Postgresql) BeginTx() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelDefault) +} + +// BeginTxWithLevel starts a transaction with given transaction isolation level. +func (db *Postgresql) BeginTxWithLevel(level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], level) +} + +// BeginTxWithReadUncommittedAndConnection starts a transaction with level LevelReadUncommitted and connection. +func (db *Postgresql) BeginTxWithReadUncommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommittedAndConnection starts a transaction with level LevelReadCommitted and connection. +func (db *Postgresql) BeginTxWithReadCommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableReadAndConnection starts a transaction with level LevelRepeatableRead and connection. +func (db *Postgresql) BeginTxWithRepeatableReadAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelRepeatableRead) +} + +// BeginTxAndConnection starts a transaction with level LevelDefault and connection. +func (db *Postgresql) BeginTxAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelDefault) +} + +// BeginTxWithLevelAndConnection starts a transaction with given transaction isolation level and connection. +func (db *Postgresql) BeginTxWithLevelAndConnection(conn string, level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], level) +} + +// QueryWithTx is query method within the transaction. +func (db *Postgresql) QueryWithTx(tx *sql.Tx, query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQueryWithTx(tx, filterQuery(query), args...) +} + +// ExecWithTx is exec method within the transaction. +func (db *Postgresql) ExecWithTx(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) { + return CommonExecWithTx(tx, filterQuery(query), args...) +} diff --git a/modules/db/sqlite.go b/modules/db/sqlite.go new file mode 100644 index 0000000000000000000000000000000000000000..e3bd35801e04c2319dc2a88bd08eb93772ffb485 --- /dev/null +++ b/modules/db/sqlite.go @@ -0,0 +1,165 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package db + +import ( + "database/sql" + + "github.com/GoAdminGroup/go-admin/modules/config" +) + +// Sqlite is a Connection of sqlite. +type Sqlite struct { + Base +} + +// GetSqliteDB return the global sqlite connection. +func GetSqliteDB() *Sqlite { + return &Sqlite{ + Base: Base{ + DbList: make(map[string]*sql.DB), + }, + } +} + +// Name implements the method Connection.Name. +func (db *Sqlite) Name() string { + return "sqlite" +} + +// GetDelimiter implements the method Connection.GetDelimiter. +func (db *Sqlite) GetDelimiter() string { + return "`" +} + +// GetDelimiter2 implements the method Connection.GetDelimiter2. +func (db *Sqlite) GetDelimiter2() string { + return "`" +} + +// GetDelimiters implements the method Connection.GetDelimiters. +func (db *Sqlite) GetDelimiters() []string { + return []string{"`", "`"} +} + +// QueryWithConnection implements the method Connection.QueryWithConnection. +func (db *Sqlite) QueryWithConnection(con string, query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQuery(db.DbList[con], query, args...) +} + +// ExecWithConnection implements the method Connection.ExecWithConnection. +func (db *Sqlite) ExecWithConnection(con string, query string, args ...interface{}) (sql.Result, error) { + return CommonExec(db.DbList[con], query, args...) +} + +// Query implements the method Connection.Query. +func (db *Sqlite) Query(query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQuery(db.DbList["default"], query, args...) +} + +// Exec implements the method Connection.Exec. +func (db *Sqlite) Exec(query string, args ...interface{}) (sql.Result, error) { + return CommonExec(db.DbList["default"], query, args...) +} + +func (db *Sqlite) QueryWith(tx *sql.Tx, conn, query string, args ...interface{}) ([]map[string]interface{}, error) { + if tx != nil { + return db.QueryWithTx(tx, query, args...) + } + return db.QueryWithConnection(conn, query, args...) +} + +func (db *Sqlite) ExecWith(tx *sql.Tx, conn, query string, args ...interface{}) (sql.Result, error) { + if tx != nil { + return db.ExecWithTx(tx, query, args...) + } + return db.ExecWithConnection(conn, query, args...) +} + +// InitDB implements the method Connection.InitDB. +func (db *Sqlite) InitDB(cfgList map[string]config.Database) Connection { + db.Configs = cfgList + db.Once.Do(func() { + for conn, cfg := range cfgList { + sqlDB, err := sql.Open("sqlite3", cfg.GetDSN()) + + if err != nil { + panic(err) + } + + sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) + sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) + sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime) + sqlDB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime) + + db.DbList[conn] = sqlDB + + if err := sqlDB.Ping(); err != nil { + panic(err) + } + } + }) + return db +} + +// BeginTxWithReadUncommitted starts a transaction with level LevelReadUncommitted. +func (db *Sqlite) BeginTxWithReadUncommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommitted starts a transaction with level LevelReadCommitted. +func (db *Sqlite) BeginTxWithReadCommitted() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableRead starts a transaction with level LevelRepeatableRead. +func (db *Sqlite) BeginTxWithRepeatableRead() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelRepeatableRead) +} + +// BeginTx starts a transaction with level LevelDefault. +func (db *Sqlite) BeginTx() *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], sql.LevelDefault) +} + +// BeginTxWithLevel starts a transaction with given transaction isolation level. +func (db *Sqlite) BeginTxWithLevel(level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList["default"], level) +} + +// BeginTxWithReadUncommittedAndConnection starts a transaction with level LevelReadUncommitted and connection. +func (db *Sqlite) BeginTxWithReadUncommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadUncommitted) +} + +// BeginTxWithReadCommittedAndConnection starts a transaction with level LevelReadCommitted and connection. +func (db *Sqlite) BeginTxWithReadCommittedAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelReadCommitted) +} + +// BeginTxWithRepeatableReadAndConnection starts a transaction with level LevelRepeatableRead and connection. +func (db *Sqlite) BeginTxWithRepeatableReadAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelRepeatableRead) +} + +// BeginTxAndConnection starts a transaction with level LevelDefault and connection. +func (db *Sqlite) BeginTxAndConnection(conn string) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], sql.LevelDefault) +} + +// BeginTxWithLevelAndConnection starts a transaction with given transaction isolation level and connection. +func (db *Sqlite) BeginTxWithLevelAndConnection(conn string, level sql.IsolationLevel) *sql.Tx { + return CommonBeginTxWithLevel(db.DbList[conn], level) +} + +// QueryWithTx is query method within the transaction. +func (db *Sqlite) QueryWithTx(tx *sql.Tx, query string, args ...interface{}) ([]map[string]interface{}, error) { + return CommonQueryWithTx(tx, query, args...) +} + +// ExecWithTx is exec method within the transaction. +func (db *Sqlite) ExecWithTx(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) { + return CommonExecWithTx(tx, query, args...) +} diff --git a/modules/db/statement.go b/modules/db/statement.go new file mode 100644 index 0000000000000000000000000000000000000000..5b28609afe69cf6dbce4d9c64335dd9212ff29b6 --- /dev/null +++ b/modules/db/statement.go @@ -0,0 +1,644 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package db + +import ( + dbsql "database/sql" + "errors" + "regexp" + "strconv" + "strings" + "sync" + + "github.com/GoAdminGroup/go-admin/modules/db/dialect" + "github.com/GoAdminGroup/go-admin/modules/logger" +) + +// SQL wraps the Connection and driver dialect methods. +type SQL struct { + dialect.SQLComponent + diver Connection + dialect dialect.Dialect + conn string + tx *dbsql.Tx +} + +// SQLPool is a object pool of SQL. +var SQLPool = sync.Pool{ + New: func() interface{} { + return &SQL{ + SQLComponent: dialect.SQLComponent{ + Fields: make([]string, 0), + TableName: "", + Args: make([]interface{}, 0), + Wheres: make([]dialect.Where, 0), + Leftjoins: make([]dialect.Join, 0), + UpdateRaws: make([]dialect.RawUpdate, 0), + WhereRaws: "", + Order: "", + Group: "", + Limit: "", + }, + diver: nil, + dialect: nil, + } + }, +} + +// H is a shorthand of map. +type H map[string]interface{} + +// newSQL get a new SQL from SQLPool. +func newSQL() *SQL { + return SQLPool.Get().(*SQL) +} + +// ******************************* +// process method +// ******************************* + +// TableName return a SQL with given table and default connection. +func Table(table string) *SQL { + sql := newSQL() + sql.TableName = table + sql.conn = "default" + return sql +} + +// WithDriver return a SQL with given driver. +func WithDriver(conn Connection) *SQL { + sql := newSQL() + sql.diver = conn + sql.dialect = dialect.GetDialectByDriver(conn.Name()) + sql.conn = "default" + return sql +} + +// WithDriverAndConnection return a SQL with given driver and connection name. +func WithDriverAndConnection(connName string, conn Connection) *SQL { + sql := newSQL() + sql.diver = conn + sql.dialect = dialect.GetDialectByDriver(conn.Name()) + sql.conn = connName + return sql +} + +// WithDriver return a SQL with given driver. +func (sql *SQL) WithDriver(conn Connection) *SQL { + sql.diver = conn + sql.dialect = dialect.GetDialectByDriver(conn.Name()) + return sql +} + +// WithConnection set the connection name of SQL. +func (sql *SQL) WithConnection(conn string) *SQL { + sql.conn = conn + return sql +} + +// WithTx set the database transaction object of SQL. +func (sql *SQL) WithTx(tx *dbsql.Tx) *SQL { + sql.tx = tx + return sql +} + +// TableName set table of SQL. +func (sql *SQL) Table(table string) *SQL { + sql.clean() + sql.TableName = table + return sql +} + +// Select set select fields. +func (sql *SQL) Select(fields ...string) *SQL { + sql.Fields = fields + sql.Functions = make([]string, len(fields)) + reg, _ := regexp.Compile(`(.*?)\((.*?)\)`) + for k, field := range fields { + res := reg.FindAllStringSubmatch(field, -1) + if len(res) > 0 && len(res[0]) > 2 { + sql.Functions[k] = res[0][1] + sql.Fields[k] = res[0][2] + } + } + return sql +} + +// OrderBy set order fields. +func (sql *SQL) OrderBy(fields ...string) *SQL { + if len(fields) == 0 { + panic("wrong order field") + } + for i := 0; i < len(fields); i++ { + if i == len(fields)-2 { + sql.Order += " " + sql.wrap(fields[i]) + " " + fields[i+1] + return sql + } + sql.Order += " " + sql.wrap(fields[i]) + " and " + } + return sql +} + +// OrderByRaw set order by. +func (sql *SQL) OrderByRaw(order string) *SQL { + if order != "" { + sql.Order += " " + order + } + return sql +} + +func (sql *SQL) GroupBy(fields ...string) *SQL { + if len(fields) == 0 { + panic("wrong group by field") + } + for i := 0; i < len(fields); i++ { + if i == len(fields)-1 { + sql.Group += " " + sql.wrap(fields[i]) + } else { + sql.Group += " " + sql.wrap(fields[i]) + "," + } + } + return sql +} + +// GroupByRaw set group by. +func (sql *SQL) GroupByRaw(group string) *SQL { + if group != "" { + sql.Group += " " + group + } + return sql +} + +// Skip set offset value. +func (sql *SQL) Skip(offset int) *SQL { + sql.Offset = strconv.Itoa(offset) + return sql +} + +// Take set limit value. +func (sql *SQL) Take(take int) *SQL { + sql.Limit = strconv.Itoa(take) + return sql +} + +// Where add the where operation and argument value. +func (sql *SQL) Where(field string, operation string, arg interface{}) *SQL { + sql.Wheres = append(sql.Wheres, dialect.Where{ + Field: field, + Operation: operation, + Qmark: "?", + }) + sql.Args = append(sql.Args, arg) + return sql +} + +// WhereIn add the where operation of "in" and argument values. +func (sql *SQL) WhereIn(field string, arg []interface{}) *SQL { + if len(arg) == 0 { + panic("wrong parameter") + } + sql.Wheres = append(sql.Wheres, dialect.Where{ + Field: field, + Operation: "in", + Qmark: "(" + strings.Repeat("?,", len(arg)-1) + "?)", + }) + sql.Args = append(sql.Args, arg...) + return sql +} + +// WhereNotIn add the where operation of "not in" and argument values. +func (sql *SQL) WhereNotIn(field string, arg []interface{}) *SQL { + if len(arg) == 0 { + panic("wrong parameter") + } + sql.Wheres = append(sql.Wheres, dialect.Where{ + Field: field, + Operation: "not in", + Qmark: "(" + strings.Repeat("?,", len(arg)-1) + "?)", + }) + sql.Args = append(sql.Args, arg...) + return sql +} + +// Find query the sql result with given id assuming that primary key name is "id". +func (sql *SQL) Find(arg interface{}) (map[string]interface{}, error) { + return sql.Where("id", "=", arg).First() +} + +// Count query the count of query results. +func (sql *SQL) Count() (int64, error) { + var ( + res map[string]interface{} + err error + driver = sql.diver.Name() + ) + + if res, err = sql.Select("count(*)").First(); err != nil { + return 0, err + } + + if driver == DriverPostgresql { + return res["count"].(int64), nil + } else if driver == DriverMssql { + return res[""].(int64), nil + } + + return res["count(*)"].(int64), nil +} + +// Sum sum the value of given field. +func (sql *SQL) Sum(field string) (float64, error) { + var ( + res map[string]interface{} + err error + key = "sum(" + sql.wrap(field) + ")" + ) + if res, err = sql.Select("sum(" + field + ")").First(); err != nil { + return 0, err + } + + if res == nil { + return 0, nil + } + + if r, ok := res[key].(float64); ok { + return r, nil + } else if r, ok := res[key].([]uint8); ok { + return strconv.ParseFloat(string(r), 64) + } else { + return 0, nil + } +} + +// Max find the maximal value of given field. +func (sql *SQL) Max(field string) (interface{}, error) { + var ( + res map[string]interface{} + err error + key = "max(" + sql.wrap(field) + ")" + ) + if res, err = sql.Select("max(" + field + ")").First(); err != nil { + return 0, err + } + + if res == nil { + return 0, nil + } + + return res[key], nil +} + +// Min find the minimal value of given field. +func (sql *SQL) Min(field string) (interface{}, error) { + var ( + res map[string]interface{} + err error + key = "min(" + sql.wrap(field) + ")" + ) + if res, err = sql.Select("min(" + field + ")").First(); err != nil { + return 0, err + } + + if res == nil { + return 0, nil + } + + return res[key], nil +} + +// Avg find the average value of given field. +func (sql *SQL) Avg(field string) (interface{}, error) { + var ( + res map[string]interface{} + err error + key = "avg(" + sql.wrap(field) + ")" + ) + if res, err = sql.Select("avg(" + field + ")").First(); err != nil { + return 0, err + } + + if res == nil { + return 0, nil + } + + return res[key], nil +} + +// WhereRaw set WhereRaws and arguments. +func (sql *SQL) WhereRaw(raw string, args ...interface{}) *SQL { + sql.WhereRaws = raw + sql.Args = append(sql.Args, args...) + return sql +} + +// UpdateRaw set UpdateRaw. +func (sql *SQL) UpdateRaw(raw string, args ...interface{}) *SQL { + sql.UpdateRaws = append(sql.UpdateRaws, dialect.RawUpdate{ + Expression: raw, + Args: args, + }) + return sql +} + +// LeftJoin add a left join info. +func (sql *SQL) LeftJoin(table string, fieldA string, operation string, fieldB string) *SQL { + sql.Leftjoins = append(sql.Leftjoins, dialect.Join{ + FieldA: fieldA, + FieldB: fieldB, + Table: table, + Operation: operation, + }) + return sql +} + +// ******************************* +// Transaction method +// ******************************* + +// TxFn is the transaction callback function. +type TxFn func(tx *dbsql.Tx) (error, map[string]interface{}) + +// WithTransaction call the callback function within the transaction and +// catch the error. +func (sql *SQL) WithTransaction(fn TxFn) (res map[string]interface{}, err error) { + + tx := sql.diver.BeginTxAndConnection(sql.conn) + + defer func() { + if p := recover(); p != nil { + // a panic occurred, rollback and repanic + _ = tx.Rollback() + panic(p) + } else if err != nil { + // something went wrong, rollback + _ = tx.Rollback() + } else { + // all good, commit + err = tx.Commit() + } + }() + + err, res = fn(tx) + return +} + +// WithTransactionByLevel call the callback function within the transaction +// of given transaction level and catch the error. +func (sql *SQL) WithTransactionByLevel(level dbsql.IsolationLevel, fn TxFn) (res map[string]interface{}, err error) { + + tx := sql.diver.BeginTxWithLevelAndConnection(sql.conn, level) + + defer func() { + if p := recover(); p != nil { + // a panic occurred, rollback and repanic + _ = tx.Rollback() + panic(p) + } else if err != nil { + // something went wrong, rollback + _ = tx.Rollback() + } else { + // all good, commit + err = tx.Commit() + } + }() + + err, res = fn(tx) + return +} + +// ******************************* +// terminal method +// ------------------------------- +// sql args order: +// update ... => where ... +// ******************************* + +// First query the result and return the first row. +func (sql *SQL) First() (map[string]interface{}, error) { + defer RecycleSQL(sql) + + sql.dialect.Select(&sql.SQLComponent) + + res, err := sql.diver.QueryWith(sql.tx, sql.conn, sql.Statement, sql.Args...) + + if err != nil { + return nil, err + } + + if len(res) < 1 { + return nil, errors.New("out of index") + } + return res[0], nil +} + +// All query all the result and return. +func (sql *SQL) All() ([]map[string]interface{}, error) { + defer RecycleSQL(sql) + + sql.dialect.Select(&sql.SQLComponent) + + return sql.diver.QueryWith(sql.tx, sql.conn, sql.Statement, sql.Args...) +} + +// ShowColumns show columns info. +func (sql *SQL) ShowColumns() ([]map[string]interface{}, error) { + defer RecycleSQL(sql) + + return sql.diver.QueryWithConnection(sql.conn, sql.dialect.ShowColumns(sql.TableName)) +} + +// ShowColumnsWithComment show columns info. +func (sql *SQL) ShowColumnsWithComment(database string) ([]map[string]interface{}, error) { + defer RecycleSQL(sql) + + return sql.diver.QueryWithConnection(sql.conn, sql.dialect.ShowColumnsWithComment(database, sql.TableName)) +} + +// ShowTables show table info. +func (sql *SQL) ShowTables() ([]string, error) { + defer RecycleSQL(sql) + + models, err := sql.diver.QueryWithConnection(sql.conn, sql.dialect.ShowTables()) + + if err != nil { + return []string{}, err + } + + tables := make([]string, 0) + if len(models) == 0 { + return tables, nil + } + + key := "Tables_in_" + sql.TableName + if sql.diver.Name() == DriverPostgresql || sql.diver.Name() == DriverSqlite { + key = "tablename" + } else if sql.diver.Name() == DriverMssql { + key = "TABLE_NAME" + } else if _, ok := models[0][key].(string); !ok { + key = "Tables_in_" + strings.ToLower(sql.TableName) + } + + for i := 0; i < len(models); i++ { + // skip sqlite system tables + if sql.diver.Name() == DriverSqlite && models[i][key].(string) == "sqlite_sequence" { + continue + } + + tables = append(tables, models[i][key].(string)) + } + + return tables, nil +} + +// Update exec the update method of given key/value pairs. +func (sql *SQL) Update(values dialect.H) (int64, error) { + defer RecycleSQL(sql) + + sql.Values = values + + sql.dialect.Update(&sql.SQLComponent) + + res, err := sql.diver.ExecWith(sql.tx, sql.conn, sql.Statement, sql.Args...) + + if err != nil { + return 0, err + } + + if affectRow, _ := res.RowsAffected(); affectRow < 1 { + return 0, errors.New("no affect row") + } + + return res.LastInsertId() +} + +// Delete exec the delete method. +func (sql *SQL) Delete() error { + defer RecycleSQL(sql) + + sql.dialect.Delete(&sql.SQLComponent) + + res, err := sql.diver.ExecWith(sql.tx, sql.conn, sql.Statement, sql.Args...) + + if err != nil { + return err + } + + if affectRow, _ := res.RowsAffected(); affectRow < 1 { + return errors.New("no affect row") + } + + return nil +} + +// Exec exec the exec method. +func (sql *SQL) Exec() (int64, error) { + defer RecycleSQL(sql) + + sql.dialect.Update(&sql.SQLComponent) + + res, err := sql.diver.ExecWith(sql.tx, sql.conn, sql.Statement, sql.Args...) + + if err != nil { + return 0, err + } + + if affectRow, _ := res.RowsAffected(); affectRow < 1 { + return 0, errors.New("no affect row") + } + + return res.LastInsertId() +} + +const postgresInsertCheckTableName = "goadmin_menu|goadmin_permissions|goadmin_roles|goadmin_users" + +// Insert exec the insert method of given key/value pairs. +func (sql *SQL) Insert(values dialect.H) (int64, error) { + defer RecycleSQL(sql) + + sql.Values = values + + sql.dialect.Insert(&sql.SQLComponent) + + if sql.diver.Name() == DriverPostgresql && (strings.Contains(postgresInsertCheckTableName, sql.TableName)) { + + resMap, err := sql.diver.QueryWith(sql.tx, sql.conn, sql.Statement+" RETURNING id", sql.Args...) + + if err != nil { + + // Fixed java h2 database postgresql mode + _, err := sql.diver.QueryWith(sql.tx, sql.conn, sql.Statement, sql.Args...) + + if err != nil { + return 0, err + } + + res, err := sql.diver.QueryWithConnection(sql.conn, `SELECT max("id") as "id" FROM "`+sql.TableName+`"`) + + if err != nil { + return 0, err + } + + if len(res) != 0 { + return res[0]["id"].(int64), nil + } + + return 0, err + } + + if len(resMap) == 0 { + return 0, errors.New("no affect row") + } + + return resMap[0]["id"].(int64), nil + } + + res, err := sql.diver.ExecWith(sql.tx, sql.conn, sql.Statement, sql.Args...) + + if err != nil { + return 0, err + } + + if affectRow, _ := res.RowsAffected(); affectRow < 1 { + return 0, errors.New("no affect row") + } + + return res.LastInsertId() +} + +func (sql *SQL) wrap(field string) string { + return sql.diver.GetDelimiter() + field + sql.diver.GetDelimiter2() +} + +func (sql *SQL) clean() { + sql.Functions = make([]string, 0) + sql.Group = "" + sql.Values = make(map[string]interface{}) + sql.Fields = make([]string, 0) + sql.TableName = "" + sql.Wheres = make([]dialect.Where, 0) + sql.Leftjoins = make([]dialect.Join, 0) + sql.Args = make([]interface{}, 0) + sql.Order = "" + sql.Offset = "" + sql.Limit = "" + sql.WhereRaws = "" + sql.UpdateRaws = make([]dialect.RawUpdate, 0) + sql.Statement = "" +} + +// RecycleSQL clear the SQL and put into the pool. +func RecycleSQL(sql *SQL) { + + logger.LogSQL(sql.Statement, sql.Args) + + sql.clean() + + sql.conn = "" + sql.diver = nil + sql.tx = nil + sql.dialect = nil + + SQLPool.Put(sql) +} diff --git a/modules/db/statement_mssql_test.go b/modules/db/statement_mssql_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c35938978ebf41b9dd3ccf107e8b7f9250b08afa --- /dev/null +++ b/modules/db/statement_mssql_test.go @@ -0,0 +1,51 @@ +package db + +import ( + "testing" + + "github.com/GoAdminGroup/go-admin/modules/config" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mssql" +) + +var driverTestMssqlConn Connection + +func InitMssql() { + driverTestMssqlConn = testConn(DriverMssql, config.Database{ + Host: "127.0.0.1", + Port: "1433", + User: "sa", + Pwd: "Aa123456", + Name: "goadmin", + }) +} + +func TestMssqlSQL_WhereIn(t *testing.T) { testSQLWhereIn(t, driverTestMssqlConn) } +func TestMssqlSQL_Count(t *testing.T) { testSQLCount(t, driverTestMssqlConn) } +func TestMssqlSQL_Select(t *testing.T) { testSQLSelect(t, driverTestMssqlConn) } +func TestMssqlSQL_OrderBy(t *testing.T) { testSQLOrderBy(t, driverTestMssqlConn) } +func TestMssqlSQL_GroupBy(t *testing.T) { testSQLGroupBy(t, driverTestMssqlConn) } +func TestMssqlSQL_Skip(t *testing.T) { testSQLSkip(t, driverTestMssqlConn) } +func TestMssqlSQL_Take(t *testing.T) { testSQLTake(t, driverTestMssqlConn) } +func TestMssqlSQL_Where(t *testing.T) { testSQLWhere(t, driverTestMssqlConn) } +func TestMssqlSQL_WhereNotIn(t *testing.T) { testSQLWhereNotIn(t, driverTestMssqlConn) } +func TestMssqlSQL_Find(t *testing.T) { testSQLFind(t, driverTestMssqlConn) } +func TestMssqlSQL_Sum(t *testing.T) { testSQLSum(t, driverTestMssqlConn) } +func TestMssqlSQL_Max(t *testing.T) { testSQLMax(t, driverTestMssqlConn) } +func TestMssqlSQL_Min(t *testing.T) { testSQLMin(t, driverTestMssqlConn) } +func TestMssqlSQL_Avg(t *testing.T) { testSQLAvg(t, driverTestMssqlConn) } +func TestMssqlSQL_WhereRaw(t *testing.T) { testSQLWhereRaw(t, driverTestMssqlConn) } +func TestMssqlSQL_UpdateRaw(t *testing.T) { testSQLUpdateRaw(t, driverTestMssqlConn) } +func TestMssqlSQL_LeftJoin(t *testing.T) { testSQLLeftJoin(t, driverTestMssqlConn) } +func TestMssqlSQL_WithTransaction(t *testing.T) { testSQLWithTransaction(t, driverTestMssqlConn) } +func TestMssqlSQL_WithTransactionByLevel(t *testing.T) { + testSQLWithTransactionByLevel(t, driverTestMssqlConn) +} +func TestMssqlSQL_First(t *testing.T) { testSQLFirst(t, driverTestMssqlConn) } +func TestMssqlSQL_All(t *testing.T) { testSQLAll(t, driverTestMssqlConn) } +func TestMssqlSQL_ShowColumns(t *testing.T) { testSQLShowColumns(t, driverTestMssqlConn) } +func TestMssqlSQL_ShowTables(t *testing.T) { testSQLShowTables(t, driverTestMssqlConn) } +func TestMssqlSQL_Update(t *testing.T) { testSQLUpdate(t, driverTestMssqlConn) } +func TestMssqlSQL_Delete(t *testing.T) { testSQLDelete(t, driverTestMssqlConn) } +func TestMssqlSQL_Exec(t *testing.T) { testSQLExec(t, driverTestMssqlConn) } +func TestMssqlSQL_Insert(t *testing.T) { testSQLInsert(t, driverTestMssqlConn) } +func TestMssqlSQL_Wrap(t *testing.T) { testSQLWrap(t, driverTestMssqlConn) } diff --git a/modules/db/statement_mysql_test.go b/modules/db/statement_mysql_test.go new file mode 100644 index 0000000000000000000000000000000000000000..309e8bc3b20265b992e31246346f4b301553fe76 --- /dev/null +++ b/modules/db/statement_mysql_test.go @@ -0,0 +1,63 @@ +package db + +import ( + "fmt" + "os/exec" + "testing" + + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" +) + +var driverTestMysqlConn Connection + +const ( + driverTestDBName = "go-admin-statement-test" +) + +func InitMysql() { + c := testConnDSN(DriverMysql, fmt.Sprintf("root:root@tcp(127.0.0.1:3306)/%s", driverTestDBName)) + _, err := c.Exec(fmt.Sprintf("create database if not exists `%s`", driverTestDBName)) + if err != nil { + panic(err) + } + + cmd := exec.Command("mysql", "-u", "root", "-proot", driverTestDBName, + "-e", "source "+testCurrentPath()+"/../../data/admin.sql") + err = cmd.Run() + if err != nil { + panic(err) + } + + driverTestMysqlConn = testConnDSN(DriverMysql, fmt.Sprintf("root:root@tcp(127.0.0.1:3306)/%s", driverTestDBName)) +} + +func TestMysqlSQL_WhereIn(t *testing.T) { testSQLWhereIn(t, driverTestMysqlConn) } +func TestMysqlSQL_Count(t *testing.T) { testSQLCount(t, driverTestMysqlConn) } +func TestMysqlSQL_Select(t *testing.T) { testSQLSelect(t, driverTestMysqlConn) } +func TestMysqlSQL_OrderBy(t *testing.T) { testSQLOrderBy(t, driverTestMysqlConn) } +func TestMysqlSQL_GroupBy(t *testing.T) { testSQLGroupBy(t, driverTestMysqlConn) } +func TestMysqlSQL_Skip(t *testing.T) { testSQLSkip(t, driverTestMysqlConn) } +func TestMysqlSQL_Take(t *testing.T) { testSQLTake(t, driverTestMysqlConn) } +func TestMysqlSQL_Where(t *testing.T) { testSQLWhere(t, driverTestMysqlConn) } +func TestMysqlSQL_WhereNotIn(t *testing.T) { testSQLWhereNotIn(t, driverTestMysqlConn) } +func TestMysqlSQL_Find(t *testing.T) { testSQLFind(t, driverTestMysqlConn) } +func TestMysqlSQL_Sum(t *testing.T) { testSQLSum(t, driverTestMysqlConn) } +func TestMysqlSQL_Max(t *testing.T) { testSQLMax(t, driverTestMysqlConn) } +func TestMysqlSQL_Min(t *testing.T) { testSQLMin(t, driverTestMysqlConn) } +func TestMysqlSQL_Avg(t *testing.T) { testSQLAvg(t, driverTestMysqlConn) } +func TestMysqlSQL_WhereRaw(t *testing.T) { testSQLWhereRaw(t, driverTestMysqlConn) } +func TestMysqlSQL_UpdateRaw(t *testing.T) { testSQLUpdateRaw(t, driverTestMysqlConn) } +func TestMysqlSQL_LeftJoin(t *testing.T) { testSQLLeftJoin(t, driverTestMysqlConn) } +func TestMysqlSQL_WithTransaction(t *testing.T) { testSQLWithTransaction(t, driverTestMysqlConn) } +func TestMysqlSQL_WithTransactionByLevel(t *testing.T) { + testSQLWithTransactionByLevel(t, driverTestMysqlConn) +} +func TestMysqlSQL_First(t *testing.T) { testSQLFirst(t, driverTestMysqlConn) } +func TestMysqlSQL_All(t *testing.T) { testSQLAll(t, driverTestMysqlConn) } +func TestMysqlSQL_ShowColumns(t *testing.T) { testSQLShowColumns(t, driverTestMysqlConn) } +func TestMysqlSQL_ShowTables(t *testing.T) { testSQLShowTables(t, driverTestMysqlConn) } +func TestMysqlSQL_Update(t *testing.T) { testSQLUpdate(t, driverTestMysqlConn) } +func TestMysqlSQL_Delete(t *testing.T) { testSQLDelete(t, driverTestMysqlConn) } +func TestMysqlSQL_Exec(t *testing.T) { testSQLExec(t, driverTestMysqlConn) } +func TestMysqlSQL_Insert(t *testing.T) { testSQLInsert(t, driverTestMysqlConn) } +func TestMysqlSQL_Wrap(t *testing.T) { testSQLWrap(t, driverTestMysqlConn) } diff --git a/modules/db/statement_postgresql_test.go b/modules/db/statement_postgresql_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bc74ce31bd9e0f4b61d07fadadd922773b797e18 --- /dev/null +++ b/modules/db/statement_postgresql_test.go @@ -0,0 +1,64 @@ +package db + +import ( + "fmt" + "os" + "os/exec" + "path" + "testing" + + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/postgres" +) + +var driverTestPgConn Connection + +func InitPostgresql() { + + cmd := exec.Command("createdb -p 5433 -U postgres " + driverTestDBName) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "PGPASSWORD=root") + _ = cmd.Run() + + cmd = exec.Command("psql", "-h", "localhost", "-U", "root", "-proot", "-d", driverTestDBName, + "-f", path.Dir(path.Dir(testCurrentPath()))+"/data/admin.pgsql") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "PGPASSWORD=root") + err := cmd.Run() + if err != nil { + panic(err) + } + + driverTestPgConn = testConnDSN(DriverPostgresql, fmt.Sprintf("host=127.0.0.1 port=5433 user=postgres "+ + "password=root dbname=%s sslmode=disable", driverTestDBName)) +} + +func TestPgSQL_WhereIn(t *testing.T) { testSQLWhereIn(t, driverTestPgConn) } +func TestPgSQL_Count(t *testing.T) { testSQLCount(t, driverTestPgConn) } +func TestPgSQL_Select(t *testing.T) { testSQLSelect(t, driverTestPgConn) } +func TestPgSQL_OrderBy(t *testing.T) { testSQLOrderBy(t, driverTestPgConn) } +func TestPgSQL_GroupBy(t *testing.T) { testSQLGroupBy(t, driverTestPgConn) } +func TestPgSQL_Skip(t *testing.T) { testSQLSkip(t, driverTestPgConn) } +func TestPgSQL_Take(t *testing.T) { testSQLTake(t, driverTestPgConn) } +func TestPgSQL_Where(t *testing.T) { testSQLWhere(t, driverTestPgConn) } +func TestPgSQL_WhereNotIn(t *testing.T) { testSQLWhereNotIn(t, driverTestPgConn) } +func TestPgSQL_Find(t *testing.T) { testSQLFind(t, driverTestPgConn) } +func TestPgSQL_Sum(t *testing.T) { testSQLSum(t, driverTestPgConn) } +func TestPgSQL_Max(t *testing.T) { testSQLMax(t, driverTestPgConn) } +func TestPgSQL_Min(t *testing.T) { testSQLMin(t, driverTestPgConn) } +func TestPgSQL_Avg(t *testing.T) { testSQLAvg(t, driverTestPgConn) } +func TestPgSQL_WhereRaw(t *testing.T) { testSQLWhereRaw(t, driverTestPgConn) } +func TestPgSQL_UpdateRaw(t *testing.T) { testSQLUpdateRaw(t, driverTestPgConn) } +func TestPgSQL_LeftJoin(t *testing.T) { testSQLLeftJoin(t, driverTestPgConn) } +func TestPgSQL_WithTransaction(t *testing.T) { testSQLWithTransaction(t, driverTestPgConn) } +func TestPgSQL_WithTransactionByLevel(t *testing.T) { + testSQLWithTransactionByLevel(t, driverTestPgConn) +} +func TestPgSQL_First(t *testing.T) { testSQLFirst(t, driverTestPgConn) } +func TestPgSQL_All(t *testing.T) { testSQLAll(t, driverTestPgConn) } +func TestPgSQL_ShowColumns(t *testing.T) { testSQLShowColumns(t, driverTestPgConn) } +func TestPgSQL_ShowTables(t *testing.T) { testSQLShowTables(t, driverTestPgConn) } +func TestPgSQL_Update(t *testing.T) { testSQLUpdate(t, driverTestPgConn) } +func TestPgSQL_Delete(t *testing.T) { testSQLDelete(t, driverTestPgConn) } +func TestPgSQL_Exec(t *testing.T) { testSQLExec(t, driverTestPgConn) } +func TestPgSQL_Insert(t *testing.T) { testSQLInsert(t, driverTestPgConn) } +func TestPgSQL_Wrap(t *testing.T) { testSQLWrap(t, driverTestPgConn) } diff --git a/modules/db/statement_sqlite_test.go b/modules/db/statement_sqlite_test.go new file mode 100644 index 0000000000000000000000000000000000000000..87594dfdd84000a2f6ff4fefc32632f4037d7c51 --- /dev/null +++ b/modules/db/statement_sqlite_test.go @@ -0,0 +1,45 @@ +package db + +import ( + "testing" + + "github.com/GoAdminGroup/go-admin/modules/config" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/sqlite" +) + +var driverTestSQLiteConn Connection + +func InitSqlite() { + driverTestSQLiteConn = testConn(DriverSqlite, config.Database{File: "/admin.db"}) +} + +func TestSQLiteSQL_WhereIn(t *testing.T) { testSQLWhereIn(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Count(t *testing.T) { testSQLCount(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Select(t *testing.T) { testSQLSelect(t, driverTestSQLiteConn) } +func TestSQLiteSQL_OrderBy(t *testing.T) { testSQLOrderBy(t, driverTestSQLiteConn) } +func TestSQLiteSQL_GroupBy(t *testing.T) { testSQLGroupBy(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Skip(t *testing.T) { testSQLSkip(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Take(t *testing.T) { testSQLTake(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Where(t *testing.T) { testSQLWhere(t, driverTestSQLiteConn) } +func TestSQLiteSQL_WhereNotIn(t *testing.T) { testSQLWhereNotIn(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Find(t *testing.T) { testSQLFind(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Sum(t *testing.T) { testSQLSum(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Max(t *testing.T) { testSQLMax(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Min(t *testing.T) { testSQLMin(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Avg(t *testing.T) { testSQLAvg(t, driverTestSQLiteConn) } +func TestSQLiteSQL_WhereRaw(t *testing.T) { testSQLWhereRaw(t, driverTestSQLiteConn) } +func TestSQLiteSQL_UpdateRaw(t *testing.T) { testSQLUpdateRaw(t, driverTestSQLiteConn) } +func TestSQLiteSQL_LeftJoin(t *testing.T) { testSQLLeftJoin(t, driverTestSQLiteConn) } +func TestSQLiteSQL_WithTransaction(t *testing.T) { testSQLWithTransaction(t, driverTestSQLiteConn) } +func TestSQLiteSQL_WithTransactionByLevel(t *testing.T) { + testSQLWithTransactionByLevel(t, driverTestSQLiteConn) +} +func TestSQLiteSQL_First(t *testing.T) { testSQLFirst(t, driverTestSQLiteConn) } +func TestSQLiteSQL_All(t *testing.T) { testSQLAll(t, driverTestSQLiteConn) } +func TestSQLiteSQL_ShowColumns(t *testing.T) { testSQLShowColumns(t, driverTestSQLiteConn) } +func TestSQLiteSQL_ShowTables(t *testing.T) { testSQLShowTables(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Update(t *testing.T) { testSQLUpdate(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Delete(t *testing.T) { testSQLDelete(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Exec(t *testing.T) { testSQLExec(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Insert(t *testing.T) { testSQLInsert(t, driverTestSQLiteConn) } +func TestSQLiteSQL_Wrap(t *testing.T) { testSQLWrap(t, driverTestSQLiteConn) } diff --git a/modules/db/statement_test.go b/modules/db/statement_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b2f0d495737c82e182b3b2f065b3019dcd9ac027 --- /dev/null +++ b/modules/db/statement_test.go @@ -0,0 +1,105 @@ +package db + +import ( + "database/sql" + "testing" + + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mssql" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/postgres" + "github.com/magiconair/properties/assert" +) + +func testSQLWhereIn(t *testing.T, conn Connection) { + + item, _ := WithDriver(conn).Table("goadmin_users").WhereIn("id", []interface{}{"1", "2"}).First() + assert.Equal(t, len(item), 2) + + _, _ = WithDriver(conn).WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + item, _ := WithDriver(conn).WithTx(tx).Table("goadmin_users").WhereIn("id", []interface{}{"1", "2"}).All() + assert.Equal(t, len(item), 2) + return nil, nil + }) +} + +func testSQLCount(t *testing.T, conn Connection) { + count, _ := WithDriver(conn).Table("goadmin_users").Count() + assert.Equal(t, count, int64(2)) +} + +// TODO +func testSQLSelect(t *testing.T, conn Connection) {} + +// TODO +func testSQLOrderBy(t *testing.T, conn Connection) {} + +// TODO +func testSQLGroupBy(t *testing.T, conn Connection) {} + +// TODO +func testSQLSkip(t *testing.T, conn Connection) {} + +// TODO +func testSQLTake(t *testing.T, conn Connection) {} + +// TODO +func testSQLWhere(t *testing.T, conn Connection) {} + +// TODO +func testSQLWhereNotIn(t *testing.T, conn Connection) {} + +// TODO +func testSQLFind(t *testing.T, conn Connection) {} + +// TODO +func testSQLSum(t *testing.T, conn Connection) {} + +// TODO +func testSQLMax(t *testing.T, conn Connection) {} + +// TODO +func testSQLMin(t *testing.T, conn Connection) {} + +// TODO +func testSQLAvg(t *testing.T, conn Connection) {} + +// TODO +func testSQLWhereRaw(t *testing.T, conn Connection) {} + +// TODO +func testSQLUpdateRaw(t *testing.T, conn Connection) {} + +// TODO +func testSQLLeftJoin(t *testing.T, conn Connection) {} + +// TODO +func testSQLWithTransaction(t *testing.T, conn Connection) {} + +// TODO +func testSQLWithTransactionByLevel(t *testing.T, conn Connection) {} + +// TODO +func testSQLFirst(t *testing.T, conn Connection) {} + +// TODO +func testSQLAll(t *testing.T, conn Connection) {} + +// TODO +func testSQLShowColumns(t *testing.T, conn Connection) {} + +// TODO +func testSQLShowTables(t *testing.T, conn Connection) {} + +// TODO +func testSQLUpdate(t *testing.T, conn Connection) {} + +// TODO +func testSQLDelete(t *testing.T, conn Connection) {} + +// TODO +func testSQLExec(t *testing.T, conn Connection) {} + +// TODO +func testSQLInsert(t *testing.T, conn Connection) {} + +// TODO +func testSQLWrap(t *testing.T, conn Connection) {} diff --git a/modules/db/types.go b/modules/db/types.go new file mode 100644 index 0000000000000000000000000000000000000000..3df5d17ed67f6b9e27a1da9d5048d5061b81d0e8 --- /dev/null +++ b/modules/db/types.go @@ -0,0 +1,282 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package db + +import ( + "fmt" + "html/template" + "strconv" +) + +// DatabaseType is the database field type. +type DatabaseType string + +const ( + // ================================= + // integer + // ================================= + + Int DatabaseType = "INT" + Tinyint DatabaseType = "TINYINT" + Mediumint DatabaseType = "MEDIUMINT" + Smallint DatabaseType = "SMALLINT" + Bigint DatabaseType = "BIGINT" + Bit DatabaseType = "BIT" + Int8 DatabaseType = "INT8" + Int4 DatabaseType = "INT4" + Int2 DatabaseType = "INT2" + + Integer DatabaseType = "INTEGER" + Numeric DatabaseType = "NUMERIC" + Smallserial DatabaseType = "SMALLSERIAL" + Serial DatabaseType = "SERIAL" + Bigserial DatabaseType = "BIGSERIAL" + Money DatabaseType = "MONEY" + + // ================================= + // float + // ================================= + + Real DatabaseType = "REAL" + Float DatabaseType = "FLOAT" + Float4 DatabaseType = "FLOAT4" + Float8 DatabaseType = "FLOAT8" + Double DatabaseType = "DOUBLE" + Decimal DatabaseType = "DECIMAL" + + Doubleprecision DatabaseType = "DOUBLEPRECISION" + + // ================================= + // string + // ================================= + + Date DatabaseType = "DATE" + Time DatabaseType = "TIME" + Year DatabaseType = "YEAR" + Datetime DatabaseType = "DATETIME" + Timestamp DatabaseType = "TIMESTAMP" + + Text DatabaseType = "TEXT" + Longtext DatabaseType = "LONGTEXT" + Mediumtext DatabaseType = "MEDIUMTEXT" + Tinytext DatabaseType = "TINYTEXT" + + Varchar DatabaseType = "VARCHAR" + Char DatabaseType = "CHAR" + Bpchar DatabaseType = "BPCHAR" + JSON DatabaseType = "JSON" + + Blob DatabaseType = "BLOB" + Tinyblob DatabaseType = "TINYBLOB" + Mediumblob DatabaseType = "MEDIUMBLOB" + Longblob DatabaseType = "LONGBLOB" + + Interval DatabaseType = "INTERVAL" + Boolean DatabaseType = "BOOLEAN" + Bool DatabaseType = "BOOL" + + Point DatabaseType = "POINT" + Line DatabaseType = "LINE" + Lseg DatabaseType = "LSEG" + Box DatabaseType = "BOX" + Path DatabaseType = "PATH" + Polygon DatabaseType = "POLYGON" + Circle DatabaseType = "CIRCLE" + + Cidr DatabaseType = "CIDR" + Inet DatabaseType = "INET" + Macaddr DatabaseType = "MACADDR" + + Character DatabaseType = "CHARACTER" + Varyingcharacter DatabaseType = "VARYINGCHARACTER" + Nchar DatabaseType = "NCHAR" + Nativecharacter DatabaseType = "NATIVECHARACTER" + Nvarchar DatabaseType = "NVARCHAR" + Clob DatabaseType = "CLOB" + + Binary DatabaseType = "BINARY" + Varbinary DatabaseType = "VARBINARY" + Enum DatabaseType = "ENUM" + Set DatabaseType = "SET" + + Geometry DatabaseType = "GEOMETRY" + + Multilinestring DatabaseType = "MULTILINESTRING" + Multipolygon DatabaseType = "MULTIPOLYGON" + Linestring DatabaseType = "LINESTRING" + Multipoint DatabaseType = "MULTIPOINT" + Geometrycollection DatabaseType = "GEOMETRYCOLLECTION" + + Name DatabaseType = "NAME" + UUID DatabaseType = "UUID" + + Timestamptz DatabaseType = "TIMESTAMPTZ" + Timetz DatabaseType = "TIMETZ" +) + +// DT turn the string value into DatabaseType. +func DT(s string) DatabaseType { + return DatabaseType(s) +} + +// GetDTAndCheck check the DatabaseType. +func GetDTAndCheck(s string) DatabaseType { + ss := DatabaseType(s) + if !Contains(ss, BoolTypeList) && + !Contains(ss, IntTypeList) && + !Contains(ss, FloatTypeList) && + !Contains(ss, UintTypeList) && + !Contains(ss, StringTypeList) { + panic("wrong type: " + s) + } + return ss +} + +var ( + // StringTypeList is a DatabaseType list of string. + StringTypeList = []DatabaseType{Date, Time, Year, Datetime, Timestamptz, Timestamp, Timetz, + Varchar, Char, Mediumtext, Longtext, Tinytext, + Text, JSON, Blob, Tinyblob, Mediumblob, Longblob, + Interval, Point, Bpchar, + Line, Lseg, Box, Path, Polygon, Circle, Cidr, Inet, Macaddr, Character, Varyingcharacter, + Nchar, Nativecharacter, Nvarchar, Clob, Binary, Varbinary, Enum, Set, Geometry, Multilinestring, + Multipolygon, Linestring, Multipoint, Geometrycollection, Name, UUID, Timestamptz, + Name, UUID, Inet} + + // BoolTypeList is a DatabaseType list of bool. + BoolTypeList = []DatabaseType{Bool, Boolean} + + // IntTypeList is a DatabaseType list of integer. + IntTypeList = []DatabaseType{Int4, Int2, Int8, + Int, + Tinyint, + Mediumint, + Smallint, + Smallserial, Serial, Bigserial, + Integer, + Bigint} + + // FloatTypeList is a DatabaseType list of float. + FloatTypeList = []DatabaseType{Float, Float4, Float8, Double, Real, Doubleprecision} + + // UintTypeList is a DatabaseType list of uint. + UintTypeList = []DatabaseType{Decimal, Bit, Money, Numeric} +) + +// Contains check the given DatabaseType is in the list or not. +func Contains(v DatabaseType, a []DatabaseType) bool { + for _, i := range a { + if i == v { + return true + } + } + return false +} + +// Value is a string. +type Value string + +// ToInt64 turn the string to a int64. +func (v Value) ToInt64() int64 { + value, err := strconv.ParseInt(string(v), 10, 64) + if err != nil { + panic("wrong value") + } + return value +} + +// String return the string value. +func (v Value) String() string { + return string(v) +} + +// HTML return the template.HTML value. +func (v Value) HTML() template.HTML { + return template.HTML(v) +} + +func GetValueFromDatabaseType(typ DatabaseType, value interface{}, json bool) Value { + if json { + return GetValueFromJSONOfDatabaseType(typ, value) + } else { + return GetValueFromSQLOfDatabaseType(typ, value) + } +} + +// GetValueFromDatabaseType return Value of given DatabaseType and interface. +func GetValueFromSQLOfDatabaseType(typ DatabaseType, value interface{}) Value { + switch { + case Contains(typ, StringTypeList): + if v, ok := value.(string); ok { + return Value(v) + } + if v2, ok2 := value.([]byte); ok2 { + return Value(string(v2)) + } + return "" + case Contains(typ, BoolTypeList): + if v, ok := value.(bool); ok { + if v { + return "true" + } + return "false" + } + if v, ok := value.(int64); ok { + if v == 0 { + return "false" + } + return "true" + } + return "false" + case Contains(typ, IntTypeList): + if v, ok := value.(int64); ok { + return Value(fmt.Sprintf("%d", v)) + } + return "0" + case Contains(typ, FloatTypeList): + if v, ok := value.(float64); ok { + return Value(fmt.Sprintf("%f", v)) + } + return "0" + case Contains(typ, UintTypeList): + if v, ok := value.([]uint8); ok { + return Value(string(v)) + } + return "0" + } + panic("wrong type:" + string(typ)) +} + +// GetValueFromJSONOfDatabaseType return Value of given DatabaseType and interface from JSON string value. +func GetValueFromJSONOfDatabaseType(typ DatabaseType, value interface{}) Value { + switch { + case Contains(typ, StringTypeList): + if v, ok := value.(string); ok { + return Value(v) + } + return "" + case Contains(typ, BoolTypeList): + if v, ok := value.(bool); ok { + if v { + return "true" + } + return "false" + } + return "false" + case Contains(typ, IntTypeList): + if v, ok := value.(float64); ok { + return Value(fmt.Sprintf("%d", int64(v))) + } + return Value(fmt.Sprintf("%d", value)) + case Contains(typ, FloatTypeList): + return Value(fmt.Sprintf("%f", value)) + case Contains(typ, UintTypeList): + if v, ok := value.([]uint8); ok { + return Value(string(v)) + } + return "0" + } + panic("wrong type:" + string(typ)) +} diff --git a/modules/db/types_test.go b/modules/db/types_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8fb8926c79bf1cb3d392c2cb8bb91d4bad5fa269 --- /dev/null +++ b/modules/db/types_test.go @@ -0,0 +1,243 @@ +package db + +import ( + "fmt" + "os" + "regexp" + "strings" + "testing" + "time" + + "github.com/GoAdminGroup/go-admin/modules/config" + _ "github.com/GoAdminGroup/go-admin/modules/db/drivers/mysql" + "github.com/magiconair/properties/assert" +) + +const ( + typeTestdbName = "go-admin-type-test" + typeTesttableName = "all_types" + typeTestpostgresCreateSql = `CREATE TABLE public.%s +( + id integer NOT NULL, + type_1 smallint, + type_2 bigint, + type_3 numeric, + type_4 real, + type_5 double precision, + type_6 smallint NOT NULL DEFAULT nextval('all_types_type_6_seq'::regclass), + type_7 integer NOT NULL DEFAULT nextval('all_types_type_7_seq'::regclass), + type_8 bigint NOT NULL DEFAULT nextval('all_types_type_8_seq'::regclass), + type_9 money, + type_10 character varying COLLATE pg_catalog."default", + type_11 character(1) COLLATE pg_catalog."default", + type_12 text COLLATE pg_catalog."default", + type_13 timestamp with time zone, + type_14 time with time zone, + type_15 date, + type_16 timestamp without time zone, + type_17 interval, + type_18 point, + type_19 line, + type_20 lseg, + type_21 box, + type_22 path, + type_23 polygon, + type_24 circle, + type_25 cidr, + type_26 inet, + type_27 macaddr, + type_28 boolean, + CONSTRAINT all_types_pkey PRIMARY KEY (id) +) +WITH ( + OIDS = FALSE +) +TABLESPACE pg_default; + +ALTER TABLE public.all_types + OWNER to postgres;` +) + +func TestMysqlGetTypeFromString(t *testing.T) { + + conn := testConnDSN(DriverMysql, fmt.Sprintf("root:root@tcp(127.0.0.1:3306)/%s", typeTestdbName)) + _, err := conn.Exec(fmt.Sprintf("create database if not exists `%s`", typeTestdbName)) + assert.Equal(t, err, nil) + _, err = conn.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`;", typeTesttableName)) + assert.Equal(t, err, nil) + _, err = conn.Exec(fmt.Sprintf(`CREATE TABLE `+"`"+`%s`+"`"+` ( + id int(11) unsigned NOT NULL AUTO_INCREMENT, + `+testDelimiter("type_1")+` tinyint(11) DEFAULT NULL, + `+testDelimiter("type_2")+` smallint(11) DEFAULT NULL, + `+testDelimiter("type_3")+` mediumint(11) DEFAULT NULL, + `+testDelimiter("type_4")+` bigint(11) DEFAULT NULL, + `+testDelimiter("type_5")+` float DEFAULT NULL, + `+testDelimiter("type_6")+` double(5,3) DEFAULT NULL, + `+testDelimiter("type_7")+` double DEFAULT NULL, + `+testDelimiter("type_8")+` double(5,3) DEFAULT NULL, + `+testDelimiter("type_9")+` decimal(11,0) DEFAULT NULL, + `+testDelimiter("type_10")+` bit(11) DEFAULT NULL, + `+testDelimiter("type_11")+` tinyint(1) DEFAULT NULL, + `+testDelimiter("type_12")+` tinyint(1) DEFAULT NULL, + `+testDelimiter("type_13")+` decimal(10,5) DEFAULT NULL, + `+testDelimiter("type_14")+` decimal(10,0) DEFAULT NULL, + `+testDelimiter("type_15")+` decimal(10,0) DEFAULT NULL, + `+testDelimiter("type_16")+` char(11) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `+testDelimiter("type_17")+` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `+testDelimiter("type_18")+` tinytext COLLATE utf8mb4_unicode_ci, + `+testDelimiter("type_19")+` text COLLATE utf8mb4_unicode_ci, + `+testDelimiter("type_20")+` mediumtext COLLATE utf8mb4_unicode_ci, + `+testDelimiter("type_21")+` longtext COLLATE utf8mb4_unicode_ci, + `+testDelimiter("type_22")+` tinyblob, + `+testDelimiter("type_23")+` mediumblob, + `+testDelimiter("type_24")+` blob, + `+testDelimiter("type_25")+` longblob, + `+testDelimiter("type_26")+` binary(1) DEFAULT NULL, + `+testDelimiter("type_27")+` varbinary(1) DEFAULT NULL, + `+testDelimiter("type_28")+` enum('RED','GREEN','BLUE') COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `+testDelimiter("type_29")+` set('RED','GREEN','BLUE') COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `+testDelimiter("type_30")+` date DEFAULT NULL, + `+testDelimiter("type_31")+` datetime DEFAULT NULL, + `+testDelimiter("type_32")+` timestamp NULL DEFAULT NULL, + `+testDelimiter("type_33")+` time DEFAULT NULL, + `+testDelimiter("type_34")+` year(4) DEFAULT NULL, + `+testDelimiter("type_35")+` geometry DEFAULT NULL, + `+testDelimiter("type_36")+` point DEFAULT NULL, + `+testDelimiter("type_39")+` multilinestring DEFAULT NULL, + `+testDelimiter("type_41")+` multipolygon DEFAULT NULL, + `+testDelimiter("type_37")+` linestring DEFAULT NULL, + `+testDelimiter("type_38")+` polygon DEFAULT NULL, + `+testDelimiter("type_40")+` multipoint DEFAULT NULL, + `+testDelimiter("type_42")+` geometrycollection DEFAULT NULL, + `+testDelimiter("type_50")+` double(5,2) DEFAULT NULL, + `+testDelimiter("type_51")+` json DEFAULT NULL, + PRIMARY KEY (`+"`"+`id`+"`"+`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, typeTesttableName)) + + assert.Equal(t, err, nil) + + _, err = conn.Exec(`INSERT INTO ` + testDelimiter(typeTesttableName) + ` (id, type_1, type_2, type_3, type_4, type_5, type_6, type_7, type_8, type_9, type_10, type_11, type_12, type_13, type_14, type_15, type_16, type_17, type_18, type_19, type_20, type_21, type_22, type_23, type_24, type_25, type_26, type_27, type_28, type_29, type_30, type_31, type_32, type_33, type_34, type_35, type_36, type_39, type_41, type_37, type_38, type_40, type_42, type_50, type_51) +VALUES + (1, 1, 1, 1, 1, 1, 1.000, 1, 1.000, 1, 0, 1, 1, 1.00000, 1, 1, '1', '1', '1', '1', '1', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2001', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);`) + + assert.Equal(t, err, nil) + + typeField := "Type" + fieldField := "Field" + + conn = testConnDSN(DriverMysql, fmt.Sprintf("root:root@tcp(127.0.0.1:3306)/%s?charset=utf8mb4", typeTestdbName)) + + config.Initialize(&config.Config{ + SqlLog: true, + }) + + columnsModel, _ := WithDriver(conn).Table(typeTesttableName).ShowColumns() + item, err := WithDriver(conn).Table(typeTesttableName).First() + + for _, model := range columnsModel { + fieldTypeName := strings.ToUpper(testGetType(model[typeField].(string))) + GetDTAndCheck(fieldTypeName) + GetValueFromSQLOfDatabaseType(DatabaseType(fieldTypeName), item[model[fieldField].(string)]) + } + assert.Equal(t, err, nil) +} + +func TestPostgresqlGetTypeFromString(t *testing.T) { + + // pg 11 + testPG(t, "5433") + // pg 12 + //testPG(t, "5434") +} + +func testPG(t *testing.T, port string) { + connStatement := "host=127.0.0.1 port=" + port + " user=postgres password=root dbname=%s sslmode=disable" + + conn := testConnDSN(DriverPostgresql, fmt.Sprintf(connStatement, typeTestdbName)) + fmt.Println("creating database") + _, err := conn.Exec(fmt.Sprintf(`SELECT 'CREATE DATABASE %s' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '%s')`, typeTestdbName, typeTestdbName)) + assert.Equal(t, err, nil) + fmt.Println("drop table") + _, err = conn.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`;", typeTesttableName)) + assert.Equal(t, err, nil) + fmt.Println("create sequence all_types_type_6_seq") + _, err = conn.Exec(`CREATE SEQUENCE IF NOT EXISTS public.all_types_type_6_seq START 1;`) + assert.Equal(t, err, nil) + fmt.Println("create sequence all_types_type_7_seq") + _, err = conn.Exec(`CREATE SEQUENCE IF NOT EXISTS public.all_types_type_7_seq START 1;`) + assert.Equal(t, err, nil) + fmt.Println("create sequence all_types_type_8_seq") + _, err = conn.Exec(`CREATE SEQUENCE IF NOT EXISTS public.all_types_type_8_seq START 1;`) + assert.Equal(t, err, nil) + fmt.Println("create table") + _, err = conn.Exec(fmt.Sprintf(typeTestpostgresCreateSql, typeTesttableName)) + + assert.Equal(t, err, nil) + + fmt.Println("insert data") + _, err = conn.Exec(`INSERT INTO public.` + typeTesttableName + `( + id, type_1, type_2, type_3, type_4, type_5, type_6, type_7, type_8, type_9, type_10, type_11, type_12, type_13, type_14, type_15, type_16, type_17, type_18, type_19, type_20, type_21, type_22, type_23, type_24, type_25, type_26, type_27, type_28) + VALUES (1, 1, 1, 0.3, 1, 1, 1, 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'n');`) + + assert.Equal(t, err, nil) + + typeField := "udt_name" + fieldField := "column_name" + + conn = testConnDSN(DriverPostgresql, fmt.Sprintf(connStatement, typeTestdbName)) + + config.Initialize(&config.Config{ + SqlLog: true, + }) + + columnsModel, _ := WithDriver(conn).Table(typeTesttableName).ShowColumns() + item, err := WithDriver(conn).Table(typeTesttableName).First() + + for _, model := range columnsModel { + fieldTypeName := strings.ToUpper(testGetType(model[typeField].(string))) + fmt.Println("fieldTypeName", fieldTypeName) + GetDTAndCheck(fieldTypeName) + fmt.Println(model[fieldField].(string), GetValueFromSQLOfDatabaseType(DatabaseType(fieldTypeName), item[model[fieldField].(string)])) + } + + assert.Equal(t, err, nil) +} + +// ******************************* +// test helper methods +// ******************************* + +func testGetType(typeName string) string { + r, _ := regexp.Compile(`\((.*?)\)`) + typeName = r.ReplaceAllString(typeName, "") + return strings.ToLower(strings.ReplaceAll(typeName, " unsigned", "")) +} + +func testConnDSN(driver, dsn string) Connection { + return GetConnectionByDriver(driver).InitDB(map[string]config.Database{ + "default": {Dsn: dsn}, + }) +} + +func testConn(driver string, cfg config.Database) Connection { + cfg.Driver = driver + cfg.MaxIdleConns = 10 + cfg.MaxOpenConns = 80 + cfg.ConnMaxLifetime = time.Hour + cfg.ConnMaxIdleTime = 0 + return GetConnectionByDriver(driver).InitDB(map[string]config.Database{ + "default": cfg, + }) +} + +func testDelimiter(s string) string { + return "`" + s + "`" +} + +func testCurrentPath() string { + dir, err := os.Getwd() + if err != nil { + panic(err) + } + return dir +} diff --git a/modules/errors/error.go b/modules/errors/error.go new file mode 100644 index 0000000000000000000000000000000000000000..cd03ad0f53cfd97d7f19e3966f96652fce879241 --- /dev/null +++ b/modules/errors/error.go @@ -0,0 +1,49 @@ +package errors + +import ( + "errors" + "html/template" + + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/template/icon" +) + +var ( + Msg string + MsgHTML template.HTML + MsgWithIcon template.HTML +) + +const ( + PermissionDenied = "permission denied" + WrongID = "wrong id" + OperationNotAllow = "operation not allow" + EditFailWrongToken = "edit fail, wrong token" + CreateFailWrongToken = "create fail, wrong token" + NoPermission = "no permission" + SiteOff = "site is off" +) + +func WrongPK(pk string) string { + return "wrong " + pk +} + +func Init() { + Msg = language.Get("error") + MsgHTML = language.GetFromHtml("error") + MsgWithIcon = icon.Icon(icon.Warning, 2) + MsgHTML + `!` + + PageError404 = errors.New(language.Get("not found")) + PageError500 = errors.New(language.Get("internal error")) + PageError403 = errors.New(language.Get("permission denied")) + PageError401 = errors.New(language.Get("unauthorized")) +} + +type PageError error + +var ( + PageError404 PageError + PageError500 PageError + PageError403 PageError + PageError401 PageError +) diff --git a/modules/file/file.go b/modules/file/file.go new file mode 100644 index 0000000000000000000000000000000000000000..b958138107038da955fb2a67a339972a1b37adab --- /dev/null +++ b/modules/file/file.go @@ -0,0 +1,141 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package file + +import ( + "fmt" + "io" + "mime/multipart" + "os" + "path" + "sync" + + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" +) + +// Uploader is a file uploader which contains the method Upload. +type Uploader interface { + Upload(*multipart.Form) error +} + +// UploaderGenerator is a function return an Uploader. +type UploaderGenerator func() Uploader + +var uploaderList = map[string]UploaderGenerator{ + "local": GetLocalFileUploader, +} + +var upMu sync.Mutex + +// AddUploader makes a uploader generator available by the provided theme name. +// If Add is called twice with the same name or if uploader is nil, +// it panics. +func AddUploader(name string, up UploaderGenerator) { + upMu.Lock() + defer upMu.Unlock() + if up == nil { + panic("uploader generator is nil") + } + if _, dup := uploaderList[name]; dup { + panic("add uploader generator twice " + name) + } + uploaderList[name] = up +} + +// GetFileEngine return the Uploader of given name. +func GetFileEngine(name string) Uploader { + if up, ok := uploaderList[name]; ok { + return up() + } + panic("wrong uploader name") +} + +// UploadFun is a function to process the uploading logic. +type UploadFun func(*multipart.FileHeader, string) (string, error) + +// Upload receive the return value of given UploadFun and put them into the form. +func Upload(c UploadFun, form *multipart.Form) error { + var ( + suffix string + filename string + ) + + for k := range form.File { + for _, fileObj := range form.File[k] { + suffix = path.Ext(fileObj.Filename) + filename = modules.Uuid() + suffix + + pathStr, err := c(fileObj, filename) + + if err != nil { + return err + } + + form.Value[k] = append(form.Value[k], pathStr) + form.Value[k+"_size"] = append(form.Value[k+"_size"], fmt.Sprintf("%d", fileObj.Size)) + } + } + + return nil +} + +// SaveMultipartFile used in a local Uploader which help to save file in the local path. +func SaveMultipartFile(fh *multipart.FileHeader, path string) error { + f, err := fh.Open() + if err != nil { + return err + } + + if ff, ok := f.(*os.File); ok { + // Windows can't rename files that are opened. + if err := f.Close(); err != nil { + return err + } + + // If renaming fails we try the normal copying method. + // Renaming could fail if the files are on different devices. + if os.Rename(ff.Name(), path) == nil { + return nil + } + + // Reopen f for the code below. + f, err = fh.Open() + if err != nil { + return err + } + } + + defer func() { + if err2 := f.Close(); err2 != nil { + err = err2 + } + }() + + ff, err := os.Create(path) + if err != nil { + return err + } + + defer func() { + if err2 := ff.Close(); err2 != nil { + err = err2 + } + }() + _, err = copyZeroAlloc(ff, f) + return err +} + +func copyZeroAlloc(w io.Writer, r io.Reader) (int64, error) { + buf := copyBufPool.Get().([]byte) + n, err := io.CopyBuffer(w, r, buf) + copyBufPool.Put(buf) + return n, err +} + +var copyBufPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 4096) + }, +} diff --git a/modules/file/local.go b/modules/file/local.go new file mode 100644 index 0000000000000000000000000000000000000000..a3692ea3634c0a33b6201e804e93a26d3d2bad1e --- /dev/null +++ b/modules/file/local.go @@ -0,0 +1,33 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package file + +import ( + "mime/multipart" + + "github.com/GoAdminGroup/go-admin/modules/config" +) + +// LocalFileUploader is an Uploader of local file engine. +type LocalFileUploader struct { + BasePath string +} + +// GetLocalFileUploader return the default Uploader. +func GetLocalFileUploader() Uploader { + return &LocalFileUploader{ + config.GetStore().Path, + } +} + +// Upload implements the Uploader.Upload. +func (local *LocalFileUploader) Upload(form *multipart.Form) error { + return Upload(func(fileObj *multipart.FileHeader, filename string) (string, error) { + if err := SaveMultipartFile(fileObj, (*local).BasePath+"/"+filename); err != nil { + return "", err + } + return filename, nil + }, form) +} diff --git a/modules/language/cn.go b/modules/language/cn.go new file mode 100644 index 0000000000000000000000000000000000000000..1d127a05544f16fd41e87b75f8a6478ddedd3150 --- /dev/null +++ b/modules/language/cn.go @@ -0,0 +1,454 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package language + +import "strings" + +var cn = LangSet{ + "managers": "管理员", + "managers manage": "管理员管理", + "name": "用户名", + "nickname": "昵称", + "role": "角色", + "createdat": "创建时间", + "updatedat": "更新时间", + "path": "路径", + "submit": "提交", + "filter": "筛选", + + "new": "新建", + "export": "导出", + "action": "操作", + "toggle dropdown": "下拉", + "delete": "删除", + "refresh": "刷新", + "back": "返回", + "reset": "重置", + "save": "保存", + "edit": "编辑", + "expand": "展开", + "collapse": "折叠", + "online": "在线", + "setting": "设置", + "sign out": "登出", + "home": "首页", + "all": "全部", + "more": "更多", + "browse": "打开", + "remove": "移除", + + "permission manage": "权限管理", + "menus": "菜单", + "menus manage": "菜单管理", + "roles manage": "角色管理", + "operation log": "操作日志", + "method": "方法", + "input": "输入", + "operation": "操作", + "menu name": "菜单名", + "reload succeeded": "加载成功", + "search": "搜索", + + "permission denied": "没有权限", + "error": "错误", + "success": "成功", + "fail": "失败", + "current page": "当前页", + + "goadmin is now running. \nrunning in \"debug\" mode. switch to \"release\" mode in production.\n\n": "GoAdmin 启动成功。\n目前处于 \"debug\" 模式。请在生产环境中切换为 \"release\" 模式。\n\n", + + "wrong goadmin version, theme %s required goadmin version are %s": "错误的 GoAdmin 版本,当前主题 %s 需要 GoAdmin 版本为 %s", + "wrong theme version, goadmin %s required version of theme %s is %s": "错误的主题版本, GoAdmin %s 需要主题 %s 的版本为 %s", + + "adapter is nil, import the default adapter or use addadapter method add the adapter": "适配器为空,请先 import 对应的适配器或使用 AddAdapter 方法引入", + + "are you sure to delete": "你确定要删除吗?", + "yes": "确定", + "confirm": "确认", + "got it": "知道了", + "cancel": "取消", + "refresh succeeded": "刷新成功", + "delete succeed": "删除成功", + "edit fail": "编辑失败", + "create fail": "新增失败", + "delete fail": "删除失败", + "confirm password": "确认密码", + "all method if empty": "为空默认为所有方法", + + "detail": "详情", + "query time": "查询时间", + + "avatar": "头像", + "password": "密码", + "username": "用户名", + "slug": "标志", + "permission": "权限", + "userid": "用户ID", + "content": "内容", + "parent": "父级", + "icon": "图标", + "uri": "路径", + "close": "关闭", + + "login": "登录", + "login fail": "登录失败", + + "admin": "管理", + "user": "用户", + "users": "用户", + "roles": "角色", + "menu": "菜单", + "dashboard": "仪表盘", + + "continue editing": "继续编辑", + "continue creating": "继续新增", + + "username and password can not be empty": "用户名密码不能为空", + "operation not allow": "不允许的操作", + "password does not match": "密码不一致", + "should be unique": "需要保证唯一", + "slug exists": "标志已经存在了", + "no corresponding options?": "没找到对应选项?", + "create here.": "在这里新建一个。", + "use for login": "用于登录", + "use to display": "用来展示", + "a path a line, without global prefix": "一行一个路径,换行输入新路径,路径不包含全局路由前缀", + "slug or http_path or name should not be empty": "标志或路径或权限名不能为空", + "no roles": "无角色", + "no permission": "没有权限", + "fixed the sidebar": "固定侧边栏", + "enter fullscreen": "进入全屏", + "exit fullscreen": "退出全屏", + "wrong captcha": "错误的验证码", + "modify success": "修改成功", + + "not found": "找不到记录", + "internal error": "系统内部错误", + "unauthorized": "未认证", + + "login overdue, please login again": "登录信息过期,请重新登录", + "login info": "登录信息", + + "initialize configuration": "初始化配置", + "initialize navigation buttons": "初始化导航栏按钮", + "initialize plugins": "初始化插件", + "initialize database connections": "初始化数据库连接", + "initialize success": "初始化成功🍺🍺", + + "plugins": "插件", + "plugin store": "插件商店", + "get more plugins": "获取更多插件", + "uninstalled": "未安装", + "plugin setting": "插件设置", + + "view": "查看", + "del": "删除", + + "showing %s to %s of %s entries": "显示第 %s 到第 %s 条记录,总共 %s 条记录", + + "second": "秒", + "seconds": "秒", + "minute": "分", + "minutes": "分", + "hour": "小时", + "hours": "小时", + "day": "天", + "days": "天", + "week": "周", + "weeks": "周", + "month": "月", + "months": "月", + "year": "年", + "years": "年", + + "site setting": "网站设置", + "config.site setting": "网站设置", + "config.domain": "网站域名", + "config.language": "网站语言", + "config.url prefix": "URL前缀", + "config.theme": "主题", + "config.title": "标题", + "config.index url": "首页URL", + "config.login url": "登录URL", + "config.env": "开发环境", + "config.color scheme": "颜色主题", + "config.cdn url": "cdn资源URL", + "config.login title": "登录标题", + "config.auth user table": "登录用户表", + "config.extra": "额外配置", + "config.store": "文件存储设置", + "config.databases": "数据库设置", + "config.general": "通用", + "config.log": "日志", + "config.custom": "定制", + "config.debug": "Debug模式", + "config.site off": "关闭网站", + "config.true": "是", + "config.false": "否", + + "config.test": "测试环境", + "config.prod": "生产环境", + "config.local": "本地环境", + + "config.logo": "Logo", + "config.mini logo": "Mini Logo", + "config.session life time": "Session时长", + "config.bootstrap file path": "插件文件路径", + "config.go mod file path": "go.mod文件路径", + "config.custom head html": "自定义Head HTML", + "config.custom foot html": "自定义Foot HTML", + "config.custom 404 html": "自定义404页面", + "config.custom 403 html": "自定义403页面", + "config.custom 500 html": "自定义500页面", + "config.hide config center entrance": "隐藏配置中心入口", + "config.hide app info entrance": "隐藏应用信息入口", + "config.hide tool entrance": "隐藏工具入口", + "config.hide plugin entrance": "隐藏插件列表入口", + "config.footer info": "自定义底部信息", + "config.login logo": "登录Logo", + "config.no limit login ip": "取消限制多IP登录", + "config.operation log off": "关闭操作日志", + "config.allow delete operation log": "允许删除操作日志", + "config.animation type": "动画类型", + "config.animation duration": "动画间隔(秒)", + "config.animation delay": "动画延迟(秒)", + "config.file upload engine": "文件上传引擎", + + "config.logger rotate": "日志切割设置", + "config.logger rotate max size": "存储最大文件大小(m)", + "config.logger rotate max backups": "存储最多文件数", + "config.logger rotate max age": "最长存储时间(天)", + "config.logger rotate compress": "压缩", + + "config.info log path": "信息日志存储路径", + "config.error log path": "错误日志存储路径", + "config.access log path": "访问日志存储路径", + "config.info log off": "关闭信息日志", + "config.error log off": "关闭错误日志", + "config.access log off": "关闭访问日志", + "config.access assets log off": "关闭静态资源访问日志", + "config.sql log on": "打开SQL日志", + "config.log level": "日志级别", + + "config.logger rotate encoder": "日志encoder设置", + "config.logger rotate encoder time key": "Time Key", + "config.logger rotate encoder level key": "Level Key", + "config.logger rotate encoder name key": "Name Key", + "config.logger rotate encoder caller key": "Caller Key", + "config.logger rotate encoder message key": "Message Key", + "config.logger rotate encoder stacktrace key": "Stacktrace Key", + "config.logger rotate encoder level": "Level字段编码", + "config.logger rotate encoder time": "Time字段编码", + "config.logger rotate encoder duration": "Duration字段编码", + "config.logger rotate encoder caller": "Caller字段编码", + "config.logger rotate encoder encoding": "输出格式", + + "config.capital": "大写", + "config.capitalcolor": "大写带颜色", + "config.lowercase": "小写", + "config.lowercasecolor": "小写带颜色", + + "config.seconds": "秒", + "config.nanosecond": "纳秒", + "config.microsecond": "微秒", + "config.millisecond": "毫秒", + + "config.full path": "完整路径", + "config.short path": "简短路径", + + "config.do not modify when you have not set up all assets": "不要修改,当你还没有设置好所有资源文件的时候", + "config.it will work when theme is adminlte": "当主题为adminlte时生效", + "config.must bigger than 900 seconds": "必须大于900秒", + + "config.language." + CN: "中文", + "config.language." + EN: "英文", + "config.language." + JP: "日文", + "config.language." + strings.ToLower(TC): "繁体中文", + "config.language." + PTBR: "Brazilian Portuguese", + + "config.modify site config": "修改网站配置", + "config.modify site config success": "修改网站配置成功", + "config.modify site config fail": "修改网站配置失败", + + "site info": "运行信息", + "system.site info": "运行信息", + "system.application": "应用信息", + "system.application run": "应用运行信息", + "system.system": "系统信息", + + "system.process_id": "进程ID", + "system.golang_version": "Golang版本", + "system.server_uptime": "服务运行时间", + "system.current_goroutine": "当前 Goroutines 数量", + "system.current_memory_usage": "当前内存使用量", + "system.total_memory_allocated": "所有被分配的内存", + "system.memory_obtained": "内存占用量", + "system.pointer_lookup_times": "指针查找次数", + "system.memory_allocate_times": "内存分配次数", + "system.memory_free_times": "内存释放次数", + "system.current_heap_usage": "当前 Heap 内存使用量", + "system.heap_memory_obtained": "Heap 内存占用量", + "system.heap_memory_idle": "Heap 内存空闲量", + "system.heap_memory_in_use": "正在使用的 Heap 内存", + "system.heap_memory_released": "被释放的 Heap 内存", + "system.heap_objects": "Heap 对象数量", + "system.bootstrap_stack_usage": "启动 Stack 使用量", + "system.stack_memory_obtained": "被分配的 Stack 内存", + "system.mspan_structures_usage": "MSpan 结构内存使用量", + "system.mspan_structures_obtained": "被分配的 MSpan 结构内存", + "system.mcache_structures_usage": "MCache 结构内存使用量", + "system.mcache_structures_obtained": "被分配的 MCache 结构内存", + "system.profiling_bucket_hash_table_obtained": "被分配的剖析哈希表内存", + "system.gc_metadata_obtained": "被分配的 GC 元数据内存", + "system.other_system_allocation_obtained": "其它被分配的系统内存", + "system.next_gc_recycle": "下次 GC 内存回收量", + "system.last_gc_time": "距离上次 GC 时间", + "system.total_gc_time": "GC 执行时间总量", + "system.total_gc_pause": "GC 暂停时间总量", + "system.last_gc_pause": "上次 GC 暂停时间", + "system.gc_times": "GC 执行次数", + + "system.cpu_logical_core": "cpu逻辑核数", + "system.cpu_core": "cpu物理核数", + "system.os_platform": "系统平台", + "system.os_family": "系统家族", + "system.os_version": "系统版本", + "system.load1": "1分钟内负载", + "system.load5": "5分钟内负载", + "system.load15": "15分钟内负载", + "system.mem_total": "总内存", + "system.mem_available": "可用内存", + "system.mem_used": "使用内存", + + "system.app_name": "应用名", + "system.go_admin_version": "应用版本", + "system.theme_name": "主题", + "system.theme_version": "主题版本", + + "code generate tool": "代码生成器", + "tool.code generate tool": "代码生成器", + "tool.table": "表格", + "tool.connection": "连接", + "tool.output path is empty": "输出路径为空", + "tool.package": "包名", + "tool.output": "输出路径", + "tool.field": "字段", + "tool.title": "标题", + "tool.field name": "字段名", + "tool.db type": "数据类型", + "tool.form type": "表单类型", + "tool.generate table model": "生成CRUD模型", + "tool.primarykey": "主键", + "tool.field filterable": "可筛选", + "tool.field sortable": "可排序", + "tool.yes": "是", + "tool.no": "否", + "tool.hide": "隐藏", + "tool.show": "显示", + "tool.generate success": "生成成功", + "tool.display": "显示", + "tool.use absolute path": "使用绝对路径", + "tool.basic info": "基本信息", + "tool.table info": "表格信息", + "tool.form info": "表单信息", + "tool.field editable": "允许编辑", + "tool.field can add": "允许新增", + "tool.info field editable": "可编辑", + "tool.field default": "默认值", + "tool.filter area": "筛选框", + "tool.new button": "新建按钮", + "tool.export button": "导出按钮", + "tool.edit button": "编辑按钮", + "tool.delete button": "删除按钮", + "tool.extra import package": "导入包", + "tool.detail button": "详情按钮", + "tool.filter button": "筛选按钮", + "tool.row selector": "列选择按钮", + "tool.pagination": "分页", + "tool.query info": "查询信息", + "tool.filter form layout": "筛选表单布局", + "tool.generate": "生成", + "tool.generated tables": "生成过的表格", + "tool.description": "描述", + "tool.label": "标签", + "tool.image": "图片", + "tool.bool": "布尔", + "tool.link": "链接", + "tool.fileSize": "文件大小", + "tool.date": "日期", + "tool.icon": "Icon", + "tool.dot": "标点", + "tool.progressBar": "进度条", + "tool.loading": "Loading", + "tool.downLoadable": "可下载", + "tool.copyable": "可复制", + "tool.carousel": "图片轮播", + "tool.qrcode": "二维码", + "tool.field hide": "隐藏", + "tool.field display": "显示", + "tool.table permission": "生成表格权限", + "tool.extra code": "额外代码", + + "tool.detail display": "显示", + "tool.detail info": "详情页信息", + "tool.follow list page": "跟随列表页", + "tool.inherit from list page": "继承列表页", + "tool.independent from list page": "独立", + + "tool.continue edit checkbox": "继续编辑按钮", + "tool.continue new checkbox": "继续新增按钮", + "tool.reset button": "重设按钮", + "tool.back button": "返回按钮", + + "tool.field display normal": "显示", + "tool.field diplay hide": "隐藏", + "tool.field diplay edit hide": "编辑隐藏", + "tool.field diplay create hide": "新建隐藏", + + "tool.generate table model success": "生成成功", + "tool.generate table model fail": "生成失败", + + "generator.query": "查询", + "generator.show edit form page": "编辑页显示", + "generator.show create form page": "新建记录页显示", + "generator.edit": "编辑", + "generator.create": "新建", + "generator.delete": "删除", + "generator.export": "导出", + + "plugin.plugin": "插件", + "plugin.plugin detail": "插件详情", + "plugin.introduction": "介绍", + "plugin.website": "网站", + "plugin.version": "版本", + "plugin.created at": "创建日期", + "plugin.updated at": "更新日期", + "plugin.provided by %s": "由 %s 提供", + "plugin.upgrade": "升级", + "plugin.install": "安装", + "plugin.info": "详细信息", + "plugin.download": "下载", + "plugin.buy": "购买", + "plugin.downloading": "下载中", + "plugin.login": "登录", + "plugin.login to goadmin member system": "登录到GoAdmin会员系统", + "plugin.account": "账户名", + "plugin.password": "密码", + "plugin.learn more": "了解更多", + + "plugin.no account? click %s here %s to register.": "没有账号?点击%s这里%s注册。", + "plugin.download fail, wrong name": "下载失败,错误的参数", + "plugin.change to debug mode first": "先切换到debug模式", + "plugin.download fail, plugin not exist": "下载失败,插件不存在", + "plugin.download fail": "下载失败", + "plugin.golang develop environment does not exist": "golang开发环境不存在", + "plugin.download success, restart to install": "下载成功,重启程序进行安装", + "plugin.restart to install": "重启程序进行安装", + "plugin.can not connect to the goadmin remote server": "连接到GoAdmin远程服务器失败,请检查您的网络连接。", + + "admin.basic admin": "基础Admin", + "admin.a built-in plugins of goadmin which help you to build a crud manager platform quickly.": "一个内置GoAdmin插件,帮助您快速搭建curd简易管理后台。", + "admin.official": "GoAdmin官方", +} diff --git a/modules/language/en.go b/modules/language/en.go new file mode 100644 index 0000000000000000000000000000000000000000..145ce777e8c0140512e05cb6d8c593a5c52f11d1 --- /dev/null +++ b/modules/language/en.go @@ -0,0 +1,450 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package language + +import "strings" + +var en = LangSet{ + "managers": "Managers", + "name": "Name", + "nickname": "Nickname", + "role": "Role", + "createdat": "createdAt", + "updatedat": "updatedAt", + "path": "path", + "new": "New", + "filter": "Filter", + "action": "Action", + "toggle dropdown": "Toggle Dropdown", + "delete": "Delete", + "refresh": "Refresh", + "expand": "Expand", + "collapse": "Collapse", + "back": "Back", + "reset": "Reset", + "save": "Save", + "edit": "Edit", + "operation": "Operation", + "method": "Method", + "input": "input", + "online": "Online", + "setting": "Setting", + "sign out": "Sign out", + "all": "All", + "confirm password": "Confirm Password", + "search": "Search", + "remove": "Remove", + + "goadmin is now running. \nrunning in \"debug\" mode. switch to \"release\" mode in production.\n\n": "GoAdmin is now running. \nRunning in \"debug\" mode. Switch to \"release\" mode in production.\n\n", + + "wrong goadmin version, theme %s required goadmin version are %s": "wrong GoAdmin version, theme %s required GoAdmin version are %s", + "wrong theme version, goadmin %s required version of theme %s is %s": "wrong Theme version, GoAdmin %s required version of theme %s is %s", + + "adapter is nil, import the default adapter or use addadapter method add the adapter": "adapter is nil, import the default adapter or use AddAdapter method add the adapter", + + "are you sure to delete": "Are you sure to delete", + "yes": "yes", + "got it": "got it", + "cancel": "cancel", + "refresh succeeded": "Refresh succeeded", + "reload succeeded": "Reload succeeded", + "all method if empty": "All method if empty", + "password does not match": "Password does not match", + "should be unique": "Should be unique", + "slug exists": "Slug exists", + "no corresponding options?": "No corresponding options?", + "create here.": "Create here.", + "use for login": "Use for login", + "use to display": "Use to display", + "a path a line, without global prefix": "A path a line", + "slug or http_path or name should not be empty": "slug or http_path or name should not be empty", + "no roles": "no roles", + "fixed the sidebar": "Fixed the sidebar", + "enter fullscreen": "Enter fullscreen", + "exit fullscreen": "Exit fullscreen", + + "permission manage": "Permission Manage", + "menus manage": "Menus Manage", + "roles manage": "Roles manage", + "operation log": "Operation log", + + "continue editing": "Continue editing", + "continue creating": "Continue creating", + + "browse": "Browse", + "avatar": "Avatar", + "password": "Password", + "username": "Username", + "slug": "Slug", + "permission": "Permission", + "userid": "UserID", + "content": "Content", + "parent": "Parent", + "icon": "Icon", + "uri": "Uri", + + "detail": "Detail", + + "admin": "Admin", + "users": "Users", + "roles": "Roles", + "menu": "Menu", + "dashboard": "Dashboard", + "home": "Home", + + "initialize configuration": "Initialize configuration", + "initialize navigation buttons": "Initialize navigation buttons", + "initialize plugins": "Initialize plugins", + "initialize database connections": "Initialize database connections", + "initialize success": "Initialize success🍺🍺", + + "not found": "Not found", + "internal error": "Internal error", + "unauthorized": "Unauthorized", + "plugin setting": "Plugin Setting", + + "plugins": "Plugins", + "plugin store": "Plugin Store", + "get more plugins": "Get more plugins", + "uninstalled": "Uninstalled", + + "second": "second", + "seconds": "seconds", + "minute": "minute", + "minutes": "minutes", + "hour": "hour", + "hours": "hours", + "day": "day", + "days": "days", + "week": "week", + "weeks": "weeks", + "month": "month", + "months": "months", + "year": "year", + "years": "years", + + "config.domain": "Website Domain", + "config.language": "Website Language", + "config.url prefix": "URL Prefix", + "config.theme": "Theme", + "config.title": "Title", + "config.index url": "Home URL", + "config.login url": "Login URL", + "config.env": "Env", + "config.color scheme": "Color Scheme", + "config.cdn url": "CDN Asset URL", + "config.login title": "Login Title", + "config.auth user table": "Auth User Table", + "config.extra": "Extra Configuration", + "config.store": "File Store Setting", + "config.databases": "Database Setting", + + "config.general": "General", + "config.log": "Log", + "config.site setting": "Site Settings", + "config.custom": "Customize", + "config.debug": "Debug Mode", + "config.site off": "Site Offline", + "config.true": "On", + "config.false": "Off", + + "config.logo": "Logo", + "config.mini logo": "Mini Logo", + "config.bootstrap file path": "Bootstrap File Path", + "config.go mod file path": "go.mod File Path", + "config.session life time": "Session Life Time", + "config.custom head html": "Head HTML", + "config.custom foot html": "Foot HTML", + "config.custom 404 html": "404 Page", + "config.custom 403 html": "403 Page", + "config.custom 500 html": "500 Page", + "config.hide config center entrance": "Hide Config Button", + "config.hide app info entrance": "Hide App Info Button", + "config.hide tool entrance": "Hide Tool Button", + "config.footer info": "Footer Info", + "config.login logo": "Login Logo", + "config.no limit login ip": "No Limit Login Multi IPs", + "config.operation log off": "Operation Log Off", + "config.allow delete operation log": "Allow Delete Operation Log", + "config.animation type": "Animation Type", + "config.animation duration": "Animation Duration(s)", + "config.animation delay": "Animation Delay(s)", + "config.file upload engine": "File Upload Engine", + + "config.logger rotate": "Log Rotate Settings", + "config.logger rotate max size": "Max Size(m)", + "config.logger rotate max backups": "Max Buckups", + "config.logger rotate max age": "Max Age(day)", + "config.logger rotate compress": "Compress", + + "config.info log path": "Info Log File Path", + "config.error log path": "Error Log File Path", + "config.access log path": "Access Log File Path", + "config.info log off": "Info Log Off", + "config.error log off": "Error Log Off", + "config.access log off": "Access Log Off", + "config.access assets log off": "Access Assets Log Off", + "config.sql log on": "Open SQL Log", + "config.log level": "Level", + + "config.logger rotate encoder": "Log Encoder Settings", + "config.logger rotate encoder time key": "Time Key", + "config.logger rotate encoder level key": "Level Key", + "config.logger rotate encoder name key": "Name Key", + "config.logger rotate encoder caller key": "Caller Key", + "config.logger rotate encoder message key": "Message Key", + "config.logger rotate encoder stacktrace key": "Stacktrace Key", + "config.logger rotate encoder level": "Level Encoder", + "config.logger rotate encoder time": "Time Encoder", + "config.logger rotate encoder duration": "Duration Encoder", + "config.logger rotate encoder caller": "Caller Encoder", + "config.logger rotate encoder encoding": "Output Format", + + "config.capital": "Capital", + "config.capitalcolor": "Capital with color", + "config.lowercase": "Lowercase", + "config.lowercasecolor": "Lowercase with color", + + "config.seconds": "Seconds", + "config.nanosecond": "Nanosecond", + "config.microsecond": "Microsecond", + "config.millisecond": "Millisecond", + + "config.full path": "Full path", + "config.short path": "Short path", + + "config.do not modify when you have not set up all assets": "Do not modify when you have not set up all assets", + "config.it will work when theme is adminlte": "It will work when theme is adminlte", + + "config.language." + CN: "Chinese", + "config.language." + EN: "English", + "config.language." + JP: "Japanese", + "config.language." + strings.ToLower(TC): "Traditional Chinese", + "config.language." + PTBR: "Brazilian Portuguese", + + "config.modify site config": "Site Configuration Modification", + "config.modify site config success": "modified success", + "config.modify site config fail": "modified failed", + + "system.system info": "System And Application Info", + "system.application": "Application Info", + "system.application run": "Applications Running Info", + "system.system": "System Info", + + "system.process_id": "Process ID", + "system.golang_version": "Golang Version", + "system.server_uptime": "Server Uptime", + "system.current_goroutine": "Current Goroutines", + "system.current_memory_usage": "Current Memory Usage", + "system.total_memory_allocated": "Total Memory Allocated", + "system.memory_obtained": "Memory Obtained", + "system.pointer_lookup_times": "Pointer Lookup Times", + "system.memory_allocate_times": "Memory Allocate Times", + "system.memory_free_times": "Memory Free Times", + "system.current_heap_usage": "Current Heap Usage", + "system.heap_memory_obtained": "Heap Memory Obtained", + "system.heap_memory_idle": "Heap Memory Idle", + "system.heap_memory_in_use": "Heap Memory In Use", + "system.heap_memory_released": "Heap Memory Released", + "system.heap_objects": "Heap Objects", + "system.bootstrap_stack_usage": "Bootstrap Stack Usage", + "system.stack_memory_obtained": "Stack Memory Obtained", + "system.mspan_structures_usage": "MSpan Structures Usage", + "system.mspan_structures_obtained": "MSpan Structures Obtained", + "system.mcache_structures_usage": "MCache Structures Usage", + "system.mcache_structures_obtained": "MCache Structures Obtained", + "system.profiling_bucket_hash_table_obtained": "Profiling Bucket Hash Table Obtained", + "system.gc_metadata_obtained": "GC Metadata Obtained", + "system.other_system_allocation_obtained": "Other System Allocation Obtained", + "system.next_gc_recycle": "Next GC Recycle", + "system.last_gc_time": "Since Last GC Time", + "system.total_gc_time": "Total GC Pause", + "system.total_gc_pause": "Total GC Pause", + "system.last_gc_pause": "Last GC Pause", + "system.gc_times": "GC Times", + + "system.cpu_logical_core": "CPU Logical Core", + "system.cpu_core": "CPU Physical Core", + "system.os_platform": "OS Platform", + "system.os_family": "OS Family", + "system.os_version": "OS Version", + "system.load1": "Load1", + "system.load5": "Load5", + "system.load15": "Load15", + "system.mem_total": "Total Memory", + "system.mem_available": "Available Memory", + "system.mem_used": "Used Memory", + + "system.app_name": "App Name", + "system.go_admin_version": "App Version", + "system.theme_name": "Theme", + "system.theme_version": "Theme Version", + + "tool.tool": "Tool", + "tool.table": "Table", + "tool.connection": "Connection", + "tool.package": "Package", + "tool.output": "Output Path", + "tool.output path is empty": "Output path is empty", + "tool.field": "Field", + "tool.title": "Title", + "tool.field name": "Name", + "tool.db type": "Database Type", + "tool.form type": "Form Type", + "tool.generate table model": "Generate Table Model", + "tool.primarykey": "Primary Key", + "tool.field filterable": "Filterable", + "tool.field sortable": "Sortable", + "tool.yes": "Yes", + "tool.no": "No", + "tool.hide": "Hide", + "tool.show": "Show", + "tool.generate success": "Generate Success", + "tool.hide filter area": "Hide Filter Area", + "tool.use absolute path": "Use absolute path", + "tool.display": "Display", + "tool.basic info": "Basic", + "tool.table info": "Table", + "tool.form info": "Form", + "tool.field editable": "Editable", + "tool.info field editable": "Editable", + "tool.extra import package": "Import Package", + "tool.field can add": "Can Add", + "tool.field default": "Default", + "tool.filter area": "Filter Area", + "tool.new button": "New Button", + "tool.export button": "Export Button", + "tool.edit button": "Edit Button", + "tool.delete button": "Delete Button", + "tool.detail button": "Detail Button", + "tool.filter button": "Filter Button", + "tool.row selector": "Row Selector", + "tool.pagination": "Pagination", + "tool.query info": "Query Info", + "tool.filter form layout": "Filter Form Layout", + "tool.continue edit checkbox": "Continue Edit Checkbox", + "tool.continue new checkbox": "Continue New Checkbox", + "tool.reset button": "Reset Button", + "tool.back button": "Back Button", + "tool.generate": "Generate", + "tool.generated tables": "Generated Tables", + "tool.description": "Description", + "tool.label": "Label", + "tool.image": "Image", + "tool.bool": "Bool", + "tool.link": "Link", + "tool.fileSize": "File Size", + "tool.date": "Date", + "tool.icon": "Icon", + "tool.dot": "Dot", + "tool.progressBar": "ProgressBar", + "tool.loading": "Loading", + "tool.downLoadable": "DownLoadable", + "tool.copyable": "Copyable", + "tool.carousel": "Carousel", + "tool.qrcode": "Qrcode", + "tool.field hide": "Hide", + "tool.field display": "Display", + "tool.table permission": "Generate Permissions", + "tool.extra code": "Extra Code", + + "tool.field display normal": "Normal", + "tool.field diplay hide": "Hide", + "tool.field diplay edit hide": "Edit Hide", + "tool.field diplay create hide": "Create Hide", + + "tool.generate table model success": "generate success", + "tool.generate table model fail": "generate fail", + + "tool.detail display": "Display", + "tool.detail info": "Detail Info", + "tool.follow list page": "Follow List Page", + "tool.inherit from list page": "Inherit From List Page", + "tool.independent from list page": "Independent From List Page", + + "generator.query": "Query", + "generator.show edit form page": "Show Edit Form Page", + "generator.show create form page": "Show Create Form Page", + "generator.edit": "Edit", + "generator.create": "Create", + "generator.delete": "Delete", + "generator.export": "Export", + + "plugin.plugin": "Plugin", + "plugin.plugin detail": "Plugin Detail", + "plugin.introduction": "Introduction", + "plugin.website": "Website", + "plugin.version": "Version", + "plugin.created at": "Created At", + "plugin.updated at": "Updated At", + "plugin.provided by %s": "Provided by %s", + "plugin.upgrade": "Upgrade", + "plugin.install": "Install", + "plugin.download": "Download", + "plugin.buy": "Buy", + "plugin.downloading": "Downloading", + "plugin.info": "Detail", + "plugin.login": "Login", + "plugin.login to goadmin member system": "Login to GoAdmin member system", + "plugin.account": "Account", + "plugin.password": "Password", + "plugin.learn more": "Learn more", + + "plugin.no account? click %s here %s to register.": "No account?Click %shere%s to register。", + "plugin.download fail, wrong name": "Download fail, wrong name", + "plugin.change to debug mode first": "Change to debug mode first", + "plugin.download fail, plugin not exist": "Download fail, plugin not exist", + "plugin.download fail": "Download fail", + "plugin.golang develop environment does not exist": "Golang develop environment does not exist", + "plugin.download success, restart to install": "Download success, restart to install", + "plugin.restart to install": "Restart to install", + "plugin.can not connect to the goadmin remote server": "Failed to connect the GoAdmin remote server, check your network connection.", + + "admin.basic admin": "Basic Admin", + "admin.a built-in plugins of goadmin which help you to build a crud manager platform quickly.": "A built-in plugins of GoAdmin which help you to build a crud manager platform quickly.", + "admin.official": "Official", + + "current page": "Current Page", + "login": "Login", + "username and password can not be empty": "Username and password can not be empty", + "showing %s to %s of %s entries": "showing %s to %s of %s entries", + "code generate tool": "Code Generator", + "close": "close", + "fail": "fail", + "wrong captcha": "incorrect verification code", + "config.must bigger than 900 seconds": "Must be greater than 900 seconds", + "permission denied": "permission denied", + "config.local": "local env", + "view": "view", + "site setting": "Site Setting", + "del": "delete", + "error": "error", + "operation not allow": "operation not allowed", + "config.prod": "production env", + "edit fail": "edit failed", + "login info": "Login Info", + "config.hide plugin entrance": "Hide plugin page entry", + "submit": "Submit", + "managers manage": "Managers Manage", + "menus": "Menu", + "delete succeed": "delete succeed", + "query time": "query time", + "login overdue, please login again": "Login information expired. Please log in again", + "create fail": "create failed", + "tool.code generate tool": "Code Generator", + "user": "user", + "modify success": "modify success", + "menu name": "menu name", + "login fail": "login failed", + "export": "export", + "no permission": "no permission", + "confirm": "confirm", + "delete fail": "deletion failed", + "success": "success", + "system.site info": "Site Info", + "site info": "Site Info", + "more": "More", + "config.test": "test env", +} diff --git a/modules/language/jp.go b/modules/language/jp.go new file mode 100644 index 0000000000000000000000000000000000000000..7a7f319d98d95b4119ad19a343e86dea58f3b462 --- /dev/null +++ b/modules/language/jp.go @@ -0,0 +1,453 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package language + +import "strings" + +var jp = LangSet{ + "managers": "管理者", + "name": "ユーザー名", + "nickname": "ニックネーム", + "role": "ロール", + "createdat": "作成日時", + "updatedat": "更新日時", + "path": "パス", + "submit": "提出", + "filter": "フィルター", + + "new": "新規", + "export": "出力", + "action": "操作", + "toggle dropdown": "プルダウン", + "delete": "削除", + "refresh": "更新", + "back": "戻る", + "reset": "リセット", + "save": "保存", + "edit": "編集", + "expand": "展開", + "collapse": "折り畳み", + "online": "オンライン", + "setting": "設定", + "sign out": "ログアウト", + "home": "ホーム", + "all": "全て", + "more": "更に", + "remove": "削除する", + + "permission manage": "権限管理", + "menus manage": "メニュー管理", + "roles manage": "ロール管理", + "operation log": "操作履歴", + "method": "メソッド", + "input": "入力", + "operation": "操作", + "menu name": "表示名", + "reload succeeded": "再読み込み完了", + "search": "検索", + + "permission denied": "権限がありません", + "error": "エラー", + "current page": "現在のページ", + "query time": "クエリ時間", + + "are you sure to delete": "本当に削除しますか", + "yes": "はい", + "got it": "はい", + "cancel": "キャンセル", + "refresh succeeded": "更新に成功しました", + "edit fail": "編集に失敗しました", + "create fail": "作成に失敗しました", + "confirm password": "パスワード(確認)", + "all method if empty": "空の場合は全メソッド", + + "detail": "詳細", + + "avatar": "アバター", + "password": "パスワード", + "username": "ユーザー名", + "slug": "スラッグ", + "permission": "権限", + "userid": "ユーザーID", + "content": "内容", + "parent": "親メニュー", + "icon": "アイコン", + "uri": "URL", + + "login": "ログイン", + "login fail": "ログイン失敗", + + "admin": "運営管理", + "users": "ユーザー", + "roles": "ロール", + "menu": "メニュー", + "dashboard": "ダッシュボード", + + "goadmin is now running. \nrunning in \"debug\" mode. switch to \"release\" mode in production.\n\n": "GoAdminが現在稼働中です。\n\"デバッグ\"モードで実行中です。本番環境では\"リリース\"モードに切り替えてください。\n\n", + + "wrong goadmin version, theme %s required goadmin version are %s": "GoAdminバージョンが間違っています、テーマ%sの必要なGoAdminバージョンは%sです。", + "wrong theme version, goadmin %s required version of theme %s is %s": "テーマのバージョンが間違っています、GoAdmin%sの必要なテーマ%sのバージョンは%sです。", + + "adapter is nil, import the default adapter or use addadapter method add the adapter": "アダプターがnilです。デフォルトのアダプターをインポートするか、AddAdapterメソッドを使用してアダプターを追加してください。", + + "username and password can not be empty": "アカウントまたパスワードが正しく入力されていることを確認してください", + "operation not allow": "この操作を実行するアクセス許可が必要です", + "password does not match": "パスワードが正しくありません", + "should be unique": "重複しないことを確認してください", + "slug exists": "スラッグが既に存在していないことを確認してください", + "no corresponding options?": "対応できるオプションがありません", + "create here.": "ここに新規作成", + "use for login": "ログインに使う", + "use to display": "表示に使う", + "a path a line, without global prefix": "パスを1行ずつ入力してください", + "slug or http_path or name should not be empty": "スラッグ、http_pathまたユーザー名が正しく入力されていることを確認してください", + "no roles": "ロールなし", + + "initialize configuration": "設定の初期化", + "initialize navigation buttons": "ナビゲーションボタンの初期化", + "initialize plugins": "プラグインの初期化", + "initialize database connections": "データベース接続の初期化", + "initialize success": "初期化成功🍺🍺 ", + + "not found": "見つかりません", + "internal error": "内部エラー", + "unauthorized": "権限がありません", + + "plugins": "プラグイン", + "plugin store": "プラグインストア", + "get more plugins": "もっとプラグインを取得する", + "uninstalled": "アンインストールされました", + "plugin setting": "プラグイン設定", + + "second": "秒", + "seconds": "秒", + "minute": "分", + "minutes": "分", + "hour": "時間", + "hours": "時間", + "day": "日", + "days": "日", + "week": "週間", + "weeks": "週間", + "month": "月", + "months": "月", + "year": "年", + "years": "年", + + "config.domain": "ウェブサイトのドメイン", + "config.language": "ウェブサイトの言語", + "config.url prefix": "URLプレフィックス", + "config.theme": "テーマ", + "config.title": "タイトル", + "config.index url": "ホームURL", + "config.login url": "ログインURL", + "config.env": "環境", + "config.color scheme": "カラースキーム", + "config.cdn url": "CDNアセットURL", + "config.login title": "ログインタイトル", + "config.auth user table": "認証ユーザーテーブル", + "config.extra": "追加の設定", + "config.store": "ファイルストアの設定", + "config.databases": "データベースの設定", + + "config.general": "一般", + "config.log": "ログ", + "config.site setting": "サイト設定", + "config.custom": "カスタマイズ", + "config.debug": "デバッグモード", + "config.site off": "サイトオフライン", + "config.true": "オン", + "config.false": "オフ", + + "config.logo": "ロゴ", + "config.mini logo": "ミニロゴ", + "config.bootstrap file path": "Bootstrapファイルのパス", + "config.go mod file path": "go.modファイルのパス", + "config.session life time": "セッションの有効期間", + "config.custom head html": "ヘッドHTML", + "config.custom foot html": "フッターHTML", + "config.custom 404 html": "404ページ", + "config.custom 403 html": "403ページ", + "config.custom 500 html": "500ページ", + "config.hide config center entrance": "設定ボタンを非表示にする", + "config.hide app info entrance": "アプリ情報ボタンを非表示にする", + "config.hide tool entrance": "ツールボタンを非表示にする", + "config.hide plugin entrance": "プラグインリストボタンを非表示にする", + "config.footer info": "フッター情報", + "config.login logo": "ログインロゴ", + "config.no limit login ip": "制限なしの複数IPでのログイン", + "config.operation log off": "操作ログオフ", + "config.allow delete operation log": "操作ログの削除を許可する", + "config.animation type": "アニメーションタイプ", + "config.animation duration": "アニメーションの期間(秒)", + "config.animation delay": "アニメーションの遅延(秒)", + "config.file upload engine": "ファイルアップロードエンジン", + + "config.logger rotate": "ログローテーション設定", + "config.logger rotate max size": "最大サイズ(MB)", + "config.logger rotate max backups": "最大バックアップ数", + "config.logger rotate max age": "最大保存期間(日)", + "config.logger rotate compress": "圧縮", + + "config.info log path": "情報ログファイルのパス", + "config.error log path": "エラーログファイルのパス", + "config.access log path": "アクセスログファイルのパス", + "config.info log off": "情報ログオフ", + "config.error log off": "エラーログオフ", + "config.access log off": "アクセスログオフ", + "config.access assets log off": "アクセスアセットログオフ", + "config.sql log on": "SQLログを開く", + "config.log level": "レベル", + + "config.logger rotate encoder": "ログエンコーダー設定", + "config.logger rotate encoder time key": "時間キー", + "config.logger rotate encoder level key": "レベルキー", + "config.logger rotate encoder name key": "名前キー", + "config.logger rotate encoder caller key": "呼び出し元キー", + "config.logger rotate encoder message key": "メッセージキー", + "config.logger rotate encoder stacktrace key": "スタックトレースキー", + "config.logger rotate encoder level": "レベルエンコーダー", + "config.logger rotate encoder time": "時間エンコーダー", + "config.logger rotate encoder duration": "持続時間エンコーダー", + "config.logger rotate encoder caller": "呼び出し元エンコーダー", + "config.logger rotate encoder encoding": "出力形式", + + "config.capital": "大文字", + "config.capitalcolor": "色付き大文字", + "config.lowercase": "小文字", + "config.lowercasecolor": "色付き小文字", + + "config.seconds": "秒", + "config.nanosecond": "ナノ秒", + "config.microsecond": "マイクロ秒", + "config.millisecond": "ミリ秒", + + "config.full path": "完全なパス", + "config.short path": "短縮パス", + + "config.do not modify when you have not set up all assets": "すべてのアセットが設定されていない場合は変更しないでください", + "config.it will work when theme is adminlte": "テーマがadminlteの場合に機能します", + + "config.language." + CN: "中国語", + "config.language." + EN: "英語", + "config.language." + JP: "日本語", + "config.language." + PTBR: "ブラジルポルトガル語", + "config.language." + strings.ToLower(TC): "繁体字中国語", + + "config.modify site config": "サイト設定の変更", + "config.modify site config success": "変更成功", + "config.modify site config fail": "変更失敗", + + "system.system info": "システムおよびアプリケーション情報", + "system.application": "アプリケーション情報", + "system.application run": "実行中アプリケーション情報", + "system.system": "システム情報", + + "system.process_id": "プロセスID", + "system.golang_version": "Golangバージョン", + "system.server_uptime": "サーバーの稼働時間", + "system.current_goroutine": "現在のゴールーチン数", + "system.current_memory_usage": "現在のメモリ使用量", + "system.total_memory_allocated": "割り当てられた総メモリ量", + "system.memory_obtained": "取得されたメモリ", + "system.pointer_lookup_times": "ポインター検索回数", + "system.memory_allocate_times": "メモリ割り当て回数", + "system.memory_free_times": "メモリ解放回数", + "system.current_heap_usage": "現在のヒープ使用量", + "system.heap_memory_obtained": "取得されたヒープメモリ", + "system.heap_memory_idle": "アイドル状態のヒープメモリ", + "system.heap_memory_in_use": "使用中のヒープメモリ", + "system.heap_memory_released": "解放されたヒープメモリ", + "system.heap_objects": "ヒープオブジェクト数", + "system.bootstrap_stack_usage": "ブートストラップスタック使用量", + "system.stack_memory_obtained": "取得されたスタックメモリ", + "system.mspan_structures_usage": "MSpan構造の使用量", + "system.mspan_structures_obtained": "取得されたMSpan構造", + "system.mcache_structures_usage": "MCache構造の使用量", + "system.mcache_structures_obtained": "取得されたMCache構造", + "system.profiling_bucket_hash_table_obtained": "プロファイリングバケットハッシュテーブルの取得", + "system.gc_metadata_obtained": "GCメタデータの取得", + "system.other_system_allocation_obtained": "その他のシステム割り当ての取得", + "system.next_gc_recycle": "次のGC再利用", + "system.last_gc_time": "最後のGCからの時間経過", + "system.total_gc_time": "総GC時間", + "system.total_gc_pause": "総GC停止時間", + "system.last_gc_pause": "最後のGC停止時間", + "system.gc_times": "GC回数", + + "system.cpu_logical_core": "CPU論理コア", + "system.cpu_core": "CPU物理コア", + "system.os_platform": "OSプラットフォーム", + "system.os_family": "OSファミリー", + "system.os_version": "OSバージョン", + "system.load1": "Load1", + "system.load5": "Load5", + "system.load15": "Load15", + "system.mem_total": "総メモリ量", + "system.mem_available": "利用可能なメモリ", + "system.mem_used": "使用中のメモリ", + + "system.app_name": "アプリ名", + "system.go_admin_version": "アプリバージョン", + "system.theme_name": "テーマ", + "system.theme_version": "テーマバージョン", + + "tool.tool": "ツール", + "tool.table": "テーブル", + "tool.connection": "接続", + "tool.package": "パッケージ", + "tool.output": "出力パス", + "tool.output path is empty": "出力パスが空です", + "tool.field": "データベースフィールド", + "tool.title": "タイトル", + "tool.field name": "名前", + "tool.db type": "データベースタイプ", + "tool.form type": "フォームタイプ", + "tool.generate table model": "テーブルモデルを生成する", + "tool.primarykey": "主キー", + "tool.field filterable": "フィルター可能", + "tool.field sortable": "ソート可能", + "tool.yes": "はい", + "tool.no": "いいえ", + "tool.hide": "非表示", + "tool.show": "表示", + "tool.generate success": "生成成功", + "tool.hide filter area": "フィルターエリアを非表示にする", + "tool.use absolute path": "絶対パスを使用する", + "tool.display": "表示", + "tool.basic info": "基本", + "tool.table info": "テーブル", + "tool.form info": "フォーム", + "tool.field editable": "編集可能", + "tool.info field editable": "編集非表示", + "tool.field can add": "追加可能", + "tool.field default": "デフォルト", + "tool.filter area": "フィルターエリア", + "tool.new button": "新規ボタン", + "tool.extra import package": "パッケージのインポート", + "tool.export button": "エクスポートボタン", + "tool.edit button": "編集ボタン", + "tool.delete button": "削除ボタン", + "tool.detail button": "詳細ボタン", + "tool.filter button": "フィルターボタン", + "tool.row selector": "行選択器", + "tool.pagination": "ページネーション", + "tool.query info": "クエリ情報", + "tool.filter form layout": "フィルターフォームレイアウト", + "tool.continue edit checkbox": "編集の続行チェックボックス", + "tool.continue new checkbox": "新規作成の続行チェックボックス", + "tool.reset button": "リセットボタン", + "tool.back button": "戻るボタン", + "tool.generate": "生成する", + "tool.generated tables": "生成されたテーブル", + "tool.description": "説明", + "tool.label": "ラベル", + "tool.image": "画像", + "tool.bool": "ブール", + "tool.link": "リンク", + "tool.fileSize": "ファイルサイズ", + "tool.date": "日付", + "tool.icon": "アイコン", + "tool.dot": "ドット", + "tool.progressBar": "プログレスバー", + "tool.loading": "ロード中", + "tool.downLoadable": "ダウンロード可能", + "tool.copyable": "コピー可能", + "tool.carousel": "カルーセル", + "tool.qrcode": "QRコード", + "tool.field hide": "非表示", + "tool.field display": "表示", + "tool.table permission": "権限を生成する", + "tool.extra code": "追加コード", + + "tool.field display normal": "通常", + "tool.field diplay hide": "非表示", + "tool.field diplay edit hide": "編集非表示", + "tool.field diplay create hide": "作成非表示", + + "tool.detail display": "表示", + "tool.detail info": "詳細情報", + "tool.follow list page": "リストページに従う", + "tool.inherit from list page": "リストページから継承", + "tool.independent from list page": "リストページから独立", + + "tool.generate table model success": "生成成功", + "tool.generate table model fail": "生成失敗", + + "generator.query": "クエリ", + "generator.show edit form page": "編集フォームページを表示", + "generator.show create form page": "作成フォームページを表示", + "generator.edit": "編集", + "generator.create": "作成", + "generator.delete": "削除", + "generator.export": "エクスポート", + + "plugin.plugin": "プラグイン", + "plugin.plugin detail": "プラグイン詳細", + "plugin.introduction": "導入", + "plugin.website": "ウェブサイト", + "plugin.version": "バージョン", + "plugin.created at": "作成日時", + "plugin.updated at": "更新日時", + "plugin.provided by %s": "%sに提供されます", + "plugin.upgrade": "アップグレード", + "plugin.install": "インストール", + "plugin.info": "詳細", + "plugin.download": "ダウンロード", + "plugin.buy": "購入", + "plugin.downloading": "ダウンロード中", + "plugin.login": "ログイン", + "plugin.login to goadmin member system": "GoAdminメンバーシステムにログインする", + "plugin.account": "アカウント", + "plugin.password": "パスワード", + "plugin.learn more": "もっと学ぶ", + + "plugin.no account? click %s here %s to register.": "アカウントがありませんか?登録するには%sここ%sをクリックしてください。", + "plugin.download fail, wrong name": "ダウンロードに失敗しました、間違った名前です", + "plugin.change to debug mode first": "まずデバッグモードに変更してください", + "plugin.download fail, plugin not exist": "ダウンロードに失敗しました、プラグインが存在しません", + "plugin.download fail": "ダウンロードに失敗しました", + "plugin.golang develop environment does not exist": "Golangの開発環境が存在しません", + "plugin.download success, restart to install": "ダウンロードが成功しました、インストールのために再起動します", + "plugin.restart to install": "インストールのために再起動します", + "plugin.can not connect to the goadmin remote server": "GoAdminリモートサーバーに接続できませんでした、ネットワーク接続を確認してください。", + + "admin.basic admin": "基本管理者", + "admin.a built-in plugins of goadmin which help you to build a crud manager platform quickly.": "GoAdminの組み込みプラグインで、CRUDマネージャープラットフォームを素早く構築できます。", + "admin.official": "公式", + + "browse": "開く", + "system.site info": "実行情報", + "delete succeed": "削除成功", + "continue creating": "追加続行", + "wrong captcha": "エラーの認証コード", + "code generate tool": "コードジェネレーター", + "fixed the sidebar": "固定サイドバー", + "config.must bigger than 900 seconds": "900秒以上である必要があります", + "enter fullscreen": "フルスクリーンに入る", + "fail": "失敗", + "modify success": "修正成功", + "menus": "メニュー", + "config.test": "テスト環境", + "del": "削除", + "close": "閉じる", + "view": "表示", + "tool.code generate tool": "コードジェネレーター", + "login info": "ログイン情報", + "login overdue, please login again": "ログイン情報が期限切れです。再ログインしてください。", + "site setting": "ウェブサイト設定", + "managers manage": "管理者管理", + "continue editing": "編集を続ける", + "site info": "実行情報", + "success": "成功", + "delete fail": "削除失敗", + "exit fullscreen": "フルスクリーンを終了する", + "confirm": "確認", + "user": "ユーザー", + "showing %s to %s of %s entries": "第%sから第%sのレコードを表示、合計%sレコード", + "config.local": "ローカル環境", + "config.prod": "プロダクション環境", + "no permission": "権限がありません", +} diff --git a/modules/language/language.go b/modules/language/language.go new file mode 100644 index 0000000000000000000000000000000000000000..11539fb7a0527412ae8e83cc8984066967746ff7 --- /dev/null +++ b/modules/language/language.go @@ -0,0 +1,162 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package language + +import ( + "html/template" + "strings" + + "github.com/GoAdminGroup/go-admin/modules/config" + "golang.org/x/text/language" +) + +var ( + EN = language.English.String() + CN = language.Chinese.String() + JP = language.Japanese.String() + TC = language.TraditionalChinese.String() + PTBR = language.BrazilianPortuguese.String() +) + +func FixedLanguageKey(key string) string { + if key == "en" { + return EN + } + if key == "cn" { + return CN + } + if key == "jp" { + return JP + } + if key == "tc" { + return TC + } + if key == "pt-br" { + return PTBR + } + return key +} + +var Langs = [...]string{EN, CN, JP, TC} + +// Get return the value of default scope. +func Get(value string) string { + return GetWithScope(value) +} + +// GetWithScope return the value of given scopes. +func GetWithScope(value string, scopes ...string) string { + if config.GetLanguage() == "" { + return value + } + + return GetWithScopeAndLanguageSet(value, config.GetLanguage(), scopes...) +} + +// GetWithLang return the value of given language set. +func GetWithLang(value, lang string) string { + if lang == "" { + lang = config.GetLanguage() + } + return GetWithScopeAndLanguageSet(value, lang) +} + +// GetWithScopeAndLanguageSet return the value of given scopes and language set. +func GetWithScopeAndLanguageSet(value, lang string, scopes ...string) string { + if locale, ok := Lang[lang][JoinScopes(scopes)+strings.ToLower(value)]; ok { + return locale + } + + return value +} + +// GetFromHtml return the value of given scopes and template.HTML value. +func GetFromHtml(value template.HTML, scopes ...string) template.HTML { + if config.GetLanguage() == "" { + return value + } + + if locale, ok := Lang[config.GetLanguage()][JoinScopes(scopes)+strings.ToLower(string(value))]; ok { + return template.HTML(locale) + } + + return value +} + +// WithScopes join scopes prefix and the value. +func WithScopes(value string, scopes ...string) string { + return JoinScopes(scopes) + strings.ToLower(value) +} + +type LangSet map[string]string + +func (l LangSet) Add(key, value string) { + l[key] = value +} + +func (l LangSet) Combine(set LangSet) LangSet { + for k, s := range set { + l[k] = s + } + return l +} + +// LangMap is the map of language packages. +type LangMap map[string]LangSet + +// Lang is the global LangMap. +var Lang = LangMap{ + language.Chinese.String(): cn, + language.English.String(): en, + language.Japanese.String(): jp, + language.TraditionalChinese.String(): tc, + language.BrazilianPortuguese.String(): ptbr, + + "cn": cn, + "en": en, + "jp": jp, + "tc": tc, + "pt-br": ptbr, +} + +// Get get the value from LangMap. +func (lang LangMap) Get(value string) string { + return lang.GetWithScope(value) +} + +// GetWithScope get the value from LangMap with given scopes. +func (lang LangMap) GetWithScope(value string, scopes ...string) string { + if config.GetLanguage() == "" { + return value + } + + if locale, ok := lang[config.GetLanguage()][JoinScopes(scopes)+strings.ToLower(value)]; ok { + return locale + } + + return value +} + +// Add add a language package to the Lang. +func Add(key string, lang map[string]string) { + Lang[key] = lang +} + +// AppendTo add more language translations to the given language set. +func AppendTo(lang string, set map[string]string) { + for key, value := range set { + Lang[lang][key] = value + } +} + +func JoinScopes(scopes []string) string { + j := "" + for _, scope := range scopes { + if scope != "" { + j += scope + "." + } + } + return j +} diff --git a/modules/language/language_test.go b/modules/language/language_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ec2c40b6c594cf753d27b418a0de83fe1d9d600c --- /dev/null +++ b/modules/language/language_test.go @@ -0,0 +1,54 @@ +package language + +import ( + "fmt" + "html/template" + "testing" + + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/stretchr/testify/assert" +) + +func TestKK(t *testing.T) { + for key := range cn { + if _, ok := ptbr[key]; !ok { + fmt.Println(key, "===", cn[key]) + } + } +} + +func TestAdd(t *testing.T) { + Add("cn", map[string]string{}) +} + +func TestGetWithScope(t *testing.T) { + config.Initialize(&config.Config{ + Language: CN, + }) + cn["foo"] = "bar" + assert.Equal(t, GetWithScope("foo"), "bar") + cn["user.table.foo2"] = "bar" + assert.Equal(t, GetWithScope("foo2"), "foo2") + assert.Equal(t, GetWithScope("foo2", "user"), "foo2") + assert.Equal(t, GetWithScope("foo2", "user", "table"), "bar") +} + +func TestGet(t *testing.T) { + config.Initialize(&config.Config{ + Language: CN, + }) + cn["foo"] = "bar" + assert.Equal(t, Get("foo"), "bar") +} + +func TestWithScopes(t *testing.T) { + assert.Equal(t, WithScopes("foo", "user", "table"), "user.table.foo") +} + +func TestGetFromHtml(t *testing.T) { + config.Initialize(&config.Config{ + Language: CN, + }) + cn["user.table.foo"] = "bar" + assert.Equal(t, GetFromHtml("foo", "user", "table"), template.HTML("bar")) +} diff --git a/modules/language/pt-BR.go b/modules/language/pt-BR.go new file mode 100644 index 0000000000000000000000000000000000000000..70e2a4512cfd751ea0360427b159c7d86cd443f9 --- /dev/null +++ b/modules/language/pt-BR.go @@ -0,0 +1,450 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package language + +import "strings" + +var ptbr = LangSet{ + "managers": "Gerentes", + "name": "Nome", + "nickname": "Apelido", + "role": "Role", + "createdat": "criadoEm", + "updatedat": "atualizadoEm", + "path": "caminho", + "new": "Novo", + "filter": "Filtro", + "action": "Ação", + "toggle dropdown": "Toggle Dropdown", + "delete": "Excluir", + "refresh": "Atualizar", + "expand": "Expandir", + "collapse": "Recolher", + "back": "Voltar", + "reset": "Redefinir", + "save": "Salvar", + "edit": "Editar", + "operation": "Operação", + "method": "Método", + "input": "entrada", + "online": "Online", + "setting": "Configuração", + "sign out": "Sair", + "all": "Tudo", + "confirm password": "Confirmar a Senha", + "search": "Procurar", + "remove": "Remover", + "query time": "tempo de consulta", + + "goadmin is now running. \nrunning in \"debug\" mode. switch to \"release\" mode in production.\n\n": "GoAdmin agora está em execução. \nExecutando no modo \"debug \". Mude para o modo de \"release\" em produção. \n\n", + + "wrong goadmin version, theme %s required goadmin version are %s": "Versão incorreta do GoAdmin, o tema %s requer a versão do GoAdmin %s", + "wrong theme version, goadmin %s required version of theme %s is %s": "Versão incorreta do tema, o GoAdmin %s requer a versão do tema %s como %s", + + "adapter is nil, import the default adapter or use addadapter method add the adapter": "O adaptador está nulo, importe o adaptador padrão ou use o método AddAdapter para adicionar o adaptador", + + "are you sure to delete": "Você tem certeza que quer deletar", + "yes": "sim", + "got it": "entendi", + "cancel": "cancelar", + "refresh succeeded": "Atualizado com sucesso", + "reload succeeded": "Recarregado com sucesso", + "all method if empty": "Todo método é vazio", + "password does not match": "Senha não confere", + "should be unique": "Deve ser único", + "slug exists": "Slug existe", + "no corresponding options?": "Sem opções correspondentes ?", + "create here.": "Crie aqui.", + "use for login": "Use para login", + "use to display": "Use para exibir", + "a path a line, without global prefix": "Um caminho uma linha", + "slug or http_path or name should not be empty": "slug ou http_path ou nome não deve estar vazio", + "no roles": "sem roles", + "fixed the sidebar": "Barra lateral fixada", + "enter fullscreen": "Entrar em tela cheia", + "exit fullscreen": "Sair da tela inteira", + + "permission manage": "Gerenciamento de permissão", + "menus manage": "Gerenciar menu", + "roles manage": "Gerenciar roles", + "operation log": "Log de operação", + + "continue editing": "Continue editando", + "continue creating": "Continue criando", + + "browse": "Navegar", + "avatar": "Avatar", + "password": "Senha", + "username": "Nome do usuário", + "slug": "Slug", + "permission": "Permissão", + "userid": "UserID", + "content": "Conteúdo", + "parent": "Pai", + "icon": "Ícone", + "uri": "Uri", + + "detail": "Detalhe", + + "admin": "Administração", + "users": "Usuários", + "roles": "Funções", + "menu": "Menu", + "dashboard": "Painel de Controle", + "home": "Página Inicial", + + "initialize configuration": "Inicializando configuração", + "initialize navigation buttons": "Inicializando botões de navegação", + "initialize plugins": "Inicializando plugins", + "initialize database connections": "Inicializando conexões de banco de dados", + "initialize success": "Inicialização feita com sucesso🍺🍺", + + "not found": "Não encontrado", + "internal error": "Erro interno", + "unauthorized": "Não autorizado", + "plugin setting": "Configuração de Plugin", + + "plugins": "Plugins", + "plugin store": "Loja de Plugins", + "get more plugins": "Mais plugins", + "uninstalled": "Desinstalado", + + "second": "segundo", + "seconds": "segundos", + "minute": "minuto", + "minutes": "minutos", + "hour": "hora", + "hours": "horass", + "day": "dia", + "days": "dias", + "week": "semana", + "weeks": "semanas", + "month": "mês", + "months": "meses", + "year": "ano", + "years": "anos", + + "config.domain": "Domínio do site", + "config.language": "Idioma do site", + "config.url prefix": "URL Prefix", + "config.theme": "Tema", + "config.title": "Título", + "config.index url": "Home URL", + "config.login url": "Login URL", + "config.env": "Env", + "config.color scheme": "Color Scheme", + "config.cdn url": "CDN Asset URL", + "config.login title": "Título de Login", + "config.auth user table": "Auth User Table", + "config.extra": "Extra Configuration", + "config.store": "File Store Setting", + "config.databases": "Database Setting", + + "config.general": "Geral", + "config.log": "Registro", + "config.site setting": "Configurações do Site", + "config.custom": "Personalizar", + "config.debug": "Modo de Depuração", + "config.site off": "Site Offline", + "config.true": "Ligado", + "config.false": "Desligado", + + "config.logo": "Logotipo", + "config.mini logo": "Mini Logotipo", + "config.bootstrap file path": "Caminho do Arquivo Bootstrap", + "config.go mod file path": "Caminho do Arquivo go.mod", + "config.session life time": "Tempo de Vida da Sessão", + "config.custom head html": "HTML do Cabeçalho", + "config.custom foot html": "HTML do Rodapé", + "config.custom 404 html": "Página 404", + "config.custom 403 html": "Página 403", + "config.custom 500 html": "Página 500", + "config.hide config center entrance": "Ocultar Botão de Configuração", + "config.hide app info entrance": "Ocultar Botão de Informações do Aplicativo", + "config.hide tool entrance": "Ocultar Botão de Ferramenta", + "config.footer info": "Informações do Rodapé", + "config.login logo": "Logotipo de Login", + "config.no limit login ip": "Login sem Limite de IPs Múltiplos", + "config.operation log off": "Desligar Log de Operações", + "config.allow delete operation log": "Permitir Excluir Log de Operações", + "config.animation type": "Tipo de Animação", + "config.animation duration": "Duração da Animação (s)", + "config.animation delay": "Atraso da Animação (s)", + "config.file upload engine": "Motor de Envio de Arquivos", + + "config.logger rotate": "Configurações de Rotação de Log", + "config.logger rotate max size": "Tamanho Máximo (m)", + "config.logger rotate max backups": "Backup Máximo", + "config.logger rotate max age": "Idade Máxima (dias)", + "config.logger rotate compress": "Comprimir", + + "config.info log path": "Caminho do Arquivo de Log de Informações", + "config.error log path": "Caminho do Arquivo de Log de Erro", + "config.access log path": "Caminho do Arquivo de Log de Acesso", + "config.info log off": "Desligar Log de Informações", + "config.error log off": "Desligar Log de Erro", + "config.access log off": "Desligar Log de Acesso", + "config.access assets log off": "Desligar Log de Ativos de Acesso", + "config.sql log on": "Abrir log SQL", + "config.log level": "Nível", + + "config.logger rotate encoder": "Configurações do codificador de registro", + "config.logger rotate encoder time key": "Chave de tempo", + "config.logger rotate encoder level key": "Chave de Nível", + "config.logger rotate encoder name key": "Chave do Nome", + "config.logger rotate encoder caller key": "Tecla do chamador", + "config.logger rotate encoder message key": "Chave de mensagem", + "config.logger rotate encoder stacktrace key": "Chave Stacktrace", + "config.logger rotate encoder level": "Codificador de Nível", + "config.logger rotate encoder time": "Codificador de tempo", + "config.logger rotate encoder duration": "Codificador de duração", + "config.logger rotate encoder caller": "Codificador de Chamadas", + "config.logger rotate encoder encoding": "Formato de saída", + + "config.capital": "Maiúsculo", + "config.capitalcolor": "Maiúsculo com cor", + "config.lowercase": "Minúsculas", + "config.lowercasecolor": "Minúsculas com cor", + + "config.seconds": "Segundos", + "config.nanosecond": "Nanossegundo", + "config.microsecond": "Microssegundo", + "config.millisecond": "Milissegundo", + + "config.full path": "Caminho completo", + "config.short path": "Caminho curto", + + "config.do not modify when you have not set up all assets": "Não modifique quando você não configurou todos os assets", + "config.it will work when theme is adminlte": "Funcionará quando o tema for adminlte", + + "config.language." + CN: "Chinês", + "config.language." + EN: "Inglês", + "config.language." + JP: "Japonês", + "config.language." + strings.ToLower(TC): "Chinês Tradicional", + "config.language." + PTBR: "Português Brasileiro", + + "config.modify site config": "Modificação da configuração do site", + "config.modify site config success": "modificado com successo", + "config.modify site config fail": "modificação falhou", + + "system.system info": "Informações do sistema e do aplicativo", + "system.application": "Informações do aplicativo", + "system.application run": "Informações de execução de aplicativos", + "system.system": "Informação do sistema", + + "system.process_id": "ID do Processo", + "system.golang_version": "Versão do Golang", + "system.server_uptime": "Tempo de atividade do servidor", + "system.current_goroutine": "Goroutines Atuais", + "system.current_memory_usage": "Uso de Memória Atual", + "system.total_memory_allocated": "Memória Total Alocada", + "system.memory_obtained": "Memória Obtida", + "system.pointer_lookup_times": "Tempos de Pesquisa de Ponteiro", + "system.memory_allocate_times": "Tempos de Alocação de Memória", + "system.memory_free_times": "Tempos de Liberação de Memória", + "system.current_heap_usage": "Uso de Heap Atual", + "system.heap_memory_obtained": "Memória de Heap Obtida", + "system.heap_memory_idle": "Memória de Heap Inativa", + "system.heap_memory_in_use": "Memória de Heap em Uso", + "system.heap_memory_released": "Memória de Heap Liberada", + "system.heap_objects": "Objetos de Heap", + "system.bootstrap_stack_usage": "Uso de Pilha de Inicialização", + "system.stack_memory_obtained": "Memória de Pilha Obtida", + "system.mspan_structures_usage": "Uso de Estruturas MSpan", + "system.mspan_structures_obtained": "Estruturas MSpan Obtidas", + "system.mcache_structures_usage": "Uso de Estruturas MCache", + "system.mcache_structures_obtained": "Estruturas MCache Obtidas", + "system.profiling_bucket_hash_table_obtained": "Tabela de Hash de Baldes de Perfil Objetida", + "system.gc_metadata_obtained": "Metadados de GC Obtidos", + "system.other_system_allocation_obtained": "Outras Alocações de Sistema Obtidas", + "system.next_gc_recycle": "Próxima Reciclagem de GC", + "system.last_gc_time": "Desde a Última Hora de GC", + "system.total_gc_time": "Pausa Total de GC", + "system.total_gc_pause": "Pausa Total de GC", + "system.last_gc_pause": "Última Pausa de GC", + "system.gc_times": "Tempos de GC", + + "system.cpu_logical_core": "CPU Core Lógico", + "system.cpu_core": "CPU Core Físico", + "system.os_platform": "OS Plataforma", + "system.os_family": "OS Familia", + "system.os_version": "OS Versão", + "system.load1": "Load1", + "system.load5": "Load5", + "system.load15": "Load15", + "system.mem_total": "Memória Total", + "system.mem_available": "Memória disponível", + "system.mem_used": "Memória usada", + + "system.app_name": "Nome da aplicação", + "system.go_admin_version": "Versão da aplicação", + "system.theme_name": "Tema", + "system.theme_version": "Versão do Tema", + + "tool.tool": "Ferramenta", + "tool.table": "Tabela", + "tool.connection": "Conexão", + "tool.package": "Pacote", + "tool.output": "Caminho de saída", + "tool.output path is empty": "O caminho de saída está vazio", + "tool.field": "Campo", + "tool.title": "Título", + "tool.field name": "Nome", + "tool.db type": "Tipo de banco de dados", + "tool.form type": "Tipo de Formulário", + "tool.generate table model": "Modelo de tabela gerado", + "tool.primarykey": "Chave primária", + "tool.field filterable": "Filtrável", + "tool.field sortable": "Classificável", + "tool.yes": "Sim", + "tool.no": "Não", + "tool.hide": "Esconder", + "tool.show": "Mostrar", + "tool.generate success": "Gerado com sucesso", + "tool.hide filter area": "Esconder a Área do Filtro", + "tool.use absolute path": "Use o caminho absoluto", + "tool.display": "Exibição", + "tool.basic info": "Básico", + "tool.table info": "Tabela", + "tool.form info": "Formulário", + "tool.field editable": "Editável", + "tool.info field editable": "Editável", + "tool.extra import package": "Pacote de Importação", + "tool.field can add": "Pode adicionar", + "tool.field default": "Default", + "tool.filter area": "Area de filtro", + "tool.new button": "Botão Novo", + "tool.export button": "Botão exportar", + "tool.edit button": "Botão editar", + "tool.delete button": "Botão excluir", + "tool.detail button": "Botão Detalhe", + "tool.filter button": "Botão Filtro", + "tool.row selector": "Seletor de linha", + "tool.pagination": "Paginação", + "tool.query info": "Informações de consulta", + "tool.filter form layout": "Layout do formulário de filtro", + "tool.continue edit checkbox": "Continue Edição Checkbox", + "tool.continue new checkbox": "Continuar Novo Checkbox", + "tool.reset button": "Botão Redefinir", + "tool.back button": "Botão Voltar", + "tool.generate": "Gerado", + "tool.generated tables": "Tabelas geradas", + "tool.description": "Descrição", + "tool.label": "Etiqueta", + "tool.image": "Imagem", + "tool.bool": "Bool", + "tool.link": "Link", + "tool.fileSize": "Tamanho do Arquivo", + "tool.date": "Data", + "tool.icon": "Ícone", + "tool.dot": "Ponto", + "tool.progressBar": "Barra de progresso", + "tool.loading": "Carregando", + "tool.downLoadable": "DownLoadable", + "tool.copyable": "Copiável", + "tool.carousel": "Carrossel", + "tool.qrcode": "Qrcode", + "tool.field hide": "Esconder", + "tool.field display": "Exibição", + "tool.table permission": "Gerar permissões", + "tool.extra code": "Código Extra", + + "tool.field display normal": "Normal", + "tool.field diplay hide": "Esconder", + "tool.field diplay edit hide": "Editar Esconder", + "tool.field diplay create hide": "Criar Esconder", + + "tool.generate table model success": "sucesso ao gerar", + "tool.generate table model fail": "falha ao gerar", + + "tool.detail display": "Exibição", + "tool.detail info": "Informação detalhada", + "tool.follow list page": "Seguir a página da lista", + "tool.inherit from list page": "Herdar da página da lista", + "tool.independent from list page": "Independente da página da lista", + + "generator.query": "Query", + "generator.show edit form page": "Mostrar formulário de edição", + "generator.show create form page": "Mostrar formulário de criação", + "generator.edit": "Editar", + "generator.create": "Criar", + "generator.delete": "Excluir", + "generator.export": "Exportar", + + "plugin.plugin": "Plugin", + "plugin.plugin detail": "Detalhe do plugin", + "plugin.introduction": "Introdução", + "plugin.website": "Website", + "plugin.version": "Versão", + "plugin.created at": "Criado Em", + "plugin.updated at": "Atualizado Em", + "plugin.provided by %s": "Fornecido por %s", + "plugin.upgrade": "Upgrade", + "plugin.install": "Instalar", + "plugin.download": "Download", + "plugin.buy": "Comprar", + "plugin.downloading": "Baixando", + "plugin.info": "Detalhe", + "plugin.login": "Login", + "plugin.login to goadmin member system": "Faça login no sistema de membros GoAdmin", + "plugin.account": "Conta", + "plugin.password": "Senha", + "plugin.learn more": "Saber mais", + + "plugin.no account? click %s here %s to register.": "Sem conta? Clique %saqui%s para se registrar.", + "plugin.download fail, wrong name": "Falha no download, nome errado", + "plugin.change to debug mode first": "Mude para o modo debug primeiro", + "plugin.download fail, plugin not exist": "O download falhou, o plugin não existe", + "plugin.download fail": "Falha no download", + "plugin.golang develop environment does not exist": "O ambiente de desenvolvimento de Golang não existe", + "plugin.download success, restart to install": "Download bem-sucedido, reinicie para instalar", + "plugin.restart to install": "Reinicie para instalar", + "plugin.can not connect to the goadmin remote server": "Falha ao conectar o servidor remoto GoAdmin, verifique sua conexão de rede.", + + "admin.basic admin": "Admin básico", + "admin.a built-in plugins of goadmin which help you to build a crud manager platform quickly.": "Plug-ins integrados ao GoAdmin que ajudam você a construir uma plataforma de gerenciamento crud rapidamente.", + "admin.official": "Oficial", + + "create fail": "Falha ao adicionar", + "modify success": "Modificado com sucesso", + "system.site info": "Informações de execução", + "username and password can not be empty": "Nome de usuário e senha não podem estar vazios", + "login": "Entrar", + "confirm": "Confirmar", + "export": "Exportar", + "delete fail": "Falha ao excluir", + "user": "Usuário", + "error": "Erro", + "current page": "Página atual", + "config.must bigger than 900 seconds": "Deve ser superior a 900 segundos", + "no permission": "Sem permissão", + "wrong captcha": "Código de verificação incorreto", + "tool.code generate tool": "Gerador de código", + "site info": "Informações de execução", + "login overdue, please login again": "As informações de login expiraram, faça o login novamente", + "menu name": "Nome do menu", + "success": "Sucesso", + "delete succeed": "Excluído com sucesso", + "edit fail": "Falha ao editar", + "config.local": "Ambiente local", + "menus": "Menu", + "del": "Excluir", + "managers manage": "Gerenciamento de administradores", + "login fail": "Falha ao fazer login", + "operation not allow": "Operação não permitida", + "config.hide plugin entrance": "Ocultar entrada da lista de plugins", + "showing %s to %s of %s entries": "Mostrando %s a %s registros, totalizando %s registros", + "login info": "Informações de login", + "view": "Visualizar", + "code generate tool": "Gerador de código", + "config.test": "Ambiente de teste", + "submit": "Enviar", + "close": "Fechar", + "fail": "Falha", + "more": "Mais", + "config.prod": "Ambiente de produção", + "site setting": "Configurações do site", + "permission denied": "Sem permissão", +} diff --git a/modules/language/tc.go b/modules/language/tc.go new file mode 100644 index 0000000000000000000000000000000000000000..1f131afb6a23f21193e061f4577c4ae6ce4137fc --- /dev/null +++ b/modules/language/tc.go @@ -0,0 +1,451 @@ +package language + +import "strings" + +var tc = LangSet{ + "managers": "管理員管理", + "name": "用戶名", + "nickname": "暱稱", + "role": "角色", + "createdat": "創建時間", + "updatedat": "更新時間", + "path": "路徑", + "submit": "提交", + "filter": "篩選", + + "new": "新建", + "action": "操作", + "toggle dropdown": "下拉", + "delete": "刪除", + "refresh": "刷新", + "back": "返回", + "reset": "重置", + "save": "保存", + "edit": "編輯", + "expand": "展開", + "collapse": "折疊", + "online": "在線", + "setting": "設置", + "sign out": "登出", + "remove": "移除", + + "permission manage": "權限管理", + "menus manage": "菜單管理", + "roles manage": "角色管理", + "operation log": "操作日誌", + "method": "方法", + "input": "輸入", + "operation": "操作", + "menu name": "菜單名", + "query time": "查询时间", + + "are you sure to delete": "你確定要刪除嗎", + "delete succeed": "刪除成功", + "yes": "確定", + "got it": "知道了", + "cancel": "取消", + "refresh succeeded": "刷新成功", + + "avatar": "頭像", + "password": "密碼", + "slug": "標誌", + "permission": "權限", + "userid": "用戶ID", + "content": "內容", + "parent": "父級", + "icon": "圖標", + "uri": "路徑", + + "export": "導出", + "home": "首頁", + "all": "全部", + "more": "更多", + "browse": "打開", + "admin": "管理", + "users": "用戶", + "roles": "角色", + "menu": "菜單", + "dashboard": "儀表盤", + + "permission denied": "沒有權限", + "error": "錯誤", + "success": "成功", + "fail": "失敗", + "current page": "當前頁", + + "confirm": "確認", + "edit fail": "編輯失敗", + "create fail": "新增失敗", + "delete fail": "刪除失敗", + "confirm password": "確認密碼", + "all method if empty": "為空默認為所有方法", + + "detail": "詳情", + "username": "用戶名", + "close": "關閉", + "login": "登錄", + "login fail": "登錄失敗", + "user": "用戶", + + "continue editing": "繼續編輯", + "continue creating": "繼續新增", + + "goadmin is now running. \nrunning in \"debug\" mode. switch to \"release\" mode in production.\n\n": "GoAdmin 啟動成功。\n目前處於 \"debug\" 模式。請在生產環境中切換為 \"release\" 模式。\n\n", + + "wrong goadmin version, theme %s required goadmin version are %s": "錯誤的 GoAdmin 版本,當前主題 %s 需要 GoAdmin 版本為 %s", + "wrong theme version, goadmin %s required version of theme %s is %s": "錯誤的主題版本, GoAdmin %s 需要主題 %s 的版本為 %s", + + "adapter is nil, import the default adapter or use addadapter method add the adapter": "適配器為空,請先 import 對應的適配器或使用 AddAdapter 方法引入", + + "username and password can not be empty": "用戶名密碼不能為空", + "operation not allow": "不允許的操作", + "password does not match": "密碼不壹致", + "should be unique": "需要保證唯壹", + "slug exists": "標誌已經存在了", + "no corresponding options?": "沒找到對應選項?", + "create here.": "在這裏新建壹個。", + "use for login": "用於登錄", + "use to display": "用來展示", + "a path a line, without global prefix": "壹行壹個路徑,換行輸入新路徑,路徑不包含全局路由前綴", + "slug or http_path or name should not be empty": "標誌或路徑或權限名不能為空", + "no roles": "無角色", + "no permission": "沒有權限", + "fixed the sidebar": "固定側邊欄", + "enter fullscreen": "進入全屏", + "exit fullscreen": "退出全屏", + "wrong captcha": "錯誤的驗證碼", + "modify success": "修改成功", + + "not found": "找不到記錄", + "internal error": "系統內部錯誤", + "unauthorized": "未認證", + + "login overdue, please login again": "登錄信息過期,請重新登錄", + "login info": "登錄信息", + + "initialize configuration": "初始化配置", + "initialize navigation buttons": "初始化導航欄按鈕", + "initialize plugins": "初始化插件", + "initialize database connections": "初始化數據庫連接", + "initialize success": "初始化成功🍺🍺", + + "plugins": "插件", + "plugin store": "插件商店", + "get more plugins": "獲取更多插件", + "uninstalled": "未安裝", + "plugin setting": "插件設置", + + "second": "秒", + "seconds": "秒", + "minute": "分", + "minutes": "分", + "hour": "小時", + "hours": "小時", + "day": "天", + "days": "天", + "week": "周", + "weeks": "周", + "month": "月", + "months": "月", + "year": "年", + "years": "年", + + "config.domain": "網站域名", + "config.language": "網站語言", + "config.url prefix": "URL前綴", + "config.theme": "主題", + "config.title": "標題", + "config.index url": "首頁URL", + "config.login url": "登錄URL", + "config.env": "開發環境", + "config.color scheme": "顏色主題", + "config.cdn url": "cdn資源URL", + "config.login title": "登錄標題", + "config.auth user table": "登錄用戶表", + "config.extra": "額外配置", + "config.store": "文件存儲設置", + "config.databases": "數據庫設置", + "config.general": "通用", + "config.log": "日誌", + "config.site setting": "網站設置", + "config.custom": "定制", + "config.debug": "Debug模式", + "config.site off": "關閉網站", + "config.true": "是", + "config.false": "否", + + "config.test": "測試環境", + "config.prod": "生產環境", + "config.local": "本地環境", + + "config.logo": "Logo", + "config.mini logo": "Mini Logo", + "config.bootstrap file path": "引导文件路徑", + "config.go mod file path": "go.mod文件路徑", + "config.session life time": "Session時長", + "config.custom head html": "自定義Head HTML", + "config.custom foot html": "自定義Foot HTML", + "config.custom 404 html": "自定義404頁面", + "config.custom 403 html": "自定義403頁面", + "config.custom 500 html": "自定義500頁面", + "config.hide config center entrance": "隱藏配置中心入口", + "config.hide app info entrance": "隱藏應用信息入口", + "config.hide tool entrance": "隱藏工具入口", + "config.hide plugin entrance": "隱藏插件列表入口", + "config.footer info": "自定義底部信息", + "config.login logo": "登錄Logo", + "config.no limit login ip": "取消限制多IP登錄", + "config.operation log off": "關閉操作日誌", + "config.allow delete operation log": "允許刪除操作日誌", + "config.animation type": "動畫類型", + "config.animation duration": "動畫間隔(秒)", + "config.animation delay": "動畫延遲(秒)", + "config.file upload engine": "文件上傳引擎", + + "config.logger rotate": "日誌切割設置", + "config.logger rotate max size": "存儲最大文件大小(m)", + "config.logger rotate max backups": "存儲最多文件數", + "config.logger rotate max age": "最長存儲時間(天)", + "config.logger rotate compress": "壓縮", + + "config.info log path": "信息日誌存儲路徑", + "config.error log path": "錯誤日誌存儲路徑", + "config.access log path": "訪問日誌存儲路徑", + "config.info log off": "關閉信息日誌", + "config.error log off": "關閉錯誤日誌", + "config.access log off": "關閉訪問日誌", + "config.access assets log off": "關閉靜態資源訪問日誌", + "config.sql log on": "打開SQL日誌", + "config.log level": "日誌級別", + + "config.logger rotate encoder": "日誌encoder設置", + "config.logger rotate encoder time key": "Time Key", + "config.logger rotate encoder level key": "Level Key", + "config.logger rotate encoder name key": "Name Key", + "config.logger rotate encoder caller key": "Caller Key", + "config.logger rotate encoder message key": "Message Key", + "config.logger rotate encoder stacktrace key": "Stacktrace Key", + "config.logger rotate encoder level": "Level字段編碼", + "config.logger rotate encoder time": "Time字段編碼", + "config.logger rotate encoder duration": "Duration字段編碼", + "config.logger rotate encoder caller": "Caller字段編碼", + "config.logger rotate encoder encoding": "輸出格式", + + "config.capital": "大寫", + "config.capitalcolor": "大寫帶顏色", + "config.lowercase": "小寫", + "config.lowercasecolor": "小寫帶顏色", + + "config.seconds": "秒", + "config.nanosecond": "納秒", + "config.microsecond": "微秒", + "config.millisecond": "毫秒", + + "config.full path": "完整路徑", + "config.short path": "簡短路徑", + + "config.do not modify when you have not set up all assets": "不要修改,當妳還沒有設置好所有資源文件的時候", + "config.it will work when theme is adminlte": "當主題為adminlte時生效", + "config.must bigger than 900 seconds": "必須大於900秒", + + "config.language." + CN: "中文", + "config.language." + EN: "英文", + "config.language." + JP: "日文", + "config.language." + strings.ToLower(TC): "繁體中文", + "config.language." + PTBR: "Brazilian Portuguese", + + "config.modify site config": "修改網站配置", + "config.modify site config success": "修改網站配置成功", + "config.modify site config fail": "修改網站配置失敗", + + "system.system info": "應用系統信息", + "system.application": "應用信息", + "system.application run": "應用運行信息", + "system.system": "系統信息", + + "system.process_id": "進程ID", + "system.golang_version": "Golang版本", + "system.server_uptime": "服務運行時間", + "system.current_goroutine": "當前 Goroutines 數量", + "system.current_memory_usage": "當前內存使用量", + "system.total_memory_allocated": "所有被分配的內存", + "system.memory_obtained": "內存占用量", + "system.pointer_lookup_times": "指針查找次數", + "system.memory_allocate_times": "內存分配次數", + "system.memory_free_times": "內存釋放次數", + "system.current_heap_usage": "當前 Heap 內存使用量", + "system.heap_memory_obtained": "Heap 內存占用量", + "system.heap_memory_idle": "Heap 內存空閑量", + "system.heap_memory_in_use": "正在使用的 Heap 內存", + "system.heap_memory_released": "被釋放的 Heap 內存", + "system.heap_objects": "Heap 對象數量", + "system.bootstrap_stack_usage": "啟動 Stack 使用量", + "system.stack_memory_obtained": "被分配的 Stack 內存", + "system.mspan_structures_usage": "MSpan 結構內存使用量", + "system.mspan_structures_obtained": "被分配的 MSpan 結構內存", + "system.mcache_structures_usage": "MCache 結構內存使用量", + "system.mcache_structures_obtained": "被分配的 MCache 結構內存", + "system.profiling_bucket_hash_table_obtained": "被分配的剖析哈希表內存", + "system.gc_metadata_obtained": "被分配的 GC 元數據內存", + "system.other_system_allocation_obtained": "其它被分配的系統內存", + "system.next_gc_recycle": "下次 GC 內存回收量", + "system.last_gc_time": "距離上次 GC 時間", + "system.total_gc_time": "GC 執行時間總量", + "system.total_gc_pause": "GC 暫停時間總量", + "system.last_gc_pause": "上次 GC 暫停時間", + "system.gc_times": "GC 執行次數", + + "system.cpu_logical_core": "cpu邏輯核數", + "system.cpu_core": "cpu物理核數", + "system.os_platform": "系統平臺", + "system.os_family": "系統家族", + "system.os_version": "系統版本", + "system.load1": "1分鐘內負載", + "system.load5": "5分鐘內負載", + "system.load15": "15分鐘內負載", + "system.mem_total": "總內存", + "system.mem_available": "可用內存", + "system.mem_used": "使用內存", + + "system.app_name": "應用名", + "system.go_admin_version": "應用版本", + "system.theme_name": "主題", + "system.theme_version": "主題版本", + + "tool.tool": "工具", + "tool.table": "表格", + "tool.connection": "連接", + "tool.package": "包名", + "tool.output": "輸出路徑", + "tool.output path is empty": "輸出路徑為空", + "tool.field": "字段名", + "tool.title": "標題", + "tool.field name": "字段名", + "tool.db type": "數據類型", + "tool.form type": "表單類型", + "tool.generate table model": "生成CRUD模型", + "tool.primarykey": "主鍵", + "tool.field filterable": "可篩選", + "tool.field sortable": "可排序", + "tool.yes": "是", + "tool.no": "否", + "tool.generate success": "生成成功", + "tool.hide filter area": "隱藏篩選框", + "tool.use absolute path": "使用絕對路徑", + "tool.hide": "隱藏", + "tool.show": "顯示", + "tool.display": "顯示", + "tool.basic info": "基本信息", + "tool.table info": "表格信息", + "tool.form info": "表單信息", + "tool.field editable": "允許編輯", + "tool.field can add": "允許新增", + "tool.info field editable": "可編輯", + "tool.extra import package": "導入包", + "tool.field default": "默認值", + "tool.filter area": "篩選框", + "tool.new button": "新建按鈕", + "tool.export button": "導出按鈕", + "tool.edit button": "編輯按鈕", + "tool.delete button": "刪除按鈕", + "tool.detail button": "詳情按鈕", + "tool.filter button": "篩選按鈕", + "tool.row selector": "列選擇按鈕", + "tool.pagination": "分頁", + "tool.query info": "查詢信息", + "tool.filter form layout": "篩選表單布局", + "tool.continue edit checkbox": "繼續編輯按鈕", + "tool.continue new checkbox": "繼續新增按鈕", + "tool.reset button": "重設按鈕", + "tool.back button": "返回按鈕", + "tool.generate": "生成", + "tool.generated tables": "生成過的表格", + "tool.description": "描述", + "tool.label": "標簽", + "tool.image": "圖片", + "tool.bool": "布爾", + "tool.link": "鏈接", + "tool.fileSize": "文件大小", + "tool.date": "日期", + "tool.icon": "Icon", + "tool.dot": "標點", + "tool.progressBar": "進度條", + "tool.loading": "Loading", + "tool.downLoadable": "可下載", + "tool.copyable": "可復制", + "tool.carousel": "圖片輪播", + "tool.qrcode": "二維碼", + "tool.field hide": "隱藏", + "tool.field display": "顯示", + "tool.table permission": "生成表格權限", + "tool.extra code": "額外代碼", + + "tool.field display normal": "顯示", + "tool.field diplay hide": "隱藏", + "tool.field diplay edit hide": "編輯隱藏", + "tool.field diplay create hide": "新建隱藏", + + "tool.detail display": "顯示", + "tool.detail info": "詳情頁信息", + "tool.follow list page": "跟隨列表頁", + "tool.inherit from list page": "繼承列表頁", + "tool.independent from list page": "獨立", + + "tool.generate table model success": "生成成功", + "tool.generate table model fail": "生成失敗", + + "generator.query": "查詢", + "generator.show edit form page": "編輯頁顯示", + "generator.show create form page": "新建記錄頁顯示", + "generator.edit": "編輯", + "generator.create": "新建", + "generator.delete": "刪除", + "generator.export": "導出", + + "plugin.plugin": "插件", + "plugin.plugin detail": "插件詳情", + "plugin.introduction": "介紹", + "plugin.website": "網站", + "plugin.version": "版本", + "plugin.created at": "創建日期", + "plugin.updated at": "更新日期", + "plugin.provided by %s": "由 %s 提供", + "plugin.upgrade": "升級", + "plugin.install": "安裝", + "plugin.info": "詳細信息", + "plugin.download": "下载", + "plugin.buy": "購買", + "plugin.downloading": "下载中", + "plugin.login": "登錄", + "plugin.login to goadmin member system": "登錄到GoAdmin會員系統", + "plugin.account": "賬戶名", + "plugin.password": "密碼", + "plugin.learn more": "了解更多", + + "plugin.no account? click %s here %s to register.": "沒有賬號?點擊%s這裏%s註冊。", + "plugin.download fail, wrong name": "下載失敗,錯誤的參數", + "plugin.change to debug mode first": "先切換到debug模式", + "plugin.download fail, plugin not exist": "下載失敗,插件不存在", + "plugin.download fail": "下載失敗", + "plugin.golang develop environment does not exist": "golang開發環境不存在", + "plugin.download success, restart to install": "下載成功,重啟程序進行安裝", + "plugin.restart to install": "重啟程序進行安裝", + "plugin.can not connect to the goadmin remote server": "不能連接到GoAdmin遠程服務器,請檢查您的網絡連接。", + + "admin.basic admin": "基礎Admin", + "admin.a built-in plugins of goadmin which help you to build a crud manager platform quickly.": "壹個內置GoAdmin插件,幫助您快速搭建curd簡易管理後臺。", + "admin.official": "GoAdmin官方", + + "search": "搜索", + "reload succeeded": "載入成功", + "showing %s to %s of %s entries": "顯示第 %s 到第 %s 條記錄,總共 %s 條記錄", + "site info": "運行信息", + "menus": "菜單", + "tool.code generate tool": "代碼生成器", + "del": "刪除", + "site setting": "網站設置", + "code generate tool": "代碼生成器", + "managers manage": "管理員管理", + "system.site info": "運行信息", + "view": "查看", +} diff --git a/modules/logger/logger.go b/modules/logger/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..29eafe728be34166b0151b4bc202776f66e320e4 --- /dev/null +++ b/modules/logger/logger.go @@ -0,0 +1,418 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package logger + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/trace" + "github.com/GoAdminGroup/go-admin/modules/utils" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +var ( + defaultEncoderCfg = EncoderCfg{ + TimeKey: "ts", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "stacktrace", + Level: "capitalColor", + Time: "ISO8601", + Duration: "seconds", + Caller: "short", + Encoding: "console", + } + defaultRotateCfg = RotateCfg{ + MaxSize: 10, + MaxBackups: 5, + MaxAge: 30, + Compress: false, + } + + logger = &Logger{ + rotate: defaultRotateCfg, + encoder: defaultEncoderCfg, + Level: zapcore.InfoLevel, + } + + infoLevelEnabler = zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl == zapcore.InfoLevel + }) + + errorLevelEnabler = zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl >= zapcore.ErrorLevel + }) + + accessLevelEnabler = zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl == zapcore.WarnLevel + }) +) + +func init() { + logger.Init() +} + +type Logger struct { + logger *zap.Logger + sugaredLogger *zap.SugaredLogger + + infoLogOff bool + errorLogOff bool + accessLogOff bool + + accessAssetsLogOff bool + + debug bool + + sqlLogOpen bool + + infoLogPath string + errorLogPath string + accessLogPath string + + rotate RotateCfg + encoder EncoderCfg + + Level zapcore.Level +} + +type EncoderCfg struct { + TimeKey string + LevelKey string + NameKey string + CallerKey string + MessageKey string + StacktraceKey string + Level string + Time string + Duration string + Caller string + Encoding string +} + +type RotateCfg struct { + MaxSize int + MaxBackups int + MaxAge int + Compress bool +} + +func (l *Logger) Init() { + zapLogger := zap.New(zapcore.NewTee( + zapcore.NewCore(l.getEncoder(l.encoder.LevelKey), l.getLogWriter(l.infoLogPath), infoLevelEnabler), + zapcore.NewCore(l.getEncoder(l.encoder.LevelKey), l.getLogWriter(l.errorLogPath), errorLevelEnabler), + zapcore.NewCore(l.getEncoder(""), l.getLogWriter(l.accessLogPath), accessLevelEnabler), + ), zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(errorLevelEnabler)) + l.sugaredLogger = zapLogger.Sugar() + l.logger = zapLogger +} + +func (l *Logger) getEncoder(levelKey string) zapcore.Encoder { + + var ( + timeEncoder = new(zapcore.TimeEncoder) + durationEncoder = new(zapcore.DurationEncoder) + callerEncoder = new(zapcore.CallerEncoder) + nameEncoder = new(zapcore.NameEncoder) + levelEncoder = new(zapcore.LevelEncoder) + ) + + _ = timeEncoder.UnmarshalText([]byte(l.encoder.Time)) + _ = durationEncoder.UnmarshalText([]byte(l.encoder.Duration)) + _ = callerEncoder.UnmarshalText([]byte(l.encoder.Caller)) + _ = nameEncoder.UnmarshalText([]byte("full")) + _ = levelEncoder.UnmarshalText([]byte(l.encoder.Level)) + + encoderConfig := zapcore.EncoderConfig{ + TimeKey: l.encoder.TimeKey, + LevelKey: levelKey, + NameKey: l.encoder.NameKey, + CallerKey: l.encoder.CallerKey, + MessageKey: l.encoder.MessageKey, + StacktraceKey: l.encoder.StacktraceKey, + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: *levelEncoder, + EncodeTime: *timeEncoder, + EncodeDuration: *durationEncoder, + EncodeCaller: *callerEncoder, + EncodeName: *nameEncoder, + } + + return filterZapEncoder(l.encoder.Encoding, encoderConfig) +} + +func (l *Logger) getLogWriter(path string) zapcore.WriteSyncer { + if path != "" { + lumberJackLogger := &lumberjack.Logger{ + Filename: path, + MaxSize: l.rotate.MaxSize, + MaxBackups: l.rotate.MaxBackups, + MaxAge: l.rotate.MaxAge, + Compress: l.rotate.Compress, + } + if l.debug { + return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(lumberJackLogger)) + } + return zapcore.AddSync(lumberJackLogger) + } + return zapcore.AddSync(os.Stdout) +} + +func (l *Logger) SetRotate(cfg RotateCfg) { + if cfg.MaxSize != 0 && cfg.MaxAge != 0 && cfg.MaxBackups != 0 { + l.rotate = cfg + } +} + +func (l *Logger) SetEncoder(cfg EncoderCfg) { + cfg.TimeKey = utils.SetDefault(cfg.TimeKey, "", defaultEncoderCfg.TimeKey) + cfg.LevelKey = utils.SetDefault(cfg.LevelKey, "", defaultEncoderCfg.LevelKey) + cfg.NameKey = utils.SetDefault(cfg.NameKey, "", defaultEncoderCfg.NameKey) + cfg.CallerKey = utils.SetDefault(cfg.CallerKey, "", defaultEncoderCfg.CallerKey) + cfg.MessageKey = utils.SetDefault(cfg.MessageKey, "", defaultEncoderCfg.MessageKey) + cfg.StacktraceKey = utils.SetDefault(cfg.StacktraceKey, "", defaultEncoderCfg.StacktraceKey) + cfg.Level = utils.SetDefault(cfg.Level, "", defaultEncoderCfg.Level) + cfg.Time = utils.SetDefault(cfg.Time, "", defaultEncoderCfg.Time) + cfg.Duration = utils.SetDefault(cfg.Duration, "", defaultEncoderCfg.Duration) + cfg.Caller = utils.SetDefault(cfg.Caller, "", defaultEncoderCfg.Caller) + cfg.Encoding = utils.SetDefault(cfg.Encoding, "", defaultEncoderCfg.Encoding) + l.encoder = cfg +} + +type Config struct { + InfoLogOff bool + ErrorLogOff bool + AccessLogOff bool + + SqlLogOpen bool + + InfoLogPath string + ErrorLogPath string + AccessLogPath string + + AccessAssetsLogOff bool + + Rotate RotateCfg + Encode EncoderCfg + + Level int8 + + Debug bool +} + +func InitWithConfig(cfg Config) { + logger.infoLogPath = cfg.InfoLogPath + logger.infoLogOff = cfg.InfoLogOff + logger.errorLogPath = cfg.ErrorLogPath + logger.errorLogOff = cfg.ErrorLogOff + logger.accessLogPath = cfg.AccessLogPath + logger.accessLogOff = cfg.AccessLogOff + logger.sqlLogOpen = cfg.SqlLogOpen + logger.accessAssetsLogOff = cfg.AccessAssetsLogOff + logger.debug = cfg.Debug + logger.SetRotate(cfg.Rotate) + logger.SetEncoder(cfg.Encode) + logger.Level = filterZapAtomicLevelByViper(cfg.Level) + logger.Init() +} + +func SetRotate(cfg RotateCfg) { + logger.rotate = cfg + logger.Init() +} + +// OpenSQLLog set the sqlLogOpen true. +func OpenSQLLog() { + logger.sqlLogOpen = true +} + +// Debug print the debug message. +func Debug(info ...interface{}) { + if !logger.infoLogOff { + if logger.Level <= zapcore.DebugLevel { + logger.sugaredLogger.Info(info...) + } + } +} + +// Debugf print the debug message. +func Debugf(template string, args ...interface{}) { + if !logger.infoLogOff && logger.Level <= zapcore.DebugLevel { + logger.sugaredLogger.Infof(template, args...) + } +} + +// Info print the info message. +func Info(info ...interface{}) { + if !logger.infoLogOff && logger.Level <= zapcore.InfoLevel { + logger.sugaredLogger.Info(info...) + } +} + +// InfoCtx print the info message with ctx. +func InfoCtx(ctx *context.Context, format string, args ...interface{}) { + if !logger.infoLogOff && logger.Level <= zapcore.InfoLevel { + logCtx(ctx, logger.logger.Info, format, args...) + } +} + +type logFunc func(msg string, fields ...zapcore.Field) + +func logCtx(ctx *context.Context, logFunc logFunc, format string, args ...interface{}) { + logFunc(fmt.Sprintf(format, args...), zap.String("traceID", trace.GetTraceID(ctx))) +} + +// Info print the info message. +func Infof(template string, args ...interface{}) { + if !logger.infoLogOff && logger.Level <= zapcore.InfoLevel { + logger.sugaredLogger.Infof(template, args...) + } +} + +// Warn print the warning message. +func Warn(info ...interface{}) { + if !logger.infoLogOff && logger.Level <= zapcore.WarnLevel { + logger.sugaredLogger.Warn(info...) + } +} + +// WarnCtx print the warning message with ctx. +func WarnCtx(ctx *context.Context, format string, args ...interface{}) { + if !logger.infoLogOff && logger.Level <= zapcore.WarnLevel { + logCtx(ctx, logger.logger.Warn, format, args...) + } +} + +// Warnf print the warning message. +func Warnf(template string, args ...interface{}) { + if !logger.infoLogOff && logger.Level <= zapcore.WarnLevel { + logger.sugaredLogger.Warnf(template, args...) + } +} + +// Error print the error message. +func Error(err ...interface{}) { + if !logger.errorLogOff && logger.Level <= zapcore.ErrorLevel { + logger.sugaredLogger.Error(err...) + } +} + +// ErrorCtx print the error message with ctx. +func ErrorCtx(ctx *context.Context, format string, args ...interface{}) { + if !logger.errorLogOff && logger.Level <= zapcore.ErrorLevel { + logCtx(ctx, logger.logger.Error, format, args...) + } +} + +// Errorf print the error message. +func Errorf(template string, args ...interface{}) { + if !logger.errorLogOff && logger.Level <= zapcore.ErrorLevel { + logger.sugaredLogger.Errorf(template, args...) + } +} + +// Fatal print the fatal message. +func Fatal(info ...interface{}) { + if !logger.errorLogOff && logger.Level <= zapcore.ErrorLevel { + logger.sugaredLogger.Fatal(info...) + } +} + +// FatalCtx print the fatal message with ctx. +func FatalCtx(ctx *context.Context, format string, args ...interface{}) { + if !logger.errorLogOff && logger.Level <= zapcore.FatalLevel { + logCtx(ctx, logger.logger.Fatal, format, args...) + } +} + +// Fatalf print the fatal message. +func Fatalf(template string, args ...interface{}) { + if !logger.errorLogOff && logger.Level <= zapcore.ErrorLevel { + logger.sugaredLogger.Fatalf(template, args...) + } +} + +// Fatal print the panic message. +func Panic(info ...interface{}) { + logger.sugaredLogger.Panic(info...) +} + +// PanicCtx print the panic message with ctx. +func PanicCtx(ctx *context.Context, format string, args ...interface{}) { + logCtx(ctx, logger.logger.Panic, format, args...) +} + +// Panicf print the panic message. +func Panicf(template string, args ...interface{}) { + logger.sugaredLogger.Panicf(template, args...) +} + +// Access print the access message. +func Access(ctx *context.Context) { + if !logger.accessLogOff && logger.Level <= zapcore.InfoLevel { + if logger.accessAssetsLogOff { + if filepath.Ext(ctx.Path()) == "" { + logger.logger.Info("[GoAdmin] access log", + zap.String("traceID", trace.GetTraceID(ctx)), + zap.String("statuscode", strconv.Itoa(ctx.Response.StatusCode)), + zap.String("method", string(ctx.Method())), + zap.String("path", ctx.Path())) + } + } else { + logger.logger.Info("[GoAdmin] access log", + zap.String("traceID", trace.GetTraceID(ctx)), + zap.String("statuscode", strconv.Itoa(ctx.Response.StatusCode)), + zap.String("method", string(ctx.Method())), + zap.String("path", ctx.Path())) + } + } +} + +// LogSQL print the sql info message. +func LogSQL(statement string, args []interface{}) { + if !logger.infoLogOff && logger.sqlLogOpen && statement != "" { + if logger.Level <= zapcore.InfoLevel { + logger.sugaredLogger.With("statement", statement, "args", args).Info("[GoAdmin]") + } + } +} + +func filterZapEncoder(encoding string, encoderConfig zapcore.EncoderConfig) zapcore.Encoder { + var encoder zapcore.Encoder + switch encoding { + default: + encoder = zapcore.NewConsoleEncoder(encoderConfig) + case "json": + encoder = zapcore.NewJSONEncoder(encoderConfig) + case "console": + encoder = zapcore.NewConsoleEncoder(encoderConfig) + } + return encoder +} + +func filterZapAtomicLevelByViper(level int8) zapcore.Level { + var atomViper zapcore.Level + switch level { + default: + atomViper = zap.InfoLevel + case -1: + atomViper = zap.DebugLevel + case 0: + atomViper = zap.InfoLevel + case 1: + atomViper = zap.WarnLevel + case 2: + atomViper = zap.ErrorLevel + } + return atomViper +} diff --git a/modules/logger/logger_test.go b/modules/logger/logger_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0a762e429b8a7de6bfc2960823ca83010609c94f --- /dev/null +++ b/modules/logger/logger_test.go @@ -0,0 +1,7 @@ +package logger + +import "testing" + +func TestInfo(t *testing.T) { + Info("test") +} diff --git a/modules/menu/menu.go b/modules/menu/menu.go new file mode 100644 index 0000000000000000000000000000000000000000..aa78964fab7b2d4a1981372554911494f0c0b1a9 --- /dev/null +++ b/modules/menu/menu.go @@ -0,0 +1,267 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package menu + +import ( + "html/template" + "regexp" + "strconv" + "strings" + + "github.com/GoAdminGroup/go-admin/modules/db/dialect" + + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" +) + +// Item is an menu item. +type Item struct { + Name string `json:"name"` + ID string `json:"id"` + Url string `json:"url"` + IsLinkUrl bool `json:"isLinkUrl"` + Icon string `json:"icon"` + Header string `json:"header"` + Active string `json:"active"` + ChildrenList []Item `json:"childrenList"` +} + +// Menu contains list of menu items and other info. +type Menu struct { + List []Item `json:"list"` + Options []map[string]string `json:"options"` + MaxOrder int64 `json:"maxOrder"` + PluginName string `json:"pluginName"` + ForceUpdate bool `json:"forceUpdate"` +} + +func (menu *Menu) GetUpdateJS(updateFlag bool) template.JS { + if !updateFlag { + return "" + } + + forceUpdate := "false" + if menu.ForceUpdate { + forceUpdate = "true" + } + return template.JS(`$(function () { + let curMenuPlug = $(".main-sidebar section.sidebar ul.sidebar-menu").attr("data-plug"); + if (curMenuPlug !== '` + menu.PluginName + `' || ` + forceUpdate + `) { + $(".main-sidebar section.sidebar").html($("#sidebar-menu-tmpl").html()) + } +});`) +} + +// SetMaxOrder set the max order of menu. +func (menu *Menu) SetMaxOrder(order int64) { + menu.MaxOrder = order +} + +// AddMaxOrder add the max order of menu. +func (menu *Menu) AddMaxOrder() { + menu.MaxOrder++ +} + +// SetActiveClass set the active class of menu. +func (menu *Menu) SetActiveClass(path string) *Menu { + + reg, _ := regexp.Compile(`\?(.*)`) + path = reg.ReplaceAllString(path, "") + + for i := 0; i < len(menu.List); i++ { + menu.List[i].Active = "" + } + + for i := 0; i < len(menu.List); i++ { + if menu.List[i].Url == path && len(menu.List[i].ChildrenList) == 0 { + menu.List[i].Active = "active" + return menu + } + + for j := 0; j < len(menu.List[i].ChildrenList); j++ { + if menu.List[i].ChildrenList[j].Url == path { + menu.List[i].Active = "active" + menu.List[i].ChildrenList[j].Active = "active" + return menu + } + + menu.List[i].Active = "" + menu.List[i].ChildrenList[j].Active = "" + } + } + + return menu +} + +// FormatPath get template.HTML for front-end. +func (menu Menu) FormatPath() template.HTML { + res := template.HTML(``) + for i := 0; i < len(menu.List); i++ { + if menu.List[i].Active != "" { + if menu.List[i].Url != "#" && menu.List[i].Url != "" && len(menu.List[i].ChildrenList) > 0 { + res += template.HTML(`
  • ` + menu.List[i].Name + `
  • `) + } else { + res += template.HTML(`
  • ` + menu.List[i].Name + `
  • `) + if len(menu.List[i].ChildrenList) == 0 { + return res + } + } + for j := 0; j < len(menu.List[i].ChildrenList); j++ { + if menu.List[i].ChildrenList[j].Active != "" { + return res + template.HTML(`
  • `+menu.List[i].ChildrenList[j].Name+`
  • `) + } + } + } + } + return res +} + +// GetEditMenuList return menu items list. +func (menu *Menu) GetEditMenuList() []Item { + return menu.List +} + +type NewMenuData struct { + ParentId int64 `json:"parent_id"` + Type int64 `json:"type"` + Order int64 `json:"order"` + Title string `json:"title"` + Icon string `json:"icon"` + PluginName string `json:"plugin_name"` + Uri string `json:"uri"` + Header string `json:"header"` + Uuid string `json:"uuid"` +} + +func NewMenu(conn db.Connection, data NewMenuData) (int64, error) { + maxOrder := data.Order + checkOrder, _ := db.WithDriver(conn).Table("goadmin_menu"). + Where("plugin_name", "=", data.PluginName). + OrderBy("order", "desc"). + First() + + if checkOrder != nil { + maxOrder = checkOrder["order"].(int64) + } + + id, err := db.WithDriver(conn).Table("goadmin_menu"). + Insert(dialect.H{ + "parent_id": data.ParentId, + "type": data.Type, + "order": maxOrder, + "title": data.Title, + "uuid": data.Uuid, + "icon": data.Icon, + "plugin_name": data.PluginName, + "uri": data.Uri, + "header": data.Header, + }) + if !db.CheckError(err, db.INSERT) { + return id, nil + } + return id, err +} + +// GetGlobalMenu return Menu of given user model. +func GetGlobalMenu(user models.UserModel, conn db.Connection, lang string, pluginNames ...string) *Menu { + + var ( + menus []map[string]interface{} + menuOption = make([]map[string]string, 0) + plugName = "" + ) + + if len(pluginNames) > 0 { + plugName = pluginNames[0] + } + + user.WithRoles().WithMenus() + + if user.IsSuperAdmin() { + menus, _ = db.WithDriver(conn).Table("goadmin_menu"). + Where("id", ">", 0). + Where("plugin_name", "=", plugName). + OrderBy("order", "asc"). + All() + } else { + + var ids []interface{} + for i := 0; i < len(user.MenuIds); i++ { + ids = append(ids, user.MenuIds[i]) + } + + menus, _ = db.WithDriver(conn).Table("goadmin_menu"). + WhereIn("id", ids). + Where("plugin_name", "=", plugName). + OrderBy("order", "asc"). + All() + } + + var title string + for i := 0; i < len(menus); i++ { + + title = language.GetWithLang(menus[i]["title"].(string), lang) + menuOption = append(menuOption, map[string]string{ + "id": strconv.FormatInt(menus[i]["id"].(int64), 10), + "title": title, + }) + } + + menuList := constructMenuTree(menus, 0, lang) + maxOrder := int64(0) + if len(menus) > 0 { + maxOrder = menus[len(menus)-1]["parent_id"].(int64) + } + + return &Menu{ + List: menuList, + Options: menuOption, + MaxOrder: maxOrder, + PluginName: plugName, + } +} + +func constructMenuTree(menus []map[string]interface{}, parentID int64, lang string) []Item { + + branch := make([]Item, 0) + + var title string + for j := 0; j < len(menus); j++ { + if parentID == menus[j]["parent_id"].(int64) { + if menus[j]["type"].(int64) == 1 { + title = language.Get(menus[j]["title"].(string)) + } else { + title = menus[j]["title"].(string) + } + + header, _ := menus[j]["header"].(string) + + uri := menus[j]["uri"].(string) + + if lang != "" { + if strings.Contains(uri, "?") { + uri += "&__ga_lang=" + lang + } else { + uri += "?__ga_lang=" + lang + } + } + + child := Item{ + Name: title, + ID: strconv.FormatInt(menus[j]["id"].(int64), 10), + Url: uri, + Icon: menus[j]["icon"].(string), + Header: header, + Active: "", + ChildrenList: constructMenuTree(menus, menus[j]["id"].(int64), lang), + } + + branch = append(branch, child) + } + } + + return branch +} diff --git a/modules/menu/menu_test.go b/modules/menu/menu_test.go new file mode 100644 index 0000000000000000000000000000000000000000..19c6b4f3177fe1093694d51844200bf4cb91ec2a --- /dev/null +++ b/modules/menu/menu_test.go @@ -0,0 +1,82 @@ +package menu + +import ( + "testing" + + "github.com/magiconair/properties/assert" +) + +func TestMenu_AddMaxOrder(t *testing.T) { + menus := Menu{ + MaxOrder: 0, + } + menus.AddMaxOrder() + assert.Equal(t, menus.MaxOrder, int64(1)) +} + +func TestMenu_SetMaxOrder(t *testing.T) { + menus := Menu{ + MaxOrder: 0, + } + menus.SetMaxOrder(2) + assert.Equal(t, menus.MaxOrder, int64(2)) +} + +func TestMenu_SetActiveClass(t *testing.T) { + menus := Menu{ + List: []Item{ + { + Name: "item1", + ID: "1", + Url: "/item1", + Icon: "icon", + }, { + Name: "item2", + ID: "2", + Url: "/item2", + Icon: "icon", + }, { + Name: "item3", + ID: "3", + Url: "/item3", + Icon: "icon", + }, { + Name: "item4", + ID: "4", + Url: "/item4", + Icon: "icon", + ChildrenList: []Item{ + { + Name: "item5", + ID: "5", + Url: "/item5", + Icon: "icon", + }, { + Name: "item6", + ID: "6", + Url: "/item6", + Icon: "icon", + }, + }, + }, + }, + Options: []map[string]string{}, + MaxOrder: 0, + } + + menus.SetActiveClass("/item3") + + assert.Equal(t, menus.List[0].Active, "") + assert.Equal(t, menus.List[1].Active, "") + assert.Equal(t, menus.List[2].Active, "active") + assert.Equal(t, menus.List[3].Active, "") + + menus.SetActiveClass("/item5") + + assert.Equal(t, menus.List[0].Active, "") + assert.Equal(t, menus.List[1].Active, "") + assert.Equal(t, menus.List[2].Active, "") + assert.Equal(t, menus.List[3].Active, "active") + assert.Equal(t, menus.List[3].ChildrenList[0].Active, "active") + assert.Equal(t, menus.List[3].ChildrenList[1].Active, "") +} diff --git a/modules/page/page.go b/modules/page/page.go new file mode 100644 index 0000000000000000000000000000000000000000..086c04f2420dd164dfe6bfb158dda03be60e3e7c --- /dev/null +++ b/modules/page/page.go @@ -0,0 +1,49 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package page + +import ( + "bytes" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/menu" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" +) + +// SetPageContent set and return the panel of page content. +func SetPageContent(ctx *context.Context, user models.UserModel, c func(ctx interface{}) (types.Panel, error), conn db.Connection) { + + panel, err := c(ctx) + + if err != nil { + logger.ErrorCtx(ctx, "SetPageContent %+v", err) + panel = template.WarningPanel(ctx, err.Error()) + } + + tmpl, tmplName := template.Get(ctx, config.GetTheme()).GetTemplate(ctx.IsPjax()) + + ctx.AddHeader("Content-Type", "text/html; charset=utf-8") + + buf := new(bytes.Buffer) + + err = tmpl.ExecuteTemplate(buf, tmplName, types.NewPage(ctx, &types.NewPageParam{ + User: user, + Menu: menu.GetGlobalMenu(user, conn, ctx.Lang()).SetActiveClass(config.URLRemovePrefix(ctx.Path())), + Panel: panel.GetContent(config.IsProductionEnvironment()), + Assets: template.GetComponentAssetImportHTML(ctx), + TmplHeadHTML: template.Default(ctx).GetHeadHTML(), + TmplFootJS: template.Default(ctx).GetFootJS(), + Iframe: ctx.IsIframe(), + })) + if err != nil { + logger.ErrorCtx(ctx, "SetPageContent %+v", err) + } + ctx.WriteString(buf.String()) +} diff --git a/modules/remote_server/remote_server.go b/modules/remote_server/remote_server.go new file mode 100644 index 0000000000000000000000000000000000000000..d34e6be568e53cb62f17cb102f28bc71bb1d7230 --- /dev/null +++ b/modules/remote_server/remote_server.go @@ -0,0 +1,199 @@ +package remote_server + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "strings" + + "github.com/GoAdminGroup/go-admin/modules/system" + + "github.com/GoAdminGroup/go-admin/modules/logger" +) + +const ( + // ServerHost = "http://localhost:8055" + // ServerHostApi = "http://localhost:8055/api" + + ServerHost = "https://www.go-admin.cn" + ServerHostApi = "https://www.go-admin.cn/api" +) + +type LoginRes struct { + Code int `json:"code"` + Data struct { + Token string `json:"token"` + Name string `json:"name"` + Expire int64 `json:"expire"` + } `json:"data"` + Msg string `json:"msg"` +} + +func Login(account, password string) LoginRes { + var resData LoginRes + + req, err := http.NewRequest("POST", ServerHostApi+"/signin", strings.NewReader(`{"account":"`+account+ + `","password":"`+password+`"}`)) + + if err != nil { + logger.Error("login: ", err) + resData.Code = 500 + resData.Msg = "request error" + return resData + } + + req.Header.Add("Content-Type", "application/json") + + res, err := http.DefaultClient.Do(req) + + if err != nil { + logger.Error("login: ", err) + resData.Code = 500 + resData.Msg = "request error" + return resData + } + defer func() { + _ = res.Body.Close() + }() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + logger.Error("login: ", err) + resData.Code = 500 + resData.Msg = "request error" + return resData + } + + err = json.Unmarshal(body, &resData) + if err != nil { + logger.Error("login: ", err) + resData.Code = 500 + resData.Msg = "request error" + return resData + } + if resData.Code != 0 { + logger.Error("login to remote GoAdmin server error: ", resData.Msg) + return resData + } + return resData +} + +type GetDownloadURLRes struct { + Code int `json:"code"` + Data struct { + Url string `json:"url"` + ExtraUrl string `json:"extra_url"` + } `json:"data"` + Msg string `json:"msg"` +} + +func GetDownloadURL(uuid, token string) (string, string, error) { + var resData GetDownloadURLRes + + req, err := http.NewRequest("GET", ServerHostApi+"/plugin/download", strings.NewReader(`{"uuid":"`+uuid+`", "version":"`+system.Version()+`"}`)) + + if err != nil { + logger.Error("get plugin download url error: ", err) + return "", "", err + } + + req.Header.Add(TokenKey, token) + req.Header.Add("Content-Type", "application/json") + + res, err := http.DefaultClient.Do(req) + + if err != nil { + return "", "", err + } + defer func() { + _ = res.Body.Close() + }() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return "", "", err + } + + err = json.Unmarshal(body, &resData) + if err != nil { + return "", "", err + } + if resData.Code != 0 { + return "", "", err + } + return resData.Data.Url, resData.Data.ExtraUrl, nil +} + +const TokenKey = "GOADMIN_OFFICIAL_SESS" + +type GetOnlineReq struct { + Page string `json:"page"` + Free string `json:"free"` + PageSize string `json:"page_size"` + Filter string `json:"filter"` + Order string `json:"order"` + Lang string `json:"lang"` + CategoryId string `json:"category_id"` + Version string `json:"version"` +} + +func (req GetOnlineReq) Format() string { + res := "" + if req.Page != "" { + res += "page=" + req.Page + "&" + } + if req.PageSize != "" { + res += "page_size=" + req.PageSize + "&" + } + if req.Lang != "" { + res += "lang=" + req.Lang + "&" + } + if req.Filter != "" { + res += "filter=" + req.Filter + "&" + } + if req.Order != "" { + res += "order=" + req.Order + "&" + } + if req.CategoryId != "" { + res += "category_id=" + req.CategoryId + "&" + } + if req.Free != "" { + res += "free=" + req.Free + "&" + } + if req.Version != "" { + res += "version=" + req.Version + "&" + } + if res != "" { + return res[:len(res)-1] + } + return res +} + +func GetOnline(reqData GetOnlineReq, token string) ([]byte, error) { + // TODO: add cache + req, err := http.NewRequest("GET", ServerHostApi+"/plugin/list?"+reqData.Format(), nil) + + if err != nil { + logger.Error("get online plugins: ", err) + return nil, err + } + + if token != "" { + req.Header.Add(TokenKey, token) + } + + res, err := http.DefaultClient.Do(req) + + if err != nil { + logger.Error("get online plugins: ", err) + return nil, err + } + defer func() { + _ = res.Body.Close() + }() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + logger.Error("get online plugins: ", err) + return nil, err + } + + return body, nil +} diff --git a/modules/service/service.go b/modules/service/service.go new file mode 100644 index 0000000000000000000000000000000000000000..108d4e8ee61a803c0495e4c8bc8da0e7e53351f3 --- /dev/null +++ b/modules/service/service.go @@ -0,0 +1,61 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package service + +import ( + "log" +) + +type Service interface { + Name() string +} + +type Generator func() (Service, error) + +func Register(k string, gen Generator) { + if _, ok := services[k]; ok { + log.Panicf("service %s has been registered", k) + } + services[k] = gen +} + +func GetServices() List { + var ( + l = make(List) + err error + ) + for k, gen := range services { + if l[k], err = gen(); err != nil { + log.Panicf("service %s initialize fail, error: %v", k, err) + } + } + return l +} + +var services = make(Generators) + +type Generators map[string]Generator + +type List map[string]Service + +func (g List) Get(k string) Service { + if v, ok := g[k]; ok { + return v + } + log.Panicf("service %s not found", k) + return nil +} + +func (g List) GetOrNot(k string) (Service, bool) { + v, ok := g[k] + return v, ok +} + +func (g List) Add(k string, service Service) { + if _, ok := g[k]; ok { + log.Panicf("service %s exist", k) + } + g[k] = service +} diff --git a/modules/system/application.go b/modules/system/application.go new file mode 100644 index 0000000000000000000000000000000000000000..e22aa13cf201f88813b4211a84dca3bddaa9bddb --- /dev/null +++ b/modules/system/application.go @@ -0,0 +1,111 @@ +package system + +import ( + "fmt" + "runtime" + "time" + + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/utils" +) + +var ( + startTime = time.Now() +) + +type AppStatus struct { + Uptime string + NumGoroutine int + + // General statistics. + MemAllocated string // bytes allocated and still in use + MemTotal string // bytes allocated (even if freed) + MemSys string // bytes obtained from system (sum of XxxSys below) + Lookups uint64 // number of pointer lookups + MemMallocs uint64 // number of mallocs + MemFrees uint64 // number of frees + + // Main allocation heap statistics. + HeapAlloc string // bytes allocated and still in use + HeapSys string // bytes obtained from system + HeapIdle string // bytes in idle spans + HeapInuse string // bytes in non-idle span + HeapReleased string // bytes released to the OS + HeapObjects uint64 // total number of allocated objects + + // Low-level fixed-size structure allocator statistics. + // Inuse is bytes used now. + // Sys is bytes obtained from system. + StackInuse string // bootstrap stacks + StackSys string + MSpanInuse string // mspan structures + MSpanSys string + MCacheInuse string // mcache structures + MCacheSys string + BuckHashSys string // profiling bucket hash table + GCSys string // GC metadata + OtherSys string // other system allocations + + // Garbage collector statistics. + NextGC string // next run in HeapAlloc time (bytes) + LastGC string // last run in absolute time (ns) + PauseTotalNs string + PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256] + NumGC uint32 +} + +func GetAppStatus() AppStatus { + var app AppStatus + app.Uptime = utils.TimeSincePro(startTime, language.Lang[config.GetLanguage()]) + + m := new(runtime.MemStats) + runtime.ReadMemStats(m) + app.NumGoroutine = runtime.NumGoroutine() + + app.MemAllocated = utils.FileSize(m.Alloc) + app.MemTotal = utils.FileSize(m.TotalAlloc) + app.MemSys = utils.FileSize(m.Sys) + app.Lookups = m.Lookups + app.MemMallocs = m.Mallocs + app.MemFrees = m.Frees + + app.HeapAlloc = utils.FileSize(m.HeapAlloc) + app.HeapSys = utils.FileSize(m.HeapSys) + app.HeapIdle = utils.FileSize(m.HeapIdle) + app.HeapInuse = utils.FileSize(m.HeapInuse) + app.HeapReleased = utils.FileSize(m.HeapReleased) + app.HeapObjects = m.HeapObjects + + app.StackInuse = utils.FileSize(m.StackInuse) + app.StackSys = utils.FileSize(m.StackSys) + app.MSpanInuse = utils.FileSize(m.MSpanInuse) + app.MSpanSys = utils.FileSize(m.MSpanSys) + app.MCacheInuse = utils.FileSize(m.MCacheInuse) + app.MCacheSys = utils.FileSize(m.MCacheSys) + app.BuckHashSys = utils.FileSize(m.BuckHashSys) + app.GCSys = utils.FileSize(m.GCSys) + app.OtherSys = utils.FileSize(m.OtherSys) + + app.NextGC = utils.FileSize(m.NextGC) + app.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000) + app.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000) + app.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000) + app.NumGC = m.NumGC + + return app +} + +type SysStatus struct { + CpuLogicalCore int + CpuCore int + OSPlatform string + OSFamily string + OSVersion string + Load1 float64 + Load5 float64 + Load15 float64 + MemTotal string + MemAvailable string + MemUsed string +} diff --git a/modules/system/version.go b/modules/system/version.go new file mode 100644 index 0000000000000000000000000000000000000000..a52a69df3aab097a03d6597f9ece4b9864b45955 --- /dev/null +++ b/modules/system/version.go @@ -0,0 +1,18 @@ +package system + +const version = "v1.2.27" + +var requireThemeVersion = map[string][]string{ + "adminlte": {">=v0.0.41"}, + "sword": {">=v0.0.41"}, +} + +// Version return the version of framework. +func Version() string { + return version +} + +// RequireThemeVersion return the require official version +func RequireThemeVersion() map[string][]string { + return requireThemeVersion +} diff --git a/modules/trace/trace.go b/modules/trace/trace.go new file mode 100644 index 0000000000000000000000000000000000000000..169a454528cf4637be736229384989fd20acccea --- /dev/null +++ b/modules/trace/trace.go @@ -0,0 +1,69 @@ +package trace + +import ( + "fmt" + "net" + "os" + "sync" + "sync/atomic" + "time" + + "github.com/GoAdminGroup/go-admin/context" +) + +var ( + machineIDOnce sync.Once + machineID string + counter uint32 +) + +func getMachineID() string { + machineIDOnce.Do(func() { + addrs, err := net.InterfaceAddrs() + if err == nil { + for _, addr := range addrs { + if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { + if ipNet.IP.To4() != nil { + machineID = ipNet.IP.String() + break + } + } + } + } + + if machineID == "" { + machineID = "127.0.0.1" + } + }) + + return machineID +} + +func GenerateTraceID() string { + machineID := getMachineID() + timestamp := time.Now().UnixNano() / int64(time.Millisecond) + processID := os.Getpid() + id := atomic.AddUint32(&counter, 1) + id = id % 1000 + traceID := fmt.Sprintf("%08x%05d%013d%04d", machineIDToHex(machineID), processID, timestamp, id) + + return traceID +} + +func machineIDToHex(machineID string) uint32 { + ip := net.ParseIP(machineID) + ipUint32 := uint32(ip[12])<<24 | uint32(ip[13])<<16 | uint32(ip[14])<<8 | uint32(ip[15]) + return ipUint32 +} + +func GetTraceID(ctx *context.Context) string { + traceID, ok := ctx.GetUserValue(TraceIDKey).(string) + if !ok { + return "" + } + return traceID +} + +const ( + TraceIDKey = "traceID" +) diff --git a/modules/ui/ui.go b/modules/ui/ui.go new file mode 100644 index 0000000000000000000000000000000000000000..5d8bd194bc672104a9e6ad2ef22f2164bb99ed46 --- /dev/null +++ b/modules/ui/ui.go @@ -0,0 +1,80 @@ +package ui + +import ( + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/template/icon" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/action" +) + +type Service struct { + NavButtons *types.Buttons +} + +const ServiceKey = "ui" + +func (s *Service) Name() string { + return "ui" +} + +func GetService(srv service.List) *Service { + if v, ok := srv.Get("ui").(*Service); ok { + return v + } + panic("wrong service") +} + +func NewService(btns *types.Buttons) *Service { + return &Service{ + NavButtons: btns, + } +} + +func (s *Service) UpdateButtons() { + +} + +func (s *Service) RemoveOrShowSiteNavButton(remove bool) { + if remove { + *s.NavButtons = (*s.NavButtons).RemoveSiteNavButton() + } else { + *s.NavButtons = (*s.NavButtons).AddNavButton(icon.Gear, types.NavBtnSiteName, + action.JumpInNewTab(config.Url("/info/site/edit"), + language.GetWithScope("site setting", "config"))) + } +} + +func (s *Service) RemoveOrShowInfoNavButton(remove bool) { + if remove { + *s.NavButtons = (*s.NavButtons).RemoveInfoNavButton() + } else { + *s.NavButtons = (*s.NavButtons).AddNavButton(icon.Info, types.NavBtnInfoName, + action.JumpInNewTab(config.Url("/application/info"), + language.GetWithScope("system info", "system"))) + } + +} + +func (s *Service) RemoveOrShowToolNavButton(remove bool) { + if remove { + *s.NavButtons = (*s.NavButtons).RemoveToolNavButton() + } else { + *s.NavButtons = (*s.NavButtons).AddNavButton(icon.Wrench, types.NavBtnToolName, + action.JumpInNewTab(config.Url("/info/generate/new"), + language.GetWithScope("tool", "tool"))) + } + +} + +func (s *Service) RemoveOrShowPlugNavButton(remove bool) { + if remove { + *s.NavButtons = (*s.NavButtons).RemovePlugNavButton() + } else { + *s.NavButtons = (*s.NavButtons).AddNavButton(icon.Plug, types.NavBtnToolName, + action.JumpInNewTab(config.Url("/plugin"), + language.GetWithScope("plugin", "plugin"))) + } + +} diff --git a/modules/utils/utils.go b/modules/utils/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..62d4c53632dd96c2a406acaa54ef76054d0729a0 --- /dev/null +++ b/modules/utils/utils.go @@ -0,0 +1,502 @@ +package utils + +import ( + "archive/zip" + "bytes" + "encoding/gob" + "encoding/json" + "fmt" + "html/template" + "io" + "math" + "net/http" + "net/url" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + textTmpl "text/template" + "time" + + "github.com/NebulousLabs/fastrand" +) + +func Uuid(length int64) string { + ele := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "v", "k", + "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "Driver", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"} + ele, _ = Random(ele) + uuid := "" + var i int64 + for i = 0; i < length; i++ { + uuid += ele[fastrand.Intn(59)] + } + return uuid +} + +func Random(strings []string) ([]string, error) { + for i := len(strings) - 1; i > 0; i-- { + num := fastrand.Intn(i + 1) + strings[i], strings[num] = strings[num], strings[i] + } + + str := make([]string, 0) + for i := 0; i < len(strings); i++ { + str = append(str, strings[i]) + } + return str, nil +} + +func CompressedContent(h *template.HTML) { + st := strings.Split(string(*h), "\n") + var ss []string + for i := 0; i < len(st); i++ { + st[i] = strings.TrimSpace(st[i]) + if st[i] != "" { + ss = append(ss, st[i]) + } + } + *h = template.HTML(strings.Join(ss, "\n")) +} + +func ReplaceNth(s, old, new string, n int) string { + i := 0 + for m := 1; m <= n; m++ { + x := strings.Index(s[i:], old) + if x < 0 { + break + } + i += x + if m == n { + return s[:i] + new + s[i+len(old):] + } + i += len(old) + } + return s +} + +func InArray(arr []string, str string) bool { + for _, v := range arr { + if v == str { + return true + } + } + return false +} + +func WrapURL(u string) string { + uarr := strings.Split(u, "?") + if len(uarr) < 2 { + return url.QueryEscape(strings.ReplaceAll(u, "/", "_")) + } + v, err := url.ParseQuery(uarr[1]) + if err != nil { + return url.QueryEscape(strings.ReplaceAll(u, "/", "_")) + } + return url.QueryEscape(strings.ReplaceAll(uarr[0], "/", "_")) + "?" + + strings.ReplaceAll(v.Encode(), "%7B%7B.Id%7D%7D", "{{.Id}}") +} + +func JSON(a interface{}) string { + if a == nil { + return "" + } + b, _ := json.Marshal(a) + return string(b) +} + +func ParseBool(s string) bool { + b1, _ := strconv.ParseBool(s) + return b1 +} + +func ReplaceAll(s string, oldnew ...string) string { + repl := strings.NewReplacer(oldnew...) + return repl.Replace(s) +} + +func PackageName(v interface{}) string { + if v == nil { + return "" + } + + val := reflect.ValueOf(v) + if val.Kind() == reflect.Ptr { + return val.Elem().Type().PkgPath() + } + return val.Type().PkgPath() +} + +func ParseFloat32(f string) float32 { + s, _ := strconv.ParseFloat(f, 32) + return float32(s) +} + +func SetDefault(value, condition, def string) string { + if value == condition { + return def + } + return value +} + +func AorB(condition bool, a, b string) string { + if condition { + return a + } + return b +} + +func IsJSON(str string) bool { + var js json.RawMessage + return json.Unmarshal([]byte(str), &js) == nil +} + +func CopyMap(m map[string]string) map[string]string { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + dec := gob.NewDecoder(&buf) + err := enc.Encode(m) + if err != nil { + panic(err) + } + var cm map[string]string + err = dec.Decode(&cm) + if err != nil { + panic(err) + } + return cm +} + +func ParseTime(stringTime string) time.Time { + loc, _ := time.LoadLocation("Local") + theTime, _ := time.ParseInLocation("2006-01-02 15:04:05", stringTime, loc) + return theTime +} + +func ParseHTML(name, tmpl string, param interface{}) template.HTML { + t := template.New(name) + t, err := t.Parse(tmpl) + if err != nil { + fmt.Println("utils parseHTML error", err) + return "" + } + buf := new(bytes.Buffer) + err = t.Execute(buf, param) + if err != nil { + fmt.Println("utils parseHTML error", err) + return "" + } + return template.HTML(buf.String()) +} + +func ParseText(name, tmpl string, param interface{}) string { + t := textTmpl.New(name) + t, err := t.Parse(tmpl) + if err != nil { + fmt.Println("utils parseHTML error", err) + return "" + } + buf := new(bytes.Buffer) + err = t.Execute(buf, param) + if err != nil { + fmt.Println("utils parseHTML error", err) + return "" + } + return buf.String() +} + +func CompareVersion(src, toCompare string) bool { + if toCompare == "" { + return false + } + + exp, _ := regexp.Compile(`-(.*)`) + src = exp.ReplaceAllString(src, "") + toCompare = exp.ReplaceAllString(toCompare, "") + + srcs := strings.Split(src, "v") + srcArr := strings.Split(srcs[1], ".") + op := ">" + srcs[0] = strings.TrimSpace(srcs[0]) + if InArray([]string{">=", "<=", "=", ">", "<"}, srcs[0]) { + op = srcs[0] + } + + toCompare = strings.ReplaceAll(toCompare, "v", "") + + if op == "=" { + return srcs[1] == toCompare + } + + if srcs[1] == toCompare && (op == "<=" || op == ">=") { + return true + } + + toCompareArr := strings.Split(strings.ReplaceAll(toCompare, "v", ""), ".") + for i := 0; i < len(srcArr); i++ { + v, err := strconv.Atoi(srcArr[i]) + if err != nil { + return false + } + vv, err := strconv.Atoi(toCompareArr[i]) + if err != nil { + return false + } + switch op { + case ">", ">=": + if v < vv { + return true + } else if v > vv { + return false + } else { + continue + } + case "<", "<=": + if v > vv { + return true + } else if v < vv { + return false + } else { + continue + } + } + } + + return false +} + +const ( + Byte = 1 + KByte = Byte * 1024 + MByte = KByte * 1024 + GByte = MByte * 1024 + TByte = GByte * 1024 + PByte = TByte * 1024 + EByte = PByte * 1024 +) + +func logn(n, b float64) float64 { + return math.Log(n) / math.Log(b) +} + +func humanateBytes(s uint64, base float64, sizes []string) string { + if s < 10 { + return fmt.Sprintf("%d B", s) + } + e := math.Floor(logn(float64(s), base)) + suffix := sizes[int(e)] + val := float64(s) / math.Pow(base, math.Floor(e)) + f := "%.0f" + if val < 10 { + f = "%.1f" + } + + return fmt.Sprintf(f+" %s", val, suffix) +} + +// FileSize calculates the file size and generate user-friendly string. +func FileSize(s uint64) string { + sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} + return humanateBytes(s, 1024, sizes) +} + +func FileExist(path string) bool { + _, err := os.Stat(path) + if err != nil { + return os.IsExist(err) + } + return true +} + +// TimeSincePro calculates the time interval and generate full user-friendly string. +func TimeSincePro(then time.Time, m map[string]string) string { + now := time.Now() + diff := now.Unix() - then.Unix() + + if then.After(now) { + return "future" + } + + var timeStr, diffStr string + for { + if diff == 0 { + break + } + + diff, diffStr = computeTimeDiff(diff, m) + timeStr += ", " + diffStr + } + return strings.TrimPrefix(timeStr, ", ") +} + +// Seconds-based time units +const ( + Minute = 60 + Hour = 60 * Minute + Day = 24 * Hour + Week = 7 * Day + Month = 30 * Day + Year = 12 * Month +) + +func computeTimeDiff(diff int64, m map[string]string) (int64, string) { + diffStr := "" + switch { + case diff <= 0: + diff = 0 + diffStr = "now" + case diff < 2: + diff = 0 + diffStr = "1 " + m["second"] + case diff < 1*Minute: + diffStr = fmt.Sprintf("%d "+m["seconds"], diff) + diff = 0 + + case diff < 2*Minute: + diff -= 1 * Minute + diffStr = "1 " + m["minute"] + case diff < 1*Hour: + diffStr = fmt.Sprintf("%d "+m["minutes"], diff/Minute) + diff -= diff / Minute * Minute + + case diff < 2*Hour: + diff -= 1 * Hour + diffStr = "1 " + m["hour"] + case diff < 1*Day: + diffStr = fmt.Sprintf("%d "+m["hours"], diff/Hour) + diff -= diff / Hour * Hour + + case diff < 2*Day: + diff -= 1 * Day + diffStr = "1 " + m["day"] + case diff < 1*Week: + diffStr = fmt.Sprintf("%d "+m["days"], diff/Day) + diff -= diff / Day * Day + + case diff < 2*Week: + diff -= 1 * Week + diffStr = "1 " + m["week"] + case diff < 1*Month: + diffStr = fmt.Sprintf("%d "+m["weeks"], diff/Week) + diff -= diff / Week * Week + + case diff < 2*Month: + diff -= 1 * Month + diffStr = "1 " + m["month"] + case diff < 1*Year: + diffStr = fmt.Sprintf("%d "+m["months"], diff/Month) + diff -= diff / Month * Month + + case diff < 2*Year: + diff -= 1 * Year + diffStr = "1 " + m["year"] + default: + diffStr = fmt.Sprintf("%d "+m["years"], diff/Year) + diff = 0 + } + return diff, diffStr +} + +func DownloadTo(url, output string) error { + + req, err := http.NewRequest("GET", url, nil) + + if err != nil { + return err + } + + res, err := http.DefaultClient.Do(req) + + if err != nil { + return err + } + + defer func() { + _ = res.Body.Close() + }() + + file, err := os.Create(output) + + if err != nil { + return err + } + + _, err = io.Copy(file, res.Body) + + if err != nil { + return err + } + + return nil +} + +func UnzipDir(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer func() { + if err := r.Close(); err != nil { + panic(err) + } + }() + + err = os.MkdirAll(dest, 0750) + + if err != nil { + return err + } + + // Closure to address file descriptors issue with all the deferred .Close() methods + extractAndWriteFile := func(f *zip.File) error { + rc, err := f.Open() + if err != nil { + return err + } + defer func() { + if err := rc.Close(); err != nil { + panic(err) + } + }() + + path := filepath.Join(dest, f.Name) + + if f.FileInfo().IsDir() { + err = os.MkdirAll(path, f.Mode()) + if err != nil { + return err + } + } else { + err = os.MkdirAll(filepath.Dir(path), f.Mode()) + if err != nil { + return err + } + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + panic(err) + } + }() + + _, err = io.Copy(f, rc) + if err != nil { + return err + } + } + return nil + } + + for _, f := range r.File { + err := extractAndWriteFile(f) + if err != nil { + return err + } + } + + return nil +} diff --git a/modules/utils/utils_test.go b/modules/utils/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a9be335069d6fcf76ff356b1054ec8c5c41f76fc --- /dev/null +++ b/modules/utils/utils_test.go @@ -0,0 +1,51 @@ +package utils + +import ( + "html/template" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCompressedContent(t *testing.T) { + htmlContent1 := template.HTML(` + + + +

    Test

    + + + +

    CompressedContent

    + + + + +`) + htmlContent2 := htmlContent1 + CompressedContent(&htmlContent2) + t.Log(len(htmlContent1) > len(htmlContent2)) +} + +func TestCompareVersion(t *testing.T) { + assert.Equal(t, true, CompareVersion("v1.2.4", "v1.2.5")) + assert.Equal(t, false, CompareVersion("v1.2.4", "v1.2.4")) + assert.Equal(t, false, CompareVersion("v1.2.4", "v1.2.3")) + assert.Equal(t, false, CompareVersion("v1.2.4", "v1.1.3")) + assert.Equal(t, true, CompareVersion("v1.2.4", "v1.3.3")) + assert.Equal(t, false, CompareVersion("v1.2.4", "v0.3.3")) + + assert.Equal(t, true, CompareVersion("v1.2.4", "v1.2.5")) + assert.Equal(t, false, CompareVersion(">v1.2.4", "v1.2.4")) + assert.Equal(t, true, CompareVersion(">=v1.2.4", "v1.2.4")) + assert.Equal(t, true, CompareVersion(">=v1.2.4", "v1.2.5")) + assert.Equal(t, false, CompareVersion(">=v1.2.4", "v1.2.3")) + assert.Equal(t, false, CompareVersion("=v1.2.4", "v1.2.3")) + assert.Equal(t, true, CompareVersion("=v1.2.4", "v1.2.4")) + assert.Equal(t, true, CompareVersion("= v1.2.4", "v1.2.4")) +} diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go new file mode 100644 index 0000000000000000000000000000000000000000..b0f8c07dbceef67bae74aa177fbdfb90358b6fa9 --- /dev/null +++ b/plugins/admin/admin.go @@ -0,0 +1,172 @@ +package admin + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/modules/system" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/controller" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/action" + _ "github.com/GoAdminGroup/go-admin/template/types/display" +) + +// Admin is a GoAdmin plugin. +type Admin struct { + *plugins.Base + config *config.Config + tableList table.GeneratorList + guardian *guard.Guard + handler *controller.Handler +} + +// InitPlugin implements Plugin.InitPlugin. +// TODO: find a better way to manage the dependencies +func (admin *Admin) InitPlugin(services service.List) { + + // DO NOT DELETE + admin.InitBase(services, "") + + c := config.GetService(services.Get("config")) + st := table.NewSystemTable(admin.Conn, c) + genList := table.GeneratorList{ + "manager": st.GetManagerTable, + "permission": st.GetPermissionTable, + "roles": st.GetRolesTable, + "op": st.GetOpTable, + "menu": st.GetMenuTable, + "normal_manager": st.GetNormalManagerTable, + } + if c.IsAllowConfigModification() { + genList.Add("site", st.GetSiteTable) + } + if c.IsNotProductionEnvironment() { + genList.Add("generate", st.GetGenerateForm) + } + admin.tableList.Combine(genList) + admin.guardian = guard.New(admin.Services, admin.Conn, admin.tableList, admin.UI.NavButtons) + handlerCfg := controller.Config{ + Config: c, + Services: services, + Generators: admin.tableList, + Connection: admin.Conn, + } + admin.config = c + admin.handler.UpdateCfg(handlerCfg) + admin.initRouter() + admin.handler.SetRoutes(admin.App.Routers) + admin.handler.AddNavButton(admin.UI.NavButtons) + + table.SetServices(services) + + action.InitOperationHandlerSetter(admin.GetAddOperationFn()) +} + +func (admin *Admin) GetIndexURL() string { + return config.GetIndexURL() +} + +func (admin *Admin) GetInfo() plugins.Info { + return plugins.Info{ + Title: "Basic Admin", + Website: "https://www.go-admin.cn", + Description: "A built-in plugins of GoAdmin which help you to build a crud manager platform quickly.", + Author: "official", + Version: system.Version(), + CreateDate: utils.ParseTime("2018-07-08 00:00:00"), + UpdateDate: utils.ParseTime("2020-06-28 00:00:00"), + } +} + +func (admin *Admin) IsInstalled() bool { + return true +} + +// NewAdmin return the global Admin plugin. +func NewAdmin(tableCfg ...table.GeneratorList) *Admin { + return &Admin{ + tableList: make(table.GeneratorList).CombineAll(tableCfg), + Base: &plugins.Base{PlugName: "admin"}, + handler: controller.New(), + } +} + +func (admin *Admin) GetAddOperationFn() context.NodeProcessor { + return admin.handler.AddOperation +} + +// SetCaptcha set captcha driver. +func (admin *Admin) SetCaptcha(captcha map[string]string) *Admin { + admin.handler.SetCaptcha(captcha) + return admin +} + +// AddGenerator add table model generator. +func (admin *Admin) AddGenerator(key string, g table.Generator) *Admin { + admin.tableList.Add(key, g) + return admin +} + +// AddGenerators add table model generators. +func (admin *Admin) AddGenerators(gen ...table.GeneratorList) *Admin { + admin.tableList.CombineAll(gen) + return admin +} + +// AddGlobalDisplayProcessFn call types.AddGlobalDisplayProcessFn +func (admin *Admin) AddGlobalDisplayProcessFn(f types.FieldFilterFn) *Admin { + types.AddGlobalDisplayProcessFn(f) + return admin +} + +// AddDisplayFilterLimit call types.AddDisplayFilterLimit +func (admin *Admin) AddDisplayFilterLimit(limit int) *Admin { + types.AddLimit(limit) + return admin +} + +// AddDisplayFilterTrimSpace call types.AddDisplayFilterTrimSpace +func (admin *Admin) AddDisplayFilterTrimSpace() *Admin { + types.AddTrimSpace() + return admin +} + +// AddDisplayFilterSubstr call types.AddDisplayFilterSubstr +func (admin *Admin) AddDisplayFilterSubstr(start int, end int) *Admin { + types.AddSubstr(start, end) + return admin +} + +// AddDisplayFilterToTitle call types.AddDisplayFilterToTitle +func (admin *Admin) AddDisplayFilterToTitle() *Admin { + types.AddToTitle() + return admin +} + +// AddDisplayFilterToUpper call types.AddDisplayFilterToUpper +func (admin *Admin) AddDisplayFilterToUpper() *Admin { + types.AddToUpper() + return admin +} + +// AddDisplayFilterToLower call types.AddDisplayFilterToLower +func (admin *Admin) AddDisplayFilterToLower() *Admin { + types.AddToUpper() + return admin +} + +// AddDisplayFilterXssFilter call types.AddDisplayFilterXssFilter +func (admin *Admin) AddDisplayFilterXssFilter() *Admin { + types.AddXssFilter() + return admin +} + +// AddDisplayFilterXssJsFilter call types.AddDisplayFilterXssJsFilter +func (admin *Admin) AddDisplayFilterXssJsFilter() *Admin { + types.AddXssJsFilter() + return admin +} diff --git a/plugins/admin/controller/Update.go b/plugins/admin/controller/Update.go new file mode 100644 index 0000000000000000000000000000000000000000..7c821b581a753fe9a9180f00f86559e93f0ff0af --- /dev/null +++ b/plugins/admin/controller/Update.go @@ -0,0 +1,22 @@ +package controller + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" +) + +// Update update the table row of given id. +func (h *Handler) Update(ctx *context.Context) { + + param := guard.GetUpdateParam(ctx) + + err := param.Panel.UpdateData(ctx, param.Value) + + if err != nil { + response.Error(ctx, err.Error()) + return + } + + response.Ok(ctx) +} diff --git a/plugins/admin/controller/api_create.go b/plugins/admin/controller/api_create.go new file mode 100644 index 0000000000000000000000000000000000000000..6b8b98d10320f7dec2f069ca74a87afec6bc4a65 --- /dev/null +++ b/plugins/admin/controller/api_create.go @@ -0,0 +1,62 @@ +package controller + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/file" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" +) + +func (h *Handler) ApiCreate(ctx *context.Context) { + param := guard.GetNewFormParam(ctx) + + if len(param.MultiForm.File) > 0 { + err := file.GetFileEngine(h.config.FileUploadEngine.Name).Upload(param.MultiForm) + if err != nil { + response.Error(ctx, err.Error()) + return + } + } + + err := param.Panel.InsertData(ctx, param.Value()) + if err != nil { + response.Error(ctx, err.Error()) + return + } + + response.Ok(ctx) +} + +func (h *Handler) ApiCreateForm(ctx *context.Context) { + + var ( + params = guard.GetShowNewFormParam(ctx) + prefix, paramStr = params.Prefix, params.Param.GetRouteParamStr() + panel = h.table(prefix, ctx) + formInfo = panel.GetNewFormInfo() + infoUrl = h.routePathWithPrefix("api_info", prefix) + paramStr + newUrl = h.routePathWithPrefix("api_new", prefix) + referer = ctx.Referer() + f = panel.GetActualNewForm() + ) + + if referer != "" && !isInfoUrl(referer) && !isNewUrl(referer, ctx.Query(constant.PrefixKey)) { + infoUrl = referer + } + + response.OkWithData(ctx, map[string]interface{}{ + "panel": formInfo, + "urls": map[string]string{ + "info": infoUrl, + "new": newUrl, + }, + "pk": panel.GetPrimaryKey().Name, + "header": f.HeaderHtml, + "footer": f.FooterHtml, + "prefix": h.config.PrefixFixSlash(), + "token": h.authSrv().AddToken(), + "operation_footer": formFooter(ctx, "new", f.IsHideContinueEditCheckBox, f.IsHideContinueNewCheckBox, + f.IsHideResetButton, f.FormNewBtnWord), + }) +} diff --git a/plugins/admin/controller/api_detail.go b/plugins/admin/controller/api_detail.go new file mode 100644 index 0000000000000000000000000000000000000000..c22b3b595890aa874d54aa5e205ff7534ad63c73 --- /dev/null +++ b/plugins/admin/controller/api_detail.go @@ -0,0 +1,126 @@ +package controller + +import ( + "fmt" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +func (h *Handler) ApiDetail(ctx *context.Context) { + prefix := ctx.Query(constant.PrefixKey) + id := ctx.Query(constant.DetailPKKey) + panel := h.table(prefix, ctx) + user := auth.Auth(ctx) + + newPanel := panel.Copy() + + formModel := newPanel.GetForm() + + var fieldList types.FieldList + + if len(panel.GetDetail().FieldList) == 0 { + fieldList = panel.GetInfo().FieldList + } else { + fieldList = panel.GetDetail().FieldList + } + + formModel.FieldList = make([]types.FormField, len(fieldList)) + + for i, field := range fieldList { + formModel.FieldList[i] = types.FormField{ + Field: field.Field, + FieldClass: field.Field, + TypeName: field.TypeName, + Head: field.Head, + Hide: field.Hide, + Joins: field.Joins, + FormType: form.Default, + FieldDisplay: field.FieldDisplay, + } + } + + param := parameter.GetParam(ctx.Request.URL, + panel.GetInfo().DefaultPageSize, + panel.GetInfo().SortField, + panel.GetInfo().GetSort()) + + paramStr := param.DeleteDetailPk().GetRouteParamStr() + + editUrl := modules.AorEmpty(!panel.GetInfo().IsHideEditButton, h.routePathWithPrefix("show_edit", prefix)+paramStr+ + "&"+constant.EditPKKey+"="+ctx.Query(constant.DetailPKKey)) + deleteUrl := modules.AorEmpty(!panel.GetInfo().IsHideDeleteButton, h.routePathWithPrefix("delete", prefix)+paramStr) + infoUrl := h.routePathWithPrefix("info", prefix) + paramStr + + editUrl = user.GetCheckPermissionByUrlMethod(editUrl, h.route("show_edit").Method()) + deleteUrl = user.GetCheckPermissionByUrlMethod(deleteUrl, h.route("delete").Method()) + + deleteJs := "" + + if deleteUrl != "" { + deleteJs = fmt.Sprintf(``, language.Get("are you sure to delete"), language.Get("yes"), language.Get("cancel"), deleteUrl, infoUrl, id) + } + + desc := panel.GetDetail().Description + + if desc == "" { + desc = panel.GetInfo().Description + language.Get("Detail") + } + + formInfo, err := newPanel.GetDataWithId(param.WithPKs(id)) + + if err != nil { + response.Error(ctx, err.Error()) + return + } + + response.OkWithData(ctx, map[string]interface{}{ + "panel": formInfo, + "previous": infoUrl, + "footer": deleteJs, + "prefix": h.config.PrefixFixSlash(), + }) +} diff --git a/plugins/admin/controller/api_list.go b/plugins/admin/controller/api_list.go new file mode 100644 index 0000000000000000000000000000000000000000..ed28f9c88635530337758acd20dd863d4c4c751f --- /dev/null +++ b/plugins/admin/controller/api_list.go @@ -0,0 +1,39 @@ +package controller + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" +) + +func (h *Handler) ApiList(ctx *context.Context) { + prefix := ctx.Query(constant.PrefixKey) + + panel := h.table(prefix, ctx) + + params := parameter.GetParam(ctx.Request.URL, panel.GetInfo().DefaultPageSize, panel.GetInfo().SortField, + panel.GetInfo().GetSort()) + + panel, panelInfo, urls, err := h.showTableData(ctx, prefix, params, panel, "api_") + if err != nil { + response.Error(ctx, err.Error()) + return + } + + response.OkWithData(ctx, map[string]interface{}{ + "panel": panelInfo, + "footer": panelInfo.Paginator.GetContent() + panel.GetInfo().FooterHtml, + "header": aDataTable(ctx).GetDataTableHeader() + panel.GetInfo().HeaderHtml, + "prefix": h.config.PrefixFixSlash(), + "urls": map[string]string{ + "edit": urls[0], + "new": urls[1], + "delete": urls[2], + "export": urls[3], + "detail": urls[4], + "info": urls[5], + "update": urls[6], + }, + }) +} diff --git a/plugins/admin/controller/api_update.go b/plugins/admin/controller/api_update.go new file mode 100644 index 0000000000000000000000000000000000000000..bdcd54531d349a8f0ab442e2e9cac29b99815769 --- /dev/null +++ b/plugins/admin/controller/api_update.go @@ -0,0 +1,87 @@ +package controller + +import ( + "net/url" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/file" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +func (h *Handler) ApiUpdate(ctx *context.Context) { + param := guard.GetEditFormParam(ctx) + + if len(param.MultiForm.File) > 0 { + err := file.GetFileEngine(h.config.FileUploadEngine.Name).Upload(param.MultiForm) + if err != nil { + response.Error(ctx, err.Error()) + return + } + } + + for i := 0; i < len(param.Panel.GetForm().FieldList); i++ { + if param.Panel.GetForm().FieldList[i].FormType == form.File && + len(param.MultiForm.File[param.Panel.GetForm().FieldList[i].Field]) == 0 && + param.MultiForm.Value[param.Panel.GetForm().FieldList[i].Field+"__delete_flag"][0] != "1" { + delete(param.MultiForm.Value, param.Panel.GetForm().FieldList[i].Field) + } + } + + err := param.Panel.UpdateData(ctx, param.Value()) + if err != nil { + response.Error(ctx, err.Error()) + return + } + + response.Ok(ctx) +} + +func (h *Handler) ApiUpdateForm(ctx *context.Context) { + params := guard.GetShowFormParam(ctx) + + prefix, param := params.Prefix, params.Param + + panel := h.table(prefix, ctx) + + user := auth.Auth(ctx) + + paramStr := param.GetRouteParamStr() + + newUrl := modules.AorEmpty(panel.GetCanAdd(), h.routePathWithPrefix("api_show_new", prefix)+paramStr) + footerKind := "edit" + if newUrl == "" || !user.CheckPermissionByUrlMethod(newUrl, h.route("api_show_new").Method(), url.Values{}) { + footerKind = "edit_only" + } + + formInfo, err := panel.GetDataWithId(param) + + if err != nil { + response.Error(ctx, err.Error()) + return + } + + infoUrl := h.routePathWithPrefix("api_info", prefix) + param.DeleteField(constant.EditPKKey).GetRouteParamStr() + editUrl := h.routePathWithPrefix("api_edit", prefix) + + f := panel.GetForm() + + response.OkWithData(ctx, map[string]interface{}{ + "panel": formInfo, + "urls": map[string]string{ + "info": infoUrl, + "edit": editUrl, + }, + "pk": panel.GetPrimaryKey().Name, + "header": f.HeaderHtml, + "footer": f.FooterHtml, + "prefix": h.config.PrefixFixSlash(), + "token": h.authSrv().AddToken(), + "operation_footer": formFooter(ctx, footerKind, f.IsHideContinueEditCheckBox, f.IsHideContinueNewCheckBox, + f.IsHideResetButton, f.FormEditBtnWord), + }) +} diff --git a/plugins/admin/controller/auth.go b/plugins/admin/controller/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..cf914f30676e4eb0d8243d326a56a8114091c842 --- /dev/null +++ b/plugins/admin/controller/auth.go @@ -0,0 +1,121 @@ +package controller + +import ( + "bytes" + template2 "html/template" + "net/http" + "net/url" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/system" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/captcha" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" +) + +// Auth check the input password and username for authentication. +func (h *Handler) Auth(ctx *context.Context) { + + var ( + user models.UserModel + ok bool + errMsg = "fail" + s, exist = h.services.GetOrNot(auth.ServiceKey) + ) + + if capDriver, ok := h.captchaConfig["driver"]; ok { + capt, ok := captcha.Get(capDriver) + + if ok { + if !capt.Validate(ctx.FormValue("token")) { + response.BadRequest(ctx, "wrong captcha") + return + } + } + } + + if !exist { + password := ctx.FormValue("password") + username := ctx.FormValue("username") + + if password == "" || username == "" { + response.BadRequest(ctx, "wrong password or username") + return + } + user, ok = auth.Check(password, username, h.conn) + } else { + user, ok, errMsg = auth.GetService(s).P(ctx) + } + + if !ok { + response.BadRequest(ctx, errMsg) + return + } + + err := auth.SetCookie(ctx, user, h.conn) + + if err != nil { + response.Error(ctx, err.Error()) + return + } + + if ref := ctx.Referer(); ref != "" { + if u, err := url.Parse(ref); err == nil { + v := u.Query() + if r := v.Get("ref"); r != "" { + rr, _ := url.QueryUnescape(r) + response.OkWithData(ctx, map[string]interface{}{ + "url": rr, + }) + return + } + } + } + + response.OkWithData(ctx, map[string]interface{}{ + "url": h.config.GetIndexURL(), + }) +} + +// Logout delete the cookie. +func (h *Handler) Logout(ctx *context.Context) { + err := auth.DelCookie(ctx, db.GetConnection(h.services)) + if err != nil { + logger.ErrorCtx(ctx, "logout error %+v", err) + } + ctx.AddHeader("Location", h.config.Url(config.GetLoginUrl())) + ctx.SetStatusCode(302) +} + +// ShowLogin show the login page. +func (h *Handler) ShowLogin(ctx *context.Context) { + + tmpl, name := template.GetComp("login").GetTemplate() + buf := new(bytes.Buffer) + if err := tmpl.ExecuteTemplate(buf, name, struct { + UrlPrefix string + Title string + Logo template2.HTML + CdnUrl string + System types.SystemInfo + }{ + UrlPrefix: h.config.AssertPrefix(), + Title: h.config.LoginTitle, + Logo: h.config.LoginLogo, + System: types.SystemInfo{ + Version: system.Version(), + }, + CdnUrl: h.config.AssetUrl, + }); err == nil { + ctx.HTML(http.StatusOK, buf.String()) + } else { + logger.ErrorCtx(ctx, "ShowLogin error %+v", err) + ctx.HTML(http.StatusOK, "parse template error (;′⌒`)") + } +} diff --git a/plugins/admin/controller/common.go b/plugins/admin/controller/common.go new file mode 100644 index 0000000000000000000000000000000000000000..b350d93a9f7b5fc6da5366120f7e890564986edf --- /dev/null +++ b/plugins/admin/controller/common.go @@ -0,0 +1,473 @@ +package controller + +import ( + "bytes" + template2 "html/template" + "net/http" + "regexp" + "strings" + "sync" + + "github.com/GoAdminGroup/go-admin/template/types/action" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + c "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/menu" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/icon" + "github.com/GoAdminGroup/go-admin/template/types" +) + +type Handler struct { + config *c.Config + captchaConfig map[string]string + services service.List + conn db.Connection + routes context.RouterMap + generators table.GeneratorList + operations []context.Node + navButtons *types.Buttons + operationLock sync.Mutex + assetsTheme map[string]string +} + +func New(cfg ...Config) *Handler { + if len(cfg) == 0 { + return &Handler{ + operations: make([]context.Node, 0), + navButtons: new(types.Buttons), + } + } + return &Handler{ + config: cfg[0].Config, + services: cfg[0].Services, + conn: cfg[0].Connection, + generators: cfg[0].Generators, + operations: make([]context.Node, 0), + navButtons: new(types.Buttons), + assetsTheme: make(map[string]string), + } +} + +type Config struct { + Config *c.Config + Services service.List + Connection db.Connection + Generators table.GeneratorList +} + +func (h *Handler) UpdateCfg(cfg Config) { + h.config = cfg.Config + h.services = cfg.Services + h.conn = cfg.Connection + h.generators = cfg.Generators + h.assetsTheme = make(map[string]string) +} + +func (h *Handler) SetCaptcha(captcha map[string]string) { + h.captchaConfig = captcha +} + +func (h *Handler) AssetsTheme(asset, theme string) { + h.assetsTheme[asset] = theme +} + +func (h *Handler) SetRoutes(r context.RouterMap) { + h.routes = r +} + +func (h *Handler) table(prefix string, ctx *context.Context) table.Table { + t := h.generators[prefix](ctx) + authHandler := auth.Middleware(db.GetConnection(h.services)) + for _, cb := range t.GetInfo().Callbacks { + if cb.Value[constant.ContextNodeNeedAuth] == 1 { + h.AddOperation(context.Node{ + Path: cb.Path, + Method: cb.Method, + Handlers: append([]context.Handler{authHandler}, cb.Handlers...), + }) + } else { + h.AddOperation(context.Node{Path: cb.Path, Method: cb.Method, Handlers: cb.Handlers}) + } + } + for _, cb := range t.GetForm().Callbacks { + if cb.Value[constant.ContextNodeNeedAuth] == 1 { + h.AddOperation(context.Node{ + Path: cb.Path, + Method: cb.Method, + Handlers: append([]context.Handler{authHandler}, cb.Handlers...), + }) + } else { + h.AddOperation(context.Node{Path: cb.Path, Method: cb.Method, Handlers: cb.Handlers}) + } + } + return t +} + +func (h *Handler) route(name string) context.Router { + return h.routes.Get(name) +} + +func (h *Handler) routePath(name string, value ...string) string { + return h.routes.Get(name).GetURL(value...) +} + +func (h *Handler) routePathWithPrefix(name string, prefix string) string { + return h.routePath(name, "prefix", prefix) +} + +func (h *Handler) AddOperation(nodes ...context.Node) { + h.operationLock.Lock() + defer h.operationLock.Unlock() + // TODO: 避免重复增加,第一次加入后,后面大部分会存在重复情况,以下循环可以优化 + addNodes := make([]context.Node, 0) + for _, node := range nodes { + if h.searchOperation(node.Path, node.Method) { + continue + } + addNodes = append(addNodes, node) + } + h.operations = append(h.operations, addNodes...) +} + +func (h *Handler) AddNavButton(btns *types.Buttons) { + h.navButtons = btns + for _, btn := range *btns { + h.AddOperation(btn.GetAction().GetCallbacks()) + } +} + +func (h *Handler) searchOperation(path, method string) bool { + for _, node := range h.operations { + if node.Path == path && node.Method == method { + return true + } + } + return false +} + +func (h *Handler) OperationHandler(path string, ctx *context.Context) bool { + for _, node := range h.operations { + if node.Path == path { + for _, handler := range node.Handlers { + handler(ctx) + } + return true + } + } + return false +} + +func (h *Handler) HTML(ctx *context.Context, user models.UserModel, panel types.Panel, + options ...template.ExecuteOptions) { + buf := h.Execute(ctx, user, panel, "", options...) + ctx.HTML(http.StatusOK, buf.String()) +} + +func (h *Handler) HTMLPlug(ctx *context.Context, user models.UserModel, panel types.Panel, plugName string, + options ...template.ExecuteOptions) { + var btns types.Buttons + if plugName == "" { + btns = (*h.navButtons).CheckPermission(user) + } else { + btns = (*h.navButtons).Copy(). + RemoveToolNavButton(). + RemoveSiteNavButton(). + RemoveInfoNavButton(). + Add(types.GetDropDownButton("", icon.Gear, []*types.NavDropDownItemButton{ + types.GetDropDownItemButton(language.GetFromHtml("plugin setting"), + action.Jump(h.config.Url("/info/plugin_"+plugName+"/edit"))), + types.GetDropDownItemButton(language.GetFromHtml("menus manage"), + action.Jump(h.config.Url("/menu?__plugin_name="+plugName))), + })). + CheckPermission(user) + } + buf := h.ExecuteWithBtns(ctx, user, panel, plugName, btns, options...) + ctx.HTML(http.StatusOK, buf.String()) +} + +func (h *Handler) ExecuteWithBtns(ctx *context.Context, user models.UserModel, panel types.Panel, plugName string, btns types.Buttons, + options ...template.ExecuteOptions) *bytes.Buffer { + + tmpl, tmplName := aTemplate(ctx).GetTemplate(isPjax(ctx)) + option := template.GetExecuteOptions(options) + + return template.Execute(ctx, &template.ExecuteParam{ + User: user, + TmplName: tmplName, + Tmpl: tmpl, + Panel: panel, + Config: h.config, + Menu: menu.GetGlobalMenu(user, h.conn, ctx.Lang(), plugName).SetActiveClass(h.config.URLRemovePrefix(ctx.Path())), + Animation: option.Animation, + Buttons: btns, + Iframe: ctx.IsIframe(), + IsPjax: isPjax(ctx), + NoCompress: option.NoCompress, + }) +} + +func (h *Handler) Execute(ctx *context.Context, user models.UserModel, panel types.Panel, plugName string, + options ...template.ExecuteOptions) *bytes.Buffer { + + tmpl, tmplName := aTemplate(ctx).GetTemplate(isPjax(ctx)) + option := template.GetExecuteOptions(options) + + return template.Execute(ctx, &template.ExecuteParam{ + User: user, + TmplName: tmplName, + Tmpl: tmpl, + Panel: panel, + Config: h.config, + Menu: menu.GetGlobalMenu(user, h.conn, ctx.Lang(), plugName).SetActiveClass(h.config.URLRemovePrefix(ctx.Path())), + Animation: option.Animation, + Buttons: (*h.navButtons).CheckPermission(user), + Iframe: ctx.IsIframe(), + IsPjax: isPjax(ctx), + NoCompress: option.NoCompress, + }) +} + +func isInfoUrl(s string) bool { + reg, _ := regexp.Compile("(.*?)info/(.*?)$") + sub := reg.FindStringSubmatch(s) + return len(sub) > 2 && !strings.Contains(sub[2], "/") +} + +func isNewUrl(s string, p string) bool { + reg, _ := regexp.Compile("(.*?)info/" + p + "/new") + return reg.MatchString(s) +} + +func isEditUrl(s string, p string) bool { + reg, _ := regexp.Compile("(.*?)info/" + p + "/edit") + return reg.MatchString(s) +} + +func (h *Handler) authSrv() *auth.TokenService { + return auth.GetTokenService(h.services.Get(auth.TokenServiceKey)) +} + +func aAlert(ctx *context.Context) types.AlertAttribute { + return aTemplate(ctx).Alert() +} + +func aForm(ctx *context.Context) types.FormAttribute { + return aTemplate(ctx).Form() +} + +func aRow(ctx *context.Context) types.RowAttribute { + return aTemplate(ctx).Row() +} + +func aCol(ctx *context.Context) types.ColAttribute { + return aTemplate(ctx).Col() +} + +func aImage(ctx *context.Context) types.ImgAttribute { + return aTemplate(ctx).Image() +} + +func aButton(ctx *context.Context) types.ButtonAttribute { + return aTemplate(ctx).Button() +} + +func aTree(ctx *context.Context) types.TreeAttribute { + return aTemplate(ctx).Tree() +} + +func aTable(ctx *context.Context) types.TableAttribute { + return aTemplate(ctx).Table() +} + +func aDataTable(ctx *context.Context) types.DataTableAttribute { + return aTemplate(ctx).DataTable() +} + +func aBox(ctx *context.Context) types.BoxAttribute { + return aTemplate(ctx).Box() +} + +func aTab(ctx *context.Context) types.TabsAttribute { + return aTemplate(ctx).Tabs() +} + +func aTemplate(ctx *context.Context) template.Template { + return template.Get(ctx, c.GetTheme()) +} + +func aTemplateByTheme(ctx *context.Context, theme string) template.Template { + return template.Get(ctx, theme) +} + +func isPjax(ctx *context.Context) bool { + return ctx.IsPjax() +} + +func formFooter(ctx *context.Context, page string, isHideEdit, isHideNew, isHideReset bool, btnWord template2.HTML) template2.HTML { + col1 := aCol(ctx).SetSize(types.SizeMD(2)).GetContent() + + var ( + checkBoxs template2.HTML + checkBoxJS template2.HTML + + editCheckBox = template.HTML(` + `) + newCheckBox = template.HTML(` + `) + + editWithNewCheckBoxJs = template.HTML(`$('.continue_edit').iCheck({checkboxClass: 'icheckbox_minimal-blue'}).on('ifChanged', function (event) { + if (this.checked) { + $('.continue_new').iCheck('uncheck'); + $('input[name="` + form.PreviousKey + `"]').val(location.href) + } else { + $('input[name="` + form.PreviousKey + `"]').val(previous_url_goadmin) + } + }); `) + + newWithEditCheckBoxJs = template.HTML(`$('.continue_new').iCheck({checkboxClass: 'icheckbox_minimal-blue'}).on('ifChanged', function (event) { + if (this.checked) { + $('.continue_edit').iCheck('uncheck'); + $('input[name="` + form.PreviousKey + `"]').val(location.href.replace('/edit', '/new')) + } else { + $('input[name="` + form.PreviousKey + `"]').val(previous_url_goadmin) + } + });`) + ) + + if page == "edit" { + if isHideNew { + newCheckBox = "" + newWithEditCheckBoxJs = "" + } + if isHideEdit { + editCheckBox = "" + editWithNewCheckBoxJs = "" + } + checkBoxs = editCheckBox + newCheckBox + checkBoxJS = ` +` + } else if page == "edit_only" && !isHideEdit { + checkBoxs = editCheckBox + checkBoxJS = template.HTML(` +`) + } else if page == "new" && !isHideNew { + checkBoxs = newCheckBox + checkBoxJS = template.HTML(` +`) + } + + btn1 := aButton(ctx). + SetType("submit"). + AddClass("submit"). + SetContent(btnWord). + SetThemePrimary(). + SetOrientationRight(). + GetContent() + btn2 := template.HTML("") + if !isHideReset { + btn2 = aButton(ctx). + SetType("reset"). + AddClass("reset"). + SetContent(language.GetFromHtml("Reset")). + SetThemeWarning(). + SetOrientationLeft(). + GetContent() + } + col2 := aCol(ctx).SetSize(types.SizeMD(8)). + SetContent(btn1 + checkBoxs + btn2 + checkBoxJS).GetContent() + return col1 + col2 +} + +func filterFormFooter(ctx *context.Context, infoUrl string) template2.HTML { + col1 := aCol(ctx).SetSize(types.SizeMD(2)).GetContent() + btn1 := aButton(ctx).SetType("submit"). + AddClass("submit"). + SetContent(icon.Icon(icon.Search, 2) + language.GetFromHtml("search")). + SetThemePrimary(). + SetSmallSize(). + SetOrientationLeft(). + SetLoadingText(icon.Icon(icon.Spinner, 1) + language.GetFromHtml("search")). + GetContent() + btn2 := aButton(ctx).SetType("reset"). + AddClass("reset"). + SetContent(icon.Icon(icon.Undo, 2) + language.GetFromHtml("reset")). + SetThemeDefault(). + SetOrientationLeft(). + SetSmallSize(). + SetHref(infoUrl). + SetMarginLeft(12). + GetContent() + col2 := aCol(ctx).SetSize(types.SizeMD(8)). + SetContent(btn1 + btn2).GetContent() + return col1 + col2 +} + +func formContent(ctx *context.Context, form types.FormAttribute, isTab, iframe, isHideBack bool, header template2.HTML) template2.HTML { + if isTab { + return form.GetContent() + } + if iframe { + header = "" + } else if header == template2.HTML("") { + header = form.GetDefaultBoxHeader(isHideBack) + } + return aBox(ctx). + SetHeader(header). + WithHeadBorder(). + SetStyle(" "). + SetIframeStyle(iframe). + SetBody(form.GetContent()). + GetContent() +} + +func detailContent(ctx *context.Context, form types.FormAttribute, editUrl, deleteUrl string, iframe bool) template2.HTML { + return aBox(ctx). + SetHeader(form.GetDetailBoxHeader(editUrl, deleteUrl)). + WithHeadBorder(). + SetBody(form.GetContent()). + SetIframeStyle(iframe). + GetContent() +} + +func menuFormContent(ctx *context.Context, form types.FormAttribute) template2.HTML { + return aBox(ctx). + SetHeader(form.GetBoxHeaderNoButton()). + SetStyle(" "). + WithHeadBorder(). + SetBody(form.GetContent()). + GetContent() +} diff --git a/plugins/admin/controller/common_test.go b/plugins/admin/controller/common_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c11f86e5b569c6606a6b1b4c70d360ce2986485e --- /dev/null +++ b/plugins/admin/controller/common_test.go @@ -0,0 +1,17 @@ +package controller + +import ( + "testing" + + "github.com/magiconair/properties/assert" +) + +func TestIsInfoUrl(t *testing.T) { + u := "https://localhost:8098/admin/info/user?id=sdfs" + assert.Equal(t, true, isInfoUrl(u)) +} + +func TestIsNewUrl(t *testing.T) { + u := "https://localhost:8098/admin/info/user/new?id=sdfs" + assert.Equal(t, true, isNewUrl(u, "user")) +} diff --git a/plugins/admin/controller/delete.go b/plugins/admin/controller/delete.go new file mode 100644 index 0000000000000000000000000000000000000000..7eb80dd5d7153dacf295449b97fd6b60929a7ab2 --- /dev/null +++ b/plugins/admin/controller/delete.go @@ -0,0 +1,32 @@ +package controller + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" +) + +// Delete delete the row from database. +func (h *Handler) Delete(ctx *context.Context) { + + param := guard.GetDeleteParam(ctx) + + //token := ctx.FormValue("_t") + // + //if !auth.TokenHelper.CheckToken(token) { + // ctx.SetStatusCode(http.StatusBadRequest) + // ctx.WriteString(`{"code":400, "msg":"delete fail"}`) + // return + //} + + if err := h.table(param.Prefix, ctx).DeleteData(param.Id); err != nil { + logger.ErrorCtx(ctx, "Delete error %+v", err) + response.Error(ctx, "delete fail") + return + } + + response.OkWithData(ctx, map[string]interface{}{ + "token": h.authSrv().AddToken(), + }) +} diff --git a/plugins/admin/controller/detail.go b/plugins/admin/controller/detail.go new file mode 100644 index 0000000000000000000000000000000000000000..1a94bb226451e7a8b550cefbf28d21222395c994 --- /dev/null +++ b/plugins/admin/controller/detail.go @@ -0,0 +1,157 @@ +package controller + +import ( + "fmt" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + form2 "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +func (h *Handler) ShowDetail(ctx *context.Context) { + + var ( + prefix = ctx.Query(constant.PrefixKey) + id = ctx.Query(constant.DetailPKKey) + panel = h.table(prefix, ctx) + user = auth.Auth(ctx) + newPanel = panel.Copy() + detail = panel.GetDetail() + info = panel.GetInfo() + formModel = newPanel.GetForm() + fieldList = make(types.FieldList, 0) + ) + + if len(detail.FieldList) == 0 { + fieldList = info.FieldList + } else { + fieldList = detail.FieldList + } + + formModel.FieldList = make([]types.FormField, len(fieldList)) + + for i, field := range fieldList { + formModel.FieldList[i] = types.FormField{ + Field: field.Field, + FieldClass: field.Field, + TypeName: field.TypeName, + Head: field.Head, + Hide: field.Hide, + Joins: field.Joins, + FormType: form.Default, + FieldDisplay: field.FieldDisplay, + } + } + + if detail.Table != "" { + formModel.Table = detail.Table + } else { + formModel.Table = info.Table + } + + param := parameter.GetParam(ctx.Request.URL, + info.DefaultPageSize, + info.SortField, + info.GetSort()) + + paramStr := param.DeleteDetailPk().GetRouteParamStr() + + editUrl := modules.AorEmpty(!info.IsHideEditButton, h.routePathWithPrefix("show_edit", prefix)+paramStr+ + "&"+constant.EditPKKey+"="+ctx.Query(constant.DetailPKKey)) + deleteUrl := modules.AorEmpty(!info.IsHideDeleteButton, h.routePathWithPrefix("delete", prefix)+paramStr) + infoUrl := h.routePathWithPrefix("info", prefix) + paramStr + + editUrl = user.GetCheckPermissionByUrlMethod(editUrl, h.route("show_edit").Method()) + deleteUrl = user.GetCheckPermissionByUrlMethod(deleteUrl, h.route("delete").Method()) + + deleteJs := "" + + if deleteUrl != "" { + deleteJs = fmt.Sprintf(``, language.Get("are you sure to delete"), language.Get("yes"), + language.Get("cancel"), deleteUrl, infoUrl, id) + } + + title := "" + desc := "" + + isNotIframe := ctx.Query(constant.IframeKey) != "true" + + if isNotIframe { + title = detail.Title + + if title == "" { + title = info.Title + language.Get("Detail") + } + + desc = detail.Description + + if desc == "" { + desc = info.Description + language.Get("Detail") + } + } + + formInfo, err := newPanel.GetDataWithId(param.WithPKs(id)) + + if err != nil { + h.HTML(ctx, user, template.WarningPanelWithDescAndTitle(ctx, err.Error(), desc, title), + template.ExecuteOptions{Animation: param.Animation}) + return + } + + h.HTML(ctx, user, types.Panel{ + Content: detailContent(ctx, aForm(ctx). + SetTitle(template.HTML(title)). + SetContent(formInfo.FieldList). + SetHeader(detail.HeaderHtml). + SetFooter(template.HTML(deleteJs)+detail.FooterHtml). + SetHiddenFields(map[string]string{ + form2.PreviousKey: infoUrl, + }). + SetPrefix(h.config.PrefixFixSlash()), editUrl, deleteUrl, !isNotIframe), + Description: template.HTML(desc), + Title: template.HTML(title), + }, template.ExecuteOptions{Animation: param.Animation}) +} diff --git a/plugins/admin/controller/edit.go b/plugins/admin/controller/edit.go new file mode 100644 index 0000000000000000000000000000000000000000..825ca8724c3a609505df17ddaf6f883172a9e0ec --- /dev/null +++ b/plugins/admin/controller/edit.go @@ -0,0 +1,230 @@ +package controller + +import ( + "fmt" + template2 "html/template" + "net/http" + "net/url" + + "github.com/GoAdminGroup/go-admin/modules/logger" + + "github.com/GoAdminGroup/go-admin/template" + + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/file" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + form2 "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +// ShowForm show form page. +func (h *Handler) ShowForm(ctx *context.Context) { + param := guard.GetShowFormParam(ctx) + h.showForm(ctx, "", param.Prefix, param.Param, false) +} + +func (h *Handler) showForm(ctx *context.Context, alert template2.HTML, prefix string, param parameter.Parameters, isEdit bool, animation ...bool) { + + panel := h.table(prefix, ctx) + + if panel.GetForm().HasError() { + if panel.GetForm().PageErrorHTML != template2.HTML("") { + h.HTML(ctx, auth.Auth(ctx), + types.Panel{Content: panel.GetForm().PageErrorHTML}, template.ExecuteOptions{Animation: param.Animation}) + return + } + h.HTML(ctx, auth.Auth(ctx), + template.WarningPanel(ctx, panel.GetForm().PageError.Error(), + template.GetPageTypeFromPageError(panel.GetForm().PageError)), template.ExecuteOptions{Animation: param.Animation}) + return + } + + var ( + user = auth.Auth(ctx) + paramStr = param.GetRouteParamStr() + newUrl = modules.AorEmpty(panel.GetCanAdd(), h.routePathWithPrefix("show_new", prefix)+paramStr) + footerKind = "edit" + ) + + if newUrl == "" || !user.CheckPermissionByUrlMethod(newUrl, h.route("show_new").Method(), url.Values{}) { + footerKind = "edit_only" + } + + formInfo, err := panel.GetDataWithId(param) + + if err != nil { + logger.ErrorCtx(ctx, "receive data error: %+v", err) + h.HTML(ctx, user, template. + WarningPanelWithDescAndTitle(ctx, err.Error(), panel.GetForm().Description, panel.GetForm().Title), + template.ExecuteOptions{Animation: alert == "" || ((len(animation) > 0) && animation[0])}) + + if isEdit { + ctx.AddHeader(constant.PjaxUrlHeader, h.routePathWithPrefix("show_edit", prefix)+ + param.DeletePK().GetRouteParamStr()) + } + return + } + + showEditUrl := h.routePathWithPrefix("show_edit", prefix) + param.DeletePK().GetRouteParamStr() + infoUrl := h.routePathWithPrefix("info", prefix) + param.DeleteField(constant.EditPKKey).GetRouteParamStr() + editUrl := h.routePathWithPrefix("edit", prefix) + referer := ctx.Referer() + + if referer != "" && !isInfoUrl(referer) && !isEditUrl(referer, ctx.Query(constant.PrefixKey)) { + infoUrl = referer + } + + f := panel.GetForm() + + isNotIframe := ctx.Query(constant.IframeKey) != "true" + + hiddenFields := map[string]string{ + form2.TokenKey: h.authSrv().AddToken(), + form2.PreviousKey: infoUrl, + } + + if ctx.Query(constant.IframeKey) != "" { + hiddenFields[constant.IframeKey] = ctx.Query(constant.IframeKey) + } + + if ctx.Query(constant.IframeIDKey) != "" { + hiddenFields[constant.IframeIDKey] = ctx.Query(constant.IframeIDKey) + } + + content := formContent(ctx, aForm(ctx). + SetContent(formInfo.FieldList). + SetFieldsHTML(f.HTMLContent). + SetTabContents(formInfo.GroupFieldList). + SetTabHeaders(formInfo.GroupFieldHeaders). + SetPrefix(h.config.PrefixFixSlash()). + SetInputWidth(f.InputWidth). + SetHeadWidth(f.HeadWidth). + SetPrimaryKey(panel.GetPrimaryKey().Name). + SetUrl(editUrl). + SetTitle(f.FormEditTitle). + SetAjax(f.AjaxSuccessJS, f.AjaxErrorJS). + SetLayout(f.Layout). + SetHiddenFields(hiddenFields). + SetOperationFooter(formFooter(ctx, footerKind, + f.IsHideContinueEditCheckBox, + f.IsHideContinueNewCheckBox, + f.IsHideResetButton, f.FormEditBtnWord)). + SetHeader(f.HeaderHtml). + SetFooter(f.FooterHtml), len(formInfo.GroupFieldHeaders) > 0, !isNotIframe, f.IsHideBackButton, f.Header) + + if f.Wrapper != nil { + content = f.Wrapper(content) + } + + h.HTML(ctx, user, types.Panel{ + Content: alert + content, + Description: template2.HTML(formInfo.Description), + Title: modules.AorBHTML(isNotIframe, template2.HTML(formInfo.Title), ""), + MiniSidebar: f.HideSideBar, + }, template.ExecuteOptions{Animation: alert == "" || ((len(animation) > 0) && animation[0]), NoCompress: f.NoCompress}) + + if isEdit { + ctx.AddHeader(constant.PjaxUrlHeader, showEditUrl) + } +} + +func (h *Handler) EditForm(ctx *context.Context) { + + param := guard.GetEditFormParam(ctx) + + if len(param.MultiForm.File) > 0 { + err := file.GetFileEngine(h.config.FileUploadEngine.Name).Upload(param.MultiForm) + if err != nil { + logger.ErrorCtx(ctx, "get file engine error: %+v", err) + if ctx.WantJSON() { + response.Error(ctx, err.Error()) + } else { + h.showForm(ctx, aAlert(ctx).Warning(err.Error()), param.Prefix, param.Param, true) + } + return + } + } + + formPanel := param.Panel.GetForm() + + for i := 0; i < len(formPanel.FieldList); i++ { + if formPanel.FieldList[i].FormType == form.File && + len(param.MultiForm.File[formPanel.FieldList[i].Field]) == 0 && + len(param.MultiForm.Value[formPanel.FieldList[i].Field+"__delete_flag"]) > 0 && + param.MultiForm.Value[formPanel.FieldList[i].Field+"__delete_flag"][0] != "1" { + param.MultiForm.Value[formPanel.FieldList[i].Field] = []string{""} + } + if formPanel.FieldList[i].FormType == form.File && + len(param.MultiForm.Value[formPanel.FieldList[i].Field+"__change_flag"]) > 0 && + param.MultiForm.Value[formPanel.FieldList[i].Field+"__change_flag"][0] != "1" { + delete(param.MultiForm.Value, formPanel.FieldList[i].Field) + } + } + + err := param.Panel.UpdateData(ctx, param.Value()) + if err != nil { + logger.ErrorCtx(ctx, "update data error: %+v", err) + if ctx.WantJSON() { + response.Error(ctx, err.Error(), map[string]interface{}{ + "token": h.authSrv().AddToken(), + }) + } else { + h.showForm(ctx, aAlert(ctx).Warning(err.Error()), param.Prefix, param.Param, true) + } + return + } + + if formPanel.Responder != nil { + formPanel.Responder(ctx) + return + } + + if ctx.WantJSON() && !param.IsIframe { + response.OkWithData(ctx, map[string]interface{}{ + "url": param.PreviousPath, + "token": h.authSrv().AddToken(), + }) + return + } + + if !param.FromList { + + if isNewUrl(param.PreviousPath, param.Prefix) { + h.showNewForm(ctx, param.Alert, param.Prefix, param.Param.DeleteEditPk().GetRouteParamStr(), true) + return + } + + if isEditUrl(param.PreviousPath, param.Prefix) { + h.showForm(ctx, param.Alert, param.Prefix, param.Param, true, false) + return + } + + ctx.HTML(http.StatusOK, fmt.Sprintf(``, param.PreviousPath)) + ctx.AddHeader(constant.PjaxUrlHeader, param.PreviousPath) + return + } + + if param.IsIframe { + ctx.HTML(http.StatusOK, fmt.Sprintf(``, language.Get("success"), param.IframeID)) + return + } + + buf := h.showTable(ctx, param.Prefix, param.Param.DeletePK().DeleteEditPk(), nil) + + ctx.HTML(http.StatusOK, buf.String()) + ctx.AddHeader(constant.PjaxUrlHeader, param.PreviousPath) +} diff --git a/plugins/admin/controller/handler.go b/plugins/admin/controller/handler.go new file mode 100644 index 0000000000000000000000000000000000000000..82a5e5407b474925054996960c6e030e11fceb1b --- /dev/null +++ b/plugins/admin/controller/handler.go @@ -0,0 +1,124 @@ +package controller + +import ( + template2 "html/template" + "regexp" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// GlobalDeferHandler is a global error handler of admin plugin. +func (h *Handler) GlobalDeferHandler(ctx *context.Context) { + + logger.Access(ctx) + + if !h.config.OperationLogOff { + h.RecordOperationLog(ctx) + } + + if err := recover(); err != nil { + logger.ErrorCtx(ctx, "GlobalDeferHandler error %+v", err) + + var ( + errMsg string + ok bool + e error + ) + + if errMsg, ok = err.(string); !ok { + if e, ok = err.(error); ok { + errMsg = e.Error() + } + } + + if errMsg == "" { + errMsg = "system error" + } + + if ctx.WantJSON() { + response.Error(ctx, errMsg) + return + } + + if ok, _ = regexp.MatchString("/edit(.*)", ctx.Path()); ok { + h.setFormWithReturnErrMessage(ctx, errMsg, "edit") + return + } + if ok, _ = regexp.MatchString("/new(.*)", ctx.Path()); ok { + h.setFormWithReturnErrMessage(ctx, errMsg, "new") + return + } + + h.HTML(ctx, auth.Auth(ctx), template.WarningPanelWithDescAndTitle(ctx, errMsg, errors.Msg, errors.Msg)) + } +} + +func (h *Handler) setFormWithReturnErrMessage(ctx *context.Context, errMsg string, kind string) { + + var ( + formInfo table.FormInfo + prefix = ctx.Query(constant.PrefixKey) + panel = h.table(prefix, ctx) + btnWord template2.HTML + f *types.FormPanel + ) + + if kind == "edit" { + f = panel.GetForm() + id := ctx.Query("id") + if id == "" { + id = ctx.Request.MultipartForm.Value[panel.GetPrimaryKey().Name][0] + } + formInfo, _ = panel.GetDataWithId(parameter.GetParam(ctx.Request.URL, + panel.GetInfo().DefaultPageSize, + panel.GetInfo().SortField, + panel.GetInfo().GetSort()).WithPKs(id)) + btnWord = f.FormEditBtnWord + } else { + f = panel.GetActualNewForm() + formInfo = panel.GetNewFormInfo() + formInfo.Title = f.Title + formInfo.Description = f.Description + btnWord = f.FormNewBtnWord + } + + queryParam := parameter.GetParam(ctx.Request.URL, panel.GetInfo().DefaultPageSize, + panel.GetInfo().SortField, panel.GetInfo().GetSort()).GetRouteParamStr() + + h.HTML(ctx, auth.Auth(ctx), types.Panel{ + Content: aAlert(ctx).Warning(errMsg) + formContent(ctx, aForm(ctx). + SetContent(formInfo.FieldList). + SetTabContents(formInfo.GroupFieldList). + SetTabHeaders(formInfo.GroupFieldHeaders). + SetTitle(template2.HTML(cases.Title(language.Und).String(kind))). + SetPrimaryKey(panel.GetPrimaryKey().Name). + SetPrefix(h.config.PrefixFixSlash()). + SetHiddenFields(map[string]string{ + form.TokenKey: h.authSrv().AddToken(), + form.PreviousKey: h.config.Url("/info/" + prefix + queryParam), + }). + SetUrl(h.config.Url("/"+kind+"/"+prefix)). + SetOperationFooter(formFooter(ctx, kind, f.IsHideContinueEditCheckBox, f.IsHideContinueNewCheckBox, + f.IsHideResetButton, btnWord)). + SetHeader(f.HeaderHtml). + SetFooter(f.FooterHtml), len(formInfo.GroupFieldHeaders) > 0, + ctx.IsIframe(), + f.IsHideBackButton, f.Header), + Description: template2.HTML(formInfo.Description), + Title: template2.HTML(formInfo.Title), + }) + + ctx.AddHeader(constant.PjaxUrlHeader, h.config.Url("/info/"+prefix+"/"+kind+queryParam)) +} diff --git a/plugins/admin/controller/install.go b/plugins/admin/controller/install.go new file mode 100644 index 0000000000000000000000000000000000000000..21e3cbd10ee189ecc27c4046b41a2a3e127b8a27 --- /dev/null +++ b/plugins/admin/controller/install.go @@ -0,0 +1,72 @@ +package controller + +import ( + "bytes" + "database/sql" + "net/http" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" +) + +// ShowInstall show install page. +func (h *Handler) ShowInstall(ctx *context.Context) { + + buffer := new(bytes.Buffer) + //template.GetInstallPage(buffer) + + //rs, _ := mysql.Query("show tables;") + //fmt.Println(rs[0]["Tables_in_godmin"]) + + //rs2, _ := mysql.Query("show columns from users") + //fmt.Println(rs2[0]["Field"]) + + ctx.HTML(http.StatusOK, buffer.String()) +} + +// CheckDatabase check the database connection. +func (h *Handler) CheckDatabase(ctx *context.Context) { + + ip := ctx.FormValue("h") + port := ctx.FormValue("po") + username := ctx.FormValue("u") + password := ctx.FormValue("pa") + databaseName := ctx.FormValue("db") + + SqlDB, err := sql.Open("mysql", username+":"+password+"@tcp("+ip+":"+port+")/"+databaseName+"?charset=utf8mb4") + if SqlDB != nil { + if SqlDB.Ping() != nil { + response.Error(ctx, "请检查参数是否设置正确") + return + } + } + + defer func() { + _ = SqlDB.Close() + }() + + if err != nil { + response.Error(ctx, "请检查参数是否设置正确") + return + + } + + //db.InitDB(username, password, port, ip, databaseName, 100, 100) + + tables := make([]map[string]interface{}, 0) + + list := "[" + + for i := 0; i < len(tables); i++ { + if i != len(tables)-1 { + list += `"` + tables[i]["Tables_in_godmin"].(string) + `",` + } else { + list += `"` + tables[i]["Tables_in_godmin"].(string) + `"` + } + } + list += "]" + + response.OkWithData(ctx, map[string]interface{}{ + "list": list, + }) +} diff --git a/plugins/admin/controller/menu.go b/plugins/admin/controller/menu.go new file mode 100644 index 0000000000000000000000000000000000000000..52786ed05b55e022e098034ad39cc54b9b5a7b86 --- /dev/null +++ b/plugins/admin/controller/menu.go @@ -0,0 +1,297 @@ +package controller + +import ( + "encoding/json" + template2 "html/template" + "net/url" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/menu" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + form2 "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" +) + +// ShowMenu show menu info page. +func (h *Handler) ShowMenu(ctx *context.Context) { + h.getMenuInfoPanel(ctx, "", "") +} + +// ShowNewMenu show new menu page. +func (h *Handler) ShowNewMenu(ctx *context.Context) { + h.showNewMenu(ctx, nil) +} + +func getPlugNameFromReferer(ctx *context.Context) string { + plugName := "" + if ref := ctx.Referer(); ref != "" { + if u, err := url.Parse(ref); err == nil && u != nil { + plugName = u.Query().Get("__plugin_name") + } + } + return plugName +} + +func getMenuPlugNameParams(plugName string) string { + params := "" + if plugName != "" { + params = "?__plugin_name=" + plugName + } + return params +} + +func (h *Handler) showNewMenu(ctx *context.Context, err error) { + + var ( + alert template2.HTML + + panel = h.table("menu", ctx) + formInfo = panel.GetNewFormInfo() + user = auth.Auth(ctx) + plugName = getPlugNameFromReferer(ctx) + ) + + if err != nil { + alert = aAlert(ctx).Warning(err.Error()) + } + + h.HTMLPlug(ctx, user, types.Panel{ + Content: alert + formContent(ctx, aForm(ctx). + SetContent(formInfo.FieldList). + SetTabContents(formInfo.GroupFieldList). + SetTabHeaders(formInfo.GroupFieldHeaders). + SetPrefix(h.config.PrefixFixSlash()). + SetPrimaryKey(panel.GetPrimaryKey().Name). + SetUrl(h.routePath("menu_edit")). + SetHiddenFields(map[string]string{ + form2.TokenKey: h.authSrv().AddToken(), + form2.PreviousKey: h.routePath("menu") + getMenuPlugNameParams(plugName), + }). + SetOperationFooter(formFooter(ctx, "new", false, false, false, + panel.GetForm().FormNewBtnWord)), + false, ctx.IsIframe(), false, ""), + Description: template2.HTML(panel.GetForm().Description), + Title: template2.HTML(panel.GetForm().Title), + }, plugName) +} + +// ShowEditMenu show edit menu page. +func (h *Handler) ShowEditMenu(ctx *context.Context) { + + plugName := getPlugNameFromReferer(ctx) + + if ctx.Query("id") == "" { + h.getMenuInfoPanel(ctx, "", template.Get(ctx, h.config.Theme).Alert().Warning(errors.WrongID)) + + ctx.AddHeader("Content-Type", "text/html; charset=utf-8") + ctx.AddHeader(constant.PjaxUrlHeader, h.routePath("menu")+getMenuPlugNameParams(plugName)) + return + } + + model := h.table("menu", ctx) + formInfo, err := model.GetDataWithId(parameter.BaseParam().WithPKs(ctx.Query("id"))) + + user := auth.Auth(ctx) + + if err != nil { + h.HTMLPlug(ctx, user, template.WarningPanelWithDescAndTitle(ctx, err.Error(), + model.GetForm().Description, model.GetForm().Title), plugName) + return + } + + h.showEditMenu(ctx, plugName, formInfo, nil) +} + +func (h *Handler) showEditMenu(ctx *context.Context, plugName string, formInfo table.FormInfo, err error) { + + var alert template2.HTML + + if err != nil { + alert = aAlert(ctx).Warning(err.Error()) + } + + params := getMenuPlugNameParams(plugName) + + panel := h.table("menu", ctx) + + h.HTMLPlug(ctx, auth.Auth(ctx), types.Panel{ + Content: alert + formContent(ctx, aForm(ctx). + SetContent(formInfo.FieldList). + SetTabContents(formInfo.GroupFieldList). + SetTabHeaders(formInfo.GroupFieldHeaders). + SetPrefix(h.config.PrefixFixSlash()). + SetPrimaryKey(panel.GetPrimaryKey().Name). + SetUrl(h.routePath("menu_edit")). + SetOperationFooter(formFooter(ctx, "edit", false, false, false, + panel.GetForm().FormEditBtnWord)). + SetHiddenFields(map[string]string{ + form2.TokenKey: h.authSrv().AddToken(), + form2.PreviousKey: h.routePath("menu") + params, + }), false, ctx.IsIframe(), false, ""), + Description: template2.HTML(formInfo.Description), + Title: template2.HTML(formInfo.Title), + }, plugName) +} + +// DeleteMenu delete the menu of given id. +func (h *Handler) DeleteMenu(ctx *context.Context) { + models.MenuWithId(guard.GetMenuDeleteParam(ctx).Id).SetConn(h.conn).Delete() + response.OkWithMsg(ctx, language.Get("delete succeed")) +} + +// EditMenu edit the menu of given id. +func (h *Handler) EditMenu(ctx *context.Context) { + + param := guard.GetMenuEditParam(ctx) + params := getMenuPlugNameParams(param.PluginName) + + if param.HasAlert() { + h.getMenuInfoPanel(ctx, param.PluginName, param.Alert) + ctx.AddHeader("Content-Type", "text/html; charset=utf-8") + ctx.AddHeader(constant.PjaxUrlHeader, h.routePath("menu")+params) + return + } + + menuModel := models.MenuWithId(param.Id).SetConn(h.conn) + + // TODO: use transaction + deleteRolesErr := menuModel.DeleteRoles() + if db.CheckError(deleteRolesErr, db.DELETE) { + formInfo, _ := h.table("menu", ctx).GetDataWithId(parameter.BaseParam().WithPKs(param.Id)) + h.showEditMenu(ctx, param.PluginName, formInfo, deleteRolesErr) + ctx.AddHeader(constant.PjaxUrlHeader, h.routePath("menu")+params) + return + } + for _, roleId := range param.Roles { + _, addRoleErr := menuModel.AddRole(roleId) + if db.CheckError(addRoleErr, db.INSERT) { + formInfo, _ := h.table("menu", ctx).GetDataWithId(parameter.BaseParam().WithPKs(param.Id)) + h.showEditMenu(ctx, param.PluginName, formInfo, addRoleErr) + ctx.AddHeader(constant.PjaxUrlHeader, h.routePath("menu")+params) + return + } + } + + _, updateErr := menuModel.Update(param.Title, param.Icon, param.Uri, param.Header, param.PluginName, param.ParentId) + + if db.CheckError(updateErr, db.UPDATE) { + formInfo, _ := h.table("menu", ctx).GetDataWithId(parameter.BaseParam().WithPKs(param.Id)) + h.showEditMenu(ctx, param.PluginName, formInfo, updateErr) + ctx.AddHeader(constant.PjaxUrlHeader, h.routePath("menu")+params) + return + } + + h.getMenuInfoPanel(ctx, param.PluginName, "") + ctx.AddHeader("Content-Type", "text/html; charset=utf-8") + ctx.AddHeader(constant.PjaxUrlHeader, h.routePath("menu")+params) +} + +// NewMenu create a new menu item. +func (h *Handler) NewMenu(ctx *context.Context) { + + param := guard.GetMenuNewParam(ctx) + params := getMenuPlugNameParams(param.PluginName) + + if param.HasAlert() { + h.getMenuInfoPanel(ctx, param.PluginName, param.Alert) + ctx.AddHeader("Content-Type", "text/html; charset=utf-8") + ctx.AddHeader(constant.PjaxUrlHeader, h.routePath("menu")+params) + return + } + + user := auth.Auth(ctx) + + // TODO: use transaction + menuModel, createErr := models.Menu().SetConn(h.conn). + New(param.Title, param.Icon, param.Uri, param.Header, param.PluginName, param.ParentId, + (menu.GetGlobalMenu(user, h.conn, ctx.Lang(), param.PluginName)).MaxOrder+1) + + if db.CheckError(createErr, db.INSERT) { + h.showNewMenu(ctx, createErr) + return + } + + for _, roleId := range param.Roles { + _, addRoleErr := menuModel.AddRole(roleId) + if db.CheckError(addRoleErr, db.INSERT) { + h.showNewMenu(ctx, addRoleErr) + return + } + } + + menu.GetGlobalMenu(user, h.conn, ctx.Lang(), param.PluginName).AddMaxOrder() + + h.getMenuInfoPanel(ctx, param.PluginName, "") + ctx.AddHeader("Content-Type", "text/html; charset=utf-8") + ctx.AddHeader(constant.PjaxUrlHeader, h.routePath("menu")+params) +} + +// MenuOrder change the order of menu items. +func (h *Handler) MenuOrder(ctx *context.Context) { + + var data []map[string]interface{} + _ = json.Unmarshal([]byte(ctx.FormValue("_order")), &data) + + models.Menu().SetConn(h.conn).ResetOrder([]byte(ctx.FormValue("_order"))) + + response.Ok(ctx) +} + +func (h *Handler) getMenuInfoPanel(ctx *context.Context, plugName string, alert template2.HTML) { + user := auth.Auth(ctx) + + if plugName == "" { + plugName = ctx.Query("__plugin_name") + } + + tree := aTree(ctx). + SetTree((menu.GetGlobalMenu(user, h.conn, ctx.Lang(), plugName)).List). + SetEditUrl(h.routePath("menu_edit_show")). + SetUrlPrefix(h.config.Prefix()). + SetDeleteUrl(h.routePath("menu_delete")). + SetOrderUrl(h.routePath("menu_order")). + GetContent() + + var ( + header = aTree(ctx).GetTreeHeader() + box = aBox(ctx).SetHeader(header).SetBody(tree).GetContent() + col1 = aCol(ctx).SetSize(types.SizeMD(6)).SetContent(box).GetContent() + panel = h.table("menu", ctx) + formInfo = panel.GetNewFormInfo() + ) + + newForm := menuFormContent(ctx, aForm(ctx). + SetPrefix(h.config.PrefixFixSlash()). + SetUrl(h.routePath("menu_new")). + SetPrimaryKey(panel.GetPrimaryKey().Name). + SetHiddenFields(map[string]string{ + form2.TokenKey: h.authSrv().AddToken(), + form2.PreviousKey: h.routePath("menu") + getMenuPlugNameParams(plugName), + }). + SetOperationFooter(formFooter(ctx, "menu", false, false, false, + panel.GetForm().FormNewBtnWord)). + SetTitle("New"). + SetContent(formInfo.FieldList). + SetTabContents(formInfo.GroupFieldList). + SetTabHeaders(formInfo.GroupFieldHeaders)) + + col2 := aCol(ctx).SetSize(types.SizeMD(6)).SetContent(newForm).GetContent() + + row := aRow(ctx).SetContent(col1 + col2).GetContent() + + h.HTMLPlug(ctx, user, types.Panel{ + Content: alert + row, + Description: "Menus Manage", + Title: "Menus", + }, plugName) +} diff --git a/plugins/admin/controller/new.go b/plugins/admin/controller/new.go new file mode 100644 index 0000000000000000000000000000000000000000..6d91b3eb2724801b091726f2972ec05126ff8017 --- /dev/null +++ b/plugins/admin/controller/new.go @@ -0,0 +1,171 @@ +package controller + +import ( + "fmt" + template2 "html/template" + "net/http" + + "github.com/GoAdminGroup/go-admin/template" + + "github.com/GoAdminGroup/go-admin/modules/logger" + + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/file" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + form2 "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + "github.com/GoAdminGroup/go-admin/template/types" +) + +// ShowNewForm show a new form page. +func (h *Handler) ShowNewForm(ctx *context.Context) { + param := guard.GetShowNewFormParam(ctx) + h.showNewForm(ctx, "", param.Prefix, param.Param.GetRouteParamStr(), false) +} + +func (h *Handler) showNewForm(ctx *context.Context, alert template2.HTML, prefix, paramStr string, isNew bool) { + + var ( + user = auth.Auth(ctx) + panel = h.table(prefix, ctx) + formInfo = panel.GetNewFormInfo() + infoUrl = h.routePathWithPrefix("info", prefix) + paramStr + newUrl = h.routePathWithPrefix("new", prefix) + showNewUrl = h.routePathWithPrefix("show_new", prefix) + paramStr + referer = ctx.Referer() + f = panel.GetActualNewForm() + isNotIframe = ctx.Query(constant.IframeKey) != "true" + ) + + if referer != "" && !isInfoUrl(referer) && !isNewUrl(referer, ctx.Query(constant.PrefixKey)) { + infoUrl = referer + } + + hiddenFields := map[string]string{ + form2.TokenKey: h.authSrv().AddToken(), + form2.PreviousKey: infoUrl, + } + + if ctx.Query(constant.IframeKey) != "" { + hiddenFields[constant.IframeKey] = ctx.Query(constant.IframeKey) + } + + if ctx.Query(constant.IframeIDKey) != "" { + hiddenFields[constant.IframeIDKey] = ctx.Query(constant.IframeIDKey) + } + + content := formContent(ctx, aForm(ctx). + SetPrefix(h.config.PrefixFixSlash()). + SetFieldsHTML(f.HTMLContent). + SetContent(formInfo.FieldList). + SetTabContents(formInfo.GroupFieldList). + SetTabHeaders(formInfo.GroupFieldHeaders). + SetUrl(newUrl). + SetAjax(f.AjaxSuccessJS, f.AjaxErrorJS). + SetInputWidth(f.InputWidth). + SetHeadWidth(f.HeadWidth). + SetLayout(f.Layout). + SetPrimaryKey(panel.GetPrimaryKey().Name). + SetHiddenFields(hiddenFields). + SetTitle(f.FormNewTitle). + SetOperationFooter(formFooter(ctx, "new", f.IsHideContinueEditCheckBox, f.IsHideContinueNewCheckBox, + f.IsHideResetButton, f.FormNewBtnWord)). + SetHeader(f.HeaderHtml). + SetFooter(f.FooterHtml), len(formInfo.GroupFieldHeaders) > 0, !isNotIframe, f.IsHideBackButton, f.Header) + + if f.Wrapper != nil { + content = f.Wrapper(content) + } + + h.HTML(ctx, user, types.Panel{ + Content: alert + content, + Description: template2.HTML(f.Description), + Title: modules.AorBHTML(isNotIframe, template2.HTML(f.Title), ""), + MiniSidebar: f.HideSideBar, + }, template.ExecuteOptions{Animation: alert == ""}) + + if isNew { + ctx.AddHeader(constant.PjaxUrlHeader, showNewUrl) + } +} + +// NewForm insert a table row into database. +func (h *Handler) NewForm(ctx *context.Context) { + + param := guard.GetNewFormParam(ctx) + + // process uploading files, only support local storage + if len(param.MultiForm.File) > 0 { + err := file.GetFileEngine(h.config.FileUploadEngine.Name).Upload(param.MultiForm) + if err != nil { + logger.ErrorCtx(ctx, "get file engine error: %+v", err) + if ctx.WantJSON() { + response.Error(ctx, err.Error()) + } else { + h.showNewForm(ctx, aAlert(ctx).Warning(err.Error()), param.Prefix, param.Param.GetRouteParamStr(), true) + } + return + } + } + + err := param.Panel.InsertData(ctx, param.Value()) + if err != nil { + logger.ErrorCtx(ctx, "insert data error: %+v", err) + if ctx.WantJSON() { + response.Error(ctx, err.Error(), map[string]interface{}{ + "token": h.authSrv().AddToken(), + }) + } else { + h.showNewForm(ctx, aAlert(ctx).Warning(err.Error()), param.Prefix, param.Param.GetRouteParamStr(), true) + } + return + } + + f := param.Panel.GetActualNewForm() + + if f.Responder != nil { + f.Responder(ctx) + return + } + + if ctx.WantJSON() && !param.IsIframe { + response.OkWithData(ctx, map[string]interface{}{ + "url": param.PreviousPath, + "token": h.authSrv().AddToken(), + }) + return + } + + if !param.FromList { + + if isNewUrl(param.PreviousPath, param.Prefix) { + h.showNewForm(ctx, param.Alert, param.Prefix, param.Param.GetRouteParamStr(), true) + return + } + + ctx.HTML(http.StatusOK, fmt.Sprintf(``, param.PreviousPath)) + ctx.AddHeader(constant.PjaxUrlHeader, param.PreviousPath) + return + } + + if param.IsIframe { + ctx.HTML(http.StatusOK, fmt.Sprintf(``, language.Get("success"), param.IframeID)) + return + } + + buf := h.showTable(ctx, param.Prefix, param.Param, nil) + + ctx.HTML(http.StatusOK, buf.String()) + ctx.AddHeader(constant.PjaxUrlHeader, h.routePathWithPrefix("info", param.Prefix)+param.Param.GetRouteParamStr()) +} diff --git a/plugins/admin/controller/operation.go b/plugins/admin/controller/operation.go new file mode 100644 index 0000000000000000000000000000000000000000..d1ef2e6a29b526394f0b81dc09cf23263875409d --- /dev/null +++ b/plugins/admin/controller/operation.go @@ -0,0 +1,37 @@ +package controller + +import ( + "encoding/json" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" +) + +func (h *Handler) Operation(ctx *context.Context) { + id := ctx.Query("__goadmin_op_id") + if !h.OperationHandler(config.Url("/operation/"+id), ctx) { + errMsg := "not found" + if ctx.Headers(constant.PjaxHeader) == "" && ctx.Method() != "GET" { + response.BadRequest(ctx, errMsg) + } else { + response.Alert(ctx, errMsg, errMsg, errMsg, h.conn, h.navButtons) + } + return + } +} + +// RecordOperationLog record all operation logs, store into database. +func (h *Handler) RecordOperationLog(ctx *context.Context) { + if user, ok := ctx.UserValue["user"].(models.UserModel); ok { + var input []byte + form := ctx.Request.MultipartForm + if form != nil { + input, _ = json.Marshal((*form).Value) + } + + models.OperationLog().SetConn(h.conn).New(user.Id, ctx.Path(), ctx.Method(), ctx.LocalIP(), string(input)) + } +} diff --git a/plugins/admin/controller/plugins.go b/plugins/admin/controller/plugins.go new file mode 100644 index 0000000000000000000000000000000000000000..136e90081575517ab48c04d35dfb9f32bfd163f8 --- /dev/null +++ b/plugins/admin/controller/plugins.go @@ -0,0 +1,534 @@ +package controller + +import ( + "bytes" + "fmt" + "html/template" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/GoAdminGroup/go-admin/modules/system" + + "github.com/GoAdminGroup/go-admin/modules/logger" + + "github.com/GoAdminGroup/go-admin/modules/config" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/remote_server" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + template2 "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/form" + "github.com/GoAdminGroup/html" + "github.com/gin-gonic/gin" +) + +func (h *Handler) Plugins(ctx *context.Context) { + list := plugins.Get() + size := types.Size(6, 3, 2) + rows := template.HTML("") + if h.config.IsNotProductionEnvironment() { + getMoreCover := config.Url("/assets/dist/img/plugin_more.png") + list = list.Add(plugins.NewBasePluginWithInfoAndIndexURL(plugins.Info{ + Title: "get more plugins", + Name: "", + MiniCover: getMoreCover, + Cover: getMoreCover, + }, config.Url("/plugins/store"), true)) + } + for i := 0; i < len(list); i += 6 { + box1 := aBox(ctx). + SetBody(h.pluginBox(ctx, GetPluginBoxParamFromPlug(list[i]))). + GetContent() + content := aCol(ctx).SetSize(size).SetContent(box1).GetContent() + offset := len(list) - i + if offset > 6 { + offset = 6 + } + for j := i + 1; j < offset; j++ { + box2 := aBox(ctx). + SetBody(h.pluginBox(ctx, GetPluginBoxParamFromPlug(list[j]))). + GetContent() + content += aCol(ctx).SetSize(size).SetContent(box2).GetContent() + } + rows += aRow(ctx).SetContent(content).GetContent() + } + h.HTML(ctx, auth.Auth(ctx), types.Panel{ + Content: rows, + CSS: pluginsPageCSS, + Description: language.GetFromHtml("plugins"), + Title: language.GetFromHtml("plugins"), + }) +} + +func (h *Handler) PluginStore(ctx *context.Context) { + var ( + size = types.Size(12, 6, 4) + list, page = plugins.GetAll( + remote_server.GetOnlineReq{ + Page: ctx.Query("page"), + Free: ctx.Query("free"), + PageSize: ctx.Query("page_size"), + Filter: ctx.Query("filter"), + Order: ctx.Query("order"), + Lang: h.config.Language, + Version: system.Version(), + CategoryId: ctx.Query("category_id"), + }, ctx.Cookie(remote_server.TokenKey)) + rows = template.HTML(page.HTML) + ) + + if ctx.Query("page") == "" && len(list) == 0 { + h.HTML(ctx, auth.Auth(ctx), types.Panel{ + Content: pluginStore404(), + CSS: template.CSS(`.plugin-store-404-content { + margin: auto; + width: 80%; + text-align: center; + color: #9e9e9e; + font-size: 17px; + height: 250px; + line-height: 250px; +}`), + Description: language.GetFromHtml("plugin store"), + Title: language.GetFromHtml("plugin store"), + }) + return + } + + for i := 0; i < len(list); i += 3 { + box1 := aBox(ctx). + SetBody(h.pluginStoreBox(ctx, GetPluginBoxParamFromPlug(list[i]))). + GetContent() + col1 := aCol(ctx).SetSize(size).SetContent(box1).GetContent() + box2, col2, box3, col3 := template.HTML(""), template.HTML(""), template.HTML(""), template.HTML("") + if i+1 < len(list) { + box2 = aBox(ctx). + SetBody(h.pluginStoreBox(ctx, GetPluginBoxParamFromPlug(list[i+1]))). + GetContent() + col2 = aCol(ctx).SetSize(size).SetContent(box2).GetContent() + if i+2 < len(list) { + box3 = aBox(ctx). + SetBody(h.pluginStoreBox(ctx, GetPluginBoxParamFromPlug(list[i+2]))). + GetContent() + col3 = aCol(ctx).SetSize(size).SetContent(box3).GetContent() + } + } + rows += aRow(ctx).SetContent(col1 + col2 + col3).GetContent() + } + + detailPopupModal := template2.Default(ctx).Popup().SetID("detail-popup-modal"). + SetTitle(plugWordHTML("plugin detail")). + SetBody(pluginsPageDetailPopupBody()). + SetWidth("730px"). + SetHeight("400px"). + SetFooter("1"). + GetContent() + + buyPopupModal := template2.Default(ctx).Popup().SetID("buy-popup-modal"). + SetTitle(plugWordHTML("plugin detail")). + SetWidth("730px"). + SetHeight("400px"). + SetFooter("1"). + GetContent() + + loginPopupModal := template2.Default(ctx).Popup().SetID("login-popup-modal"). + SetTitle(plugWordHTML("login to goadmin member system")). + SetBody(aForm(ctx).SetContent(types.FormFields{ + {Field: "name", Head: plugWord("account"), FormType: form.Text, Editable: true}, + {Field: "password", Head: plugWord("password"), FormType: form.Password, Editable: true, + HelpMsg: template.HTML(fmt.Sprintf(plugWord("no account? click %s here %s to register."), + "", ""))}, + }).GetContent()). + SetWidth("540px"). + SetHeight("250px"). + SetFooterHTML(template.HTML(``)). + GetContent() + + h.HTML(ctx, auth.Auth(ctx), types.Panel{ + Content: rows + detailPopupModal + buyPopupModal + loginPopupModal, + CSS: pluginsStorePageCSS + template.CSS(page.CSS), + JS: template.JS(page.JS) + GetPluginsPageJS(PluginsPageJSData{Prefix: h.config.Prefix()}), + Description: language.GetFromHtml("plugin store"), + Title: language.GetFromHtml("plugin store"), + }) +} + +func (h *Handler) PluginDetail(ctx *context.Context) { + + name := ctx.Query("name") + + plug, exist := plugins.FindByNameAll(name) + if !exist { + ctx.JSON(http.StatusOK, gin.H{ + "code": 400, + "msg": "bad request", + }) + return + } + + info := plug.GetInfo() + + if info.MiniCover == "" { + info.MiniCover = config.Url("/assets/dist/img/plugin_default.png") + } + + ctx.JSON(http.StatusOK, gin.H{ + "code": 0, + "msg": "ok", + "data": gin.H{ + "mini_cover": info.MiniCover, + "title": language.GetWithScope(info.Title, name), + "author": fmt.Sprintf(plugWord("provided by %s"), language.GetWithScope(info.Author, name)), + "introduction": language.GetWithScope(info.Description, name), + "website": language.GetWithScope(info.Website, name), + "version": language.GetWithScope(info.Version, name), + "created_at": language.GetWithScope(info.CreateDate.Format("2006-01-02"), name), + "updated_at": language.GetWithScope(info.UpdateDate.Format("2006-01-02"), name), + "downloaded": info.Downloaded, + "download_reboot": plugins.Exist(plug), + "skip": info.SkipInstallation, + "uuid": info.Uuid, + "upgrade": info.CanUpdate, + "install": plug.IsInstalled(), + "free": info.IsFree(), + }, + }) +} + +type PluginBoxParam struct { + Info plugins.Info + Install bool + Upgrade bool + Skip bool + DownloadReboot bool + Name string + IndexURL string +} + +func GetPluginBoxParamFromPlug(plug plugins.Plugin) PluginBoxParam { + return PluginBoxParam{ + Info: plug.GetInfo(), + Install: plug.IsInstalled(), + Upgrade: plug.GetInfo().CanUpdate, + Skip: plug.GetInfo().SkipInstallation, + DownloadReboot: plugins.Exist(plug), + Name: plug.Name(), + IndexURL: plug.GetIndexURL(), + } +} + +func (h *Handler) pluginStoreBox(ctx *context.Context, param PluginBoxParam) template.HTML { + cover := template2.HTML(param.Info.MiniCover) + if cover == template2.HTML("") { + cover = template2.HTML(config.Url("/assets/dist/img/plugin_default.png")) + } + col1 := html.DivEl().SetClass("plugin-store-item-img"). + SetContent(aImage(ctx). + SetSrc(cover). + SetHeight("110px"). + SetWidth("110px"). + GetContent()). + Get() + footer := html.ButtonEl().SetClass(pluginBtnClass("plugin-info")...). + SetAttr("onclick", `pluginDetail('`+param.Name+`','`+param.Info.Uuid+`')`). + SetContent(plugWordHTML("info")). + Get() + if param.Install { + if param.Upgrade { + footer += html.ButtonEl().SetClass(pluginBtnClass("installation")...). + SetAttr("onclick", `pluginDownload('`+param.Name+`', this)`). + SetContent(plugWordHTML("upgrade")). + Get() + } + } else { + if param.Info.Downloaded { + if param.DownloadReboot { + if !param.Skip && !param.Install { + footer += html.AEl().SetAttr("href", h.config.Url(`/info/plugin_`+param.Name+`/new`)). + SetContent( + html.ButtonEl().SetClass(pluginBtnClass("installation")...). + SetAttr("onclick", `pluginInstall('`+param.Name+`')`). + SetContent(plugWordHTML("install")). + Get(), + ).Get() + } + } else { + footer += html.ButtonEl().SetClass(pluginBtnClass("installation")...). + SetAttr("onclick", `pluginRebootInstall()`). + SetContent(plugWordHTML("install")). + Get() + } + } else { + if param.Info.IsFree() || param.Info.HasBought { + footer += html.ButtonEl().SetClass(pluginBtnClass("installation")...). + SetAttr("onclick", `pluginDownload('`+param.Name+`', this)`). + SetContent(plugWordHTML("download")). + Get() + } else { + footer += html.ButtonEl().SetClass(pluginBtnClass("installation")...). + SetAttr("onclick", `pluginBuy('`+param.Name+`', '`+param.Info.Uuid+`')`). + SetContent(plugWordHTML("buy")). + Get() + } + } + } + + col2 := html.DivEl().SetClass("plugin-item-content").SetContent( + html.DivEl().SetClass("plugin-item-content-title"). + SetContent(language.GetFromHtml(template.HTML(param.Info.Title), param.Name)). + Get() + + html.DivEl().SetClass("plugin-item-content-description"). + SetContent(language.GetFromHtml(template.HTML(param.Info.Description), param.Name)). + Get() + + footer, + ).Get() + + return html.Div(col1+col2, html.M{"clear": "both"}) +} + +func (h *Handler) pluginBox(ctx *context.Context, param PluginBoxParam) template.HTML { + cover := template2.HTML(param.Info.MiniCover) + if cover == template2.HTML("") { + cover = "/admin/assets/dist/img/plugin_default.png" + } + + jump := param.IndexURL + label := template.HTML("") + if !param.Install { + jump = h.config.Url("/info/plugin_" + param.Name + "/new") + label = html.SpanEl().SetClass("plugin-item-label").SetContent(language.GetFromHtml("uninstalled")).Get() + } + col1 := html.AEl().SetContent(html.DivEl().SetClass("plugin-item-img"). + SetContent(aImage(ctx). + SetSrc(cover). + GetContent()+ + html.PEl().SetContent(language.GetFromHtml(template.HTML(param.Info.Title), param.Name)). + SetClass("plugin-item-title").Get()). + Get()+label).SetAttr("href", jump).Get() + return col1 +} + +func (h *Handler) PluginDownload(ctx *context.Context) { + + if !h.config.Debug { + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 400, + "msg": plugWord("change to debug mode first"), + }) + return + } + + name := ctx.FormValue("name") + + if name == "" { + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 400, + "msg": plugWord("download fail, wrong name"), + }) + return + } + + plug, exist := plugins.FindByNameAll(name) + + if !exist { + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 400, + "msg": plugWord("download fail, plugin not exist"), + }) + return + } + + if !plug.GetInfo().IsFree() && !plug.GetInfo().HasBought { + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 400, + "msg": plugWord("download fail, plugin has not been bought"), + }) + return + } + + downloadURL := plug.GetInfo().Url + extraDownloadURL := plug.GetInfo().ExtraDownloadUrl + + if !plug.GetInfo().IsFree() { + var err error + downloadURL, extraDownloadURL, err = remote_server.GetDownloadURL(plug.GetInfo().Uuid, ctx.Cookie(remote_server.TokenKey)) + if err != nil { + logger.ErrorCtx(ctx, "download plugins error: %+v", err) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 500, + "msg": plugWord("download fail"), + }) + return + } + } + + tempFile := "./temp-" + utils.Uuid(10) + ".zip" + + err := utils.DownloadTo(downloadURL, tempFile) + + if err != nil { + logger.ErrorCtx(ctx, "download plugins error %+v", map[string]interface{}{ + "error": err, + "downloadURL": downloadURL, + }) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 500, + "msg": plugWord("download fail"), + }) + return + } + + gopath := os.Getenv("GOPATH") + + if gopath == "" { + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 500, + "msg": plugWord("golang develop environment does not exist"), + }) + return + } + + gomodule := os.Getenv("GO111MODULE") + base := filepath.Dir(plug.GetInfo().ModulePath) + installPath := "" + + if gomodule == "off" { + installPath = filepath.ToSlash(gopath + "/src/" + base) + } else { + installPath = filepath.ToSlash(gopath + "/pkg/mod/" + base) + } + + err = utils.UnzipDir(tempFile, installPath) + + if err != nil { + logger.ErrorCtx(ctx, "download plugins, unzip error %+v", map[string]interface{}{ + "error": err, + "installPath": installPath, + }) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 500, + "msg": plugWord("download fail"), + }) + return + } + + _ = os.Remove(tempFile) + + if len(downloadURL) > 18 && downloadURL[:18] == "https://github.com" { + name := filepath.Base(plug.GetInfo().ModulePath) + version := strings.ReplaceAll(plug.GetInfo().Version, "v", "") + rawPath := installPath + "/" + name + nowPath := rawPath + "-" + version + if gomodule == "off" { + err = os.Rename(nowPath, rawPath) + } else { + err = os.Rename(nowPath, rawPath+"@"+plug.GetInfo().Version) + } + if err != nil { + logger.ErrorCtx(ctx, "download plugins, rename error %+v", map[string]interface{}{ + "error": err, + "nowPath": nowPath, + "rawPath": rawPath, + }) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 500, + "msg": plugWord("download fail"), + }) + return + } + } else if gomodule != "off" { + rawPath := installPath + "/" + name + err = os.Rename(rawPath, rawPath+"@"+plug.GetInfo().Version) + if err != nil { + logger.ErrorCtx(ctx, "download plugins, rename error %+v", map[string]interface{}{ + "error": err, + "rawPath": rawPath, + }) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 500, + "msg": plugWord("download fail"), + }) + return + } + } + + if h.config.BootstrapFilePath != "" && utils.FileExist(h.config.BootstrapFilePath) { + content, err := os.ReadFile(h.config.BootstrapFilePath) + if err != nil { + logger.ErrorCtx(ctx, "read bootstrap file error: %+v", err) + } else { + err = os.WriteFile(h.config.BootstrapFilePath, []byte(string(content)+` +import _ "`+plug.GetInfo().ModulePath+`"`), 0644) + if err != nil { + logger.ErrorCtx(ctx, "write bootstrap file error: %+v", err) + } + } + } + + if h.config.GoModFilePath != "" && utils.FileExist(h.config.GoModFilePath) && + plug.GetInfo().CanUpdate && plug.GetInfo().OldVersion != "" { + content, _ := os.ReadFile(h.config.BootstrapFilePath) + src := plug.GetInfo().ModulePath + " " + plug.GetInfo().OldVersion + dist := plug.GetInfo().ModulePath + " " + plug.GetInfo().Version + content = bytes.ReplaceAll(content, []byte(src), []byte(dist)) + _ = os.WriteFile(h.config.BootstrapFilePath, content, 0644) + } + + // TODO: 实现运行环境与编译环境隔离 + + if plug.GetInfo().ExtraDownloadUrl != "" { + err = utils.DownloadTo(extraDownloadURL, "./"+plug.Name()+"_extra_"+ + fmt.Sprintf("%d", time.Now().Unix())+".zip") + if err != nil { + logger.ErrorCtx(ctx, "failed to download "+plug.Name()+" extra data: %+v", err) + } + } + + plug.(*plugins.BasePlugin).Info.Downloaded = true + plug.(*plugins.BasePlugin).Info.CanUpdate = false + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 0, + "msg": plugWord("download success, restart to install"), + }) +} + +func (h *Handler) ServerLogin(ctx *context.Context) { + param := guard.GetServerLoginParam(ctx) + res := remote_server.Login(param.Account, param.Password) + if res.Code == 0 && res.Data.Token != "" { + ctx.SetCookie(&http.Cookie{ + Name: remote_server.TokenKey, + Value: res.Data.Token, + Expires: time.Now().Add(time.Second * time.Duration(res.Data.Expire/1000)), + HttpOnly: true, + Path: "/", + }) + } + ctx.JSON(http.StatusOK, gin.H{ + "code": res.Code, + "data": res.Data, + "msg": res.Msg, + }) +} + +func pluginBtnClass(class ...string) []string { + return append([]string{"btn", "btn-primary"}, class...) +} + +func plugWord(word string) string { + return language.GetWithScope(word, "plugin") +} + +func plugWordHTML(word template.HTML) template.HTML { + return language.GetFromHtml(word, "plugin") +} diff --git a/plugins/admin/controller/plugins_tmpl.go b/plugins/admin/controller/plugins_tmpl.go new file mode 100644 index 0000000000000000000000000000000000000000..1a3920c8ba9fa0dee1b0d9ca6a20cfde1c13b2fd --- /dev/null +++ b/plugins/admin/controller/plugins_tmpl.go @@ -0,0 +1,390 @@ +package controller + +import ( + "bytes" + "html/template" + + "github.com/GoAdminGroup/go-admin/modules/remote_server" + + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/logger" +) + +func GetPluginsPageJS(data PluginsPageJSData) template.JS { + t := template.New("plugins_page_js").Funcs(map[string]interface{}{ + "lang": language.Get, + "plugWord": plugWord, + }) + t, err := t.Parse(pluginsPageJS) + if err != nil { + logger.Error(err) + return "" + } + buf := new(bytes.Buffer) + err = t.Execute(buf, data) + if err != nil { + logger.Error(err) + return "" + } + return template.JS(buf.String()) + +} + +type PluginsPageJSData struct { + Prefix string +} + +var pluginsPageJS = ` + +function pluginInstall(name){ + location.href="{{.Prefix}}/info/plugin_"+name+"/new" +} + +var downloadLock = false; +const apiTokenKey = "GOADMIN_SERVER_API_TOKEN"; +const apiTokenExpireKey = "GOADMIN_SERVER_API_TOKEN_EXPIRE"; +const serverHost = "` + remote_server.ServerHost + `" + +function login() { + $.ajax({ + type: 'POST', + url: '{{.Prefix}}/server/login', + dataType: "json", + contentType: "application/json", + data: JSON.stringify({ + "account": $("#login-popup-modal form input.name").val(), + "password": $("#login-popup-modal form input.password").val() + }), + success: function (data) { + if (data.code == 0) { + localStorage.setItem(apiTokenKey, data.data.token); + localStorage.setItem(apiTokenExpireKey, (new Date()).getTime() + data.data.expire); + $('#login-popup-modal').modal('hide'); + if (typeof loginSuccessCallback != "undefined") { + loginSuccessCallback(data); + } + $.pjax.reload('#pjax-container'); + } else { + swal({ + type: "error", + title: data.msg, + showCancelButton: false, + html: true, + confirmButtonColor: "#3c8dbc", + confirmButtonText: '{{lang "got it"}}', + }) + } + }, + error: function (data) { + swal({ + type: "error", + title: data.responseJSON.msg ? data.responseJSON.msg : "error", + showCancelButton: false, + html: true, + confirmButtonColor: "#3c8dbc", + confirmButtonText: '{{lang "got it"}}', + }) + } + }); +} + +function pluginBuy(name, uuid) { + window.open(serverHost + "/plugins/detail/" + uuid); +} + +function pluginDetail(name, uuid) { + $.ajax({ + dataType: 'json', + type: 'POST', + url: '{{.Prefix}}/plugin/detail?name=' + name, + async: 'true', + success: function (data) { + if (data.code == 0) { + let head_ele = "#detail-popup-modal .plugin-detail .plugin-detail-head " + $(head_ele + ".plugin-detail-head-logo img").attr('src', data.data.mini_cover); + $(head_ele + ".plugin-detail-head-title .plugin-detail-title").html(data.data.title); + $(head_ele + ".plugin-detail-head-title .plugin-detail-provider").html(data.data.author); + let item_ele = "#detail-popup-modal .plugin-detail .plugin-detail-info .plugin-detail-info-item"; + $(item_ele).eq(0).find(".plugin-detail-info-item-content").html(data.data.introduction); + $(item_ele).eq(1).find(".plugin-detail-info-item-content").html(data.data.website); + $(item_ele).eq(2).find(".plugin-detail-info-item-content").html(data.data.version); + $(item_ele).eq(3).find(".plugin-detail-info-item-content").html(data.data.created_at); + $(item_ele).eq(4).find(".plugin-detail-info-item-content").html(data.data.updated_at); + $(item_ele).eq(5).find("a").attr("href", "https://www.go-admin.cn/plugins/detail/" + data.data.uuid); + + let footer_ele = "#detail-popup-modal .modal-footer .btn.btn-primary"; + + if (data.data.install) { + if (data.data.upgrade) { + $(footer_ele).html('{{plugWord "upgrade"}}').attr("onclick", "pluginDownload('"+ name +"', this)"); + $(footer_ele).show(); + } else { + $(footer_ele).hide(); + } + } else { + if (data.data.downloaded) { + if (data.data.download_reboot) { + if (data.data.skip) { + $(footer_ele).html('{{plugWord "install"}}') + } else { + $(footer_ele).html('{{plugWord "install"}}').attr("onclick", "pluginInstall('"+ name +"')") + } + } else { + $(footer_ele).html('{{plugWord "install"}}').attr("onclick", 'pluginRebootInstall()') + } + } else { + if (data.data.free) { + $(footer_ele).html('{{plugWord "download"}}').attr("onclick", "pluginInstall('"+ name +"', this)") + } else { + $(footer_ele).html('{{plugWord "buy"}}').attr("onclick", "pluginBuy('"+ name +"', '"+ uuid +"')") + } + } + $(footer_ele).show(); + } + + $("#detail-popup-modal").modal('show'); + } else { + swal({ + type: "error", + title: data.msg, + showCancelButton: false, + html: true, + confirmButtonColor: "#3c8dbc", + confirmButtonText: '{{lang "got it"}}', + }) + } + }, + error: function (data) { + swal({ + type: "error", + title: data.responseJSON.msg ? data.responseJSON.msg : "error", + showCancelButton: false, + html: true, + confirmButtonColor: "#3c8dbc", + confirmButtonText: '{{lang "got it"}}', + }) + } + }); +} + +function pluginDownload(name, ele) { + if (downloadLock) { + return + } + NProgress.start(); + downloadLock = true; + $(ele).html(' {{plugWord "downloading"}}') + $.ajax({ + dataType: 'json', + type: 'POST', + url: '{{.Prefix}}/plugin/download', + async: 'true', + data: { + 'name': name + }, + success: function (data) { + NProgress.done(); + downloadLock = false; + if (data.code == 0) { + $(ele).attr('onclick', 'pluginRebootInstall()') + $(ele).html('{{plugWord "install"}}') + swal({ + type: "success", + title: data.msg, + showCancelButton: false, + confirmButtonColor: "#3c8dbc", + confirmButtonText: '{{lang "got it"}}', + }) + } else { + $(ele).html('{{plugWord "download"}}') + swal({ + type: "error", + title: data.msg, + showCancelButton: false, + text: "点这里手动下载", + html: true, + confirmButtonColor: "#3c8dbc", + confirmButtonText: '{{lang "got it"}}', + }) + } + }, + error: function (data) { + downloadLock = false; + $(ele).html('{{plugWord "download"}}') + alert('download fail'); + } + }); +} + +function pluginRebootInstall() { + swal({ + type: "success", + title: "{{plugWord "restart to install"}}", + showCancelButton: false, + confirmButtonColor: "#3c8dbc", + confirmButtonText: '{{lang "got it"}}', + }) +} +` + +var pluginsPageCSS = template.CSS(` + .plugin-item-img { + text-align: center; + } + .plugin-item-img img { + width: 100%; + height: auto; + border: 1px solid #f5f4f4; + } + .plugin-item-title { + margin-top: 10px; + color: #717171; + overflow: hidden; + text-overflow: ellipsis; + word-break: break-all; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + } + @media screen and (min-width: 1200px) { + .pjax-container-content .row .col-lg-2 { + width: 14%; + max-width: 200px; + } + } + .plugin-item-label { + color: white; + background-color: red; + padding: 4px; + border-radius: 3px; + font-size: 12px; + position: absolute; + top: 5px; + right: 5px; + } +`) + +var pluginsStorePageCSS = template.CSS(` + .plugin-item-content { + margin-left: 15px; + } + .plugin-store-item-img img, .plugin-detail-head-logo img { + border: 1px solid #f5f4f4; + } + .plugin-item-content-title { + font-size: 15px; + margin-bottom: 6px; + font-weight: bold; + padding-top: 2px; + } + .plugin-item-content { + position: absolute; + margin-left: 121px; + padding-right: 10px; + top: 7px; + width: 100%; + padding-right: 139px; + } + .plugin-item-content-description { + overflow: hidden; + text-overflow: ellipsis; + word-break: break-all; + display: -webkit-box; + font-size: 15px; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + height: 42px; + } + .installation { + float: right; + margin-top: 10px; + } + .plugin-info { + float: right; + margin-top: 10px; + margin-left: 10px; + } + .plugin-detail { + padding: 10px; + } + .plugin-detail-head { + clear: both; + height: 112px; + margin-bottom: 33px; + } + .plugin-detail-title { + font-size: 30px; + } + .plugin-detail-provider { + font-size: 15px; + margin-top: 4px; + } + .plugin-detail-head-logo { + width: 120px; + float: left; + } + .plugin-detail-head-title { + float: left; + margin-left: 10px; + } + .plugin-detail-info-item { + clear: both; + height: 15px; + margin-bottom: 17px; + } + .plugin-detail-info-item-head { + width: 120px; + float: left; + font-weight: bold; + } + .plugin-detail-info-item-content { + float: left; + margin-left: 10px; + } +`) + +var pluginStore404 = func() template.HTML { + return template.HTML(` +

    ` + plugWord("can not connect to the goadmin remote server") + `

    +`) +} + +var pluginsPageDetailPopupBody = func() template.HTML { + return template.HTML(` +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    ` + plugWord("introduction") + `
    +
    +
    +
    +
    ` + plugWord("website") + `
    +
    +
    +
    +
    ` + plugWord("version") + `
    +
    +
    +
    +
    ` + plugWord("created at") + `
    +
    +
    +
    +
    ` + plugWord("updated at") + `
    +
    +
    + +
    +
    `) +} diff --git a/plugins/admin/controller/show.go b/plugins/admin/controller/show.go new file mode 100644 index 0000000000000000000000000000000000000000..07247526146f59b35982931a7efbdcb74fdd818c --- /dev/null +++ b/plugins/admin/controller/show.go @@ -0,0 +1,492 @@ +package controller + +import ( + "bytes" + "crypto/md5" + "fmt" + template2 "html/template" + "mime" + "net/http" + "path" + "strconv" + "strings" + "time" + + "github.com/360EntSecGroup-Skylar/excelize" + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/guard" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/icon" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/action" + form2 "github.com/GoAdminGroup/go-admin/template/types/form" + "github.com/GoAdminGroup/html" +) + +// ShowInfo show info page. +func (h *Handler) ShowInfo(ctx *context.Context) { + + prefix := ctx.Query(constant.PrefixKey) + + panel := h.table(prefix, ctx) + + if panel.GetOnlyUpdateForm() { + ctx.Redirect(h.routePathWithPrefix("show_edit", prefix)) + return + } + + if panel.GetOnlyNewForm() { + ctx.Redirect(h.routePathWithPrefix("show_new", prefix)) + return + } + + if panel.GetOnlyDetail() { + ctx.Redirect(h.routePathWithPrefix("detail", prefix)) + return + } + + params := parameter.GetParam(ctx.Request.URL, panel.GetInfo().DefaultPageSize, panel.GetInfo().SortField, + panel.GetInfo().GetSort()) + + buf := h.showTable(ctx, prefix, params, panel) + ctx.HTML(http.StatusOK, buf.String()) +} + +func (h *Handler) showTableData(ctx *context.Context, prefix string, params parameter.Parameters, + panel table.Table, urlNamePrefix string) (table.Table, table.PanelInfo, []string, error) { + if panel == nil { + panel = h.table(prefix, ctx) + } + + panelInfo, err := panel.GetData(ctx, params.WithIsAll(false)) + + if err != nil { + return panel, panelInfo, nil, err + } + + var ( + paramStr = params.DeleteIsAll().GetRouteParamStr() + + editUrl = modules.AorEmpty(!panel.GetInfo().IsHideEditButton, h.routePathWithPrefix(urlNamePrefix+"show_edit", prefix)+paramStr) + newUrl = modules.AorEmpty(!panel.GetInfo().IsHideNewButton, h.routePathWithPrefix(urlNamePrefix+"show_new", prefix)+paramStr) + deleteUrl = modules.AorEmpty(!panel.GetInfo().IsHideDeleteButton, h.routePathWithPrefix(urlNamePrefix+"delete", prefix)+paramStr) + exportUrl = modules.AorEmpty(!panel.GetInfo().IsHideExportButton, h.routePathWithPrefix(urlNamePrefix+"export", prefix)+paramStr) + detailUrl = modules.AorEmpty(!panel.GetInfo().IsHideDetailButton, h.routePathWithPrefix(urlNamePrefix+"detail", prefix)+paramStr) + + infoUrl = h.routePathWithPrefix(urlNamePrefix+"info", prefix) + updateUrl = h.routePathWithPrefix(urlNamePrefix+"update", prefix) + paramStr + + user = auth.Auth(ctx) + ) + + editUrl = user.GetCheckPermissionByUrlMethod(editUrl, h.route(urlNamePrefix+"show_edit").Method()) + newUrl = user.GetCheckPermissionByUrlMethod(newUrl, h.route(urlNamePrefix+"show_new").Method()) + deleteUrl = user.GetCheckPermissionByUrlMethod(deleteUrl, h.route(urlNamePrefix+"delete").Method()) + exportUrl = user.GetCheckPermissionByUrlMethod(exportUrl, h.route(urlNamePrefix+"export").Method()) + detailUrl = user.GetCheckPermissionByUrlMethod(detailUrl, h.route(urlNamePrefix+"detail").Method()) + + return panel, panelInfo, []string{editUrl, newUrl, deleteUrl, exportUrl, detailUrl, infoUrl, updateUrl}, nil +} + +func (h *Handler) showTable(ctx *context.Context, prefix string, params parameter.Parameters, panel table.Table) *bytes.Buffer { + + panel, panelInfo, urls, err := h.showTableData(ctx, prefix, params, panel, "") + if err != nil { + return h.Execute(ctx, auth.Auth(ctx), + template.WarningPanelWithDescAndTitle(ctx, err.Error(), errors.Msg, errors.Msg), "", + template.ExecuteOptions{Animation: params.Animation}) + } + + if panel.GetInfo().HasError() { + if panel.GetInfo().PageErrorHTML != template2.HTML("") { + return h.Execute(ctx, auth.Auth(ctx), + types.Panel{Content: panel.GetInfo().PageErrorHTML}, "", + template.ExecuteOptions{Animation: params.Animation}) + } + return h.Execute(ctx, auth.Auth(ctx), + template.WarningPanel(ctx, panel.GetInfo().PageError.Error(), + template.GetPageTypeFromPageError(panel.GetInfo().PageError)), "", + template.ExecuteOptions{Animation: params.Animation}) + } + + editUrl, newUrl, deleteUrl, exportUrl, detailUrl, infoUrl, + updateUrl := urls[0], urls[1], urls[2], urls[3], urls[4], urls[5], urls[6] + + var ( + actionJs template2.JS + body template2.HTML + dataTable types.DataTableAttribute + + user = auth.Auth(ctx) + info = panel.GetInfo() + actionBtns = info.Action + allActionBtns = info.ActionButtons.CheckPermissionWhenURLAndMethodNotEmpty(user) + ) + + hasExtAction := actionBtns == template.HTML("") && len(allActionBtns) > 0 + hasAction := hasExtAction || (editUrl != "" || newUrl != "" || deleteUrl != "") + + if hasExtAction { + if info.ActionButtonFold { + ext := template2.HTML("") + if deleteUrl != "" { + ext = html.LiEl().SetClass("divider").Get() + allActionBtns = append([]types.Button{types.GetActionButton(language.GetFromHtml("delete"), + types.NewDefaultAction(`data-id='{{.Id}}' data-param='{{(index .Value "__goadmin_delete_params").Content}}' style="cursor: pointer;"`, + ext, "", ""), "grid-row-delete")}, allActionBtns...) + } + ext = template2.HTML("") + if detailUrl != "" { + if editUrl == "" && deleteUrl == "" { + ext = html.LiEl().SetClass("divider").Get() + } + allActionBtns = append([]types.Button{types.GetActionButton(language.GetFromHtml("detail"), + action.Jump(detailUrl+"&"+constant.DetailPKKey+`={{.Id}}{{(index .Value "__goadmin_detail_params").Content}}`, ext))}, allActionBtns...) + } + if editUrl != "" { + if detailUrl == "" && deleteUrl == "" { + ext = html.LiEl().SetClass("divider").Get() + } + allActionBtns = append([]types.Button{types.GetActionButton(language.GetFromHtml("edit"), + action.Jump(editUrl+"&"+constant.EditPKKey+`={{.Id}}{{(index .Value "__goadmin_edit_params").Content}}`, ext))}, allActionBtns...) + } + + var content template2.HTML + content, actionJs = allActionBtns.Content(ctx) + + actionBtns = html.Div(html.Div( + html.A(icon.Icon(icon.EllipsisV), + html.M{"color": "#676565"}, + html.M{"href": "#"}, + ), html.M{"cursor": "pointer", "width": "100%"}, html.M{"class": "dropdown-toggle", "data-toggle": "dropdown"})+ + html.Ul(content, + html.M{"min-width": "20px !important", "left": "-112px", "overflow": "hidden"}, + html.M{"class": "dropdown-menu", "role": "menu", "aria-labelledby": "dLabel"}), + html.M{"text-align": "center"}, html.M{"class": "dropdown"}) + } else { + actionBtns, actionJs = allActionBtns.Content(ctx) + } + } else { + info.ActionButtonFold = false + } + + btns, btnsJs := info.Buttons.CheckPermissionWhenURLAndMethodNotEmpty(user).Content(ctx) + + if info.TabGroups.Valid() { + + dataTable = aDataTable(ctx). + SetThead(panelInfo.Thead). + SetDeleteUrl(deleteUrl). + SetNewUrl(newUrl). + SetExportUrl(exportUrl) + + var ( + tabsHtml = make([]map[string]template2.HTML, len(info.TabHeaders)) + infoListArr = panelInfo.InfoList.GroupBy(info.TabGroups) + theadArr = panelInfo.Thead.GroupBy(info.TabGroups) + ) + for key, header := range info.TabHeaders { + tabsHtml[key] = map[string]template2.HTML{ + "title": template2.HTML(header), + "content": aDataTable(ctx). + SetInfoList(infoListArr[key]). + SetInfoUrl(infoUrl). + SetButtons(btns). + SetSticky(hasAction). + SetActionJs(btnsJs + actionJs). + SetHasFilter(len(panelInfo.FilterFormData) > 0). + SetAction(actionBtns). + SetActionFold(info.ActionButtonFold). + SetIsTab(key != 0). + SetPrimaryKey(panel.GetPrimaryKey().Name). + SetThead(theadArr[key]). + SetHideRowSelector(info.IsHideRowSelector). + SetLayout(info.TableLayout). + SetExportUrl(exportUrl). + SetNewUrl(newUrl). + SetSortUrl(params.GetFixedParamStrWithoutSort()). + SetEditUrl(editUrl). + SetUpdateUrl(updateUrl). + SetDetailUrl(detailUrl). + SetDeleteUrl(deleteUrl). + GetContent(), + } + } + body = aTab(ctx).SetData(tabsHtml).GetContent() + } else { + dataTable = aDataTable(ctx). + SetInfoList(panelInfo.InfoList). + SetInfoUrl(infoUrl). + SetButtons(btns). + SetSticky(hasAction). + SetLayout(info.TableLayout). + SetActionJs(btnsJs + actionJs). + SetAction(actionBtns). + SetHasFilter(len(panelInfo.FilterFormData) > 0). + SetPrimaryKey(panel.GetPrimaryKey().Name). + SetThead(panelInfo.Thead). + SetExportUrl(exportUrl). + SetActionFold(info.ActionButtonFold). + SetHideRowSelector(info.IsHideRowSelector). + SetHideFilterArea(info.IsHideFilterArea). + SetNewUrl(newUrl). + SetEditUrl(editUrl). + SetSortUrl(params.GetFixedParamStrWithoutSort()). + SetUpdateUrl(updateUrl). + SetDetailUrl(detailUrl). + SetDeleteUrl(deleteUrl) + body = dataTable.GetContent() + } + + isNotIframe := ctx.Query(constant.IframeKey) != "true" + paginator := panelInfo.Paginator + + if !isNotIframe { + paginator = paginator.SetEntriesInfo("") + } + + boxModel := aBox(ctx). + SetBody(body). + SetStyle(template2.HTMLAttr(`overflow-x: auto;overflow-y: hidden;`)). + SetNoPadding(). + SetHeader(dataTable.GetDataTableHeader() + info.HeaderHtml). + WithHeadBorder(). + SetIframeStyle(!isNotIframe). + SetFooter(paginator.GetContent() + info.FooterHtml + ` + + `) + + content := template2.HTML("") + + if len(panelInfo.FilterFormData) > 0 && info.FilterFormLayout == form2.LayoutFilter { + filterBoxModel := aBox(ctx).SetClass("filter-area"). + SetAttr(`style="padding-top: 20px;margin-top: -10px;margin-bottom: 12px;padding-left: 20px;display: block;padding-bottom: 5px;"`). + SetStyle(`padding: 0px;`). + SetBody(aForm(ctx). + SetContent(panelInfo.FilterFormData). + SetHorizontal(true). + SetPrefix(h.config.PrefixFixSlash()). + SetInputWidth(info.FilterFormInputWidth). + SetHeadWidth(info.FilterFormHeadWidth). + SetMethod("get"). + SetLayout(info.FilterFormLayout). + SetUrl(infoUrl). // + params.GetFixedParamStrWithoutColumnsAndPage() + SetHiddenFields(map[string]string{ + form.NoAnimationKey: "true", + }). + GetContent()) + content = filterBoxModel.GetContent() + boxModel.GetContent() + } else { + if len(panelInfo.FilterFormData) > 0 { + boxModel = boxModel.SetSecondHeaderClass("filter-area"). + SetSecondHeader(aForm(ctx). + SetContent(panelInfo.FilterFormData). + SetPrefix(h.config.PrefixFixSlash()). + SetInputWidth(info.FilterFormInputWidth). + SetHeadWidth(info.FilterFormHeadWidth). + SetMethod("get"). + SetLayout(info.FilterFormLayout). + SetUrl(infoUrl). // + params.GetFixedParamStrWithoutColumnsAndPage() + SetHiddenFields(map[string]string{ + form.NoAnimationKey: "true", + }). + SetOperationFooter(filterFormFooter(ctx, infoUrl)). + GetContent()) + } + content = boxModel.GetContent() + } + + if info.Wrapper != nil { + content = info.Wrapper(content) + } + + interval := make([]int, 0) + autoRefresh := info.AutoRefresh != uint(0) + if autoRefresh { + interval = append(interval, int(info.AutoRefresh)) + } + + return h.Execute(ctx, user, types.Panel{ + Content: content, + Description: template2.HTML(panelInfo.Description), + Title: modules.AorBHTML(isNotIframe, template2.HTML(panelInfo.Title), ""), + MiniSidebar: info.HideSideBar, + AutoRefresh: autoRefresh, + RefreshInterval: interval, + }, "", template.ExecuteOptions{Animation: params.Animation, NoCompress: info.NoCompress}) +} + +// Assets return front-end assets according the request path. +func (h *Handler) Assets(ctx *context.Context) { + filepath := h.config.URLRemovePrefix(ctx.Path()) + + theme := h.assetsTheme[filepath] + if theme == "" { + theme = h.config.Theme + } + + data, err := aTemplateByTheme(ctx, theme).GetAsset(filepath) + + if err != nil { + data, err = template.GetAsset(filepath) + if err != nil { + logger.ErrorCtx(ctx, "asset err %+v", err) + ctx.Write(http.StatusNotFound, map[string]string{}, "") + return + } + } + + var contentType = mime.TypeByExtension(path.Ext(filepath)) + + if contentType == "" { + contentType = "application/octet-stream" + } + + etag := fmt.Sprintf("%x", md5.Sum(data)) + + if match := ctx.Headers("If-None-Match"); match != "" { + if strings.Contains(match, etag) { + ctx.SetStatusCode(http.StatusNotModified) + return + } + } + + ctx.DataWithHeaders(http.StatusOK, map[string]string{ + "Content-Type": contentType, + "Cache-Control": "max-age=2592000", + "Content-Length": strconv.Itoa(len(data)), + "ETag": etag, + }, data) +} + +// Export export table rows as excel object. +func (h *Handler) Export(ctx *context.Context) { + param := guard.GetExportParam(ctx) + + tableName := "Sheet1" + prefix := ctx.Query(constant.PrefixKey) + panel := h.table(prefix, ctx) + + f := excelize.NewFile() + index := f.NewSheet(tableName) + f.SetActiveSheet(index) + + var ( + infoData table.PanelInfo + fileName string + err error + tableInfo = panel.GetInfo() + params parameter.Parameters + ) + + if fn := panel.GetInfo().ExportProcessFn; fn != nil { + params = parameter.GetParam(ctx.Request.URL, tableInfo.DefaultPageSize, tableInfo.SortField, + tableInfo.GetSort()) + p, err := fn(params.WithIsAll(param.IsAll)) + if err != nil { + response.Error(ctx, "export error") + return + } + infoData.Thead = p.Thead + infoData.InfoList = p.InfoList + } else { + if len(param.Id) == 0 { + params = parameter.GetParam(ctx.Request.URL, tableInfo.DefaultPageSize, tableInfo.SortField, + tableInfo.GetSort()) + infoData, err = panel.GetData(ctx, params.WithIsAll(param.IsAll)) + fileName = fmt.Sprintf("%s-%d-page-%s-pageSize-%s.xlsx", tableInfo.Title, time.Now().Unix(), + params.Page, params.PageSize) + } else { + infoData, err = panel.GetDataWithIds(ctx, parameter.GetParam(ctx.Request.URL, + tableInfo.DefaultPageSize, tableInfo.SortField, tableInfo.GetSort()).WithPKs(param.Id...)) + fileName = fmt.Sprintf("%s-%d-id-%s.xlsx", tableInfo.Title, time.Now().Unix(), strings.Join(param.Id, "_")) + } + if err != nil { + response.Error(ctx, "export error") + return + } + } + + // TODO: support any numbers of fields. + orders := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", + "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"} + + if len(infoData.Thead) > 26 { + j := -1 + for i := 0; i < len(infoData.Thead)-26; i++ { + if i%26 == 0 { + j++ + } + letter := orders[j] + orders[i%26] + orders = append(orders, letter) + } + } + + columnIndex := 0 + for _, head := range infoData.Thead { + if !head.Hide { + f.SetCellValue(tableName, orders[columnIndex]+"1", head.Head) + columnIndex++ + } + } + + count := 2 + for _, info := range infoData.InfoList { + columnIndex = 0 + for _, head := range infoData.Thead { + if !head.Hide { + if tableInfo.IsExportValue() { + f.SetCellValue(tableName, orders[columnIndex]+strconv.Itoa(count), info[head.Field].Value) + } else { + f.SetCellValue(tableName, orders[columnIndex]+strconv.Itoa(count), info[head.Field].Content) + } + columnIndex++ + } + } + count++ + } + + buf, err := f.WriteToBuffer() + + if err != nil || buf == nil { + response.Error(ctx, "export error") + return + } + + ctx.AddHeader("content-disposition", `attachment; filename=`+fileName) + ctx.Data(200, "application/vnd.ms-excel", buf.Bytes()) +} diff --git a/plugins/admin/controller/system.go b/plugins/admin/controller/system.go new file mode 100644 index 0000000000000000000000000000000000000000..6d63bcf269f89c55c9c6b20300ed98f4ac542cb3 --- /dev/null +++ b/plugins/admin/controller/system.go @@ -0,0 +1,190 @@ +package controller + +import ( + "fmt" + "html/template" + "os" + "runtime" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/system" + "github.com/GoAdminGroup/go-admin/template/types" +) + +func (h *Handler) SystemInfo(ctx *context.Context) { + + size := types.Size(6, 6, 6) + + box1 := aBox(ctx). + WithHeadBorder(). + SetHeader("" + lg("application") + ""). + SetBody(stripedTable(ctx, []map[string]types.InfoItem{ + { + "key": types.InfoItem{Content: lg("app_name")}, + "value": types.InfoItem{Content: "GoAdmin"}, + }, { + "key": types.InfoItem{Content: lg("go_admin_version")}, + "value": types.InfoItem{Content: template.HTML(system.Version())}, + }, { + "key": types.InfoItem{Content: lg("theme_name")}, + "value": types.InfoItem{Content: template.HTML(aTemplate(ctx).Name())}, + }, { + "key": types.InfoItem{Content: lg("theme_version")}, + "value": types.InfoItem{Content: template.HTML(aTemplate(ctx).GetVersion())}, + }, + })). + GetContent() + + app := system.GetAppStatus() + + box2 := aBox(ctx). + WithHeadBorder(). + SetHeader("" + lg("application run") + ""). + SetBody(stripedTable(ctx, []map[string]types.InfoItem{ + { + "key": types.InfoItem{Content: lg("current_heap_usage")}, + "value": types.InfoItem{Content: template.HTML(app.HeapAlloc)}, + }, + { + "key": types.InfoItem{Content: lg("heap_memory_obtained")}, + "value": types.InfoItem{Content: template.HTML(app.HeapSys)}, + }, + { + "key": types.InfoItem{Content: lg("heap_memory_idle")}, + "value": types.InfoItem{Content: template.HTML(app.HeapIdle)}, + }, + { + "key": types.InfoItem{Content: lg("heap_memory_in_use")}, + "value": types.InfoItem{Content: template.HTML(app.HeapInuse)}, + }, + { + "key": types.InfoItem{Content: lg("heap_memory_released")}, + "value": types.InfoItem{Content: template.HTML(app.HeapReleased)}, + }, + { + "key": types.InfoItem{Content: lg("heap_objects")}, + "value": types.InfoItem{Content: itos(app.HeapObjects)}, + }, + }) + `

    ` + stripedTable(ctx, []map[string]types.InfoItem{ + { + "key": types.InfoItem{Content: lg("next_gc_recycle")}, + "value": types.InfoItem{Content: template.HTML(app.NextGC)}, + }, { + "key": types.InfoItem{Content: lg("last_gc_time")}, + "value": types.InfoItem{Content: template.HTML(app.LastGC)}, + }, { + "key": types.InfoItem{Content: lg("total_gc_pause")}, + "value": types.InfoItem{Content: template.HTML(app.PauseTotalNs)}, + }, { + "key": types.InfoItem{Content: lg("last_gc_pause")}, + "value": types.InfoItem{Content: template.HTML(app.PauseNs)}, + }, { + "key": types.InfoItem{Content: lg("gc_times")}, + "value": types.InfoItem{Content: itos(app.NumGC)}, + }, + })). + GetContent() + + col1 := aCol(ctx).SetSize(size).SetContent(box1 + box2).GetContent() + + box4 := aBox(ctx). + WithHeadBorder(). + SetHeader("" + lg("application run") + ""). + SetBody(stripedTable(ctx, []map[string]types.InfoItem{ + { + "key": types.InfoItem{Content: lg("golang_version")}, + "value": types.InfoItem{Content: template.HTML(runtime.Version())}, + }, { + "key": types.InfoItem{Content: lg("process_id")}, + "value": types.InfoItem{Content: itos(os.Getpid())}, + }, { + "key": types.InfoItem{Content: lg("server_uptime")}, + "value": types.InfoItem{Content: template.HTML(app.Uptime)}, + }, { + "key": types.InfoItem{Content: lg("current_goroutine")}, + "value": types.InfoItem{Content: itos(app.NumGoroutine)}, + }, + }) + `

    ` + stripedTable(ctx, []map[string]types.InfoItem{ + { + "key": types.InfoItem{Content: lg("current_memory_usage")}, + "value": types.InfoItem{Content: template.HTML(app.MemAllocated)}, + }, { + "key": types.InfoItem{Content: lg("total_memory_allocated")}, + "value": types.InfoItem{Content: template.HTML(app.MemTotal)}, + }, { + "key": types.InfoItem{Content: lg("memory_obtained")}, + "value": types.InfoItem{Content: itos(app.MemSys)}, + }, { + "key": types.InfoItem{Content: lg("pointer_lookup_times")}, + "value": types.InfoItem{Content: itos(app.Lookups)}, + }, { + "key": types.InfoItem{Content: lg("memory_allocate_times")}, + "value": types.InfoItem{Content: itos(app.MemMallocs)}, + }, { + "key": types.InfoItem{Content: lg("memory_free_times")}, + "value": types.InfoItem{Content: itos(app.MemFrees)}, + }, + }) + `

    ` + stripedTable(ctx, []map[string]types.InfoItem{ + { + "key": types.InfoItem{Content: lg("bootstrap_stack_usage")}, + "value": types.InfoItem{Content: template.HTML(app.StackInuse)}, + }, { + "key": types.InfoItem{Content: lg("stack_memory_obtained")}, + "value": types.InfoItem{Content: template.HTML(app.StackSys)}, + }, { + "key": types.InfoItem{Content: lg("mspan_structures_usage")}, + "value": types.InfoItem{Content: template.HTML(app.MSpanInuse)}, + }, { + "key": types.InfoItem{Content: lg("mspan_structures_obtained")}, + "value": types.InfoItem{Content: template.HTML(app.HeapSys)}, + }, { + "key": types.InfoItem{Content: lg("mcache_structures_usage")}, + "value": types.InfoItem{Content: template.HTML(app.MCacheInuse)}, + }, { + "key": types.InfoItem{Content: lg("mcache_structures_obtained")}, + "value": types.InfoItem{Content: template.HTML(app.MCacheSys)}, + }, { + "key": types.InfoItem{Content: lg("profiling_bucket_hash_table_obtained")}, + "value": types.InfoItem{Content: template.HTML(app.BuckHashSys)}, + }, { + "key": types.InfoItem{Content: lg("gc_metadata_obtained")}, + "value": types.InfoItem{Content: template.HTML(app.GCSys)}, + }, { + "key": types.InfoItem{Content: lg("other_system_allocation_obtained")}, + "value": types.InfoItem{Content: template.HTML(app.OtherSys)}, + }, + })). + GetContent() + + col2 := aCol(ctx).SetSize(size).SetContent(box4).GetContent() + + row := aRow(ctx).SetContent(col1 + col2).GetContent() + + h.HTML(ctx, auth.Auth(ctx), types.Panel{ + Content: row, + Description: language.GetFromHtml("site info", "system"), + Title: language.GetFromHtml("site info", "system"), + }) +} + +func stripedTable(ctx *context.Context, list []map[string]types.InfoItem) template.HTML { + return aTable(ctx). + SetStyle("striped"). + SetHideThead(). + SetMinWidth("0.01%"). + SetThead(types.Thead{ + types.TheadItem{Head: "key", Width: "50%"}, + types.TheadItem{Head: "value"}, + }). + SetInfoList(list).GetContent() +} + +func lg(v template.HTML) template.HTML { + return language.GetFromHtml(v, "system") +} + +func itos(i interface{}) template.HTML { + return template.HTML(fmt.Sprintf("%v", i)) +} diff --git a/plugins/admin/data/mysql/admin.sql b/plugins/admin/data/mysql/admin.sql new file mode 100644 index 0000000000000000000000000000000000000000..3076398cb92bc0a1a9f4d2ee5952281d8ac1897e --- /dev/null +++ b/plugins/admin/data/mysql/admin.sql @@ -0,0 +1,291 @@ +# ************************************************************ +# Sequel Pro SQL dump +# Version 4468 +# +# http://www.sequelpro.com/ +# https://github.com/sequelpro/sequelpro +# +# Host: 127.0.0.1 (MySQL 5.7.19) +# Database: godmin +# Generation Time: 2019-09-12 04:16:47 +0000 +# ************************************************************ + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +# Dump of table goadmin_menu +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_menu`; + +CREATE TABLE `goadmin_menu` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `parent_id` int(11) unsigned NOT NULL DEFAULT '0', + `type` tinyint(4) unsigned NOT NULL DEFAULT '0', + `order` int(11) unsigned NOT NULL DEFAULT '0', + `title` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `icon` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `uri` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `header` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_menu` WRITE; +/*!40000 ALTER TABLE `goadmin_menu` DISABLE KEYS */; + +INSERT INTO `goadmin_menu` (`id`, `parent_id`, `type`, `order`, `title`, `icon`, `uri`, `header`, `created_at`, `updated_at`) +VALUES + (1,0,1,2,'Admin','fa-tasks','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,1,1,2,'Users','fa-users','/info/manager',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (3,1,1,3,'Roles','fa-user','/info/roles',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (4,1,1,4,'Permission','fa-ban','/info/permission',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (5,1,1,5,'Menu','fa-bars','/menu',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (6,1,1,6,'Operation log','fa-history','/info/op',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (7,0,1,1,'Dashboard','fa-bar-chart','/',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_menu` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_operation_log +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_operation_log`; + +CREATE TABLE `goadmin_operation_log` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(11) unsigned NOT NULL, + `path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `method` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL, + `ip` varchar(15) COLLATE utf8mb4_unicode_ci NOT NULL, + `input` text COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `admin_operation_log_user_id_index` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + + +# Dump of table goadmin_permissions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_permissions`; + +CREATE TABLE `goadmin_permissions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `slug` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `http_method` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `http_path` text COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `admin_permissions_name_unique` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_permissions` WRITE; +/*!40000 ALTER TABLE `goadmin_permissions` DISABLE KEYS */; + +INSERT INTO `goadmin_permissions` (`id`, `name`, `slug`, `http_method`, `http_path`, `created_at`, `updated_at`) +VALUES + (1,'All permission','*','','*','2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,'Dashboard','dashboard','GET,PUT,POST,DELETE','/','2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_permissions` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_role_menu +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_role_menu`; + +CREATE TABLE `goadmin_role_menu` ( + `role_id` int(11) unsigned NOT NULL, + `menu_id` int(11) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + KEY `admin_role_menu_role_id_menu_id_index` (`role_id`,`menu_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_role_menu` WRITE; +/*!40000 ALTER TABLE `goadmin_role_menu` DISABLE KEYS */; + +INSERT INTO `goadmin_role_menu` (`role_id`, `menu_id`, `created_at`, `updated_at`) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (1,7,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,7,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (1,8,'2019-09-11 10:20:55','2019-09-11 10:20:55'), + (2,8,'2019-09-11 10:20:55','2019-09-11 10:20:55'); + +/*!40000 ALTER TABLE `goadmin_role_menu` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_role_permissions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_role_permissions`; + +CREATE TABLE `goadmin_role_permissions` ( + `role_id` int(11) unsigned NOT NULL, + `permission_id` int(11) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `admin_role_permissions` (`role_id`,`permission_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_role_permissions` WRITE; +/*!40000 ALTER TABLE `goadmin_role_permissions` DISABLE KEYS */; + +INSERT INTO `goadmin_role_permissions` (`role_id`, `permission_id`, `created_at`, `updated_at`) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (1,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_role_permissions` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_role_users +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_role_users`; + +CREATE TABLE `goadmin_role_users` ( + `role_id` int(11) unsigned NOT NULL, + `user_id` int(11) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `admin_user_roles` (`role_id`,`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_role_users` WRITE; +/*!40000 ALTER TABLE `goadmin_role_users` DISABLE KEYS */; + +INSERT INTO `goadmin_role_users` (`role_id`, `user_id`, `created_at`, `updated_at`) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_role_users` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_roles +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_roles`; + +CREATE TABLE `goadmin_roles` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `slug` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `admin_roles_name_unique` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_roles` WRITE; +/*!40000 ALTER TABLE `goadmin_roles` DISABLE KEYS */; + +INSERT INTO `goadmin_roles` (`id`, `name`, `slug`, `created_at`, `updated_at`) +VALUES + (1,'Administrator','administrator','2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,'Operator','operator','2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_roles` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_session +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_session`; + +CREATE TABLE `goadmin_session` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `sid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `values` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + + + +# Dump of table goadmin_user_permissions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_user_permissions`; + +CREATE TABLE `goadmin_user_permissions` ( + `user_id` int(11) unsigned NOT NULL, + `permission_id` int(11) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `admin_user_permissions` (`user_id`,`permission_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_user_permissions` WRITE; +/*!40000 ALTER TABLE `goadmin_user_permissions` DISABLE KEYS */; + +INSERT INTO `goadmin_user_permissions` (`user_id`, `permission_id`, `created_at`, `updated_at`) +VALUES + (1,1,'2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,2,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_user_permissions` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table goadmin_users +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `goadmin_users`; + +CREATE TABLE `goadmin_users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL, + `password` varchar(80) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `avatar` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `admin_users_username_unique` (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +LOCK TABLES `goadmin_users` WRITE; +/*!40000 ALTER TABLE `goadmin_users` DISABLE KEYS */; + +INSERT INTO `goadmin_users` (`id`, `username`, `password`, `name`, `avatar`, `remember_token`, `created_at`, `updated_at`) +VALUES + (1,'admin','$2a$10$U3F/NSaf2kaVbyXTBp7ppOn0jZFyRqXRnYXB.AMioCjXl3Ciaj4oy','admin','','tlNcBVK9AvfYH7WEnwB1RKvocJu8FfRy4um3DJtwdHuJy0dwFsLOgAc0xUfh','2019-09-10 00:00:00','2019-09-10 00:00:00'), + (2,'operator','$2a$10$rVqkOzHjN2MdlEprRflb1eGP0oZXuSrbJLOmJagFsCd81YZm0bsh.','Operator','',NULL,'2019-09-10 00:00:00','2019-09-10 00:00:00'); + +/*!40000 ALTER TABLE `goadmin_users` ENABLE KEYS */; +UNLOCK TABLES; + + + +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/plugins/admin/data/sqlite/admin.db b/plugins/admin/data/sqlite/admin.db new file mode 100644 index 0000000000000000000000000000000000000000..e1b360232fe9afe4c3fb1d531981b2a9a5fa4bfd Binary files /dev/null and b/plugins/admin/data/sqlite/admin.db differ diff --git a/plugins/admin/models/base.go b/plugins/admin/models/base.go new file mode 100644 index 0000000000000000000000000000000000000000..f8a1bf86e0baf44222d1219451e076ad3c5260e7 --- /dev/null +++ b/plugins/admin/models/base.go @@ -0,0 +1,24 @@ +package models + +import ( + "database/sql" + + "github.com/GoAdminGroup/go-admin/modules/db" +) + +// Base is base model structure. +type Base struct { + TableName string + + Conn db.Connection + Tx *sql.Tx +} + +func (b Base) SetConn(con db.Connection) Base { + b.Conn = con + return b +} + +func (b Base) Table(table string) *db.SQL { + return db.Table(table).WithDriver(b.Conn) +} diff --git a/plugins/admin/models/menu.go b/plugins/admin/models/menu.go new file mode 100644 index 0000000000000000000000000000000000000000..6ada638d8683b6c33dc03bb5e4f44cd8a06e28cf --- /dev/null +++ b/plugins/admin/models/menu.go @@ -0,0 +1,208 @@ +package models + +import ( + "encoding/json" + "strconv" + "time" + + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/db/dialect" +) + +// MenuModel is menu model structure. +type MenuModel struct { + Base + + Id int64 + Title string + ParentId int64 + Icon string + Uri string + Header string + CreatedAt string + UpdatedAt string +} + +// Menu return a default menu model. +func Menu() MenuModel { + return MenuModel{Base: Base{TableName: "goadmin_menu"}} +} + +// MenuWithId return a default menu model of given id. +func MenuWithId(id string) MenuModel { + idInt, _ := strconv.Atoi(id) + return MenuModel{Base: Base{TableName: "goadmin_menu"}, Id: int64(idInt)} +} + +func (t MenuModel) SetConn(con db.Connection) MenuModel { + t.Conn = con + return t +} + +// Find return a default menu model of given id. +func (t MenuModel) Find(id interface{}) MenuModel { + item, _ := t.Table(t.TableName).Find(id) + return t.MapToModel(item) +} + +// New create a new menu model. +func (t MenuModel) New(title, icon, uri, header, pluginName string, parentId, order int64) (MenuModel, error) { + + id, err := t.Table(t.TableName).Insert(dialect.H{ + "title": title, + "parent_id": parentId, + "icon": icon, + "uri": uri, + "order": order, + "header": header, + "plugin_name": pluginName, + }) + + t.Id = id + t.Title = title + t.ParentId = parentId + t.Icon = icon + t.Uri = uri + t.Header = header + + return t, err +} + +// Delete delete the menu model. +func (t MenuModel) Delete() { + _ = t.Table(t.TableName).Where("id", "=", t.Id).Delete() + _ = t.Table("goadmin_role_menu").Where("menu_id", "=", t.Id).Delete() + items, _ := t.Table(t.TableName).Where("parent_id", "=", t.Id).All() + + if len(items) > 0 { + ids := make([]interface{}, len(items)) + for i := 0; i < len(ids); i++ { + ids[i] = items[i]["id"] + } + _ = t.Table("goadmin_role_menu").WhereIn("menu_id", ids).Delete() + } + + _ = t.Table(t.TableName).Where("parent_id", "=", t.Id).Delete() +} + +// Update update the menu model. +func (t MenuModel) Update(title, icon, uri, header, pluginName string, parentId int64) (int64, error) { + return t.Table(t.TableName). + Where("id", "=", t.Id). + Update(dialect.H{ + "title": title, + "parent_id": parentId, + "icon": icon, + "plugin_name": pluginName, + "uri": uri, + "header": header, + "updated_at": time.Now().Format("2006-01-02 15:04:05"), + }) +} + +type OrderItems []OrderItem + +type OrderItem struct { + ID uint `json:"id"` + Children OrderItems `json:"children"` +} + +// ResetOrder update the order of menu models. +func (t MenuModel) ResetOrder(data []byte) { + + var items OrderItems + _ = json.Unmarshal(data, &items) + + count := 1 + for _, v := range items { + if len(v.Children) > 0 { + _, _ = t.Table(t.TableName). + Where("id", "=", v.ID). + Update(dialect.H{ + "order": count, + "parent_id": 0, + }) + + for _, v2 := range v.Children { + if len(v2.Children) > 0 { + + _, _ = t.Table(t.TableName). + Where("id", "=", v2.ID). + Update(dialect.H{ + "order": count, + "parent_id": v.ID, + }) + + for _, v3 := range v2.Children { + _, _ = t.Table(t.TableName). + Where("id", "=", v3.ID). + Update(dialect.H{ + "order": count, + "parent_id": v2.ID, + }) + count++ + } + } else { + _, _ = t.Table(t.TableName). + Where("id", "=", v2.ID). + Update(dialect.H{ + "order": count, + "parent_id": v.ID, + }) + count++ + } + } + } else { + _, _ = t.Table(t.TableName). + Where("id", "=", v.ID). + Update(dialect.H{ + "order": count, + "parent_id": 0, + }) + count++ + } + } +} + +// CheckRole check the role if has permission to get the menu. +func (t MenuModel) CheckRole(roleId string) bool { + checkRole, _ := t.Table("goadmin_role_menu"). + Where("role_id", "=", roleId). + Where("menu_id", "=", t.Id). + First() + return checkRole != nil +} + +// AddRole add a role to the menu. +func (t MenuModel) AddRole(roleId string) (int64, error) { + if roleId != "" { + if !t.CheckRole(roleId) { + return t.Table("goadmin_role_menu"). + Insert(dialect.H{ + "role_id": roleId, + "menu_id": t.Id, + }) + } + } + return 0, nil +} + +// DeleteRoles delete roles with menu. +func (t MenuModel) DeleteRoles() error { + return t.Table("goadmin_role_menu"). + Where("menu_id", "=", t.Id). + Delete() +} + +// MapToModel get the menu model from given map. +func (t MenuModel) MapToModel(m map[string]interface{}) MenuModel { + t.Id = m["id"].(int64) + t.Title, _ = m["title"].(string) + t.ParentId = m["parent_id"].(int64) + t.Icon, _ = m["icon"].(string) + t.Uri, _ = m["uri"].(string) + t.Header, _ = m["header"].(string) + t.CreatedAt, _ = m["created_at"].(string) + t.UpdatedAt, _ = m["updated_at"].(string) + return t +} diff --git a/plugins/admin/models/operation_log.go b/plugins/admin/models/operation_log.go new file mode 100644 index 0000000000000000000000000000000000000000..f19e72150a23493515b5991feaacf73fc1065321 --- /dev/null +++ b/plugins/admin/models/operation_log.go @@ -0,0 +1,70 @@ +package models + +import ( + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/db/dialect" +) + +// OperationLogModel is operation log model structure. +type OperationLogModel struct { + Base + + Id int64 + UserId int64 + Path string + Method string + Ip string + Input string + CreatedAt string + UpdatedAt string +} + +// OperationLog return a default operation log model. +func OperationLog() OperationLogModel { + return OperationLogModel{Base: Base{TableName: "goadmin_operation_log"}} +} + +// Find return a default operation log model of given id. +func (t OperationLogModel) Find(id interface{}) OperationLogModel { + item, _ := t.Table(t.TableName).Find(id) + return t.MapToModel(item) +} + +func (t OperationLogModel) SetConn(con db.Connection) OperationLogModel { + t.Conn = con + return t +} + +// New create a new operation log model. +func (t OperationLogModel) New(userId int64, path, method, ip, input string) OperationLogModel { + + id, _ := t.Table(t.TableName).Insert(dialect.H{ + "user_id": userId, + "path": path, + "method": method, + "ip": ip, + "input": input, + }) + + t.Id = id + t.UserId = userId + t.Path = path + t.Method = method + t.Ip = ip + t.Input = input + + return t +} + +// MapToModel get the operation log model from given map. +func (t OperationLogModel) MapToModel(m map[string]interface{}) OperationLogModel { + t.Id = m["id"].(int64) + t.UserId = m["user_id"].(int64) + t.Path, _ = m["path"].(string) + t.Method, _ = m["method"].(string) + t.Ip, _ = m["ip"].(string) + t.Input, _ = m["input"].(string) + t.CreatedAt, _ = m["created_at"].(string) + t.UpdatedAt, _ = m["updated_at"].(string) + return t +} diff --git a/plugins/admin/models/permission.go b/plugins/admin/models/permission.go new file mode 100644 index 0000000000000000000000000000000000000000..3d4431fc230712450b74430ed424f8381541ab6e --- /dev/null +++ b/plugins/admin/models/permission.go @@ -0,0 +1,93 @@ +package models + +import ( + "strconv" + "strings" + + "github.com/GoAdminGroup/go-admin/modules/db" +) + +// PermissionModel is permission model structure. +type PermissionModel struct { + Base + + Id int64 + Name string + Slug string + HttpMethod []string + HttpPath []string + CreatedAt string + UpdatedAt string +} + +// Permission return a default permission model. +func Permission() PermissionModel { + return PermissionModel{Base: Base{TableName: "goadmin_permissions"}} +} + +// PermissionWithId return a default permission model of given id. +func PermissionWithId(id string) PermissionModel { + idInt, _ := strconv.Atoi(id) + return PermissionModel{Base: Base{TableName: "goadmin_permissions"}, Id: int64(idInt)} +} + +func (t PermissionModel) SetConn(con db.Connection) PermissionModel { + t.Conn = con + return t +} + +// IsEmpty check the user model is empty or not. +func (t PermissionModel) IsEmpty() bool { + return t.Id == int64(0) +} + +// IsSlugExist check the row exist with given slug and id. +func (t PermissionModel) IsSlugExist(slug string, id string) bool { + if id == "" { + check, _ := t.Table(t.TableName).Where("slug", "=", slug).First() + return check != nil + } + check, _ := t.Table(t.TableName). + Where("slug", "=", slug). + Where("id", "!=", id). + First() + return check != nil +} + +// Find return the permission model of given id. +func (t PermissionModel) Find(id interface{}) PermissionModel { + item, _ := t.Table(t.TableName).Find(id) + return t.MapToModel(item) +} + +// FindBySlug return the permission model of given slug. +func (t PermissionModel) FindBySlug(slug string) PermissionModel { + item, _ := t.Table(t.TableName).Where("slug", "=", slug).First() + return t.MapToModel(item) +} + +// FindBySlug return the permission model of given slug. +func (t PermissionModel) FindByName(name string) PermissionModel { + item, _ := t.Table(t.TableName).Where("name", "=", name).First() + return t.MapToModel(item) +} + +// MapToModel get the permission model from given map. +func (t PermissionModel) MapToModel(m map[string]interface{}) PermissionModel { + t.Id = m["id"].(int64) + t.Name, _ = m["name"].(string) + t.Slug, _ = m["slug"].(string) + + methods, _ := m["http_method"].(string) + if methods != "" { + t.HttpMethod = strings.Split(methods, ",") + } else { + t.HttpMethod = []string{""} + } + + path, _ := m["http_path"].(string) + t.HttpPath = strings.Split(path, "\n") + t.CreatedAt, _ = m["created_at"].(string) + t.UpdatedAt, _ = m["updated_at"].(string) + return t +} diff --git a/plugins/admin/models/role.go b/plugins/admin/models/role.go new file mode 100644 index 0000000000000000000000000000000000000000..15a45f0b93f91d815ee69e512eefa7ad4bfe2f2c --- /dev/null +++ b/plugins/admin/models/role.go @@ -0,0 +1,128 @@ +package models + +import ( + "database/sql" + "strconv" + "time" + + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/db/dialect" +) + +// RoleModel is role model structure. +type RoleModel struct { + Base + + Id int64 + Name string + Slug string + CreatedAt string + UpdatedAt string +} + +// Role return a default role model. +func Role() RoleModel { + return RoleModel{Base: Base{TableName: "goadmin_roles"}} +} + +// RoleWithId return a default role model of given id. +func RoleWithId(id string) RoleModel { + idInt, _ := strconv.Atoi(id) + return RoleModel{Base: Base{TableName: "goadmin_roles"}, Id: int64(idInt)} +} + +func (t RoleModel) SetConn(con db.Connection) RoleModel { + t.Conn = con + return t +} + +func (t RoleModel) WithTx(tx *sql.Tx) RoleModel { + t.Tx = tx + return t +} + +// Find return a default role model of given id. +func (t RoleModel) Find(id interface{}) RoleModel { + item, _ := t.Table(t.TableName).Find(id) + return t.MapToModel(item) +} + +// IsSlugExist check the row exist with given slug and id. +func (t RoleModel) IsSlugExist(slug string, id string) bool { + if id == "" { + check, _ := t.Table(t.TableName).Where("slug", "=", slug).First() + return check != nil + } + check, _ := t.Table(t.TableName). + Where("slug", "=", slug). + Where("id", "!=", id). + First() + return check != nil +} + +// New create a role model. +func (t RoleModel) New(name, slug string) (RoleModel, error) { + + id, err := t.WithTx(t.Tx).Table(t.TableName).Insert(dialect.H{ + "name": name, + "slug": slug, + }) + + t.Id = id + t.Name = name + t.Slug = slug + + return t, err +} + +// Update update the role model. +func (t RoleModel) Update(name, slug string) (int64, error) { + + return t.WithTx(t.Tx).Table(t.TableName). + Where("id", "=", t.Id). + Update(dialect.H{ + "name": name, + "slug": slug, + "updated_at": time.Now().Format("2006-01-02 15:04:05"), + }) +} + +// CheckPermission check the permission of role. +func (t RoleModel) CheckPermission(permissionId string) bool { + checkPermission, _ := t.Table("goadmin_role_permissions"). + Where("permission_id", "=", permissionId). + Where("role_id", "=", t.Id). + First() + return checkPermission != nil +} + +// DeletePermissions delete all the permissions of role. +func (t RoleModel) DeletePermissions() error { + return t.WithTx(t.Tx).Table("goadmin_role_permissions"). + Where("role_id", "=", t.Id). + Delete() +} + +// AddPermission add the permissions to the role. +func (t RoleModel) AddPermission(permissionId string) (int64, error) { + if permissionId != "" { + if !t.CheckPermission(permissionId) { + return t.WithTx(t.Tx).Table("goadmin_role_permissions"). + Insert(dialect.H{ + "permission_id": permissionId, + "role_id": t.Id, + }) + } + } + return 0, nil +} + +// MapToModel get the role model from given map. +func (t RoleModel) MapToModel(m map[string]interface{}) RoleModel { + t.Id = m["id"].(int64) + t.Name, _ = m["name"].(string) + t.Slug, _ = m["slug"].(string) + t.CreatedAt, _ = m["created_at"].(string) + t.UpdatedAt, _ = m["updated_at"].(string) + return t +} diff --git a/plugins/admin/models/site.go b/plugins/admin/models/site.go new file mode 100644 index 0000000000000000000000000000000000000000..5b4dc3ee450e22f74f56a07944c9166ee47cc460 --- /dev/null +++ b/plugins/admin/models/site.go @@ -0,0 +1,133 @@ +package models + +import ( + "database/sql" + + "github.com/GoAdminGroup/go-admin/modules/utils" + + "github.com/GoAdminGroup/go-admin/modules/collection" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/db/dialect" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" +) + +// SiteModel is role model structure. +type SiteModel struct { + Base + + Id int64 + Key string + Value string + Desc string + State int64 + + CreatedAt string + UpdatedAt string +} + +const ( + SiteItemOpenState = 1 + SiteItemOffState = 0 +) + +// Site return a default role model. +func Site() SiteModel { + return SiteModel{Base: Base{TableName: "goadmin_site"}} +} + +func (t SiteModel) SetConn(con db.Connection) SiteModel { + t.Conn = con + return t +} + +func (t SiteModel) WithTx(tx *sql.Tx) SiteModel { + t.Tx = tx + return t +} + +func (t SiteModel) Init(cfg map[string]string) SiteModel { + items, err := t.Table(t.TableName).All() + if db.CheckError(err, db.QUERY) { + panic(err) + } + itemsCol := collection.Collection(items) + for key, value := range cfg { + row := itemsCol.Where("key", "=", key) + if row.Length() == 0 { + _, err := t.Table(t.TableName).Insert(dialect.H{ + "key": key, + "value": value, + "state": SiteItemOpenState, + }) + if db.CheckError(err, db.INSERT) { + panic(err) + } + } + //else { + // if value != "" { + // _, err := t.Table(t.TableName). + // Where("key", "=", key).Update(dialect.H{ + // "value": value, + // }) + // if db.CheckError(err, db.UPDATE) { + // panic(err) + // } + // } + //} + } + return t +} + +func (t SiteModel) AllToMap() map[string]string { + + var m = make(map[string]string) + + items, err := t.Table(t.TableName).Where("state", "=", SiteItemOpenState).All() + if db.CheckError(err, db.QUERY) { + return m + } + + for _, item := range items { + m[item["key"].(string)] = item["value"].(string) + } + + return m +} + +func (t SiteModel) AllToMapInterface() map[string]interface{} { + + var m = make(map[string]interface{}) + + items, err := t.Table(t.TableName).Where("state", "=", SiteItemOpenState).All() + if db.CheckError(err, db.QUERY) { + return m + } + + for _, item := range items { + m[item["key"].(string)] = item["value"] + } + + m["id"] = "1" + + return m +} + +var allowEmptyKeys = []string{ + "animation_type", "custom_head_html", "custom_foot_html", "custom_404_html", + "custom_403_html", "custom_500_html", "footer_info", "bootstrap_file_path", + "info_log_path", "error_log_path", "access_log_path", "asset_url", "extra", "domain", +} + +func (t SiteModel) Update(v form.Values) error { + for key, vv := range v { + if len(vv) > 0 && (vv[0] != "" || utils.InArray(allowEmptyKeys, key)) { + _, err := t.Table(t.TableName).Where("key", "=", key).Update(dialect.H{ + "value": vv[0], + }) + if db.CheckError(err, db.UPDATE) { + return err + } + } + } + return nil +} diff --git a/plugins/admin/models/user.go b/plugins/admin/models/user.go new file mode 100644 index 0000000000000000000000000000000000000000..b2af151be88ce72f192c8597257b6c1fd03b422c --- /dev/null +++ b/plugins/admin/models/user.go @@ -0,0 +1,507 @@ +package models + +import ( + "database/sql" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/db/dialect" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" +) + +// UserModel is user model structure. +type UserModel struct { + Base `json:"-"` + + Id int64 `json:"id"` + Name string `json:"name"` + UserName string `json:"user_name"` + Password string `json:"password"` + Avatar string `json:"avatar"` + RememberToken string `json:"remember_token"` + Permissions []PermissionModel `json:"permissions"` + MenuIds []int64 `json:"menu_ids"` + Roles []RoleModel `json:"role"` + Level string `json:"level"` + LevelName string `json:"level_name"` + + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + + cacheReplacer *strings.Replacer +} + +// User return a default user model. +func User() UserModel { + return UserModel{Base: Base{TableName: config.GetAuthUserTable()}} +} + +// UserWithId return a default user model of given id. +func UserWithId(id string) UserModel { + idInt, _ := strconv.Atoi(id) + return UserModel{Base: Base{TableName: config.GetAuthUserTable()}, Id: int64(idInt)} +} + +func (t UserModel) SetConn(con db.Connection) UserModel { + t.Conn = con + return t +} + +func (t UserModel) WithTx(tx *sql.Tx) UserModel { + t.Tx = tx + return t +} + +// Find return a default user model of given id. +func (t UserModel) Find(id interface{}) UserModel { + item, _ := t.Table(t.TableName).Find(id) + return t.MapToModel(item) +} + +// FindByUserName return a default user model of given name. +func (t UserModel) FindByUserName(username interface{}) UserModel { + item, _ := t.Table(t.TableName).Where("username", "=", username).First() + return t.MapToModel(item) +} + +// IsEmpty check the user model is empty or not. +func (t UserModel) IsEmpty() bool { + return t.Id == int64(0) +} + +// HasMenu check the user has visitable menu or not. +func (t UserModel) HasMenu() bool { + return len(t.MenuIds) != 0 || t.IsSuperAdmin() +} + +// IsSuperAdmin check the user model is super admin or not. +func (t UserModel) IsSuperAdmin() bool { + for _, per := range t.Permissions { + if len(per.HttpPath) > 0 && per.HttpPath[0] == "*" && per.HttpMethod[0] == "" { + return true + } + } + return false +} + +func (t UserModel) GetCheckPermissionByUrlMethod(path, method string) string { + if !t.CheckPermissionByUrlMethod(path, method, url.Values{}) { + return "" + } + return path +} + +func (t UserModel) IsVisitor() bool { + return !t.CheckPermissionByUrlMethod(config.Url("/info/normal_manager"), "GET", url.Values{}) +} + +func (t UserModel) HideUserCenterEntrance() bool { + return t.IsVisitor() && config.GetHideVisitorUserCenterEntrance() +} + +func (t UserModel) Template(str string) string { + if t.cacheReplacer == nil { + t.cacheReplacer = strings.NewReplacer("{{.AuthId}}", strconv.Itoa(int(t.Id)), + "{{.AuthName}}", t.Name, "{{.AuthUserName}}", t.UserName) + } + return t.cacheReplacer.Replace(str) +} + +func (t UserModel) CheckPermissionByUrlMethod(path, method string, formParams url.Values) bool { + + // path, _ = url.PathUnescape(path) + + if t.IsSuperAdmin() { + return true + } + + if path == "" { + return false + } + + logoutCheck, _ := regexp.Compile(config.Url("/logout") + "(.*?)") + + if logoutCheck.MatchString(path) { + return true + } + + if path != "/" && path[len(path)-1] == '/' { + path = path[:len(path)-1] + } + + path = utils.ReplaceAll(path, constant.EditPKKey, "id", constant.DetailPKKey, "id") + + path, params := getParam(path) + for key, value := range formParams { + if len(value) > 0 { + params.Add(key, value[0]) + } + } + + for _, v := range t.Permissions { + + if v.HttpMethod[0] == "" || inMethodArr(v.HttpMethod, method) { + + if v.HttpPath[0] == "*" { + return true + } + + for i := 0; i < len(v.HttpPath); i++ { + + matchPath := config.Url(t.Template(strings.TrimSpace(v.HttpPath[i]))) + matchPath, matchParam := getParam(matchPath) + + if matchPath == path { + if t.checkParam(params, matchParam) { + return true + } + } + + reg, err := regexp.Compile(matchPath) + + if err != nil { + logger.Error("CheckPermissions error: ", err) + continue + } + + if reg.FindString(path) == path { + if t.checkParam(params, matchParam) { + return true + } + } + } + } + } + + return false +} + +func getParam(u string) (string, url.Values) { + m := make(url.Values) + urr := strings.Split(u, "?") + if len(urr) > 1 { + m, _ = url.ParseQuery(urr[1]) + } + return urr[0], m +} + +func (t UserModel) checkParam(src, comp url.Values) bool { + if len(comp) == 0 { + return true + } + if len(src) == 0 { + return false + } + for key, value := range comp { + v, find := src[key] + if !find { + return false + } + if len(value) == 0 { + continue + } + if len(v) == 0 { + return false + } + for i := 0; i < len(v); i++ { + if v[i] == t.Template(value[i]) { + continue + } else { + return false + } + } + } + return true +} + +func inMethodArr(arr []string, str string) bool { + for i := 0; i < len(arr); i++ { + if strings.EqualFold(arr[i], str) { + return true + } + } + return false +} + +// UpdateAvatar update the avatar of user. +func (t UserModel) ReleaseConn() UserModel { + t.Conn = nil + return t +} + +// UpdateAvatar update the avatar of user. +func (t UserModel) UpdateAvatar(avatar string) { + t.Avatar = avatar +} + +// WithRoles query the role info of the user. +func (t UserModel) WithRoles() UserModel { + roleModel, _ := t.Table("goadmin_role_users"). + LeftJoin("goadmin_roles", "goadmin_roles.id", "=", "goadmin_role_users.role_id"). + Where("user_id", "=", t.Id). + Select("goadmin_roles.id", "goadmin_roles.name", "goadmin_roles.slug", + "goadmin_roles.created_at", "goadmin_roles.updated_at"). + All() + + for _, role := range roleModel { + t.Roles = append(t.Roles, Role().MapToModel(role)) + } + + if len(t.Roles) > 0 { + t.Level = t.Roles[0].Slug + t.LevelName = t.Roles[0].Name + } + + return t +} + +func (t UserModel) GetAllRoleId() []interface{} { + + var ids = make([]interface{}, len(t.Roles)) + + for key, role := range t.Roles { + ids[key] = role.Id + } + + return ids +} + +// WithPermissions query the permission info of the user. +func (t UserModel) WithPermissions() UserModel { + + var permissions = make([]map[string]interface{}, 0) + + roleIds := t.GetAllRoleId() + + if len(roleIds) > 0 { + permissions, _ = t.Table("goadmin_role_permissions"). + LeftJoin("goadmin_permissions", "goadmin_permissions.id", "=", "goadmin_role_permissions.permission_id"). + WhereIn("role_id", roleIds). + Select("goadmin_permissions.http_method", "goadmin_permissions.http_path", + "goadmin_permissions.id", "goadmin_permissions.name", "goadmin_permissions.slug", + "goadmin_permissions.created_at", "goadmin_permissions.updated_at"). + All() + } + + userPermissions, _ := t.Table("goadmin_user_permissions"). + LeftJoin("goadmin_permissions", "goadmin_permissions.id", "=", "goadmin_user_permissions.permission_id"). + Where("user_id", "=", t.Id). + Select("goadmin_permissions.http_method", "goadmin_permissions.http_path", + "goadmin_permissions.id", "goadmin_permissions.name", "goadmin_permissions.slug", + "goadmin_permissions.created_at", "goadmin_permissions.updated_at"). + All() + + permissions = append(permissions, userPermissions...) + + for i := 0; i < len(permissions); i++ { + exist := false + for j := 0; j < len(t.Permissions); j++ { + if t.Permissions[j].Id == permissions[i]["id"] { + exist = true + break + } + } + if exist { + continue + } + t.Permissions = append(t.Permissions, Permission().MapToModel(permissions[i])) + } + + return t +} + +// WithMenus query the menu info of the user. +func (t UserModel) WithMenus() UserModel { + + var menuIdsModel []map[string]interface{} + + if t.IsSuperAdmin() { + menuIdsModel, _ = t.Table("goadmin_role_menu"). + LeftJoin("goadmin_menu", "goadmin_menu.id", "=", "goadmin_role_menu.menu_id"). + Select("menu_id", "parent_id"). + All() + } else { + rolesId := t.GetAllRoleId() + if len(rolesId) > 0 { + menuIdsModel, _ = t.Table("goadmin_role_menu"). + LeftJoin("goadmin_menu", "goadmin_menu.id", "=", "goadmin_role_menu.menu_id"). + WhereIn("goadmin_role_menu.role_id", rolesId). + Select("menu_id", "parent_id"). + All() + } + } + + var menuIds []int64 + + for _, mid := range menuIdsModel { + if parentId, ok := mid["parent_id"].(int64); ok && parentId != 0 { + for _, mid2 := range menuIdsModel { + if mid2["menu_id"].(int64) == mid["parent_id"].(int64) { + menuIds = append(menuIds, mid["menu_id"].(int64)) + break + } + } + } else { + menuIds = append(menuIds, mid["menu_id"].(int64)) + } + } + + t.MenuIds = menuIds + return t +} + +// New create a user model. +func (t UserModel) New(username, password, name, avatar string) (UserModel, error) { + + id, err := t.WithTx(t.Tx).Table(t.TableName).Insert(dialect.H{ + "username": username, + "password": password, + "name": name, + "avatar": avatar, + }) + + t.Id = id + t.UserName = username + t.Password = password + t.Avatar = avatar + t.Name = name + + return t, err +} + +// Update update the user model. +func (t UserModel) Update(username, password, name, avatar string, isUpdateAvatar bool) (int64, error) { + + fieldValues := dialect.H{ + "username": username, + "name": name, + "updated_at": time.Now().Format("2006-01-02 15:04:05"), + } + + if avatar == "" || isUpdateAvatar { + fieldValues["avatar"] = avatar + } + + if password != "" { + fieldValues["password"] = password + } + + return t.WithTx(t.Tx).Table(t.TableName). + Where("id", "=", t.Id). + Update(fieldValues) +} + +// UpdatePwd update the password of the user model. +func (t UserModel) UpdatePwd(password string) UserModel { + + _, _ = t.Table(t.TableName). + Where("id", "=", t.Id). + Update(dialect.H{ + "password": password, + }) + + t.Password = password + return t +} + +// CheckRole check the role of the user model. +func (t UserModel) CheckRoleId(roleId string) bool { + checkRole, _ := t.Table("goadmin_role_users"). + Where("role_id", "=", roleId). + Where("user_id", "=", t.Id). + First() + return checkRole != nil +} + +// DeleteRoles delete all the roles of the user model. +func (t UserModel) DeleteRoles() error { + return t.Table("goadmin_role_users"). + Where("user_id", "=", t.Id). + Delete() +} + +// AddRole add a role of the user model. +func (t UserModel) AddRole(roleId string) (int64, error) { + if roleId != "" { + if !t.CheckRoleId(roleId) { + return t.WithTx(t.Tx).Table("goadmin_role_users"). + Insert(dialect.H{ + "role_id": roleId, + "user_id": t.Id, + }) + } + } + return 0, nil +} + +// CheckRole check the role of the user. +func (t UserModel) CheckRole(slug string) bool { + for _, role := range t.Roles { + if role.Slug == slug { + return true + } + } + + return false +} + +// CheckPermission check the permission of the user. +func (t UserModel) CheckPermissionById(permissionId string) bool { + checkPermission, _ := t.Table("goadmin_user_permissions"). + Where("permission_id", "=", permissionId). + Where("user_id", "=", t.Id). + First() + return checkPermission != nil +} + +// CheckPermission check the permission of the user. +func (t UserModel) CheckPermission(permission string) bool { + for _, per := range t.Permissions { + if per.Slug == permission { + return true + } + } + + return false +} + +// DeletePermissions delete all the permissions of the user model. +func (t UserModel) DeletePermissions() error { + return t.WithTx(t.Tx).Table("goadmin_user_permissions"). + Where("user_id", "=", t.Id). + Delete() +} + +// AddPermission add a permission of the user model. +func (t UserModel) AddPermission(permissionId string) (int64, error) { + if permissionId != "" { + if !t.CheckPermissionById(permissionId) { + return t.WithTx(t.Tx).Table("goadmin_user_permissions"). + Insert(dialect.H{ + "permission_id": permissionId, + "user_id": t.Id, + }) + } + } + return 0, nil +} + +// MapToModel get the user model from given map. +func (t UserModel) MapToModel(m map[string]interface{}) UserModel { + t.Id, _ = m["id"].(int64) + t.Name, _ = m["name"].(string) + t.UserName, _ = m["username"].(string) + t.Password, _ = m["password"].(string) + t.Avatar, _ = m["avatar"].(string) + t.RememberToken, _ = m["remember_token"].(string) + t.CreatedAt, _ = m["created_at"].(string) + t.UpdatedAt, _ = m["updated_at"].(string) + return t +} diff --git a/plugins/admin/modules/captcha/captcha.go b/plugins/admin/modules/captcha/captcha.go new file mode 100644 index 0000000000000000000000000000000000000000..1f4f9cd91e7befd6f2a6c4213dc3b070d829f261 --- /dev/null +++ b/plugins/admin/modules/captcha/captcha.go @@ -0,0 +1,19 @@ +package captcha + +type Captcha interface { + Validate(token string) bool +} + +var List = make(map[string]Captcha) + +func Add(key string, captcha Captcha) { + if _, exist := List[key]; exist { + panic("captcha exist") + } + List[key] = captcha +} + +func Get(key string) (Captcha, bool) { + captcha, ok := List[key] + return captcha, ok +} diff --git a/plugins/admin/modules/constant/constant.go b/plugins/admin/modules/constant/constant.go new file mode 100644 index 0000000000000000000000000000000000000000..c320cd1e2badb3df2c694013a3b798ce1da86147 --- /dev/null +++ b/plugins/admin/modules/constant/constant.go @@ -0,0 +1,22 @@ +package constant + +import ( + "github.com/GoAdminGroup/go-admin/modules/constant" +) + +const ( + // PjaxHeader is default pjax http header key. + PjaxHeader = constant.PjaxHeader + + // PjaxUrlHeader is default pjax url http header key. + PjaxUrlHeader = constant.PjaxUrlHeader + + EditPKKey = "__goadmin_edit_pk" + DetailPKKey = "__goadmin_detail_pk" + PrefixKey = "__prefix" + + IframeKey = "__goadmin_iframe" + IframeIDKey = "__goadmin_iframe_id" + + ContextNodeNeedAuth = constant.ContextNodeNeedAuth +) diff --git a/plugins/admin/modules/form/form.go b/plugins/admin/modules/form/form.go new file mode 100644 index 0000000000000000000000000000000000000000..1f7cc955e827cdbf58de67d8f34dec304da9655b --- /dev/null +++ b/plugins/admin/modules/form/form.go @@ -0,0 +1,119 @@ +package form + +import ( + "errors" +) + +const ( + PostTypeKey = "__go_admin_post_type" + PostResultKey = "__go_admin_post_result" + PostIsSingleUpdateKey = "__go_admin_is_single_update" + + PreviousKey = "__go_admin_previous_" + TokenKey = "__go_admin_t_" + MethodKey = "__go_admin_method_" + + NoAnimationKey = "__go_admin_no_animation_" +) + +// Values maps a string key to a list of values. +// It is typically used for query parameters and form values. +// Unlike in the http.Header map, the keys in a Values map +// are case-sensitive. +type Values map[string][]string + +// Get gets the first value associated with the given key. +// If there are no values associated with the key, Get returns +// the empty string. To access multiple values, use the map +// directly. +func (f Values) Get(key string) string { + if len(f[key]) > 0 { + return f[key][0] + } + return "" +} + +// Add adds the value to key. It appends to any existing +// values associated with key. +func (f Values) Add(key string, value string) { + f[key] = []string{value} +} + +// IsEmpty check the key is empty or not. +func (f Values) IsEmpty(key ...string) bool { + for _, k := range key { + if f.Get(k) == "" { + return true + } + } + return false +} + +// Has check the key exists or not. +func (f Values) Has(key ...string) bool { + for _, k := range key { + if f.Get(k) != "" { + return true + } + } + return false +} + +// Delete deletes the values associated with key. +func (f Values) Delete(key string) { + delete(f, key) +} + +// ToMap turn the values to a map[string]string type. +func (f Values) ToMap() map[string]string { + var m = make(map[string]string) + for key, v := range f { + if len(v) > 0 { + m[key] = v[0] + } + } + return m +} + +// IsUpdatePost check the param if is from an update post request type or not. +func (f Values) IsUpdatePost() bool { + return f.Get(PostTypeKey) == "0" +} + +// IsInsertPost check the param if is from an insert post request type or not. +func (f Values) IsInsertPost() bool { + return f.Get(PostTypeKey) == "1" +} + +// PostError get the post result. +func (f Values) PostError() error { + msg := f.Get(PostResultKey) + if msg == "" { + return nil + } + return errors.New(msg) +} + +// IsSingleUpdatePost check the param if from an single update post request type or not. +func (f Values) IsSingleUpdatePost() bool { + return f.Get(PostIsSingleUpdateKey) == "1" +} + +// RemoveRemark removes the PostType and IsSingleUpdate flag parameters. +func (f Values) RemoveRemark() Values { + f.Delete(PostTypeKey) + f.Delete(PostIsSingleUpdateKey) + return f +} + +// RemoveSysRemark removes all framework post flag parameters. +func (f Values) RemoveSysRemark() Values { + f.Delete(PostTypeKey) + f.Delete(PostIsSingleUpdateKey) + f.Delete(PostResultKey) + f.Delete(PreviousKey) + f.Delete(TokenKey) + f.Delete(MethodKey) + f.Delete(NoAnimationKey) + return f +} diff --git a/plugins/admin/modules/guard/delete.go b/plugins/admin/modules/guard/delete.go new file mode 100644 index 0000000000000000000000000000000000000000..7fa2adb0b0367a6ec0ba4fcfffbf66b0fd3f503c --- /dev/null +++ b/plugins/admin/modules/guard/delete.go @@ -0,0 +1,40 @@ +package guard + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" +) + +type DeleteParam struct { + Panel table.Table + Id string + Prefix string +} + +func (g *Guard) Delete(ctx *context.Context) { + panel, prefix := g.table(ctx) + if !panel.GetDeletable() { + alert(ctx, panel, errors.OperationNotAllow, g.conn, g.navBtns) + ctx.Abort() + return + } + + id := ctx.FormValue("id") + if id == "" { + alert(ctx, panel, errors.WrongID, g.conn, g.navBtns) + ctx.Abort() + return + } + + ctx.SetUserValue(deleteParamKey, &DeleteParam{ + Panel: panel, + Id: id, + Prefix: prefix, + }) + ctx.Next() +} + +func GetDeleteParam(ctx *context.Context) *DeleteParam { + return ctx.UserValue[deleteParamKey].(*DeleteParam) +} diff --git a/plugins/admin/modules/guard/edit.go b/plugins/admin/modules/guard/edit.go new file mode 100644 index 0000000000000000000000000000000000000000..a78d6885feffdbeac5a91567b0f9017acb1ca4af --- /dev/null +++ b/plugins/admin/modules/guard/edit.go @@ -0,0 +1,170 @@ +package guard + +import ( + tmpl "html/template" + "mime/multipart" + "regexp" + "strings" + + "github.com/GoAdminGroup/go-admin/template/types" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" +) + +type ShowFormParam struct { + Panel table.Table + Id string + Prefix string + Param parameter.Parameters +} + +func (g *Guard) ShowForm(ctx *context.Context) { + + panel, prefix := g.table(ctx) + + if !panel.GetEditable() { + alert(ctx, panel, errors.OperationNotAllow, g.conn, g.navBtns) + ctx.Abort() + return + } + + if panel.GetOnlyInfo() { + ctx.Redirect(config.Url("/info/" + prefix)) + ctx.Abort() + return + } + + if panel.GetOnlyDetail() { + ctx.Redirect(config.Url("/info/" + prefix + "/detail")) + ctx.Abort() + return + } + + if panel.GetOnlyNewForm() { + ctx.Redirect(config.Url("/info/" + prefix + "/new")) + ctx.Abort() + return + } + + id := ctx.Query(constant.EditPKKey) + + if id == "" { + id = "1" + } + + ctx.SetUserValue(showFormParamKey, &ShowFormParam{ + Panel: panel, + Id: id, + Prefix: prefix, + Param: parameter.GetParam(ctx.Request.URL, panel.GetInfo().DefaultPageSize, panel.GetInfo().SortField, + panel.GetInfo().GetSort()).WithPKs(id), + }) + ctx.Next() +} + +func GetShowFormParam(ctx *context.Context) *ShowFormParam { + return ctx.UserValue[showFormParamKey].(*ShowFormParam) +} + +type EditFormParam struct { + Panel table.Table + Id string + Prefix string + Param parameter.Parameters + Path string + MultiForm *multipart.Form + PreviousPath string + Alert tmpl.HTML + FromList bool + IsIframe bool + IframeID string +} + +func (e EditFormParam) Value() form.Values { + return e.MultiForm.Value +} + +func (g *Guard) EditForm(ctx *context.Context) { + + panel, prefix := g.table(ctx) + + if !panel.GetEditable() { + alert(ctx, panel, errors.OperationNotAllow, g.conn, g.navBtns) + ctx.Abort() + return + } + token := ctx.FormValue(form.TokenKey) + + if !auth.GetTokenService(g.services.Get(auth.TokenServiceKey)).CheckToken(token) { + alert(ctx, panel, errors.EditFailWrongToken, g.conn, g.navBtns) + ctx.Abort() + return + } + + var ( + previous = ctx.FormValue(form.PreviousKey) + fromList = isInfoUrl(previous) + param = parameter.GetParamFromURL(previous, panel.GetInfo().DefaultPageSize, + panel.GetInfo().GetSort(), panel.GetPrimaryKey().Name) + ) + + if fromList { + previous = config.Url("/info/" + prefix + param.GetRouteParamStr()) + } + + var ( + multiForm = ctx.Request.MultipartForm + id = multiForm.Value[panel.GetPrimaryKey().Name][0] + values = ctx.Request.MultipartForm.Value + ) + + ctx.SetUserValue(editFormParamKey, &EditFormParam{ + Panel: panel, + Id: id, + Prefix: prefix, + Param: param.WithPKs(id), + Path: strings.Split(previous, "?")[0], + MultiForm: multiForm, + IsIframe: form.Values(values).Get(constant.IframeKey) == "true", + IframeID: form.Values(values).Get(constant.IframeIDKey), + PreviousPath: previous, + FromList: fromList, + }) + ctx.Next() +} + +func isInfoUrl(s string) bool { + reg, _ := regexp.Compile("(.*?)info/(.*?)$") + sub := reg.FindStringSubmatch(s) + return len(sub) > 2 && !strings.Contains(sub[2], "/") +} + +func GetEditFormParam(ctx *context.Context) *EditFormParam { + return ctx.UserValue[editFormParamKey].(*EditFormParam) +} + +func alert(ctx *context.Context, panel table.Table, msg string, conn db.Connection, btns *types.Buttons) { + if ctx.WantJSON() { + response.BadRequest(ctx, msg) + } else { + response.Alert(ctx, panel.GetInfo().Description, panel.GetInfo().Title, msg, conn, btns) + } +} + +func alertWithTitleAndDesc(ctx *context.Context, title, desc, msg string, conn db.Connection, btns *types.Buttons) { + response.Alert(ctx, desc, title, msg, conn, btns) +} + +func getAlert(ctx *context.Context, msg string) tmpl.HTML { + return template.Get(ctx, config.GetTheme()).Alert().Warning(msg) +} diff --git a/plugins/admin/modules/guard/export.go b/plugins/admin/modules/guard/export.go new file mode 100644 index 0000000000000000000000000000000000000000..71822e8e446a0f92d734d5e592b670f0625141c2 --- /dev/null +++ b/plugins/admin/modules/guard/export.go @@ -0,0 +1,43 @@ +package guard + +import ( + "strings" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" +) + +type ExportParam struct { + Panel table.Table + Id []string + Prefix string + IsAll bool +} + +func (g *Guard) Export(ctx *context.Context) { + panel, prefix := g.table(ctx) + if !panel.GetExportable() { + alert(ctx, panel, errors.OperationNotAllow, g.conn, g.navBtns) + ctx.Abort() + return + } + + idStr := make([]string, 0) + ids := ctx.FormValue("id") + if ids != "" { + idStr = strings.Split(ctx.FormValue("id"), ",") + } + + ctx.SetUserValue(exportParamKey, &ExportParam{ + Panel: panel, + Id: idStr, + Prefix: prefix, + IsAll: ctx.FormValue("is_all") == "true", + }) + ctx.Next() +} + +func GetExportParam(ctx *context.Context) *ExportParam { + return ctx.UserValue[exportParamKey].(*ExportParam) +} diff --git a/plugins/admin/modules/guard/guard.go b/plugins/admin/modules/guard/guard.go new file mode 100644 index 0000000000000000000000000000000000000000..63a624e0b0ae08fb5525e458cf0bad4c67137ae5 --- /dev/null +++ b/plugins/admin/modules/guard/guard.go @@ -0,0 +1,66 @@ +package guard + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" +) + +type Guard struct { + services service.List + conn db.Connection + tableList table.GeneratorList + navBtns *types.Buttons +} + +func New(s service.List, c db.Connection, t table.GeneratorList, b *types.Buttons) *Guard { + return &Guard{ + services: s, + conn: c, + tableList: t, + navBtns: b, + } +} + +func (g *Guard) table(ctx *context.Context) (table.Table, string) { + prefix := ctx.Query(constant.PrefixKey) + return g.tableList[prefix](ctx), prefix +} + +func (g *Guard) CheckPrefix(ctx *context.Context) { + + prefix := ctx.Query(constant.PrefixKey) + + if _, ok := g.tableList[prefix]; !ok { + if ctx.Headers(constant.PjaxHeader) == "" && ctx.Method() != "GET" { + response.BadRequest(ctx, errors.Msg) + } else { + response.Alert(ctx, errors.Msg, errors.Msg, "table model not found", g.conn, g.navBtns, + template.Missing404Page) + } + ctx.Abort() + return + } + + ctx.Next() +} + +const ( + editFormParamKey = "edit_form_param" + deleteParamKey = "delete_param" + exportParamKey = "export_param" + serverLoginParamKey = "server_login_param" + deleteMenuParamKey = "delete_menu_param" + editMenuParamKey = "edit_menu_param" + newMenuParamKey = "new_menu_param" + newFormParamKey = "new_form_param" + updateParamKey = "update_param" + showFormParamKey = "show_form_param" + showNewFormParam = "show_new_form_param" +) diff --git a/plugins/admin/modules/guard/menu_delete.go b/plugins/admin/modules/guard/menu_delete.go new file mode 100644 index 0000000000000000000000000000000000000000..4d2d75aa563f87e4a66f1cc1b5be8c907b9d0d7c --- /dev/null +++ b/plugins/admin/modules/guard/menu_delete.go @@ -0,0 +1,32 @@ +package guard + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/errors" +) + +type MenuDeleteParam struct { + Id string +} + +func (g *Guard) MenuDelete(ctx *context.Context) { + + id := ctx.Query("id") + + if id == "" { + alertWithTitleAndDesc(ctx, "Menu", "menu", errors.WrongID, g.conn, g.navBtns) + ctx.Abort() + return + } + + // TODO: check the user permission + + ctx.SetUserValue(deleteMenuParamKey, &MenuDeleteParam{ + Id: id, + }) + ctx.Next() +} + +func GetMenuDeleteParam(ctx *context.Context) *MenuDeleteParam { + return ctx.UserValue[deleteMenuParamKey].(*MenuDeleteParam) +} diff --git a/plugins/admin/modules/guard/menu_edit.go b/plugins/admin/modules/guard/menu_edit.go new file mode 100644 index 0000000000000000000000000000000000000000..51a13056bd038fc8de7c9133924d402397e02afc --- /dev/null +++ b/plugins/admin/modules/guard/menu_edit.go @@ -0,0 +1,75 @@ +package guard + +import ( + "html/template" + "strconv" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" +) + +type MenuEditParam struct { + Id string + Title string + Header string + PluginName string + ParentId int64 + Icon string + Uri string + Roles []string + Alert template.HTML +} + +func (e MenuEditParam) HasAlert() bool { + return e.Alert != template.HTML("") +} + +func (g *Guard) MenuEdit(ctx *context.Context) { + + parentId := ctx.FormValue("parent_id") + if parentId == "" { + parentId = "0" + } + + var ( + parentIdInt, _ = strconv.Atoi(parentId) + token = ctx.FormValue(form.TokenKey) + alert template.HTML + ) + + if !auth.GetTokenService(g.services.Get(auth.TokenServiceKey)).CheckToken(token) { + alert = getAlert(ctx, errors.EditFailWrongToken) + } + + if alert == "" { + alert = checkEmpty(ctx, "id", "title", "icon") + } + + ctx.SetUserValue(editMenuParamKey, &MenuEditParam{ + Id: ctx.FormValue("id"), + Title: ctx.FormValue("title"), + Header: ctx.FormValue("header"), + PluginName: ctx.FormValue("plugin_name"), + ParentId: int64(parentIdInt), + Icon: ctx.FormValue("icon"), + Uri: ctx.FormValue("uri"), + Roles: ctx.Request.Form["roles[]"], + Alert: alert, + }) + ctx.Next() +} + +func GetMenuEditParam(ctx *context.Context) *MenuEditParam { + return ctx.UserValue[editMenuParamKey].(*MenuEditParam) +} + +func checkEmpty(ctx *context.Context, key ...string) template.HTML { + for _, k := range key { + if ctx.FormValue(k) == "" { + return getAlert(ctx, "wrong "+k) + } + } + return template.HTML("") +} diff --git a/plugins/admin/modules/guard/menu_new.go b/plugins/admin/modules/guard/menu_new.go new file mode 100644 index 0000000000000000000000000000000000000000..cd353dc2989047ff23aff88f9c5461e67099e810 --- /dev/null +++ b/plugins/admin/modules/guard/menu_new.go @@ -0,0 +1,65 @@ +package guard + +import ( + "html/template" + "strconv" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" +) + +type MenuNewParam struct { + Title string + Header string + ParentId int64 + Icon string + PluginName string + Uri string + Roles []string + Alert template.HTML +} + +func (e MenuNewParam) HasAlert() bool { + return e.Alert != template.HTML("") +} + +func (g *Guard) MenuNew(ctx *context.Context) { + + parentId := ctx.FormValue("parent_id") + if parentId == "" { + parentId = "0" + } + + var ( + alert template.HTML + token = ctx.FormValue(form.TokenKey) + ) + + if !auth.GetTokenService(g.services.Get(auth.TokenServiceKey)).CheckToken(token) { + alert = getAlert(ctx, errors.EditFailWrongToken) + } + + if alert == "" { + alert = checkEmpty(ctx, "title", "icon") + } + + parentIdInt, _ := strconv.Atoi(parentId) + + ctx.SetUserValue(newMenuParamKey, &MenuNewParam{ + Title: ctx.FormValue("title"), + Header: ctx.FormValue("header"), + PluginName: ctx.FormValue("plugin_name"), + ParentId: int64(parentIdInt), + Icon: ctx.FormValue("icon"), + Uri: ctx.FormValue("uri"), + Roles: ctx.Request.Form["roles[]"], + Alert: alert, + }) + ctx.Next() +} + +func GetMenuNewParam(ctx *context.Context) *MenuNewParam { + return ctx.UserValue[newMenuParamKey].(*MenuNewParam) +} diff --git a/plugins/admin/modules/guard/new.go b/plugins/admin/modules/guard/new.go new file mode 100644 index 0000000000000000000000000000000000000000..f83c7a8fea150c4bbcfb6768ae437931cf5cb629 --- /dev/null +++ b/plugins/admin/modules/guard/new.go @@ -0,0 +1,126 @@ +package guard + +import ( + "html/template" + "mime/multipart" + "strings" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" +) + +type ShowNewFormParam struct { + Panel table.Table + Prefix string + Param parameter.Parameters +} + +func (g *Guard) ShowNewForm(ctx *context.Context) { + + panel, prefix := g.table(ctx) + + if !panel.GetCanAdd() { + alert(ctx, panel, errors.OperationNotAllow, g.conn, g.navBtns) + ctx.Abort() + return + } + + if panel.GetOnlyInfo() { + ctx.Redirect(config.Url("/info/" + prefix)) + ctx.Abort() + return + } + + if panel.GetOnlyDetail() { + ctx.Redirect(config.Url("/info/" + prefix + "/detail")) + ctx.Abort() + return + } + + if panel.GetOnlyUpdateForm() { + ctx.Redirect(config.Url("/info/" + prefix + "/edit")) + ctx.Abort() + return + } + + ctx.SetUserValue(showNewFormParam, &ShowNewFormParam{ + Panel: panel, + Prefix: prefix, + Param: parameter.GetParam(ctx.Request.URL, panel.GetInfo().DefaultPageSize, panel.GetInfo().SortField, + panel.GetInfo().GetSort()), + }) + ctx.Next() +} + +func GetShowNewFormParam(ctx *context.Context) *ShowNewFormParam { + return ctx.UserValue[showNewFormParam].(*ShowNewFormParam) +} + +type NewFormParam struct { + Panel table.Table + Id string + Prefix string + Param parameter.Parameters + Path string + MultiForm *multipart.Form + PreviousPath string + FromList bool + IsIframe bool + IframeID string + Alert template.HTML +} + +func (e NewFormParam) Value() form.Values { + return e.MultiForm.Value +} + +func (g *Guard) NewForm(ctx *context.Context) { + + var ( + previous = ctx.FormValue(form.PreviousKey) + panel, prefix = g.table(ctx) + conn = db.GetConnection(g.services) + token = ctx.FormValue(form.TokenKey) + ) + + if !auth.GetTokenService(g.services.Get(auth.TokenServiceKey)).CheckToken(token) { + alert(ctx, panel, errors.CreateFailWrongToken, conn, g.navBtns) + ctx.Abort() + return + } + + fromList := isInfoUrl(previous) + param := parameter.GetParamFromURL(previous, panel.GetInfo().DefaultPageSize, + panel.GetInfo().GetSort(), panel.GetPrimaryKey().Name) + + if fromList { + previous = config.Url("/info/" + prefix + param.GetRouteParamStr()) + } + + values := ctx.Request.MultipartForm.Value + + ctx.SetUserValue(newFormParamKey, &NewFormParam{ + Panel: panel, + Id: "", + Prefix: prefix, + Param: param, + IsIframe: form.Values(values).Get(constant.IframeKey) == "true", + IframeID: form.Values(values).Get(constant.IframeIDKey), + Path: strings.Split(previous, "?")[0], + MultiForm: ctx.Request.MultipartForm, + PreviousPath: previous, + FromList: fromList, + }) + ctx.Next() +} + +func GetNewFormParam(ctx *context.Context) *NewFormParam { + return ctx.UserValue[newFormParamKey].(*NewFormParam) +} diff --git a/plugins/admin/modules/guard/server_login.go b/plugins/admin/modules/guard/server_login.go new file mode 100644 index 0000000000000000000000000000000000000000..5207531d01646f24a5629c17a934402e3e51c872 --- /dev/null +++ b/plugins/admin/modules/guard/server_login.go @@ -0,0 +1,38 @@ +package guard + +import ( + "encoding/json" + "io/ioutil" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/logger" +) + +type ServerLoginParam struct { + Account string + Password string +} + +func (g *Guard) ServerLogin(ctx *context.Context) { + + var p ServerLoginParam + + body, err := ioutil.ReadAll(ctx.Request.Body) + + if err != nil { + logger.Error("get server login parameter error: ", err) + } + + err = json.Unmarshal(body, &p) + + if err != nil { + logger.Error("unmarshal server login parameter error: ", err) + } + + ctx.SetUserValue(serverLoginParamKey, &p) + ctx.Next() +} + +func GetServerLoginParam(ctx *context.Context) *ServerLoginParam { + return ctx.UserValue[serverLoginParamKey].(*ServerLoginParam) +} diff --git a/plugins/admin/modules/guard/update.go b/plugins/admin/modules/guard/update.go new file mode 100644 index 0000000000000000000000000000000000000000..11216598dcdbdac495760c851fd7ce8aa140a070 --- /dev/null +++ b/plugins/admin/modules/guard/update.go @@ -0,0 +1,47 @@ +package guard + +import ( + "net/http" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" +) + +type UpdateParam struct { + Panel table.Table + Prefix string + Value form.Values +} + +func (g *Guard) Update(ctx *context.Context) { + panel, prefix := g.table(ctx) + + pname := panel.GetPrimaryKey().Name + + id := ctx.FormValue("pk") + + if id == "" { + ctx.JSON(http.StatusBadRequest, map[string]interface{}{ + "msg": "wrong " + pname, + }) + ctx.Abort() + return + } + + var f = make(form.Values) + f.Add(form.PostIsSingleUpdateKey, "1") + f.Add(pname, id) + f.Add(ctx.FormValue("name"), ctx.FormValue("value")) + + ctx.SetUserValue(updateParamKey, &UpdateParam{ + Panel: panel, + Prefix: prefix, + Value: f, + }) + ctx.Next() +} + +func GetUpdateParam(ctx *context.Context) *UpdateParam { + return ctx.UserValue[updateParamKey].(*UpdateParam) +} diff --git a/plugins/admin/modules/helper.go b/plugins/admin/modules/helper.go new file mode 100644 index 0000000000000000000000000000000000000000..cef8b6a379f466fb66d2cce0a6b699be76bbb181 --- /dev/null +++ b/plugins/admin/modules/helper.go @@ -0,0 +1,88 @@ +package modules + +import ( + "html/template" + "strconv" + + "github.com/google/uuid" +) + +func InArray(arr []string, str string) bool { + for _, v := range arr { + if v == str { + return true + } + } + return false +} + +func Delimiter(del, del2, s string) string { + return del + s + del2 +} + +func FilterField(filed, delimiter, delimiter2 string) string { + return delimiter + filed + delimiter2 +} + +func InArrayWithoutEmpty(arr []string, str string) bool { + if len(arr) == 0 { + return true + } + for _, v := range arr { + if v == str { + return true + } + } + return false +} + +func RemoveBlankFromArray(s []string) []string { + var r []string + for _, str := range s { + if str != "" { + r = append(r, str) + } + } + return r +} + +func Uuid() string { + return uuid.New().String() +} + +func SetDefault(source, def string) string { + if source == "" { + return def + } + return source +} + +func GetPage(page string) (pageInt int) { + if page == "" { + pageInt = 1 + } else { + pageInt, _ = strconv.Atoi(page) + } + return +} + +func AorB(condition bool, a, b string) string { + if condition { + return a + } + return b +} + +func AorEmpty(condition bool, a string) string { + if condition { + return a + } + return "" +} + +func AorBHTML(condition bool, a, b template.HTML) template.HTML { + if condition { + return a + } + return b +} diff --git a/plugins/admin/modules/helper_test.go b/plugins/admin/modules/helper_test.go new file mode 100644 index 0000000000000000000000000000000000000000..97b1cf99072e1dbd7f6d01d9907ce73c739b4c05 --- /dev/null +++ b/plugins/admin/modules/helper_test.go @@ -0,0 +1,17 @@ +package modules + +import ( + "regexp" + "testing" + + "github.com/magiconair/properties/assert" +) + +func TestInArray(t *testing.T) { + assert.Equal(t, isFormURL("/admin/info/profile/new"), true) +} + +func isFormURL(s string) bool { + reg, _ := regexp.Compile("(.*?)info/(.*)/(new|edit)(.*?)") + return reg.MatchString(s) +} diff --git a/plugins/admin/modules/paginator/paginator.go b/plugins/admin/modules/paginator/paginator.go new file mode 100644 index 0000000000000000000000000000000000000000..393d69653730d576c2583e21269d22dddc8490d4 --- /dev/null +++ b/plugins/admin/modules/paginator/paginator.go @@ -0,0 +1,158 @@ +package paginator + +import ( + "fmt" + "html/template" + "math" + "strconv" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + template2 "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/components" + "github.com/GoAdminGroup/go-admin/template/types" +) + +type Config struct { + Size int + Param parameter.Parameters + PageSizeList []string +} + +func Get(ctx *context.Context, cfg Config) types.PaginatorAttribute { + + paginator := template2.Default(ctx).Paginator().(*components.PaginatorAttribute) + + totalPage := int(math.Ceil(float64(cfg.Size) / float64(cfg.Param.PageSizeInt))) + + if cfg.Param.PageInt == 1 { + paginator.PreviousClass = "disabled" + paginator.PreviousUrl = cfg.Param.URLPath + } else { + paginator.PreviousClass = "" + paginator.PreviousUrl = cfg.Param.URLPath + cfg.Param.GetLastPageRouteParamStr(true) + } + + if cfg.Param.PageInt == totalPage { + paginator.NextClass = "disabled" + paginator.NextUrl = cfg.Param.URLPath + } else { + paginator.NextClass = "" + paginator.NextUrl = cfg.Param.URLPath + cfg.Param.GetNextPageRouteParamStr(true) + } + paginator.Url = cfg.Param.URLPath + cfg.Param.GetRouteParamStrWithoutPageSize("1") + "&" + form.NoAnimationKey + "=true" + paginator.CurPageEndIndex = strconv.Itoa((cfg.Param.PageInt) * cfg.Param.PageSizeInt) + paginator.CurPageStartIndex = strconv.Itoa((cfg.Param.PageInt-1)*cfg.Param.PageSizeInt + 1) + paginator.Total = strconv.Itoa(cfg.Size) + + if len(cfg.PageSizeList) == 0 { + cfg.PageSizeList = []string{"10", "20", "50", "100"} + } + + paginator.Option = make(map[string]template.HTML, len(cfg.PageSizeList)) + for i := 0; i < len(cfg.PageSizeList); i++ { + paginator.Option[cfg.PageSizeList[i]] = template.HTML("") + } + + paginator.Option[cfg.Param.PageSize] = template.HTML("selected") + + paginator.Pages = []map[string]string{} + + var pagesArr []map[string]string + + if totalPage < 10 { + for i := 1; i < totalPage+1; i++ { + if i == cfg.Param.PageInt { + pagesArr = addPageLink(pagesArr, cfg.Param, cfg.Param.PageInt, "active") + } else { + pagesArr = addPageLink(pagesArr, cfg.Param, i, "") + } + } + } else { + if cfg.Param.PageInt < 6 { + for i := 1; i < totalPage+1; i++ { + + if i == cfg.Param.PageInt { + pagesArr = addPageLink(pagesArr, cfg.Param, cfg.Param.PageInt, "active") + } else { + pagesArr = addPageLink(pagesArr, cfg.Param, i, "") + } + + if i == 6 { + pagesArr = addPageLink(pagesArr, cfg.Param, i, "split") + i = totalPage - 2 + } + } + } else if cfg.Param.PageInt < totalPage-4 { + for i := 1; i < totalPage+1; i++ { + + if i == cfg.Param.PageInt { + pagesArr = addPageLink(pagesArr, cfg.Param, cfg.Param.PageInt, "active") + } else { + pagesArr = addPageLink(pagesArr, cfg.Param, i, "") + } + + if i == 2 { + pagesArr = addPageLink(pagesArr, cfg.Param, i, "split") + i = cfg.Param.PageInt - 3 + } + + if i == cfg.Param.PageInt+2 { + pagesArr = addPageLink(pagesArr, cfg.Param, i, "split") + i = totalPage - 1 + } + } + } else { + for i := 1; i < totalPage+1; i++ { + + if i == cfg.Param.PageInt { + pagesArr = addPageLink(pagesArr, cfg.Param, cfg.Param.PageInt, "active") + } else { + pagesArr = addPageLink(pagesArr, cfg.Param, i, "") + } + + if i == 2 { + pagesArr = addPageLink(pagesArr, cfg.Param, i, "split") + i = totalPage - 6 + } + } + } + } + + paginator.Pages = pagesArr + + endNum := paginator.CurPageEndIndex + if cfg.Size < cfg.Param.PageSizeInt { + endNum = paginator.Total + } + + if cfg.Param.PageInt >= (cfg.Size+cfg.Param.PageSizeInt-1)/cfg.Param.PageSizeInt { + endNum = paginator.Total + } + + paginator.SetEntriesInfo(template.HTML(fmt.Sprintf(language.Get("showing %s to %s of %s entries"), + paginator.CurPageStartIndex, endNum, paginator.Total))) + + return paginator.SetPageSizeList(cfg.PageSizeList) +} + +func addPageLink(arr []map[string]string, params parameter.Parameters, page int, active string) []map[string]string { + + pageStr := strconv.Itoa(page) + isSplit := "0" + + if active == "split" { + isSplit = "1" + active = "" + pageStr = "" + } + + return append(arr, map[string]string{ + "page": pageStr, + "active": active, + "isSplit": isSplit, + "url": params.URLNoAnimation(pageStr), + }) +} diff --git a/plugins/admin/modules/paginator/paginator_test.go b/plugins/admin/modules/paginator/paginator_test.go new file mode 100644 index 0000000000000000000000000000000000000000..71ef79e820df653a222e8643b518ff7711268862 --- /dev/null +++ b/plugins/admin/modules/paginator/paginator_test.go @@ -0,0 +1,20 @@ +package paginator + +import ( + "testing" + + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + _ "github.com/GoAdminGroup/themes/sword" +) + +func TestGet(t *testing.T) { + config.Initialize(&config.Config{Theme: "sword"}) + param := parameter.BaseParam() + param.Page = "7" + Get(nil, Config{ + Size: 105, + Param: param, + PageSizeList: []string{"10", "20", "50", "100"}, + }) +} diff --git a/plugins/admin/modules/parameter/parameter.go b/plugins/admin/modules/parameter/parameter.go new file mode 100644 index 0000000000000000000000000000000000000000..1169404e20455a2edad38e4cbfc5e681f03f5dad --- /dev/null +++ b/plugins/admin/modules/parameter/parameter.go @@ -0,0 +1,490 @@ +package parameter + +import ( + "net/url" + "strconv" + "strings" + + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" +) + +type Parameters struct { + Page string + PageInt int + PageSize string + PageSizeInt int + SortField string + Columns []string + SortType string + Animation bool + URLPath string + Fields map[string][]string + OrConditions map[string]string + + cacheFixedStr url.Values +} + +const ( + Page = "__page" + PageSize = "__pageSize" + Sort = "__sort" + SortType = "__sort_type" + Columns = "__columns" + Prefix = "__prefix" + Pjax = "_pjax" + + sortTypeDesc = "desc" + sortTypeAsc = "asc" + + IsAll = "__is_all" + PrimaryKey = "__pk" + + True = "true" + False = "false" + + FilterRangeParamStartSuffix = "_start__goadmin" + FilterRangeParamEndSuffix = "_end__goadmin" + FilterParamJoinInfix = "_goadmin_join_" + FilterParamOperatorSuffix = "__goadmin_operator__" + FilterParamCountInfix = "__goadmin_index__" + + Separator = "__goadmin_separator__" +) + +var operators = map[string]string{ + "like": "like", + "gr": ">", + "gq": ">=", + "eq": "=", + "ne": "!=", + "le": "<", + "lq": "<=", + "free": "free", +} + +var keys = []string{Page, PageSize, Sort, Columns, Prefix, Pjax, form.NoAnimationKey} + +func BaseParam() Parameters { + return Parameters{Page: "1", PageSize: "10", PageInt: 1, PageSizeInt: 10, Fields: make(map[string][]string)} +} + +func GetParam(u *url.URL, defaultPageSize int, p ...string) Parameters { + values := u.Query() + + primaryKey := "id" + defaultSortType := "desc" + + if len(p) > 0 { + primaryKey = p[0] + defaultSortType = p[1] + } + + page := getDefault(values, Page, "1") + pageSize := getDefault(values, PageSize, strconv.Itoa(defaultPageSize)) + sortField := getDefault(values, Sort, primaryKey) + sortType := getDefault(values, SortType, defaultSortType) + columns := getDefault(values, Columns, "") + + animation := true + if values.Get(form.NoAnimationKey) == "true" { + animation = false + } + + fields := make(map[string][]string) + + for key, value := range values { + if !modules.InArray(keys, key) && len(value) > 0 && value[0] != "" { + if key == SortType { + if value[0] != sortTypeDesc && value[0] != sortTypeAsc { + fields[key] = []string{sortTypeDesc} + } + } else { + if strings.Contains(key, FilterParamOperatorSuffix) && + values.Get(strings.ReplaceAll(key, FilterParamOperatorSuffix, "")) == "" { + continue + } + fields[strings.ReplaceAll(key, "[]", "")] = value + } + } + } + + columnsArr := make([]string, 0) + if columns != "" { + columns, _ = url.QueryUnescape(columns) + columnsArr = strings.Split(columns, ",") + } + + pageInt, _ := strconv.Atoi(page) + pageSizeInt, _ := strconv.Atoi(pageSize) + + return Parameters{ + Page: page, + PageSize: pageSize, + PageSizeInt: pageSizeInt, + PageInt: pageInt, + URLPath: u.Path, + SortField: sortField, + SortType: sortType, + Fields: fields, + OrConditions: map[string]string{}, + Animation: animation, + Columns: columnsArr, + } +} + +func GetParamFromURL(urlStr string, defaultPageSize int, defaultSortType, primaryKey string) Parameters { + + u, err := url.Parse(urlStr) + + if err != nil { + return BaseParam() + } + + return GetParam(u, defaultPageSize, primaryKey, defaultSortType) +} + +func (param Parameters) WithPKs(id ...string) Parameters { + param.Fields[PrimaryKey] = []string{strings.Join(id, ",")} + return param +} + +func (param Parameters) PKs() []string { + pk := param.GetFieldValue(PrimaryKey) + if pk == "" { + return []string{} + } + return strings.Split(param.GetFieldValue(PrimaryKey), ",") +} + +func (param Parameters) DeletePK() Parameters { + delete(param.Fields, PrimaryKey) + return param +} + +func (param Parameters) PK() string { + pks := param.PKs() + if len(pks) > 0 { + return pks[0] + } + return "" +} + +func (param Parameters) IsAll() bool { + return param.GetFieldValue(IsAll) == True +} + +func (param *Parameters) WithURLPath(path string) Parameters { + param.URLPath = path + return *param +} + +func (param *Parameters) isAllTrue() { + param.Fields[IsAll] = []string{True} +} + +func (param *Parameters) isAllFalse() { + param.Fields[IsAll] = []string{False} +} + +func (param Parameters) WithIsAll(isAll bool) Parameters { + if isAll { + param.isAllTrue() + } else { + param.isAllFalse() + } + return param +} + +func (param Parameters) DeleteIsAll() Parameters { + delete(param.Fields, IsAll) + return param +} + +func (param Parameters) GetFilterFieldValueStart(field string) string { + return param.GetFieldValue(field + FilterRangeParamStartSuffix) +} + +func (param Parameters) GetFilterFieldValueEnd(field string) string { + return param.GetFieldValue(field + FilterRangeParamEndSuffix) +} + +func (param Parameters) GetFieldValue(field string) string { + value, ok := param.Fields[field] + if ok && len(value) > 0 { + return value[0] + } + return "" +} + +func (param Parameters) AddField(field, value string) Parameters { + param.Fields[field] = []string{value} + return param +} + +func (param Parameters) DeleteField(fields ...string) Parameters { + for _, field := range fields { + delete(param.Fields, field) + } + return param +} + +func (param Parameters) DeleteEditPk() Parameters { + delete(param.Fields, constant.EditPKKey) + return param +} + +func (param Parameters) DeleteDetailPk() Parameters { + delete(param.Fields, constant.DetailPKKey) + return param +} + +func (param Parameters) GetFieldValues(field string) []string { + return param.Fields[field] +} + +func (param Parameters) GetFieldValuesStr(field string) string { + return strings.Join(param.Fields[field], Separator) +} + +func (param Parameters) GetFieldOperator(field, suffix string) string { + op := param.GetFieldValue(field + FilterParamOperatorSuffix + suffix) + if op == "" { + return "eq" + } + return op +} + +func (param Parameters) Join() string { + p := param.GetFixedParamStr() + p.Add(Page, param.Page) + return p.Encode() +} + +func (param *Parameters) SetPage(page string) Parameters { + param.Page = page + param.PageInt, _ = strconv.Atoi(page) + return *param +} + +func (param *Parameters) SetPageSize(pageSize string) Parameters { + param.PageSize = pageSize + param.PageSizeInt, _ = strconv.Atoi(pageSize) + return *param +} + +func (param Parameters) GetRouteParamStr() string { + p := param.GetFixedParamStr() + p.Add(Page, param.Page) + return "?" + p.Encode() +} + +func (param Parameters) URL(page string) string { + return param.URLPath + param.SetPage(page).GetRouteParamStr() +} + +func (param Parameters) URLNoAnimation(page string) string { + return param.URLPath + param.SetPage(page).GetRouteParamStr() + "&" + form.NoAnimationKey + "=true" +} + +func (param Parameters) GetRouteParamStrWithoutPageSize(page string) string { + p := make(url.Values) + p.Add(Sort, param.SortField) + p.Add(Page, page) + p.Add(SortType, param.SortType) + if len(param.Columns) > 0 { + p.Add(Columns, strings.Join(param.Columns, ",")) + } + for key, value := range param.Fields { + p[key] = value + } + return "?" + p.Encode() +} + +func (param Parameters) GetFixedParamStrFromCache() url.Values { + if param.cacheFixedStr != nil { + return param.cacheFixedStr + } + + p := param.GetFixedParamStr() + param.cacheFixedStr = p + return p +} + +func (param Parameters) GetLastPageRouteParamStr(cache ...bool) string { + var p url.Values + if len(cache) > 0 && cache[0] { + p = param.GetFixedParamStrFromCache() + } else { + p = param.GetFixedParamStr() + } + p.Add(Page, strconv.Itoa(param.PageInt-1)) + return "?" + p.Encode() +} + +func (param Parameters) GetNextPageRouteParamStr(cache ...bool) string { + var p url.Values + if len(cache) > 0 && cache[0] { + p = param.GetFixedParamStrFromCache() + } else { + p = param.GetFixedParamStr() + } + p.Add(Page, strconv.Itoa(param.PageInt+1)) + return "?" + p.Encode() +} + +func (param Parameters) GetFixedParamStr() url.Values { + p := make(url.Values) + p.Add(Sort, param.SortField) + p.Add(PageSize, param.PageSize) + p.Add(SortType, param.SortType) + if len(param.Columns) > 0 { + p.Add(Columns, strings.Join(param.Columns, ",")) + } + for key, value := range param.Fields { + p[key] = value + } + return p +} + +func (param Parameters) GetFixedParamStrWithoutColumnsAndPage() string { + p := make(url.Values) + p.Add(Sort, param.SortField) + p.Add(PageSize, param.PageSize) + if len(param.Columns) > 0 { + p.Add(Columns, strings.Join(param.Columns, ",")) + } + p.Add(SortType, param.SortType) + return "?" + p.Encode() +} + +func (param Parameters) GetFixedParamStrWithoutSort() string { + p := make(url.Values) + p.Add(PageSize, param.PageSize) + for key, value := range param.Fields { + p[key] = value + } + p.Add(form.NoAnimationKey, "true") + if len(param.Columns) > 0 { + p.Add(Columns, strings.Join(param.Columns, ",")) + } + return "&" + p.Encode() +} + +func (param Parameters) Statement(wheres, table, delimiter, delimiter2 string, whereArgs []interface{}, columns, existKeys []string, + filterProcess func(string, string, string) string) (string, []interface{}, []string) { + var multiKey = make(map[string]uint8) + for key, value := range param.Fields { + + keyIndexSuffix := "" + + keyArr := strings.Split(key, FilterParamCountInfix) + + if len(keyArr) > 1 { + key = keyArr[0] + keyIndexSuffix = FilterParamCountInfix + keyArr[1] + } + + if keyIndexSuffix != "" { + multiKey[key] = 0 + } else if _, exist := multiKey[key]; !exist && modules.InArray(existKeys, key) { + continue + } + + var op string + if strings.Contains(key, FilterRangeParamEndSuffix) { + key = strings.ReplaceAll(key, FilterRangeParamEndSuffix, "") + op = "<=" + } else if strings.Contains(key, FilterRangeParamStartSuffix) { + key = strings.ReplaceAll(key, FilterRangeParamStartSuffix, "") + op = ">=" + } else if len(value) > 1 { + op = "in" + } else if !strings.Contains(key, FilterParamOperatorSuffix) { + op = operators[param.GetFieldOperator(key, keyIndexSuffix)] + } else { + continue + } + + if strings.Contains(key, FilterParamJoinInfix) { + keys := strings.Split(key, FilterParamJoinInfix) + val := filterProcess(key, value[0], keyIndexSuffix) + if op == "in" { + qmark := "" + for range value { + qmark += "?," + } + wheres += keys[0] + "." + modules.FilterField(keys[1], delimiter, delimiter2) + " " + op + " (" + qmark[:len(qmark)-1] + ") and " + } else { + wheres += keys[0] + "." + modules.FilterField(keys[1], delimiter, delimiter2) + " " + op + " ? and " + } + if op == "like" && !strings.Contains(val, "%") { + whereArgs = append(whereArgs, "%"+val+"%") + } else { + for _, v := range value { + whereArgs = append(whereArgs, filterProcess(key, v, keyIndexSuffix)) + } + } + } else { + if modules.InArray(columns, key) { + if op == "in" { + qmark := "" + for range value { + qmark += "?," + } + wheres += modules.Delimiter(delimiter, delimiter2, table) + "." + modules.FilterField(key, delimiter, delimiter2) + " " + op + " (" + qmark[:len(qmark)-1] + ") and " + } else { + wheres += modules.Delimiter(delimiter, delimiter2, table) + "." + modules.FilterField(key, delimiter, delimiter2) + " " + op + " ? and " + } + if op == "like" && !strings.Contains(value[0], "%") { + whereArgs = append(whereArgs, "%"+filterProcess(key, value[0], keyIndexSuffix)+"%") + } else { + for _, v := range value { + whereArgs = append(whereArgs, filterProcess(key, v, keyIndexSuffix)) + } + } + } else { + continue + } + } + + existKeys = append(existKeys, key) + } + + if len(wheres) > 3 { + wheres = wheres[:len(wheres)-4] + } + + for key, value := range param.OrConditions { + columns = strings.Split(key, ",") + op := "=" + if strings.Contains(value, "%") { + op = "like" + } + if len(wheres) > 0 { + wheres += " and " + } + wheres += "(" + for _, column := range columns { + keys := strings.Split(column, FilterParamJoinInfix) + if len(keys) > 1 { + wheres += keys[0] + "." + modules.FilterField(keys[1], delimiter, delimiter2) + " " + op + " ? or " + } else { + wheres += modules.FilterField(column, delimiter, delimiter2) + " " + op + " ? or " + } + whereArgs = append(whereArgs, value) + } + wheres = strings.TrimSuffix(wheres, "or ") + ")" + } + + return wheres, whereArgs, existKeys +} + +func getDefault(values url.Values, key, def string) string { + value := values.Get(key) + if value == "" { + return def + } + return value +} diff --git a/plugins/admin/modules/parameter/parameter_test.go b/plugins/admin/modules/parameter/parameter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f5c386e1c3583d755fbbf0d1b5f6d5bf01f97fe9 --- /dev/null +++ b/plugins/admin/modules/parameter/parameter_test.go @@ -0,0 +1,16 @@ +package parameter + +import ( + "fmt" + "testing" +) + +func TestGetParamFromUrl(t *testing.T) { + fmt.Println(GetParamFromURL("/admin/info/user?__page=1&__pageSize=10&__sort=id&__sort_type=desc", + 1, "asc", "id")) +} + +func TestParameters_PKs(t *testing.T) { + pks := BaseParam().PKs() + fmt.Println("pks", pks, "len", len(pks)) +} diff --git a/plugins/admin/modules/response/response.go b/plugins/admin/modules/response/response.go new file mode 100644 index 0000000000000000000000000000000000000000..1fbb5dd6e48a6b1e05e61ecabf6ec310c8bdd8e9 --- /dev/null +++ b/plugins/admin/modules/response/response.go @@ -0,0 +1,107 @@ +package response + +import ( + "net/http" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/menu" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" +) + +func Ok(ctx *context.Context) { + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": http.StatusOK, + "msg": "ok", + }) +} + +func OkWithMsg(ctx *context.Context, msg string) { + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": http.StatusOK, + "msg": msg, + }) +} + +func OkWithData(ctx *context.Context, data map[string]interface{}) { + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": http.StatusOK, + "msg": "ok", + "data": data, + }) +} + +func BadRequest(ctx *context.Context, msg string) { + ctx.JSON(http.StatusBadRequest, map[string]interface{}{ + "code": http.StatusBadRequest, + "msg": language.Get(msg), + }) +} + +func Alert(ctx *context.Context, desc, title, msg string, conn db.Connection, btns *types.Buttons, + pageType ...template.PageType) { + user := auth.Auth(ctx) + + pt := template.Error500Page + if len(pageType) > 0 { + pt = pageType[0] + } + + pageTitle, description, content := template.GetPageContentFromPageType(ctx, title, desc, msg, pt) + + tmpl, tmplName := template.Default(ctx).GetTemplate(ctx.IsPjax()) + buf := template.Execute(ctx, &template.ExecuteParam{ + User: user, + TmplName: tmplName, + Tmpl: tmpl, + Panel: types.Panel{ + Content: content, + Description: description, + Title: pageTitle, + }, + Config: config.Get(), + Menu: menu.GetGlobalMenu(user, conn, ctx.Lang()).SetActiveClass(config.URLRemovePrefix(ctx.Path())), + Animation: true, + Buttons: *btns, + IsPjax: ctx.IsPjax(), + Iframe: ctx.IsIframe(), + }) + ctx.HTML(http.StatusOK, buf.String()) +} + +func Error(ctx *context.Context, msg string, datas ...map[string]interface{}) { + res := map[string]interface{}{ + "code": http.StatusInternalServerError, + "msg": language.Get(msg), + } + if len(datas) > 0 { + res["data"] = datas[0] + } + ctx.JSON(http.StatusInternalServerError, res) +} + +func Denied(ctx *context.Context, msg string) { + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "code": http.StatusForbidden, + "msg": language.Get(msg), + }) +} + +var OffLineHandler = func(ctx *context.Context) { + if config.GetSiteOff() { + if ctx.WantHTML() { + ctx.HTML(http.StatusOK, `

    The website is offline

    `) + } else { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "code": http.StatusForbidden, + "msg": language.Get(errors.SiteOff), + }) + } + ctx.Abort() + } +} diff --git a/plugins/admin/modules/table/config.go b/plugins/admin/modules/table/config.go new file mode 100644 index 0000000000000000000000000000000000000000..4d4da008b78a73635d5ddddb43831aadd6b4a20c --- /dev/null +++ b/plugins/admin/modules/table/config.go @@ -0,0 +1,138 @@ +package table + +import ( + "github.com/GoAdminGroup/go-admin/modules/db" +) + +type Config struct { + Driver string + DriverMode string + Connection string + CanAdd bool + Editable bool + Deletable bool + Exportable bool + PrimaryKey PrimaryKey + SourceURL string + GetDataFun GetDataFun + OnlyInfo bool + OnlyNewForm bool + OnlyUpdateForm bool + OnlyDetail bool +} + +func DefaultConfig() Config { + return Config{ + Driver: db.DriverMysql, + CanAdd: true, + Editable: true, + Deletable: true, + Exportable: true, + Connection: DefaultConnectionName, + PrimaryKey: PrimaryKey{ + Type: db.Int, + Name: DefaultPrimaryKeyName, + }, + } +} + +func (config Config) SetPrimaryKey(name string, typ db.DatabaseType) Config { + config.PrimaryKey.Name = name + config.PrimaryKey.Type = typ + return config +} + +func (config Config) SetDriverMode(mode string) Config { + config.DriverMode = mode + return config +} + +func (config Config) SetPrimaryKeyType(typ string) Config { + config.PrimaryKey.Type = db.GetDTAndCheck(typ) + return config +} + +func (config Config) SetCanAdd(canAdd bool) Config { + config.CanAdd = canAdd + return config +} + +func (config Config) SetSourceURL(url string) Config { + config.SourceURL = url + return config +} + +func (config Config) SetGetDataFun(fun GetDataFun) Config { + config.GetDataFun = fun + return config +} + +func (config Config) SetEditable(editable bool) Config { + config.Editable = editable + return config +} + +func (config Config) SetDeletable(deletable bool) Config { + config.Deletable = deletable + return config +} + +func (config Config) SetOnlyInfo() Config { + config.OnlyInfo = true + return config +} + +func (config Config) SetOnlyUpdateForm() Config { + config.OnlyUpdateForm = true + return config +} + +func (config Config) SetOnlyNewForm() Config { + config.OnlyNewForm = true + return config +} + +func (config Config) SetOnlyDetail() Config { + config.OnlyDetail = true + return config +} + +func (config Config) SetExportable(exportable bool) Config { + config.Exportable = exportable + return config +} + +func (config Config) SetConnection(connection string) Config { + config.Connection = connection + return config +} + +func DefaultConfigWithDriver(driver string) Config { + return Config{ + Driver: driver, + Connection: DefaultConnectionName, + CanAdd: true, + Editable: true, + Deletable: true, + Exportable: true, + PrimaryKey: PrimaryKey{ + Type: db.Int, + Name: DefaultPrimaryKeyName, + }, + } +} + +func DefaultConfigWithDriverAndConnection(driver, conn string) Config { + return Config{ + Driver: driver, + Connection: conn, + CanAdd: true, + Editable: true, + Deletable: true, + Exportable: true, + PrimaryKey: PrimaryKey{ + Type: db.Int, + Name: DefaultPrimaryKeyName, + }, + } +} diff --git a/plugins/admin/modules/table/default.go b/plugins/admin/modules/table/default.go new file mode 100644 index 0000000000000000000000000000000000000000..638f4507c647362ff7d3df8acbfeed99887c1b07 --- /dev/null +++ b/plugins/admin/modules/table/default.go @@ -0,0 +1,1186 @@ +package table + +import ( + "encoding/json" + "errors" + "fmt" + "html/template" + "io/ioutil" + "net/http" + "strconv" + "strings" + "time" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/config" + + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/db/dialect" + errs "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/constant" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/paginator" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/template/types" +) + +// DefaultTable is an implementation of table.Table +type DefaultTable struct { + *BaseTable + connectionDriver string + connectionDriverMode string + connection string + sourceURL string + getDataFun GetDataFun + + dbObj db.Connection +} + +type GetDataFun func(params parameter.Parameters) ([]map[string]interface{}, int) + +func NewDefaultTable(ctx *context.Context, cfgs ...Config) Table { + + var cfg Config + + if len(cfgs) > 0 && cfgs[0].PrimaryKey.Name != "" { + cfg = cfgs[0] + } else { + cfg = DefaultConfig() + } + + return &DefaultTable{ + BaseTable: &BaseTable{ + Info: types.NewInfoPanel(ctx, cfg.PrimaryKey.Name), + Form: types.NewFormPanel(), + NewForm: types.NewFormPanel(), + Detail: types.NewInfoPanel(ctx, cfg.PrimaryKey.Name), + CanAdd: cfg.CanAdd, + Editable: cfg.Editable, + Deletable: cfg.Deletable, + Exportable: cfg.Exportable, + PrimaryKey: cfg.PrimaryKey, + OnlyNewForm: cfg.OnlyNewForm, + OnlyUpdateForm: cfg.OnlyUpdateForm, + OnlyDetail: cfg.OnlyDetail, + OnlyInfo: cfg.OnlyInfo, + }, + connectionDriver: cfg.Driver, + connectionDriverMode: cfg.DriverMode, + connection: cfg.Connection, + sourceURL: cfg.SourceURL, + getDataFun: cfg.GetDataFun, + } +} + +// Copy copy a new table.Table from origin DefaultTable +func (tb *DefaultTable) Copy() Table { + return &DefaultTable{ + BaseTable: &BaseTable{ + Form: types.NewFormPanel().SetTable(tb.Form.Table). + SetDescription(tb.Form.Description). + SetTitle(tb.Form.Title), + NewForm: types.NewFormPanel().SetTable(tb.Form.Table). + SetDescription(tb.Form.Description). + SetTitle(tb.Form.Title), + Info: types.NewInfoPanel(tb.Info.Ctx, tb.PrimaryKey.Name).SetTable(tb.Info.Table). + SetDescription(tb.Info.Description). + SetTitle(tb.Info.Title). + SetGetDataFn(tb.Info.GetDataFn), + Detail: types.NewInfoPanel(tb.Info.Ctx, tb.PrimaryKey.Name).SetTable(tb.Detail.Table). + SetDescription(tb.Detail.Description). + SetTitle(tb.Detail.Title). + SetGetDataFn(tb.Detail.GetDataFn), + CanAdd: tb.CanAdd, + Editable: tb.Editable, + Deletable: tb.Deletable, + Exportable: tb.Exportable, + PrimaryKey: tb.PrimaryKey, + }, + connectionDriver: tb.connectionDriver, + connectionDriverMode: tb.connectionDriverMode, + connection: tb.connection, + sourceURL: tb.sourceURL, + getDataFun: tb.getDataFun, + } +} + +// GetData query the data set. +func (tb *DefaultTable) GetData(ctx *context.Context, params parameter.Parameters) (PanelInfo, error) { + + var ( + data []map[string]interface{} + size int + beginTime = time.Now() + ) + + if tb.Info.UpdateParametersFns != nil { + for _, fn := range tb.Info.UpdateParametersFns { + fn(¶ms) + } + } + + if tb.Info.QueryFilterFn != nil { + var ids []string + var stopQuery bool + + if tb.getDataFun == nil && tb.Info.GetDataFn == nil { + ids, stopQuery = tb.Info.QueryFilterFn(params, tb.db()) + } else { + ids, stopQuery = tb.Info.QueryFilterFn(params, nil) + } + + if stopQuery { + return tb.GetDataWithIds(ctx, params.WithPKs(ids...)) + } + } + + if tb.getDataFun != nil { + data, size = tb.getDataFun(params) + } else if tb.sourceURL != "" { + data, size = tb.getDataFromURL(params) + } else if tb.Info.GetDataFn != nil { + data, size = tb.Info.GetDataFn(params) + } else if params.IsAll() { + return tb.getAllDataFromDatabase(params) + } else { + return tb.getDataFromDatabase(ctx, params) + } + + infoList := make(types.InfoList, 0) + + for i := 0; i < len(data); i++ { + infoList = append(infoList, tb.getTempModelData(data[i], params, []string{})) + } + + thead, _, _, _, _, filterForm := tb.getTheadAndFilterForm(params, []string{}) + + endTime := time.Now() + + extraInfo := "" + + if !tb.Info.IsHideQueryInfo { + extraInfo = fmt.Sprintf("" + language.Get("query time") + ": " + + fmt.Sprintf("%.3fms", endTime.Sub(beginTime).Seconds()*1000)) + } + + return PanelInfo{ + Thead: thead, + InfoList: infoList, + Paginator: paginator.Get(ctx, paginator.Config{ + Size: size, + Param: params, + PageSizeList: tb.Info.GetPageSizeList(), + }).SetExtraInfo(template.HTML(extraInfo)), + Title: tb.Info.Title, + FilterFormData: filterForm, + Description: tb.Info.Description, + }, nil +} + +type GetDataFromURLRes struct { + Data []map[string]interface{} + Size int +} + +func (tb *DefaultTable) getDataFromURL(params parameter.Parameters) ([]map[string]interface{}, int) { + + u := "" + if strings.Contains(tb.sourceURL, "?") { + u = tb.sourceURL + "&" + params.Join() + } else { + u = tb.sourceURL + "?" + params.Join() + } + res, err := http.Get(u + "&pk=" + strings.Join(params.PKs(), ",")) + + if err != nil { + return []map[string]interface{}{}, 0 + } + + defer func() { + _ = res.Body.Close() + }() + + body, err := ioutil.ReadAll(res.Body) + + if err != nil { + return []map[string]interface{}{}, 0 + } + + var data GetDataFromURLRes + + err = json.Unmarshal(body, &data) + + if err != nil { + return []map[string]interface{}{}, 0 + } + + return data.Data, data.Size +} + +// GetDataWithIds query the data set. +func (tb *DefaultTable) GetDataWithIds(ctx *context.Context, params parameter.Parameters) (PanelInfo, error) { + + var ( + data []map[string]interface{} + size int + beginTime = time.Now() + ) + + if tb.getDataFun != nil { + data, size = tb.getDataFun(params) + } else if tb.sourceURL != "" { + data, size = tb.getDataFromURL(params) + } else if tb.Info.GetDataFn != nil { + data, size = tb.Info.GetDataFn(params) + } else { + return tb.getDataFromDatabase(ctx, params) + } + + infoList := make([]map[string]types.InfoItem, 0) + + for i := 0; i < len(data); i++ { + infoList = append(infoList, tb.getTempModelData(data[i], params, []string{})) + } + + thead, _, _, _, _, filterForm := tb.getTheadAndFilterForm(params, []string{}) + + endTime := time.Now() + + return PanelInfo{ + Thead: thead, + InfoList: infoList, + Paginator: paginator.Get(ctx, paginator.Config{ + Size: size, + Param: params, + PageSizeList: tb.Info.GetPageSizeList(), + }). + SetExtraInfo(template.HTML(fmt.Sprintf("" + language.Get("query time") + ": " + + fmt.Sprintf("%.3fms", endTime.Sub(beginTime).Seconds()*1000)))), + Title: tb.Info.Title, + FilterFormData: filterForm, + Description: tb.Info.Description, + }, nil +} + +func (tb *DefaultTable) getTempModelData(res map[string]interface{}, params parameter.Parameters, columns Columns) map[string]types.InfoItem { + + var tempModelData = map[string]types.InfoItem{ + "__goadmin_edit_params": {}, + "__goadmin_delete_params": {}, + "__goadmin_detail_params": {}, + } + headField := "" + editParams := "" + deleteParams := "" + detailParams := "" + + primaryKeyValue := db.GetValueFromDatabaseType(tb.PrimaryKey.Type, res[tb.PrimaryKey.Name], len(columns) == 0) + + for _, field := range tb.Info.FieldList { + + headField = field.Field + + if field.Joins.Valid() { + headField = field.Joins.Last().GetTableName() + parameter.FilterParamJoinInfix + field.Field + } + + if field.Hide { + continue + } + if !modules.InArrayWithoutEmpty(params.Columns, headField) { + continue + } + + typeName := field.TypeName + + if field.Joins.Valid() { + typeName = db.Varchar + } + + combineValue := db.GetValueFromDatabaseType(typeName, res[headField], len(columns) == 0).String() + + // TODO: ToDisplay some same logic execute repeatedly, it can be improved. + var value interface{} + if len(columns) == 0 || modules.InArray(columns, headField) || field.Joins.Valid() { + value = field.ToDisplay(types.FieldModel{ + ID: primaryKeyValue.String(), + Value: combineValue, + Row: res, + }) + } else { + value = field.ToDisplay(types.FieldModel{ + ID: primaryKeyValue.String(), + Value: "", + Row: res, + }) + } + var valueStr string + var ok bool + if valueStr, ok = value.(string); ok { + tempModelData[headField] = types.InfoItem{ + Content: template.HTML(valueStr), + Value: combineValue, + } + } else { + valueStr = string(value.(template.HTML)) + tempModelData[headField] = types.InfoItem{ + Content: value.(template.HTML), + Value: combineValue, + } + } + + if field.IsEditParam { + editParams += "__goadmin_edit_" + field.Field + "=" + valueStr + "&" + } + if field.IsDeleteParam { + deleteParams += "__goadmin_delete_" + field.Field + "=" + valueStr + "&" + } + if field.IsDetailParam { + detailParams += "__goadmin_detail_" + field.Field + "=" + valueStr + "&" + } + } + + if editParams != "" { + tempModelData["__goadmin_edit_params"] = types.InfoItem{Content: template.HTML("&" + editParams[:len(editParams)-1])} + } + if deleteParams != "" { + tempModelData["__goadmin_delete_params"] = types.InfoItem{Content: template.HTML("&" + deleteParams[:len(deleteParams)-1])} + } + if detailParams != "" { + tempModelData["__goadmin_detail_params"] = types.InfoItem{Content: template.HTML("&" + detailParams[:len(detailParams)-1])} + } + + primaryKeyField := tb.Info.FieldList.GetFieldByFieldName(tb.PrimaryKey.Name) + value := primaryKeyField.ToDisplay(types.FieldModel{ + ID: primaryKeyValue.String(), + Value: primaryKeyValue.String(), + Row: res, + }) + if valueStr, ok := value.(string); ok { + tempModelData[tb.PrimaryKey.Name] = types.InfoItem{ + Content: template.HTML(valueStr), + Value: primaryKeyValue.String(), + } + } else { + tempModelData[tb.PrimaryKey.Name] = types.InfoItem{ + Content: value.(template.HTML), + Value: primaryKeyValue.String(), + } + } + + return tempModelData +} + +func (tb *DefaultTable) getAllDataFromDatabase(params parameter.Parameters) (PanelInfo, error) { + var ( + connection = tb.db() + queryStatement = "select %s from %s %s %s %s order by " + modules.Delimiter(connection.GetDelimiter(), connection.GetDelimiter2(), "%s") + " %s" + ) + + columns, _ := tb.getColumns(tb.Info.Table) + + thead, fields, joins := tb.Info.FieldList.GetThead(types.TableInfo{ + Table: tb.Info.Table, + Delimiter: connection.GetDelimiter(), + Delimiter2: connection.GetDelimiter2(), + Driver: tb.connectionDriver, + PrimaryKey: tb.PrimaryKey.Name, + }, params, columns) + + fields += tb.Info.Table + "." + modules.FilterField(tb.PrimaryKey.Name, connection.GetDelimiter(), connection.GetDelimiter2()) + + groupBy := "" + if joins != "" { + groupBy = " GROUP BY " + tb.Info.Table + "." + modules.Delimiter(connection.GetDelimiter(), connection.GetDelimiter2(), tb.PrimaryKey.Name) + } + + var ( + wheres = "" + whereArgs = make([]interface{}, 0) + existKeys = make([]string, 0) + ) + + wheres, whereArgs, existKeys = params.Statement(wheres, tb.Info.Table, connection.GetDelimiter(), connection.GetDelimiter2(), whereArgs, columns, existKeys, + tb.Info.FieldList.GetFieldFilterProcessValue) + wheres, whereArgs = tb.Info.Wheres.Statement(wheres, connection.GetDelimiter(), connection.GetDelimiter2(), whereArgs, existKeys, columns) + wheres, whereArgs = tb.Info.WhereRaws.Statement(wheres, whereArgs) + + if wheres != "" { + wheres = " where " + wheres + } + + if !modules.InArray(columns, params.SortField) { + params.SortField = tb.PrimaryKey.Name + } + + queryCmd := fmt.Sprintf(queryStatement, fields, tb.Info.Table, joins, wheres, groupBy, params.SortField, params.SortType) + + logger.LogSQL(queryCmd, []interface{}{}) + + res, err := connection.QueryWithConnection(tb.connection, queryCmd, whereArgs...) + + if err != nil { + return PanelInfo{}, err + } + + infoList := make([]map[string]types.InfoItem, 0) + + for i := 0; i < len(res); i++ { + infoList = append(infoList, tb.getTempModelData(res[i], params, columns)) + } + + return PanelInfo{ + InfoList: infoList, + Thead: thead, + Title: tb.Info.Title, + Description: tb.Info.Description, + }, nil +} + +// TODO: refactor +func (tb *DefaultTable) getDataFromDatabase(ctx *context.Context, params parameter.Parameters) (PanelInfo, error) { + + var ( + connection = tb.db() + delimiter = connection.GetDelimiter() + delimiter2 = connection.GetDelimiter2() + placeholder = modules.Delimiter(delimiter, delimiter2, "%s") + queryStatement string + countStatement string + ids = params.PKs() + table = modules.Delimiter(delimiter, delimiter2, tb.Info.Table) + pk = table + "." + modules.Delimiter(delimiter, delimiter2, tb.PrimaryKey.Name) + ) + + beginTime := time.Now() + + if len(ids) > 0 { + countExtra := "" + if connection.Name() == db.DriverMssql { + countExtra = "as [size]" + } + // %s means: fields, table, join table, pk values, group by, order by field, order by type + queryStatement = "select %s from " + placeholder + " %s where " + pk + " in (%s) %s ORDER BY %s." + placeholder + " %s" + // %s means: table, join table, pk values + countStatement = "select count(*) " + countExtra + " from " + placeholder + " %s where " + pk + " in (%s)" + } else { + if connection.Name() == db.DriverMssql { + // %s means: order by field, order by type, fields, table, join table, wheres, group by + queryStatement = "SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s." + placeholder + " %s) as ROWNUMBER_, %s from " + + placeholder + "%s %s %s ) as TMP_ WHERE TMP_.ROWNUMBER_ > ? AND TMP_.ROWNUMBER_ <= ?" + // %s means: table, join table, wheres + countStatement = "select count(*) as [size] from (select 1 as [size] from " + placeholder + " %s %s %s) src" + } else { + // %s means: fields, table, join table, wheres, group by, order by field, order by type + queryStatement = "select %s from " + placeholder + "%s %s %s order by " + placeholder + "." + placeholder + " %s LIMIT ? OFFSET ?" + // %s means: table, join table, wheres + countStatement = "select count(*) from (select " + pk + " from " + placeholder + " %s %s %s) src" + } + } + + columns, _ := tb.getColumns(tb.Info.Table) + + thead, fields, joinFields, joins, joinTables, filterForm := tb.getTheadAndFilterForm(params, columns) + + fields += pk + + allFields := fields + groupFields := fields + + if joinFields != "" { + allFields += "," + joinFields[:len(joinFields)-1] + if connection.Name() == db.DriverMssql { + for _, field := range tb.Info.FieldList { + if field.TypeName == db.Text || field.TypeName == db.Longtext { + f := modules.Delimiter(connection.GetDelimiter(), connection.GetDelimiter2(), field.Field) + headField := table + "." + f + allFields = strings.ReplaceAll(allFields, headField, "CAST("+headField+" AS NVARCHAR(MAX)) as "+f) + groupFields = strings.ReplaceAll(groupFields, headField, "CAST("+headField+" AS NVARCHAR(MAX))") + } + } + } + } + + if !modules.InArray(columns, params.SortField) { + params.SortField = tb.PrimaryKey.Name + } + + var ( + wheres = "" + whereArgs = make([]interface{}, 0) + args = make([]interface{}, 0) + existKeys = make([]string, 0) + ) + + if len(ids) > 0 { + for _, value := range ids { + if value != "" { + wheres += "?," + args = append(args, value) + } + } + wheres = wheres[:len(wheres)-1] + } else { + + // parameter + wheres, whereArgs, existKeys = params.Statement(wheres, tb.Info.Table, connection.GetDelimiter(), connection.GetDelimiter2(), whereArgs, columns, existKeys, + tb.Info.FieldList.GetFieldFilterProcessValue) + // pre query + wheres, whereArgs = tb.Info.Wheres.Statement(wheres, connection.GetDelimiter(), connection.GetDelimiter2(), whereArgs, existKeys, columns) + wheres, whereArgs = tb.Info.WhereRaws.Statement(wheres, whereArgs) + + if wheres != "" { + wheres = " where " + wheres + } + + if connection.Name() == db.DriverMssql { + args = append(whereArgs, (params.PageInt-1)*params.PageSizeInt, params.PageInt*params.PageSizeInt) + } else { + args = append(whereArgs, params.PageSizeInt, (params.PageInt-1)*params.PageSizeInt) + } + } + + groupBy := "" + if len(joinTables) > 0 { + if connection.Name() == db.DriverMssql || connection.Name() == db.DriverPostgresql { + groupBy = " GROUP BY " + groupFields + } else { + groupBy = " GROUP BY " + pk + } + } + + queryCmd := "" + if connection.Name() == db.DriverMssql && len(ids) == 0 { + queryCmd = fmt.Sprintf(queryStatement, tb.Info.Table, params.SortField, params.SortType, + allFields, tb.Info.Table, joins, wheres, groupBy) + } else { + queryCmd = fmt.Sprintf(queryStatement, allFields, tb.Info.Table, joins, wheres, groupBy, + tb.Info.Table, params.SortField, params.SortType) + } + + logger.LogSQL(queryCmd, args) + + res, err := connection.QueryWithConnection(tb.connection, queryCmd, args...) + + if err != nil { + return PanelInfo{}, err + } + + infoList := make([]map[string]types.InfoItem, 0) + + for i := 0; i < len(res); i++ { + infoList = append(infoList, tb.getTempModelData(res[i], params, columns)) + } + + // TODO: use the dialect + var size int + + if len(ids) == 0 { + countCmd := fmt.Sprintf(countStatement, tb.Info.Table, joins, wheres, groupBy) + + total, err := connection.QueryWithConnection(tb.connection, countCmd, whereArgs...) + + if err != nil { + return PanelInfo{}, err + } + + logger.LogSQL(countCmd, nil) + + if tb.connectionDriver == "postgresql" { + if tb.connectionDriverMode == "h2" { + size = int(total[0]["count(*)"].(int64)) + } else if config.GetDatabases().GetDefault().DriverMode == "h2" { + size = int(total[0]["count(*)"].(int64)) + } else { + size = int(total[0]["count"].(int64)) + } + } else if tb.connectionDriver == db.DriverMssql { + size = int(total[0]["size"].(int64)) + } else { + size = int(total[0]["count(*)"].(int64)) + } + } + + endTime := time.Now() + + return PanelInfo{ + Thead: thead, + InfoList: infoList, + Paginator: tb.GetPaginator(ctx, size, params, + template.HTML(fmt.Sprintf(""+language.Get("query time")+": "+ + fmt.Sprintf("%.3fms", endTime.Sub(beginTime).Seconds()*1000)))), + Title: tb.Info.Title, + FilterFormData: filterForm, + Description: tb.Info.Description, + }, nil +} + +func getDataRes(list []map[string]interface{}, _ int) map[string]interface{} { + if len(list) > 0 { + return list[0] + } + return nil +} + +// GetDataWithId query the single row of data. +func (tb *DefaultTable) GetDataWithId(param parameter.Parameters) (FormInfo, error) { + + var ( + res map[string]interface{} + columns Columns + id = param.PK() + ) + + if tb.getDataFun != nil { + res = getDataRes(tb.getDataFun(param)) + } else if tb.sourceURL != "" { + res = getDataRes(tb.getDataFromURL(param)) + } else if tb.Detail.GetDataFn != nil { + res = getDataRes(tb.Detail.GetDataFn(param)) + } else if tb.Info.GetDataFn != nil { + res = getDataRes(tb.Info.GetDataFn(param)) + } else { + + columns, _ = tb.getColumns(tb.Form.Table) + + var ( + fields, joinFields, joins, groupBy = "", "", "", "" + + err error + joinTables = make([]string, 0) + args = []interface{}{id} + connection = tb.db() + delimiter = connection.GetDelimiter() + delimiter2 = connection.GetDelimiter2() + tableName = modules.Delimiter(delimiter, delimiter2, tb.GetForm().Table) + pk = tableName + "." + modules.Delimiter(delimiter, delimiter2, tb.PrimaryKey.Name) + queryStatement = "select %s from %s %s where " + pk + " = ? %s " + ) + + for i := 0; i < len(tb.Form.FieldList); i++ { + + if tb.Form.FieldList[i].Field != pk && modules.InArray(columns, tb.Form.FieldList[i].Field) && + !tb.Form.FieldList[i].Joins.Valid() { + fields += tableName + "." + modules.FilterField(tb.Form.FieldList[i].Field, delimiter, delimiter2) + "," + } + + if tb.Form.FieldList[i].Joins.Valid() { + headField := tb.Form.FieldList[i].Joins.Last().GetTableName() + parameter.FilterParamJoinInfix + tb.Form.FieldList[i].Field + joinFields += db.GetAggregationExpression(connection.Name(), tb.Form.FieldList[i].Joins.Last().GetTableName(delimiter, delimiter2)+"."+ + modules.FilterField(tb.Form.FieldList[i].Field, delimiter, delimiter2), headField, types.JoinFieldValueDelimiter) + "," + for _, join := range tb.Form.FieldList[i].Joins { + if !modules.InArray(joinTables, join.GetTableName(delimiter, delimiter2)) { + joinTables = append(joinTables, join.GetTableName(delimiter, delimiter2)) + if join.BaseTable == "" { + join.BaseTable = tableName + } + joins += " left join " + modules.FilterField(join.Table, delimiter, delimiter2) + " " + join.TableAlias + " on " + + join.GetTableName(delimiter, delimiter2) + "." + modules.FilterField(join.JoinField, delimiter, delimiter2) + " = " + + join.BaseTable + "." + modules.FilterField(join.Field, delimiter, delimiter2) + } + } + } + } + + fields += pk + groupFields := fields + + if joinFields != "" { + fields += "," + joinFields[:len(joinFields)-1] + if connection.Name() == db.DriverMssql { + for i := 0; i < len(tb.Form.FieldList); i++ { + if tb.Form.FieldList[i].TypeName == db.Text || tb.Form.FieldList[i].TypeName == db.Longtext { + f := modules.Delimiter(connection.GetDelimiter(), connection.GetDelimiter2(), tb.Form.FieldList[i].Field) + headField := tb.Info.Table + "." + f + fields = strings.ReplaceAll(fields, headField, "CAST("+headField+" AS NVARCHAR(MAX)) as "+f) + groupFields = strings.ReplaceAll(groupFields, headField, "CAST("+headField+" AS NVARCHAR(MAX))") + } + } + } + } + + if len(joinTables) > 0 { + if connection.Name() == db.DriverMssql || connection.Name() == db.DriverPostgresql { + groupBy = " GROUP BY " + groupFields + } else { + groupBy = " GROUP BY " + pk + } + } + + queryCmd := fmt.Sprintf(queryStatement, fields, tableName, joins, groupBy) + + logger.LogSQL(queryCmd, args) + + result, err := connection.QueryWithConnection(tb.connection, queryCmd, args...) + + if err != nil { + return FormInfo{Title: tb.Form.Title, Description: tb.Form.Description}, err + } + + if len(result) == 0 { + return FormInfo{Title: tb.Form.Title, Description: tb.Form.Description}, errors.New(errs.WrongID) + } + + res = result[0] + } + + var ( + groupFormList = make([]types.FormFields, 0) + groupHeaders = make([]string, 0) + ) + + if len(tb.Form.TabGroups) > 0 { + groupFormList, groupHeaders = tb.Form.GroupFieldWithValue(tb.PrimaryKey.Name, id, columns, res, tb.sqlObjOrNil) + return FormInfo{ + FieldList: tb.Form.FieldList, + GroupFieldList: groupFormList, + GroupFieldHeaders: groupHeaders, + Title: tb.Form.Title, + Description: tb.Form.Description, + }, nil + } + + var fieldList = tb.Form.FieldsWithValue(tb.PrimaryKey.Name, id, columns, res, tb.sqlObjOrNil) + + return FormInfo{ + FieldList: fieldList, + GroupFieldList: groupFormList, + GroupFieldHeaders: groupHeaders, + Title: tb.Form.Title, + Description: tb.Form.Description, + }, nil +} + +// UpdateData update data. +func (tb *DefaultTable) UpdateData(ctx *context.Context, dataList form.Values) error { + + dataList.Add(form.PostTypeKey, "0") + + var ( + errMsg = "" + err error + ) + + if tb.Form.PostHook != nil { + defer func() { + dataList.Add(form.PostTypeKey, "0") + dataList.Add(form.PostResultKey, errMsg) + go func() { + defer func() { + if err := recover(); err != nil { + logger.ErrorCtx(ctx, "UpdateData error %+v", err) + } + }() + + err := tb.Form.PostHook(dataList) + if err != nil { + logger.ErrorCtx(ctx, "UpdateData PostHook error %+v", err) + } + }() + }() + } + + if tb.Form.Validator != nil { + if err := tb.Form.Validator(dataList); err != nil { + errMsg = "post error: " + err.Error() + return err + } + } + + if tb.Form.PreProcessFn != nil { + dataList = tb.Form.PreProcessFn(dataList) + } + + if tb.Form.UpdateFn != nil { + dataList.Delete(form.PostTypeKey) + err = tb.Form.UpdateFn(tb.PreProcessValue(dataList, types.PostTypeUpdate)) + if err != nil { + errMsg = "post error: " + err.Error() + } + return err + } + + if len(dataList) == 0 { + return nil + } + + _, err = tb.sql().Table(tb.Form.Table). + Where(tb.PrimaryKey.Name, "=", dataList.Get(tb.PrimaryKey.Name)). + Update(tb.getInjectValueFromFormValue(dataList, types.PostTypeUpdate)) + + // NOTE: some errors should be ignored. + if db.CheckError(err, db.UPDATE) { + if err != nil { + errMsg = "post error: " + err.Error() + } + return err + } + + return nil +} + +// InsertData insert data. +func (tb *DefaultTable) InsertData(ctx *context.Context, dataList form.Values) error { + + dataList.Add(form.PostTypeKey, "1") + + var ( + id = int64(0) + err error + errMsg = "" + f = tb.GetActualNewForm() + ) + + if f.PostHook != nil { + defer func() { + dataList.Add(form.PostTypeKey, "1") + dataList.Add(tb.GetPrimaryKey().Name, strconv.Itoa(int(id))) + dataList.Add(form.PostResultKey, errMsg) + + go func() { + defer func() { + if err := recover(); err != nil { + logger.ErrorCtx(ctx, "InsertData error %+v", err) + } + }() + + err := f.PostHook(dataList) + if err != nil { + logger.ErrorCtx(ctx, "InsertData PostHook error %+v", err) + } + }() + }() + } + + if f.Validator != nil { + if err := f.Validator(dataList); err != nil { + errMsg = "post error: " + err.Error() + return err + } + } + + if f.PreProcessFn != nil { + dataList = f.PreProcessFn(dataList) + } + + if f.InsertFn != nil { + dataList.Delete(form.PostTypeKey) + err = f.InsertFn(tb.PreProcessValue(dataList, types.PostTypeCreate)) + if err != nil { + errMsg = "post error: " + err.Error() + } + return err + } + + if len(dataList) == 0 { + return nil + } + + id, err = tb.sql().Table(f.Table).Insert(tb.getInjectValueFromFormValue(dataList, types.PostTypeCreate)) + + // NOTE: some errors should be ignored. + if db.CheckError(err, db.INSERT) { + errMsg = "post error: " + err.Error() + return err + } + + return nil +} + +func (tb *DefaultTable) getInjectValueFromFormValue(dataList form.Values, typ types.PostType) dialect.H { + + var ( + value = make(dialect.H) + exceptString = make([]string, 0) + columns, auto = tb.getColumns(tb.Form.Table) + + fun types.PostFieldFilterFn + ) + + // If a key is a auto increment primary key, it can`t be insert or update. + if auto { + exceptString = []string{tb.PrimaryKey.Name, form.PreviousKey, form.MethodKey, form.TokenKey, + constant.IframeKey, constant.IframeIDKey} + } else { + exceptString = []string{form.PreviousKey, form.MethodKey, form.TokenKey, + constant.IframeKey, constant.IframeIDKey} + } + + if !dataList.IsSingleUpdatePost() { + for i := 0; i < len(tb.Form.FieldList); i++ { + if tb.Form.FieldList[i].FormType.IsMultiSelect() { + if _, ok := dataList[tb.Form.FieldList[i].Field+"[]"]; !ok { + dataList[tb.Form.FieldList[i].Field+"[]"] = []string{""} + } + } + } + } + + dataList = dataList.RemoveRemark() + + for k, v := range dataList { + k = strings.ReplaceAll(k, "[]", "") + if !modules.InArray(exceptString, k) { + if modules.InArray(columns, k) { + field := tb.Form.FieldList.FindByFieldName(k) + delimiter := "," + if field != nil { + fun = field.PostFilterFn + delimiter = modules.SetDefault(field.DefaultOptionDelimiter, ",") + } + vv := modules.RemoveBlankFromArray(v) + if fun != nil { + value[k] = fun(types.PostFieldModel{ + ID: dataList.Get(tb.PrimaryKey.Name), + Value: vv, + Row: dataList.ToMap(), + PostType: typ, + }) + } else { + if len(vv) > 1 { + value[k] = strings.Join(vv, delimiter) + } else if len(vv) > 0 { + value[k] = vv[0] + } else { + value[k] = "" + } + } + } else { + field := tb.Form.FieldList.FindByFieldName(k) + if field != nil && field.PostFilterFn != nil { + field.PostFilterFn(types.PostFieldModel{ + ID: dataList.Get(tb.PrimaryKey.Name), + Value: modules.RemoveBlankFromArray(v), + Row: dataList.ToMap(), + PostType: typ, + }) + } + } + } + } + return value +} + +func (tb *DefaultTable) PreProcessValue(dataList form.Values, typ types.PostType) form.Values { + + exceptString := []string{form.PreviousKey, form.MethodKey, form.TokenKey, + constant.IframeKey, constant.IframeIDKey} + dataList = dataList.RemoveRemark() + var fun types.PostFieldFilterFn + + for k, v := range dataList { + k = strings.ReplaceAll(k, "[]", "") + if !modules.InArray(exceptString, k) { + field := tb.Form.FieldList.FindByFieldName(k) + if field != nil { + fun = field.PostFilterFn + } + vv := modules.RemoveBlankFromArray(v) + if fun != nil { + dataList.Add(k, fmt.Sprintf("%s", fun(types.PostFieldModel{ + ID: dataList.Get(tb.PrimaryKey.Name), + Value: vv, + Row: dataList.ToMap(), + PostType: typ, + }))) + } + } + } + return dataList +} + +// DeleteData delete data. +func (tb *DefaultTable) DeleteData(id string) error { + + var ( + idArr = strings.Split(id, ",") + err error + ) + + if tb.Info.DeleteHook != nil { + defer func() { + go func() { + defer func() { + if recoverErr := recover(); recoverErr != nil { + logger.Error(recoverErr) + } + }() + + if hookErr := tb.Info.DeleteHook(idArr); hookErr != nil { + logger.Error(hookErr) + } + }() + }() + } + + if tb.Info.DeleteHookWithRes != nil { + defer func() { + go func() { + defer func() { + if recoverErr := recover(); recoverErr != nil { + logger.Error(recoverErr) + } + }() + + if hookErr := tb.Info.DeleteHookWithRes(idArr, err); hookErr != nil { + logger.Error(hookErr) + } + }() + }() + } + + if tb.Info.PreDeleteFn != nil { + if err = tb.Info.PreDeleteFn(idArr); err != nil { + return err + } + } + + if tb.Info.DeleteFn != nil { + err = tb.Info.DeleteFn(idArr) + return err + } + + if len(idArr) == 0 || tb.Info.Table == "" { + err = errors.New("delete error: wrong parameter") + return err + } + + err = tb.delete(tb.Info.Table, tb.PrimaryKey.Name, idArr) + return err +} + +func (tb *DefaultTable) GetNewFormInfo() FormInfo { + + f := tb.GetActualNewForm() + + if len(f.TabGroups) == 0 { + return FormInfo{FieldList: f.FieldsWithDefaultValue(tb.sqlObjOrNil)} + } + + newForm, headers := f.GroupField(tb.sqlObjOrNil) + + return FormInfo{GroupFieldList: newForm, GroupFieldHeaders: headers} +} + +// *************************************** +// helper function for database operation +// *************************************** + +func (tb *DefaultTable) delete(table, key string, values []string) error { + + var vals = make([]interface{}, len(values)) + for i, v := range values { + vals[i] = v + } + + return tb.sql().Table(table). + WhereIn(key, vals). + Delete() +} + +func (tb *DefaultTable) getTheadAndFilterForm(params parameter.Parameters, columns Columns) (types.Thead, + string, string, string, []string, []types.FormField) { + + return tb.Info.FieldList.GetTheadAndFilterForm(types.TableInfo{ + Table: tb.Info.Table, + Delimiter: tb.delimiter(), + Delimiter2: tb.delimiter2(), + Driver: tb.connectionDriver, + PrimaryKey: tb.PrimaryKey.Name, + }, params, columns, tb.sqlObjOrNil) +} + +// db is a helper function return raw db connection. +func (tb *DefaultTable) db() db.Connection { + if tb.dbObj == nil { + tb.dbObj = db.GetConnectionFromService(services.Get(tb.connectionDriver)) + } + return tb.dbObj +} + +func (tb *DefaultTable) delimiter() string { + if tb.getDataFromDB() { + return tb.db().GetDelimiter() + } + return "" +} + +func (tb *DefaultTable) delimiter2() string { + if tb.getDataFromDB() { + return tb.db().GetDelimiter2() + } + return "" +} + +func (tb *DefaultTable) getDataFromDB() bool { + return tb.sourceURL == "" && tb.getDataFun == nil && tb.Info.GetDataFn == nil && tb.Detail.GetDataFn == nil +} + +// sql is a helper function return db sql. +func (tb *DefaultTable) sql() *db.SQL { + return db.WithDriverAndConnection(tb.connection, tb.db()) +} + +// sqlObjOrNil is a helper function return db sql obj or nil. +func (tb *DefaultTable) sqlObjOrNil() *db.SQL { + if tb.connectionDriver != "" && tb.getDataFromDB() { + return db.WithDriverAndConnection(tb.connection, tb.db()) + } + return nil +} + +type Columns []string + +func (tb *DefaultTable) getColumns(table string) (Columns, bool) { + + columnsModel, _ := tb.sql().Table(table).ShowColumns() + + columns := make(Columns, len(columnsModel)) + switch tb.connectionDriver { + case db.DriverPostgresql: + auto := false + for key, model := range columnsModel { + columns[key] = model["column_name"].(string) + if columns[key] == tb.PrimaryKey.Name { + if v, ok := model["column_default"].(string); ok { + if strings.Contains(v, "nextval") { + auto = true + } + } + } + } + return columns, auto + case db.DriverMysql: + auto := false + for key, model := range columnsModel { + columns[key] = model["Field"].(string) + if columns[key] == tb.PrimaryKey.Name { + if v, ok := model["Extra"].(string); ok { + if v == "auto_increment" { + auto = true + } + } + } + } + return columns, auto + case db.DriverSqlite: + for key, model := range columnsModel { + columns[key] = string(model["name"].(string)) + } + + num, _ := tb.sql().Table("sqlite_sequence"). + Where("name", "=", tb.GetForm().Table).Count() + + return columns, num > 0 + case db.DriverMssql: + for key, model := range columnsModel { + columns[key] = string(model["column_name"].(string)) + } + return columns, true + default: + panic("wrong driver") + } +} diff --git a/plugins/admin/modules/table/default_test.go b/plugins/admin/modules/table/default_test.go new file mode 100644 index 0000000000000000000000000000000000000000..eff5472eafe9879ce9800390723ece2b29cd7f76 --- /dev/null +++ b/plugins/admin/modules/table/default_test.go @@ -0,0 +1 @@ +package table diff --git a/plugins/admin/modules/table/generators.go b/plugins/admin/modules/table/generators.go new file mode 100644 index 0000000000000000000000000000000000000000..51ade3d442c99b25880e39d5f8a0096b487f6ed6 --- /dev/null +++ b/plugins/admin/modules/table/generators.go @@ -0,0 +1,2003 @@ +package table + +import ( + "database/sql" + "errors" + "fmt" + tmpl "html/template" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/collection" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/db/dialect" + errs "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/ui" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + form2 "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/tools" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/action" + "github.com/GoAdminGroup/go-admin/template/types/form" + selection "github.com/GoAdminGroup/go-admin/template/types/form/select" + "github.com/GoAdminGroup/html" + "golang.org/x/crypto/bcrypt" + "golang.org/x/text/cases" + textLang "golang.org/x/text/language" +) + +type SystemTable struct { + conn db.Connection + c *config.Config +} + +func NewSystemTable(conn db.Connection, c *config.Config) *SystemTable { + return &SystemTable{conn: conn, c: c} +} + + +var filterType = types.FilterType{NoIcon: true, HeadWidth: 4, InputWidth: 8} + +func (s *SystemTable) GetManagerTable(ctx *context.Context) (managerTable Table) { + managerTable = NewDefaultTable(ctx, DefaultConfigWithDriver(config.GetDatabases().GetDefault().Driver)) + + info := managerTable.GetInfo().AddXssJsFilter().SetFilterFormLayout(form.LayoutFilter) + + info.AddField("ID", "id", db.Int).FieldSortable() + filterType := types.FilterType{NoIcon: true, HeadWidth: 4, InputWidth: 8} + info.AddField(lg("Name"), "username", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("Nickname"), "name", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("role"), "name", db.Varchar). + FieldJoin(types.Join{ + Table: "goadmin_role_users", + JoinField: "user_id", + Field: "id", + }). + FieldJoin(types.Join{ + Table: "goadmin_roles", + JoinField: "id", + Field: "role_id", + BaseTable: "goadmin_role_users", + }). + FieldDisplay(func(model types.FieldModel) interface{} { + labels := template.HTML("") + labelTpl := label(ctx).SetType("success") + + labelValues := strings.Split(model.Value, types.JoinFieldValueDelimiter) + for key, label := range labelValues { + if key == len(labelValues)-1 { + labels += labelTpl.SetContent(template.HTML(label)).GetContent() + } else { + labels += labelTpl.SetContent(template.HTML(label)).GetContent() + "

    " + } + } + + if labels == template.HTML("") { + return lg("no roles") + } + + return labels + }).FieldFilterable(filterType) + info.AddField(lg("createdAt"), "created_at", db.Timestamp) + info.AddField(lg("updatedAt"), "updated_at", db.Timestamp) + + info.SetTable("goadmin_users"). + SetTitle(lg("Managers")). + SetDescription(lg("Managers manage")). + SetDeleteFn(func(idArr []string) error { + + var ids = interfaces(idArr) + + _, txErr := s.connection().WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + + deleteUserRoleErr := s.connection().WithTx(tx). + Table("goadmin_role_users"). + WhereIn("user_id", ids). + Delete() + + if db.CheckError(deleteUserRoleErr, db.DELETE) { + return deleteUserRoleErr, nil + } + + deleteUserPermissionErr := s.connection().WithTx(tx). + Table("goadmin_user_permissions"). + WhereIn("user_id", ids). + Delete() + + if db.CheckError(deleteUserPermissionErr, db.DELETE) { + return deleteUserPermissionErr, nil + } + + deleteUserErr := s.connection().WithTx(tx). + Table("goadmin_users"). + WhereIn("id", ids). + Delete() + + if db.CheckError(deleteUserErr, db.DELETE) { + return deleteUserErr, nil + } + + return nil, nil + }) + + return txErr + }) + + formList := managerTable.GetForm().AddXssJsFilter() + + formList.AddField("ID", "id", db.Int, form.Default).FieldDisplayButCanNotEditWhenUpdate().FieldDisableWhenCreate() + formList.AddField(lg("Name"), "username", db.Varchar, form.Text). + FieldHelpMsg(template.HTML(lg("use for login"))).FieldMust() + formList.AddField(lg("Nickname"), "name", db.Varchar, form.Text). + FieldHelpMsg(template.HTML(lg("use to display"))).FieldMust() + formList.AddField(lg("Avatar"), "avatar", db.Varchar, form.File) + formList.AddField(lg("role"), "role_id", db.Varchar, form.Select). + FieldOptionsFromTable("goadmin_roles", "slug", "id"). + FieldDisplay(func(model types.FieldModel) interface{} { + var roles []string + + if model.ID == "" { + return roles + } + roleModel, _ := s.table("goadmin_role_users").Select("role_id"). + Where("user_id", "=", model.ID).All() + for _, v := range roleModel { + roles = append(roles, strconv.FormatInt(v["role_id"].(int64), 10)) + } + return roles + }).FieldHelpMsg(template.HTML(lg("no corresponding options?")) + + link("/admin/info/roles/new", "Create here.")) + + formList.AddField(lg("permission"), "permission_id", db.Varchar, form.Select). + FieldOptionsFromTable("goadmin_permissions", "slug", "id"). + FieldDisplay(func(model types.FieldModel) interface{} { + var permissions []string + + if model.ID == "" { + return permissions + } + permissionModel, _ := s.table("goadmin_user_permissions"). + Select("permission_id").Where("user_id", "=", model.ID).All() + for _, v := range permissionModel { + permissions = append(permissions, strconv.FormatInt(v["permission_id"].(int64), 10)) + } + return permissions + }).FieldHelpMsg(template.HTML(lg("no corresponding options?")) + + link("/admin/info/permission/new", "Create here.")) + + formList.AddField(lg("password"), "password", db.Varchar, form.Password). + FieldDisplay(func(value types.FieldModel) interface{} { + return "" + }) + formList.AddField(lg("confirm password"), "password_again", db.Varchar, form.Password). + FieldDisplay(func(value types.FieldModel) interface{} { + return "" + }) + + formList.SetTable("goadmin_users").SetTitle(lg("Managers")).SetDescription(lg("Managers manage")) + formList.SetUpdateFn(func(values form2.Values) error { + + if values.IsEmpty("name", "username") { + return errors.New("username and password can not be empty") + } + + user := models.UserWithId(values.Get("id")).SetConn(s.conn) + + password := values.Get("password") + + if password != "" { + + if password != values.Get("password_again") { + return errors.New("password does not match") + } + + password = encodePassword([]byte(values.Get("password"))) + } + + _, txErr := s.connection().WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + + avatar := values.Get("avatar") + + if values.Get("avatar__delete_flag") == "1" { + avatar = "" + } + + _, updateUserErr := user.WithTx(tx).Update(values.Get("username"), + password, values.Get("name"), avatar, values.Get("avatar__change_flag") == "1") + + if db.CheckError(updateUserErr, db.UPDATE) { + return updateUserErr, nil + } + + delRoleErr := user.WithTx(tx).DeleteRoles() + + if db.CheckError(delRoleErr, db.DELETE) { + return delRoleErr, nil + } + + for i := 0; i < len(values["role_id[]"]); i++ { + _, addRoleErr := user.WithTx(tx).AddRole(values["role_id[]"][i]) + if db.CheckError(addRoleErr, db.INSERT) { + return addRoleErr, nil + } + } + + delPermissionErr := user.WithTx(tx).DeletePermissions() + + if db.CheckError(delPermissionErr, db.DELETE) { + return delPermissionErr, nil + } + + for i := 0; i < len(values["permission_id[]"]); i++ { + _, addPermissionErr := user.WithTx(tx).AddPermission(values["permission_id[]"][i]) + if db.CheckError(addPermissionErr, db.INSERT) { + return addPermissionErr, nil + } + } + + return nil, nil + }) + + return txErr + }) + formList.SetInsertFn(func(values form2.Values) error { + if values.IsEmpty("name", "username", "password") { + return errors.New("username and password can not be empty") + } + + password := values.Get("password") + + if password != values.Get("password_again") { + return errors.New("password does not match") + } + + _, txErr := s.connection().WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + + user, createUserErr := models.User().WithTx(tx).SetConn(s.conn).New(values.Get("username"), + encodePassword([]byte(values.Get("password"))), + values.Get("name"), + values.Get("avatar")) + + if db.CheckError(createUserErr, db.INSERT) { + return createUserErr, nil + } + + for i := 0; i < len(values["role_id[]"]); i++ { + _, addRoleErr := user.WithTx(tx).AddRole(values["role_id[]"][i]) + if db.CheckError(addRoleErr, db.INSERT) { + return addRoleErr, nil + } + } + + for i := 0; i < len(values["permission_id[]"]); i++ { + _, addPermissionErr := user.WithTx(tx).AddPermission(values["permission_id[]"][i]) + if db.CheckError(addPermissionErr, db.INSERT) { + return addPermissionErr, nil + } + } + + return nil, nil + }) + return txErr + }) + + detail := managerTable.GetDetail() + detail.AddField("ID", "id", db.Int) + detail.AddField(lg("Name"), "username", db.Varchar) + detail.AddField(lg("Avatar"), "avatar", db.Varchar). + FieldDisplay(func(model types.FieldModel) interface{} { + if model.Value == "" || config.GetStore().Prefix == "" { + model.Value = config.Url("/assets/dist/img/avatar04.png") + } else { + model.Value = config.GetStore().URL(model.Value) + } + return template.Default(ctx).Image(). + SetSrc(template.HTML(model.Value)). + SetHeight("120").SetWidth("120").WithModal().GetContent() + }) + detail.AddField(lg("Nickname"), "name", db.Varchar) + detail.AddField(lg("role"), "roles", db.Varchar). + FieldDisplay(func(model types.FieldModel) interface{} { + labelModels, _ := s.table("goadmin_role_users"). + Select("goadmin_roles.name"). + LeftJoin("goadmin_roles", "goadmin_roles.id", "=", "goadmin_role_users.role_id"). + Where("user_id", "=", model.ID). + All() + + labels := template.HTML("") + labelTpl := label(ctx).SetType("success") + + for key, label := range labelModels { + if key == len(labelModels)-1 { + labels += labelTpl.SetContent(template.HTML(label["name"].(string))).GetContent() + } else { + labels += labelTpl.SetContent(template.HTML(label["name"].(string))).GetContent() + "

    " + } + } + + if labels == template.HTML("") { + return lg("no roles") + } + + return labels + }) + detail.AddField(lg("permission"), "roles", db.Varchar). + FieldDisplay(func(model types.FieldModel) interface{} { + permissionModel, _ := s.table("goadmin_user_permissions"). + Select("goadmin_permissions.name"). + LeftJoin("goadmin_permissions", "goadmin_permissions.id", "=", "goadmin_user_permissions.permission_id"). + Where("user_id", "=", model.ID). + All() + + permissions := template.HTML("") + permissionTpl := label(ctx).SetType("success") + + for key, label := range permissionModel { + if key == len(permissionModel)-1 { + permissions += permissionTpl.SetContent(template.HTML(label["name"].(string))).GetContent() + } else { + permissions += permissionTpl.SetContent(template.HTML(label["name"].(string))).GetContent() + "

    " + } + } + + return permissions + }) + detail.AddField(lg("createdAt"), "created_at", db.Timestamp) + detail.AddField(lg("updatedAt"), "updated_at", db.Timestamp) + + return +} + +func (s *SystemTable) GetNormalManagerTable(ctx *context.Context) (managerTable Table) { + managerTable = NewDefaultTable(ctx, DefaultConfigWithDriver(config.GetDatabases().GetDefault().Driver)) + + info := managerTable.GetInfo().AddXssJsFilter().SetFilterFormLayout(form.LayoutFilter) + + info.AddField("ID", "id", db.Int).FieldSortable() + info.AddField(lg("Name"), "username", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("Nickname"), "name", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("role"), "name", db.Varchar). + FieldJoin(types.Join{ + Table: "goadmin_role_users", + JoinField: "user_id", + Field: "id", + }). + FieldJoin(types.Join{ + Table: "goadmin_roles", + JoinField: "id", + Field: "role_id", + BaseTable: "goadmin_role_users", + }). + FieldDisplay(func(model types.FieldModel) interface{} { + labels := template.HTML("") + labelTpl := label(ctx).SetType("success") + + labelValues := strings.Split(model.Value, types.JoinFieldValueDelimiter) + for key, label := range labelValues { + if key == len(labelValues)-1 { + labels += labelTpl.SetContent(template.HTML(label)).GetContent() + } else { + labels += labelTpl.SetContent(template.HTML(label)).GetContent() + "

    " + } + } + + if labels == template.HTML("") { + return lg("no roles") + } + + return labels + }) + info.AddField(lg("createdAt"), "created_at", db.Timestamp) + info.AddField(lg("updatedAt"), "updated_at", db.Timestamp) + + info.SetTable("goadmin_users"). + SetTitle(lg("Managers")). + SetDescription(lg("Managers manage")). + SetDeleteFn(func(idArr []string) error { + + var ids = interfaces(idArr) + + _, txErr := s.connection().WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + + deleteUserRoleErr := s.connection().WithTx(tx). + Table("goadmin_role_users"). + WhereIn("user_id", ids). + Delete() + + if db.CheckError(deleteUserRoleErr, db.DELETE) { + return deleteUserRoleErr, nil + } + + deleteUserPermissionErr := s.connection().WithTx(tx). + Table("goadmin_user_permissions"). + WhereIn("user_id", ids). + Delete() + + if db.CheckError(deleteUserPermissionErr, db.DELETE) { + return deleteUserPermissionErr, nil + } + + deleteUserErr := s.connection().WithTx(tx). + Table("goadmin_users"). + WhereIn("id", ids). + Delete() + + if db.CheckError(deleteUserErr, db.DELETE) { + return deleteUserErr, nil + } + + return nil, nil + }) + + return txErr + }) + + formList := managerTable.GetForm().AddXssJsFilter() + + formList.AddField("ID", "id", db.Int, form.Default).FieldDisplayButCanNotEditWhenUpdate().FieldDisableWhenCreate() + formList.AddField(lg("Name"), "username", db.Varchar, form.Text).FieldHelpMsg(template.HTML(lg("use for login"))).FieldMust() + formList.AddField(lg("Nickname"), "name", db.Varchar, form.Text).FieldHelpMsg(template.HTML(lg("use to display"))).FieldMust() + formList.AddField(lg("Avatar"), "avatar", db.Varchar, form.File) + formList.AddField(lg("password"), "password", db.Varchar, form.Password). + FieldDisplay(func(value types.FieldModel) interface{} { + return "" + }) + formList.AddField(lg("confirm password"), "password_again", db.Varchar, form.Password). + FieldDisplay(func(value types.FieldModel) interface{} { + return "" + }) + + formList.SetTable("goadmin_users").SetTitle(lg("Managers")).SetDescription(lg("Managers")) + formList.SetUpdateFn(func(values form2.Values) error { + + if values.IsEmpty("name", "username") { + return errors.New("username and password can not be empty") + } + + user := models.UserWithId(values.Get("id")).SetConn(s.conn) + + if values.Has("permission", "role") { + return errors.New(errs.NoPermission) + } + + password := values.Get("password") + + if password != "" { + + if password != values.Get("password_again") { + return errors.New("password does not match") + } + + password = encodePassword([]byte(values.Get("password"))) + } + + avatar := values.Get("avatar") + + if values.Get("avatar__delete_flag") == "1" { + avatar = "" + } + + _, updateUserErr := user.Update(values.Get("username"), + password, values.Get("name"), avatar, values.Get("avatar__change_flag") == "1") + + if db.CheckError(updateUserErr, db.UPDATE) { + return updateUserErr + } + + return nil + }) + formList.SetInsertFn(func(values form2.Values) error { + if values.IsEmpty("name", "username", "password") { + return errors.New("username and password can not be empty") + } + + password := values.Get("password") + + if password != values.Get("password_again") { + return errors.New("password does not match") + } + + if values.Has("permission", "role") { + return errors.New(errs.NoPermission) + } + + _, createUserErr := models.User().SetConn(s.conn).New(values.Get("username"), + encodePassword([]byte(values.Get("password"))), + values.Get("name"), + values.Get("avatar")) + + if db.CheckError(createUserErr, db.INSERT) { + return createUserErr + } + + return nil + }) + + return +} + +func (s *SystemTable) GetPermissionTable(ctx *context.Context) (permissionTable Table) { + permissionTable = NewDefaultTable(ctx, DefaultConfigWithDriver(config.GetDatabases().GetDefault().Driver)) + + info := permissionTable.GetInfo().AddXssJsFilter().SetFilterFormLayout(form.LayoutFilter) + + info.AddField("ID", "id", db.Int).FieldSortable() + info.AddField(lg("permission"), "name", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("slug"), "slug", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("method"), "http_method", db.Varchar).FieldDisplay(func(value types.FieldModel) interface{} { + if value.Value == "" { + return "All methods" + } + return value.Value + }) + info.AddField(lg("path"), "http_path", db.Varchar). + FieldDisplay(func(model types.FieldModel) interface{} { + pathArr := strings.Split(model.Value, "\n") + res := "" + for i := 0; i < len(pathArr); i++ { + if i == len(pathArr)-1 { + res += string(label(ctx).SetContent(template.HTML(pathArr[i])).GetContent()) + } else { + res += string(label(ctx).SetContent(template.HTML(pathArr[i])).GetContent()) + "

    " + } + } + return res + }) + info.AddField(lg("createdAt"), "created_at", db.Timestamp) + info.AddField(lg("updatedAt"), "updated_at", db.Timestamp) + + info.SetTable("goadmin_permissions"). + SetTitle(lg("Permission")). + SetDescription(lg("Permission Manage")). + SetDeleteFn(func(idArr []string) error { + + var ids = interfaces(idArr) + + _, txErr := s.connection().WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + + deleteRolePermissionErr := s.connection().WithTx(tx). + Table("goadmin_role_permissions"). + WhereIn("permission_id", ids). + Delete() + + if db.CheckError(deleteRolePermissionErr, db.DELETE) { + return deleteRolePermissionErr, nil + } + + deleteUserPermissionErr := s.connection().WithTx(tx). + Table("goadmin_user_permissions"). + WhereIn("permission_id", ids). + Delete() + + if db.CheckError(deleteUserPermissionErr, db.DELETE) { + return deleteUserPermissionErr, nil + } + + deletePermissionsErr := s.connection().WithTx(tx). + Table("goadmin_permissions"). + WhereIn("id", ids). + Delete() + + if deletePermissionsErr != nil { + return deletePermissionsErr, nil + } + + return nil, nil + }) + + return txErr + }) + + formList := permissionTable.GetForm().AddXssJsFilter() + + formList.AddField("ID", "id", db.Int, form.Default).FieldDisplayButCanNotEditWhenUpdate().FieldDisableWhenCreate() + formList.AddField(lg("permission"), "name", db.Varchar, form.Text).FieldMust() + formList.AddField(lg("slug"), "slug", db.Varchar, form.Text).FieldHelpMsg(template.HTML(lg("should be unique"))).FieldMust() + formList.AddField(lg("method"), "http_method", db.Varchar, form.Select). + FieldOptions(types.FieldOptions{ + {Value: "GET", Text: "GET"}, + {Value: "PUT", Text: "PUT"}, + {Value: "POST", Text: "POST"}, + {Value: "DELETE", Text: "DELETE"}, + {Value: "PATCH", Text: "PATCH"}, + {Value: "OPTIONS", Text: "OPTIONS"}, + {Value: "HEAD", Text: "HEAD"}, + }). + FieldDisplay(func(model types.FieldModel) interface{} { + return strings.Split(model.Value, ",") + }). + FieldPostFilterFn(func(model types.PostFieldModel) interface{} { + return strings.Join(model.Value, ",") + }). + FieldHelpMsg(template.HTML(lg("all method if empty"))) + + formList.AddField(lg("path"), "http_path", db.Varchar, form.TextArea). + FieldPostFilterFn(func(model types.PostFieldModel) interface{} { + return strings.TrimSpace(model.Value.Value()) + }). + FieldHelpMsg(template.HTML(lg("a path a line, without global prefix"))) + formList.AddField(lg("updatedAt"), "updated_at", db.Timestamp, form.Default).FieldDisableWhenCreate() + formList.AddField(lg("createdAt"), "created_at", db.Timestamp, form.Default).FieldDisableWhenCreate() + + formList.SetTable("goadmin_permissions"). + SetTitle(lg("Permission Manage")). + SetDescription(lg("Permission Manage")). + SetPostValidator(func(values form2.Values) error { + + if values.IsEmpty("slug", "http_path", "name") { + return errors.New("slug or http_path or name should not be empty") + } + + if models.Permission().SetConn(s.conn).IsSlugExist(values.Get("slug"), values.Get("id")) { + return errors.New("slug exists") + } + return nil + }).SetPostHook(func(values form2.Values) error { + + if values.IsInsertPost() { + return nil + } + + _, err := s.connection().Table("goadmin_permissions"). + Where("id", "=", values.Get("id")). + Update(dialect.H{ + "updated_at": time.Now().Format("2006-01-02 15:04:05"), + }) + + if db.CheckError(err, db.UPDATE) { + return err + } + + return nil + }) + + return +} + +func (s *SystemTable) GetRolesTable(ctx *context.Context) (roleTable Table) { + roleTable = NewDefaultTable(ctx, DefaultConfigWithDriver(config.GetDatabases().GetDefault().Driver)) + + info := roleTable.GetInfo().AddXssJsFilter().SetFilterFormLayout(form.LayoutFilter) + + info.AddField("ID", "id", db.Int).FieldSortable() + info.AddField(lg("role"), "name", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("slug"), "slug", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("createdAt"), "created_at", db.Timestamp) + info.AddField(lg("updatedAt"), "updated_at", db.Timestamp) + + info.SetTable("goadmin_roles"). + SetTitle(lg("Roles")). + SetDescription(lg("Roles Manage")). + SetDeleteFn(func(idArr []string) error { + + var ids = interfaces(idArr) + + _, txErr := s.connection().WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + + deleteRoleUserErr := s.connection().WithTx(tx). + Table("goadmin_role_users"). + WhereIn("role_id", ids). + Delete() + + if db.CheckError(deleteRoleUserErr, db.DELETE) { + return deleteRoleUserErr, nil + } + + deleteRoleMenuErr := s.connection().WithTx(tx). + Table("goadmin_role_menu"). + WhereIn("role_id", ids). + Delete() + + if db.CheckError(deleteRoleMenuErr, db.DELETE) { + return deleteRoleMenuErr, nil + } + + deleteRolePermissionErr := s.connection().WithTx(tx). + Table("goadmin_role_permissions"). + WhereIn("role_id", ids). + Delete() + + if db.CheckError(deleteRolePermissionErr, db.DELETE) { + return deleteRolePermissionErr, nil + } + + deleteRolesErr := s.connection().WithTx(tx). + Table("goadmin_roles"). + WhereIn("id", ids). + Delete() + + if db.CheckError(deleteRolesErr, db.DELETE) { + return deleteRolesErr, nil + } + + return nil, nil + }) + + return txErr + }) + + formList := roleTable.GetForm().AddXssJsFilter() + + formList.AddField("ID", "id", db.Int, form.Default).FieldDisplayButCanNotEditWhenUpdate().FieldDisableWhenCreate() + formList.AddField(lg("role"), "name", db.Varchar, form.Text).FieldMust() + formList.AddField(lg("slug"), "slug", db.Varchar, form.Text).FieldHelpMsg(template.HTML(lg("should be unique"))).FieldMust() + formList.AddField(lg("permission"), "permission_id", db.Varchar, form.SelectBox). + FieldOptionsFromTable("goadmin_permissions", "name", "id"). + FieldDisplay(func(model types.FieldModel) interface{} { + var permissions = make([]string, 0) + + if model.ID == "" { + return permissions + } + perModel, _ := s.table("goadmin_role_permissions"). + Select("permission_id"). + Where("role_id", "=", model.ID). + All() + for _, v := range perModel { + permissions = append(permissions, strconv.FormatInt(v["permission_id"].(int64), 10)) + } + return permissions + }).FieldHelpMsg(template.HTML(lg("no corresponding options?")) + + link("/admin/info/permission/new", "Create here.")) + + formList.AddField(lg("updatedAt"), "updated_at", db.Timestamp, form.Default).FieldDisableWhenCreate() + formList.AddField(lg("createdAt"), "created_at", db.Timestamp, form.Default).FieldDisableWhenCreate() + + formList.SetTable("goadmin_roles"). + SetTitle(lg("Roles")). + SetDescription(lg("Roles Manage")) + + formList.SetUpdateFn(func(values form2.Values) error { + + if models.Role().SetConn(s.conn).IsSlugExist(values.Get("slug"), values.Get("id")) { + return errors.New("slug exists") + } + + role := models.RoleWithId(values.Get("id")).SetConn(s.conn) + + _, txErr := s.connection().WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + + _, updateRoleErr := role.WithTx(tx).Update(values.Get("name"), values.Get("slug")) + + if db.CheckError(updateRoleErr, db.UPDATE) { + return updateRoleErr, nil + } + + delPermissionErr := role.WithTx(tx).DeletePermissions() + + if db.CheckError(delPermissionErr, db.DELETE) { + return delPermissionErr, nil + } + + for i := 0; i < len(values["permission_id[]"]); i++ { + _, addPermissionErr := role.WithTx(tx).AddPermission(values["permission_id[]"][i]) + if db.CheckError(addPermissionErr, db.INSERT) { + return addPermissionErr, nil + } + } + + return nil, nil + }) + + return txErr + }) + + formList.SetInsertFn(func(values form2.Values) error { + + if models.Role().SetConn(s.conn).IsSlugExist(values.Get("slug"), "") { + return errors.New("slug exists") + } + + _, txErr := s.connection().WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + role, createRoleErr := models.Role().WithTx(tx).SetConn(s.conn).New(values.Get("name"), values.Get("slug")) + + if db.CheckError(createRoleErr, db.INSERT) { + return createRoleErr, nil + } + + for i := 0; i < len(values["permission_id[]"]); i++ { + _, addPermissionErr := role.WithTx(tx).AddPermission(values["permission_id[]"][i]) + if db.CheckError(addPermissionErr, db.INSERT) { + return addPermissionErr, nil + } + } + + return nil, nil + }) + + return txErr + }) + + return +} + +func (s *SystemTable) GetOpTable(ctx *context.Context) (opTable Table) { + opTable = NewDefaultTable(ctx, Config{ + Driver: config.GetDatabases().GetDefault().Driver, + CanAdd: false, + Editable: false, + Deletable: config.GetAllowDelOperationLog(), + Exportable: true, + Connection: "default", + PrimaryKey: PrimaryKey{ + Type: db.Int, + Name: DefaultPrimaryKeyName, + }, + }) + + info := opTable.GetInfo().AddXssJsFilter(). + HideDetailButton().HideEditButton().HideNewButton().SetFilterFormLayout(form.LayoutFilter) + + if !config.GetAllowDelOperationLog() { + info = info.HideDeleteButton() + } + + info.AddField("ID", "id", db.Int).FieldSortable() + info.AddField("userID", "user_id", db.Int).FieldHide() + info.AddField(lg("user"), "name", db.Varchar).FieldJoin(types.Join{ + Table: config.GetAuthUserTable(), + JoinField: "id", + Field: "user_id", + }).FieldDisplay(func(value types.FieldModel) interface{} { + return template.Default(ctx). + Link(). + SetURL(config.Url("/info/manager/detail?__goadmin_detail_pk=") + strconv.Itoa(int(value.Row["user_id"].(int64)))). + SetContent(template.HTML(value.Value)). + OpenInNewTab(). + SetTabTitle("Manager Detail"). + GetContent() + }) + info.AddField(lg("path"), "path", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("method"), "method", db.Varchar) + info.AddField(lg("ip"), "ip", db.Varchar).FieldFilterable(filterType) + info.AddField(lg("content"), "input", db.Text).FieldWidth(230) + info.AddField(lg("createdAt"), "created_at", db.Timestamp) + + users, _ := s.table(config.GetAuthUserTable()).Select("id", "name").All() + options := make(types.FieldOptions, len(users)) + for k, user := range users { + options[k].Value = fmt.Sprintf("%v", user["id"]) + options[k].Text = fmt.Sprintf("%v", user["name"]) + } + info.AddSelectBox(ctx, language.Get("user"), options, action.FieldFilter("user_id")) + info.AddSelectBox(ctx, language.Get("method"), types.FieldOptions{ + {Value: "GET", Text: "GET"}, + {Value: "POST", Text: "POST"}, + {Value: "OPTIONS", Text: "OPTIONS"}, + {Value: "PUT", Text: "PUT"}, + {Value: "HEAD", Text: "HEAD"}, + {Value: "DELETE", Text: "DELETE"}, + }, action.FieldFilter("method")) + + info.SetTable("goadmin_operation_log"). + SetTitle(lg("operation log")). + SetDescription(lg("operation log")) + + formList := opTable.GetForm().AddXssJsFilter() + + formList.AddField("ID", "id", db.Int, form.Default).FieldDisplayButCanNotEditWhenUpdate().FieldDisableWhenCreate() + formList.AddField(lg("userID"), "user_id", db.Int, form.Text) + formList.AddField(lg("path"), "path", db.Varchar, form.Text) + formList.AddField(lg("method"), "method", db.Varchar, form.Text) + formList.AddField(lg("ip"), "ip", db.Varchar, form.Text) + formList.AddField(lg("content"), "input", db.Varchar, form.Text) + formList.AddField(lg("updatedAt"), "updated_at", db.Timestamp, form.Default).FieldDisableWhenCreate() + formList.AddField(lg("createdAt"), "created_at", db.Timestamp, form.Default).FieldDisableWhenCreate() + + formList.SetTable("goadmin_operation_log"). + SetTitle(lg("operation log")). + SetDescription(lg("operation log")) + + return +} + +func (s *SystemTable) GetMenuTable(ctx *context.Context) (menuTable Table) { + menuTable = NewDefaultTable(ctx, DefaultConfigWithDriver(config.GetDatabases().GetDefault().Driver)) + + name := ctx.Query("__plugin_name") + + info := menuTable.GetInfo().AddXssJsFilter().HideFilterArea().Where("plugin_name", "=", name) + + info.AddField("ID", "id", db.Int).FieldSortable() + info.AddField(lg("parent"), "parent_id", db.Int) + info.AddField(lg("menu name"), "title", db.Varchar) + info.AddField(lg("icon"), "icon", db.Varchar) + info.AddField(lg("uri"), "uri", db.Varchar) + info.AddField(lg("role"), "roles", db.Varchar) + info.AddField(lg("header"), "header", db.Varchar) + info.AddField(lg("createdAt"), "created_at", db.Timestamp) + info.AddField(lg("updatedAt"), "updated_at", db.Timestamp) + + info.SetTable("goadmin_menu"). + SetTitle(lg("Menus Manage")). + SetDescription(lg("Menus Manage")). + SetDeleteFn(func(idArr []string) error { + + var ids = interfaces(idArr) + + _, txErr := s.connection().WithTransaction(func(tx *sql.Tx) (e error, i map[string]interface{}) { + + deleteRoleMenuErr := s.connection().WithTx(tx). + Table("goadmin_role_menu"). + WhereIn("menu_id", ids). + Delete() + + if db.CheckError(deleteRoleMenuErr, db.DELETE) { + return deleteRoleMenuErr, nil + } + + deleteMenusErr := s.connection().WithTx(tx). + Table("goadmin_menu"). + WhereIn("id", ids). + Delete() + + if db.CheckError(deleteMenusErr, db.DELETE) { + return deleteMenusErr, nil + } + + return nil, map[string]interface{}{} + }) + + return txErr + }) + + var parentIDOptions = types.FieldOptions{ + { + Text: "ROOT", + Value: "0", + }, + } + + allMenus, _ := s.connection().Table("goadmin_menu"). + Where("parent_id", "=", 0). + Where("plugin_name", "=", name). + Select("id", "title"). + OrderBy("order", "asc"). + All() + allMenuIDs := make([]interface{}, len(allMenus)) + + if len(allMenuIDs) > 0 { + for i := 0; i < len(allMenus); i++ { + allMenuIDs[i] = allMenus[i]["id"] + } + + secondLevelMenus, _ := s.connection().Table("goadmin_menu"). + WhereIn("parent_id", allMenuIDs). + Where("plugin_name", "=", name). + Select("id", "title", "parent_id"). + All() + + secondLevelMenusCol := collection.Collection(secondLevelMenus) + + for i := 0; i < len(allMenus); i++ { + parentIDOptions = append(parentIDOptions, types.FieldOption{ + TextHTML: "  ┝ " + language.GetFromHtml(template.HTML(allMenus[i]["title"].(string))), + Value: strconv.Itoa(int(allMenus[i]["id"].(int64))), + }) + col := secondLevelMenusCol.Where("parent_id", "=", allMenus[i]["id"].(int64)) + for i := 0; i < len(col); i++ { + parentIDOptions = append(parentIDOptions, types.FieldOption{ + TextHTML: "         ┝ " + + language.GetFromHtml(template.HTML(col[i]["title"].(string))), + Value: strconv.Itoa(int(col[i]["id"].(int64))), + }) + } + } + } + + formList := menuTable.GetForm().AddXssJsFilter() + formList.AddField("ID", "id", db.Int, form.Default).FieldDisplayButCanNotEditWhenUpdate().FieldDisableWhenCreate() + formList.AddField(lg("parent"), "parent_id", db.Int, form.SelectSingle). + FieldOptions(parentIDOptions). + FieldDisplay(func(model types.FieldModel) interface{} { + var menuItem []string + + if model.ID == "" { + return menuItem + } + + menuModel, _ := s.table("goadmin_menu").Select("parent_id").Find(model.ID) + menuItem = append(menuItem, strconv.FormatInt(menuModel["parent_id"].(int64), 10)) + return menuItem + }) + formList.AddField(lg("menu name"), "title", db.Varchar, form.Text).FieldMust() + formList.AddField(lg("header"), "header", db.Varchar, form.Text) + formList.AddField(lg("icon"), "icon", db.Varchar, form.IconPicker) + formList.AddField(lg("uri"), "uri", db.Varchar, form.Text) + formList.AddField("PluginName", "plugin_name", db.Varchar, form.Text).FieldDefault(name).FieldHide() + formList.AddField(lg("role"), "roles", db.Int, form.Select). + FieldOptionsFromTable("goadmin_roles", "slug", "id"). + FieldDisplay(func(model types.FieldModel) interface{} { + var roles []string + + if model.ID == "" { + return roles + } + + roleModel, _ := s.table("goadmin_role_menu"). + Select("role_id"). + Where("menu_id", "=", model.ID). + All() + + for _, v := range roleModel { + roles = append(roles, strconv.FormatInt(v["role_id"].(int64), 10)) + } + return roles + }) + + formList.AddField(lg("updatedAt"), "updated_at", db.Timestamp, form.Default).FieldDisableWhenCreate() + formList.AddField(lg("createdAt"), "created_at", db.Timestamp, form.Default).FieldDisableWhenCreate() + + formList.SetTable("goadmin_menu"). + SetTitle(lg("Menus Manage")). + SetDescription(lg("Menus Manage")) + + return +} + +func (s *SystemTable) GetSiteTable(ctx *context.Context) (siteTable Table) { + siteTable = NewDefaultTable(ctx, DefaultConfigWithDriver(config.GetDatabases().GetDefault().Driver). + SetOnlyUpdateForm(). + SetGetDataFun(func(params parameter.Parameters) (i []map[string]interface{}, i2 int) { + return []map[string]interface{}{models.Site().SetConn(s.conn).AllToMapInterface()}, 1 + })) + + trueStr := lgWithConfigScore("true") + falseStr := lgWithConfigScore("false") + + formList := siteTable.GetForm().AddXssJsFilter() + formList.AddField("ID", "id", db.Varchar, form.Default).FieldDefault("1").FieldHide() + formList.AddField(lgWithConfigScore("site off"), "site_off", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("debug"), "debug", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("env"), "env", db.Varchar, form.Default). + FieldDisplay(func(value types.FieldModel) interface{} { + return s.c.Env + }) + + langOps := make(types.FieldOptions, len(language.Langs)) + for k, t := range language.Langs { + langOps[k] = types.FieldOption{Text: lgWithConfigScore(t, "language"), Value: t} + } + formList.AddField(lgWithConfigScore("language"), "language", db.Varchar, form.SelectSingle). + FieldDisplay(func(value types.FieldModel) interface{} { + return language.FixedLanguageKey(value.Value) + }). + FieldOptions(langOps) + themes := template.Themes() + themesOps := make(types.FieldOptions, len(themes)) + for k, t := range themes { + themesOps[k] = types.FieldOption{Text: t, Value: t} + } + + formList.AddField(lgWithConfigScore("theme"), "theme", db.Varchar, form.SelectSingle). + FieldOptions(themesOps). + FieldOnChooseShow("adminlte", + "color_scheme") + formList.AddField(lgWithConfigScore("title"), "title", db.Varchar, form.Text).FieldMust() + formList.AddField(lgWithConfigScore("color scheme"), "color_scheme", db.Varchar, form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: "skin-black", Value: "skin-black"}, + {Text: "skin-black-light", Value: "skin-black-light"}, + {Text: "skin-blue", Value: "skin-blue"}, + {Text: "skin-blue-light", Value: "skin-blue-light"}, + {Text: "skin-green", Value: "skin-green"}, + {Text: "skin-green-light", Value: "skin-green-light"}, + {Text: "skin-purple", Value: "skin-purple"}, + {Text: "skin-purple-light", Value: "skin-purple-light"}, + {Text: "skin-red", Value: "skin-red"}, + {Text: "skin-red-light", Value: "skin-red-light"}, + {Text: "skin-yellow", Value: "skin-yellow"}, + {Text: "skin-yellow-light", Value: "skin-yellow-light"}, + }).FieldHelpMsg(template.HTML(lgWithConfigScore("It will work when theme is adminlte"))) + formList.AddField(lgWithConfigScore("login title"), "login_title", db.Varchar, form.Text).FieldMust() + formList.AddField(lgWithConfigScore("extra"), "extra", db.Varchar, form.TextArea) + formList.AddField(lgWithConfigScore("logo"), "logo", db.Varchar, form.Code).FieldMust() + formList.AddField(lgWithConfigScore("mini logo"), "mini_logo", db.Varchar, form.Code).FieldMust() + if s.c.IsNotProductionEnvironment() { + formList.AddField(lgWithConfigScore("bootstrap file path"), "bootstrap_file_path", db.Varchar, form.Text) + formList.AddField(lgWithConfigScore("go mod file path"), "go_mod_file_path", db.Varchar, form.Text) + } + formList.AddField(lgWithConfigScore("session life time"), "session_life_time", db.Varchar, form.Number). + FieldMust(). + FieldHelpMsg(template.HTML(lgWithConfigScore("must bigger than 900 seconds"))) + formList.AddField(lgWithConfigScore("custom head html"), "custom_head_html", db.Varchar, form.Code) + formList.AddField(lgWithConfigScore("custom foot Html"), "custom_foot_html", db.Varchar, form.Code) + formList.AddField(lgWithConfigScore("custom 404 html"), "custom_404_html", db.Varchar, form.Code) + formList.AddField(lgWithConfigScore("custom 403 html"), "custom_403_html", db.Varchar, form.Code) + formList.AddField(lgWithConfigScore("custom 500 Html"), "custom_500_html", db.Varchar, form.Code) + formList.AddField(lgWithConfigScore("footer info"), "footer_info", db.Varchar, form.Code) + formList.AddField(lgWithConfigScore("login logo"), "login_logo", db.Varchar, form.Code) + formList.AddField(lgWithConfigScore("no limit login ip"), "no_limit_login_ip", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("operation log off"), "operation_log_off", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("allow delete operation log"), "allow_del_operation_log", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("hide config center entrance"), "hide_config_center_entrance", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("hide app info entrance"), "hide_app_info_entrance", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("hide tool entrance"), "hide_tool_entrance", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("hide plugin entrance"), "hide_plugin_entrance", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("animation type"), "animation_type", db.Varchar, form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: "", Value: ""}, + {Text: "bounce", Value: "bounce"}, {Text: "flash", Value: "flash"}, {Text: "pulse", Value: "pulse"}, + {Text: "rubberBand", Value: "rubberBand"}, {Text: "shake", Value: "shake"}, {Text: "swing", Value: "swing"}, + {Text: "tada", Value: "tada"}, {Text: "wobble", Value: "wobble"}, {Text: "jello", Value: "jello"}, + {Text: "heartBeat", Value: "heartBeat"}, {Text: "bounceIn", Value: "bounceIn"}, {Text: "bounceInDown", Value: "bounceInDown"}, + {Text: "bounceInLeft", Value: "bounceInLeft"}, {Text: "bounceInRight", Value: "bounceInRight"}, {Text: "bounceInUp", Value: "bounceInUp"}, + {Text: "fadeIn", Value: "fadeIn"}, {Text: "fadeInDown", Value: "fadeInDown"}, {Text: "fadeInDownBig", Value: "fadeInDownBig"}, + {Text: "fadeInLeft", Value: "fadeInLeft"}, {Text: "fadeInLeftBig", Value: "fadeInLeftBig"}, {Text: "fadeInRight", Value: "fadeInRight"}, + {Text: "fadeInRightBig", Value: "fadeInRightBig"}, {Text: "fadeInUp", Value: "fadeInUp"}, {Text: "fadeInUpBig", Value: "fadeInUpBig"}, + {Text: "flip", Value: "flip"}, {Text: "flipInX", Value: "flipInX"}, {Text: "flipInY", Value: "flipInY"}, + {Text: "lightSpeedIn", Value: "lightSpeedIn"}, {Text: "rotateIn", Value: "rotateIn"}, {Text: "rotateInDownLeft", Value: "rotateInDownLeft"}, + {Text: "rotateInDownRight", Value: "rotateInDownRight"}, {Text: "rotateInUpLeft", Value: "rotateInUpLeft"}, {Text: "rotateInUpRight", Value: "rotateInUpRight"}, + {Text: "slideInUp", Value: "slideInUp"}, {Text: "slideInDown", Value: "slideInDown"}, {Text: "slideInLeft", Value: "slideInLeft"}, {Text: "slideInRight", Value: "slideInRight"}, + {Text: "slideOutRight", Value: "slideOutRight"}, {Text: "zoomIn", Value: "zoomIn"}, {Text: "zoomInDown", Value: "zoomInDown"}, + {Text: "zoomInLeft", Value: "zoomInLeft"}, {Text: "zoomInRight", Value: "zoomInRight"}, {Text: "zoomInUp", Value: "zoomInUp"}, + {Text: "hinge", Value: "hinge"}, {Text: "jackInTheBox", Value: "jackInTheBox"}, {Text: "rollIn", Value: "rollIn"}, + }).FieldOnChooseHide("", "animation_duration", "animation_delay"). + FieldOptionExt(map[string]interface{}{"allowClear": true}). + FieldHelpMsg(`see more: https://daneden.github.io/animate.css/`) + + formList.AddField(lgWithConfigScore("animation duration"), "animation_duration", db.Varchar, form.Number) + formList.AddField(lgWithConfigScore("animation delay"), "animation_delay", db.Varchar, form.Number) + + formList.AddField(lgWithConfigScore("file upload engine"), "file_upload_engine", db.Varchar, form.Text) + + formList.AddField(lgWithConfigScore("cdn url"), "asset_url", db.Varchar, form.Text). + FieldHelpMsg(template.HTML(lgWithConfigScore("Do not modify when you have not set up all assets"))) + + formList.AddField(lgWithConfigScore("info log path"), "info_log_path", db.Varchar, form.Text) + formList.AddField(lgWithConfigScore("error log path"), "error_log_path", db.Varchar, form.Text) + formList.AddField(lgWithConfigScore("access log path"), "access_log_path", db.Varchar, form.Text) + formList.AddField(lgWithConfigScore("info log off"), "info_log_off", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("error log off"), "error_log_off", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("access log off"), "access_log_off", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("access assets log off"), "access_assets_log_off", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("sql log on"), "sql_log", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }) + formList.AddField(lgWithConfigScore("log level"), "logger_level", db.Varchar, form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: "Debug", Value: "-1"}, + {Text: "Info", Value: "0"}, + {Text: "Warn", Value: "1"}, + {Text: "Error", Value: "2"}, + }).FieldDisplay(defaultFilterFn("0")) + + formList.AddField(lgWithConfigScore("logger rotate max size"), "logger_rotate_max_size", db.Varchar, form.Number). + FieldDivider(lgWithConfigScore("logger rotate")).FieldDisplay(defaultFilterFn("10", "0")) + formList.AddField(lgWithConfigScore("logger rotate max backups"), "logger_rotate_max_backups", db.Varchar, form.Number). + FieldDisplay(defaultFilterFn("5", "0")) + formList.AddField(lgWithConfigScore("logger rotate max age"), "logger_rotate_max_age", db.Varchar, form.Number). + FieldDisplay(defaultFilterFn("30", "0")) + formList.AddField(lgWithConfigScore("logger rotate compress"), "logger_rotate_compress", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: trueStr, Value: "true"}, + {Text: falseStr, Value: "false"}, + }).FieldDisplay(defaultFilterFn("false")) + + formList.AddField(lgWithConfigScore("logger rotate encoder encoding"), "logger_encoder_encoding", db.Varchar, + form.SelectSingle). + FieldDivider(lgWithConfigScore("logger rotate encoder")). + FieldOptions(types.FieldOptions{ + {Text: "JSON", Value: "json"}, + {Text: "Console", Value: "console"}, + }).FieldDisplay(defaultFilterFn("console")). + FieldOnChooseHide("Console", + "logger_encoder_time_key", "logger_encoder_level_key", "logger_encoder_caller_key", + "logger_encoder_message_key", "logger_encoder_stacktrace_key", "logger_encoder_name_key") + + formList.AddField(lgWithConfigScore("logger rotate encoder time key"), "logger_encoder_time_key", db.Varchar, form.Text). + FieldDisplay(defaultFilterFn("ts")) + formList.AddField(lgWithConfigScore("logger rotate encoder level key"), "logger_encoder_level_key", db.Varchar, form.Text). + FieldDisplay(defaultFilterFn("level")) + formList.AddField(lgWithConfigScore("logger rotate encoder name key"), "logger_encoder_name_key", db.Varchar, form.Text). + FieldDisplay(defaultFilterFn("logger")) + formList.AddField(lgWithConfigScore("logger rotate encoder caller key"), "logger_encoder_caller_key", db.Varchar, form.Text). + FieldDisplay(defaultFilterFn("caller")) + formList.AddField(lgWithConfigScore("logger rotate encoder message key"), "logger_encoder_message_key", db.Varchar, form.Text). + FieldDisplay(defaultFilterFn("msg")) + formList.AddField(lgWithConfigScore("logger rotate encoder stacktrace key"), "logger_encoder_stacktrace_key", db.Varchar, form.Text). + FieldDisplay(defaultFilterFn("stacktrace")) + + formList.AddField(lgWithConfigScore("logger rotate encoder level"), "logger_encoder_level", db.Varchar, + form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: lgWithConfigScore("capital"), Value: "capital"}, + {Text: lgWithConfigScore("capitalcolor"), Value: "capitalColor"}, + {Text: lgWithConfigScore("lowercase"), Value: "lowercase"}, + {Text: lgWithConfigScore("lowercasecolor"), Value: "color"}, + }).FieldDisplay(defaultFilterFn("capitalColor")) + formList.AddField(lgWithConfigScore("logger rotate encoder time"), "logger_encoder_time", db.Varchar, + form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: "ISO8601(2006-01-02T15:04:05.000Z0700)", Value: "iso8601"}, + {Text: lgWithConfigScore("millisecond"), Value: "millis"}, + {Text: lgWithConfigScore("nanosecond"), Value: "nanos"}, + {Text: "RFC3339(2006-01-02T15:04:05Z07:00)", Value: "rfc3339"}, + {Text: "RFC3339 Nano(2006-01-02T15:04:05.999999999Z07:00)", Value: "rfc3339nano"}, + }).FieldDisplay(defaultFilterFn("iso8601")) + formList.AddField(lgWithConfigScore("logger rotate encoder duration"), "logger_encoder_duration", db.Varchar, + form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: lgWithConfigScore("seconds"), Value: "string"}, + {Text: lgWithConfigScore("nanosecond"), Value: "nanos"}, + {Text: lgWithConfigScore("microsecond"), Value: "ms"}, + }).FieldDisplay(defaultFilterFn("string")) + formList.AddField(lgWithConfigScore("logger rotate encoder caller"), "logger_encoder_caller", db.Varchar, + form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: lgWithConfigScore("full path"), Value: "full"}, + {Text: lgWithConfigScore("short path"), Value: "short"}, + }).FieldDisplay(defaultFilterFn("full")) + + formList.HideBackButton().HideContinueEditCheckBox().HideContinueNewCheckBox() + formList.SetTabGroups(types.NewTabGroups("id", "debug", "env", "language", "theme", "color_scheme", + "asset_url", "title", "login_title", "session_life_time", "bootstrap_file_path", "go_mod_file_path", "no_limit_login_ip", + "operation_log_off", "allow_del_operation_log", "hide_config_center_entrance", "hide_app_info_entrance", "hide_tool_entrance", + "hide_plugin_entrance", "animation_type", + "animation_duration", "animation_delay", "file_upload_engine", "extra"). + AddGroup("access_log_off", "access_assets_log_off", "info_log_off", "error_log_off", "sql_log", "logger_level", + "info_log_path", "error_log_path", + "access_log_path", "logger_rotate_max_size", "logger_rotate_max_backups", + "logger_rotate_max_age", "logger_rotate_compress", + "logger_encoder_encoding", "logger_encoder_time_key", "logger_encoder_level_key", "logger_encoder_name_key", + "logger_encoder_caller_key", "logger_encoder_message_key", "logger_encoder_stacktrace_key", "logger_encoder_level", + "logger_encoder_time", "logger_encoder_duration", "logger_encoder_caller"). + AddGroup("logo", "mini_logo", "custom_head_html", "custom_foot_html", "footer_info", "login_logo", + "custom_404_html", "custom_403_html", "custom_500_html")). + SetTabHeaders(lgWithConfigScore("general"), lgWithConfigScore("log"), lgWithConfigScore("custom")) + + formList.SetTable("goadmin_site"). + SetTitle(lgWithConfigScore("site setting")). + SetDescription(lgWithConfigScore("site setting")) + + formList.SetUpdateFn(func(values form2.Values) error { + + ses := values.Get("session_life_time") + sesInt, _ := strconv.Atoi(ses) + if sesInt < 900 { + return errors.New("wrong session life time, must bigger than 900 seconds") + } + if err := checkJSON(values, "file_upload_engine"); err != nil { + return err + } + + values["logo"][0] = escape(values.Get("logo")) + values["mini_logo"][0] = escape(values.Get("mini_logo")) + values["custom_head_html"][0] = escape(values.Get("custom_head_html")) + values["custom_foot_html"][0] = escape(values.Get("custom_foot_html")) + values["custom_404_html"][0] = escape(values.Get("custom_404_html")) + values["custom_403_html"][0] = escape(values.Get("custom_403_html")) + values["custom_500_html"][0] = escape(values.Get("custom_500_html")) + values["footer_info"][0] = escape(values.Get("footer_info")) + values["login_logo"][0] = escape(values.Get("login_logo")) + + var err error + if s.c.UpdateProcessFn != nil { + values, err = s.c.UpdateProcessFn(values) + if err != nil { + return err + } + } + + ui.GetService(services).RemoveOrShowSiteNavButton(values["hide_config_center_entrance"][0] == "true") + ui.GetService(services).RemoveOrShowInfoNavButton(values["hide_app_info_entrance"][0] == "true") + ui.GetService(services).RemoveOrShowToolNavButton(values["hide_tool_entrance"][0] == "true") + ui.GetService(services).RemoveOrShowPlugNavButton(values["hide_plugin_entrance"][0] == "true") + + // TODO: add transaction + err = models.Site().SetConn(s.conn).Update(values.RemoveSysRemark()) + if err != nil { + return err + } + return s.c.Update(values.ToMap()) + }) + + formList.EnableAjax(lgWithConfigScore("modify site config"), + lgWithConfigScore("modify site config"), + "", + lgWithConfigScore("modify site config success"), + lgWithConfigScore("modify site config fail")) + + return +} + +func (s *SystemTable) GetGenerateForm(ctx *context.Context) (generateTool Table) { + generateTool = NewDefaultTable(ctx, DefaultConfigWithDriver(config.GetDatabases().GetDefault().Driver). + SetOnlyNewForm()) + + formList := generateTool.GetForm().AddXssJsFilter(). + SetHeadWidth(1). + SetInputWidth(4). + HideBackButton(). + HideContinueNewCheckBox() + + formList.AddField("ID", "id", db.Varchar, form.Default).FieldDefault("1").FieldHide() + + connNames := config.GetDatabases().Connections() + ops := make(types.FieldOptions, len(connNames)) + for i, name := range connNames { + ops[i] = types.FieldOption{Text: name, Value: name} + } + + // General options + // ================================ + + formList.AddField(lgWithScore("connection", "tool"), "conn", db.Varchar, form.SelectSingle). + FieldOptions(ops). + FieldOnChooseAjax("table", "/tool/choose/conn", + func(ctx *context.Context) (success bool, msg string, data interface{}) { + connName := ctx.FormValue("value") + if connName == "" { + return false, "wrong parameter", nil + } + cfg := s.c.Databases[connName] + conn := db.GetConnectionFromService(services.Get(cfg.Driver)) + tables, err := db.WithDriverAndConnection(connName, conn).Table(cfg.Name).ShowTables() + if err != nil { + return false, err.Error(), nil + } + ops := make(selection.Options, len(tables)) + for i, table := range tables { + ops[i] = selection.Option{Text: table, ID: table} + } + return true, "ok", ops + }) + formList.AddField(lgWithScore("table", "tool"), "table", db.Varchar, form.SelectSingle). + FieldOnChooseAjax("xxxx", "/tool/choose/table", + func(ctx *context.Context) (success bool, msg string, data interface{}) { + + var ( + tableName = ctx.FormValue("value") + connName = ctx.FormValue("conn") + driver = s.c.Databases[connName].Driver + conn = db.GetConnectionFromService(services.Get(driver)) + columnsModel, _ = db.WithDriverAndConnection(connName, conn).Table(tableName).ShowColumns() + + fieldField = "Field" + typeField = "Type" + ) + + if driver == "postgresql" { + fieldField = "column_name" + typeField = "udt_name" + } + if driver == "sqlite" { + fieldField = "name" + typeField = "type" + } + if driver == "mssql" { + fieldField = "column_name" + typeField = "data_type" + } + + headName := make([]string, len(columnsModel)) + fieldName := make([]string, len(columnsModel)) + dbTypeList := make([]string, len(columnsModel)) + formTypeList := make([]string, len(columnsModel)) + + for i, model := range columnsModel { + typeName := getType(model[typeField].(string)) + + headName[i] = cases.Title(textLang.Und).String(model[fieldField].(string)) + fieldName[i] = model[fieldField].(string) + dbTypeList[i] = typeName + formTypeList[i] = form.GetFormTypeFromFieldType(db.DT(strings.ToUpper(typeName)), + model[fieldField].(string)) + } + + return true, "ok", [][]string{headName, fieldName, dbTypeList, formTypeList} + }, template.HTML(utils.ParseText("choose_table_ajax", tmpls["choose_table_ajax"], nil)), + `"conn":$('.conn').val(),`) + formList.AddField(lgWithScore("package", "tool"), "package", db.Varchar, form.Text).FieldDefault("tables") + formList.AddField(lgWithScore("primarykey", "tool"), "pk", db.Varchar, form.Text).FieldDefault("id") + + formList.AddField(lgWithScore("table permission", "tool"), "permission", db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: lgWithScore("yes", "tool"), Value: "y"}, + {Text: lgWithScore("no", "tool"), Value: "n"}, + }).FieldDefault("n") + + formList.AddField(lgWithScore("extra import package", "tool"), "extra_import_package", db.Varchar, form.Select). + FieldOptions(types.FieldOptions{ + {Text: "time", Value: "time"}, + {Text: "log", Value: "log"}, + {Text: "fmt", Value: "fmt"}, + {Text: "github.com/GoAdminGroup/go-admin/modules/db/dialect", Value: "github.com/GoAdminGroup/go-admin/modules/db/dialect"}, + {Text: "github.com/GoAdminGroup/go-admin/modules/db", Value: "github.com/GoAdminGroup/go-admin/modules/db"}, + {Text: "github.com/GoAdminGroup/go-admin/modules/language", Value: "github.com/GoAdminGroup/go-admin/modules/language"}, + {Text: "github.com/GoAdminGroup/go-admin/modules/logger", Value: "github.com/GoAdminGroup/go-admin/modules/logger"}, + }). + FieldDefault(""). + FieldOptionExt(map[string]interface{}{ + "tags": true, + }) + + formList.AddField(lgWithScore("output", "tool"), "path", db.Varchar, form.Text). + FieldDefault("").FieldMust().FieldHelpMsg(template.HTML(lgWithScore("use absolute path", "tool"))) + + formList.AddField(lgWithScore("extra code", "tool"), "extra_code", db.Varchar, form.Code). + FieldDefault("").FieldInputWidth(11) + + // Info table generate options + // ================================ + + formList.AddField(lgWithScore("title", "tool"), "table_title", db.Varchar, form.Text) + formList.AddField(lgWithScore("description", "tool"), "table_description", db.Varchar, form.Text) + + formList.AddRow(func(panel *types.FormPanel) { + addSwitchForTool(panel, "filter area", "hide_filter_area", "n", 2) + panel.AddField(lgWithScore("filter form layout", "tool"), "filter_form_layout", db.Varchar, form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: form.LayoutDefault.String(), Value: form.LayoutDefault.String()}, + {Text: form.LayoutTwoCol.String(), Value: form.LayoutTwoCol.String()}, + {Text: form.LayoutThreeCol.String(), Value: form.LayoutThreeCol.String()}, + {Text: form.LayoutFourCol.String(), Value: form.LayoutFourCol.String()}, + {Text: form.LayoutFlow.String(), Value: form.LayoutFlow.String()}, + }).FieldDefault(form.LayoutDefault.String()). + FieldRowWidth(4).FieldHeadWidth(3) + }) + + formList.AddRow(func(panel *types.FormPanel) { + addSwitchForTool(panel, "new button", "hide_new_button", "n", 2) + addSwitchForTool(panel, "export button", "hide_export_button", "n", 4, 3) + addSwitchForTool(panel, "edit button", "hide_edit_button", "n", 4, 2) + }) + + formList.AddRow(func(panel *types.FormPanel) { + addSwitchForTool(panel, "pagination", "hide_pagination", "n", 2) + addSwitchForTool(panel, "delete button", "hide_delete_button", "n", 4, 3) + addSwitchForTool(panel, "detail button", "hide_detail_button", "n", 4, 2) + }) + + formList.AddRow(func(panel *types.FormPanel) { + addSwitchForTool(panel, "filter button", "hide_filter_button", "n", 2) + addSwitchForTool(panel, "row selector", "hide_row_selector", "n", 4, 3) + addSwitchForTool(panel, "query info", "hide_query_info", "n", 4, 2) + }) + + formList.AddTable(lgWithScore("field", "tool"), "fields", func(pa *types.FormPanel) { + pa.AddField(lgWithScore("title", "tool"), "field_head", db.Varchar, form.Text).FieldHideLabel(). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{""} + }) + pa.AddField(lgWithScore("field name", "tool"), "field_name", db.Varchar, form.Text).FieldHideLabel(). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{""} + }) + pa.AddField(lgWithScore("field filterable", "tool"), "field_filterable", db.Varchar, form.CheckboxSingle). + FieldOptions(types.FieldOptions{ + {Text: "", Value: "y"}, + {Text: "", Value: "n"}, + }). + FieldDefault("n"). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"n"} + }) + pa.AddField(lgWithScore("field sortable", "tool"), "field_sortable", db.Varchar, form.CheckboxSingle). + FieldOptions(types.FieldOptions{ + {Text: "", Value: "y"}, + {Text: "", Value: "n"}, + }). + FieldDefault("n"). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"n"} + }) + pa.AddField(lgWithScore("field hide", "tool"), "field_hide", db.Varchar, form.CheckboxSingle). + FieldOptions(types.FieldOptions{ + {Text: "", Value: "y"}, + {Text: "", Value: "n"}, + }). + FieldDefault("n"). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"n"} + }) + pa.AddField(lgWithScore("info field editable", "tool"), "info_field_editable", db.Varchar, form.CheckboxSingle). + FieldOptions(types.FieldOptions{ + {Text: "", Value: "y"}, + {Text: "", Value: "n"}, + }). + FieldDefault("n"). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"n"} + }) + //pa.AddField(lgWithScore("db display type", "tool"), "field_display_type", db.Varchar, form.SelectSingle). + // FieldOptions(infoFieldDisplayTypeOptions()). + // FieldDisplay(func(value types.FieldModel) interface{} { + // return []string{""} + // }) + pa.AddField(lgWithScore("db type", "tool"), "field_db_type", db.Varchar, form.SelectSingle). + FieldOptions(databaseTypeOptions()). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"Int"} + }) + }).FieldInputWidth(11) + + // Form generate options + // ================================ + + formList.AddField(lgWithScore("title", "tool"), "form_title", db.Varchar, form.Text) + formList.AddField(lgWithScore("description", "tool"), "form_description", db.Varchar, form.Text) + + formList.AddRow(func(panel *types.FormPanel) { + addSwitchForTool(panel, "continue edit checkbox", "hide_continue_edit_check_box", "n", 2) + addSwitchForTool(panel, "reset button", "hide_reset_button", "n", 5, 3) + }) + + formList.AddRow(func(panel *types.FormPanel) { + addSwitchForTool(panel, "continue new checkbox", "hide_continue_new_check_box", "n", 2) + addSwitchForTool(panel, "back button", "hide_back_button", "n", 5, 3) + }) + + formList.AddTable(lgWithScore("field", "tool"), "fields_form", func(pa *types.FormPanel) { + pa.AddField(lgWithScore("title", "tool"), "field_head_form", db.Varchar, form.Text).FieldHideLabel(). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{""} + }) + pa.AddField(lgWithScore("field name", "tool"), "field_name_form", db.Varchar, form.Text).FieldHideLabel(). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{""} + }) + pa.AddField(lgWithScore("field editable", "tool"), "field_canedit", db.Varchar, form.CheckboxSingle). + FieldOptions(types.FieldOptions{ + {Text: "", Value: "y"}, + {Text: "", Value: "n"}, + }). + FieldDefault("y"). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"y"} + }) + pa.AddField(lgWithScore("field can add", "tool"), "field_canadd", db.Varchar, form.CheckboxSingle). + FieldOptions(types.FieldOptions{ + {Text: "", Value: "y"}, + {Text: "", Value: "n"}, + }). + FieldDefault("y"). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"y"} + }) + pa.AddField(lgWithScore("field default", "tool"), "field_default", db.Varchar, form.Text).FieldHideLabel(). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{""} + }) + pa.AddField(lgWithScore("field display", "tool"), "field_display", db.Varchar, form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: lgWithScore("field display normal", "tool"), Value: "0"}, + {Text: lgWithScore("field diplay hide", "tool"), Value: "1"}, + {Text: lgWithScore("field diplay edit hide", "tool"), Value: "2"}, + {Text: lgWithScore("field diplay create hide", "tool"), Value: "3"}, + }). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"0"} + }) + pa.AddField(lgWithScore("db type", "tool"), "field_db_type_form", db.Varchar, form.SelectSingle). + FieldOptions(databaseTypeOptions()). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"Int"} + }) + pa.AddField(lgWithScore("form type", "tool"), "field_form_type_form", db.Varchar, form.SelectSingle). + FieldOptions(formTypeOptions()).FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"Text"} + }) + }).FieldInputWidth(11) + + // Detail page generate options + // ================================ + + formList.AddField(lgWithScore("title", "tool"), "detail_title", db.Varchar, form.Text) + formList.AddField(lgWithScore("description", "tool"), "detail_description", db.Varchar, form.Text) + + formList.AddField(lgWithScore("detail display", "tool"), "detail_display", db.Varchar, form.SelectSingle). + FieldOptions(types.FieldOptions{ + {Text: lgWithScore("follow list page", "tool"), Value: "0"}, + {Text: lgWithScore("inherit from list page", "tool"), Value: "1"}, + {Text: lgWithScore("independent from list page", "tool"), Value: "2"}, + }). + FieldDefault("0"). + FieldOnChooseHide("0", "detail_title", "detail_description", "fields_detail") + + formList.AddTable(lgWithScore("field", "tool"), "fields_detail", func(pa *types.FormPanel) { + pa.AddField(lgWithScore("title", "tool"), "detail_field_head", db.Varchar, form.Text).FieldHideLabel(). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{""} + }) + pa.AddField(lgWithScore("field name", "tool"), "detail_field_name", db.Varchar, form.Text).FieldHideLabel(). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{""} + }) + pa.AddField(lgWithScore("db type", "tool"), "detail_field_db_type", db.Varchar, form.SelectSingle). + FieldOptions(databaseTypeOptions()). + FieldDisplay(func(value types.FieldModel) interface{} { + return []string{"Int"} + }) + }).FieldInputWidth(11) + + formList.SetTabGroups(types. + NewTabGroups("conn", "table", "package", "pk", "permission", "extra_import_package", "path", "extra_code"). + AddGroup("table_title", "table_description", "hide_filter_area", "filter_form_layout", + "hide_new_button", "hide_export_button", "hide_edit_button", + "hide_pagination", "hide_delete_button", "hide_detail_button", + "hide_filter_button", "hide_row_selector", "hide_query_info", + "fields"). + AddGroup("form_title", "form_description", "hide_continue_edit_check_box", "hide_reset_button", + "hide_continue_new_check_box", "hide_back_button", + "fields_form"). + AddGroup("detail_display", "detail_title", "detail_description", "fields_detail")). + SetTabHeaders(lgWithScore("basic info", "tool"), lgWithScore("table info", "tool"), + lgWithScore("form info", "tool"), lgWithScore("detail info", "tool")) + + formList.SetTable("tool"). + SetTitle(lgWithScore("code generate tool", "tool")). + SetDescription(lgWithScore("code generate tool", "tool")). + SetHeader(template.HTML(`

    ` + + lgWithScore("generate table model", "tool") + `

    `)) + + formList.SetInsertFn(func(values form2.Values) error { + + table := values.Get("table") + + if table == "" { + return errors.New("table is empty") + } + + if values.Get("permission") == "y" { + tools.InsertPermissionOfTable(s.conn, table) + } + + output := values.Get("path") + + if output == "" { + return errors.New("output path is empty") + } + + connName := values.Get("conn") + + fields := make(tools.Fields, len(values["field_head"])) + + for i := 0; i < len(values["field_head"]); i++ { + fields[i] = tools.Field{ + Head: values["field_head"][i], + Name: values["field_name"][i], + DBType: values["field_db_type"][i], + Filterable: values["field_filterable"][i] == "y", + Sortable: values["field_sortable"][i] == "y", + Hide: values["field_hide"][i] == "y", + InfoEditable: values["info_field_editable"][i] == "y", + } + } + + extraImport := "" + for _, pack := range values["extra_import_package[]"] { + if extraImport != "" { + extraImport += ` +` + } + extraImport += ` "` + pack + `"` + } + + formFields := make(tools.Fields, len(values["field_head_form"])) + + for i := 0; i < len(values["field_head_form"]); i++ { + extraFun := "" + if values["field_name_form"][i] == `created_at` { + extraFun += `.FieldNowWhenInsert()` + } else if values["field_name_form"][i] == `updated_at` { + extraFun += `.FieldNowWhenUpdate()` + } else if values["field_default"][i] != "" && !strings.Contains(values["field_default"][i], `"`) { + values["field_default"][i] = `"` + values["field_default"][i] + `"` + } + formFields[i] = tools.Field{ + Head: values["field_head_form"][i], + Name: values["field_name_form"][i], + Default: values["field_default"][i], + FormType: values["field_form_type_form"][i], + DBType: values["field_db_type_form"][i], + CanAdd: values["field_canadd"][i] == "y", + Editable: values["field_canedit"][i] == "y", + FormHide: values["field_display"][i] == "1", + CreateHide: values["field_display"][i] == "2", + EditHide: values["field_display"][i] == "3", + ExtraFun: extraFun, + } + } + + detailFields := make(tools.Fields, len(values["detail_field_head"])) + + for i := 0; i < len(values["detail_field_head"]); i++ { + detailFields[i] = tools.Field{ + Head: values["detail_field_head"][i], + Name: values["detail_field_name"][i], + DBType: values["detail_field_db_type"][i], + } + } + + detailDisplay, _ := strconv.ParseUint(values.Get("detail_display"), 10, 64) + + err := tools.Generate(tools.NewParamWithFields(tools.Config{ + Connection: connName, + Driver: s.c.Databases[connName].Driver, + Package: values.Get("package"), + Table: table, + HideFilterArea: values.Get("hide_filter_area") == "y", + HideNewButton: values.Get("hide_new_button") == "y", + HideExportButton: values.Get("hide_export_button") == "y", + HideEditButton: values.Get("hide_edit_button") == "y", + HideDeleteButton: values.Get("hide_delete_button") == "y", + HideDetailButton: values.Get("hide_detail_button") == "y", + HideFilterButton: values.Get("hide_filter_button") == "y", + HideRowSelector: values.Get("hide_row_selector") == "y", + HidePagination: values.Get("hide_pagination") == "y", + HideQueryInfo: values.Get("hide_query_info") == "y", + HideContinueEditCheckBox: values.Get("hide_continue_edit_check_box") == "y", + HideContinueNewCheckBox: values.Get("hide_continue_new_check_box") == "y", + HideResetButton: values.Get("hide_reset_button") == "y", + HideBackButton: values.Get("hide_back_button") == "y", + FilterFormLayout: form.GetLayoutFromString(values.Get("filter_form_layout")), + Schema: values.Get("schema"), + Output: output, + DetailDisplay: uint8(detailDisplay), + FormTitle: values.Get("form_title"), + FormDescription: values.Get("form_description"), + DetailTitle: values.Get("detail_title"), + DetailDescription: values.Get("detail_description"), + TableTitle: values.Get("table_title"), + TableDescription: values.Get("table_description"), + ExtraImport: extraImport, + ExtraCode: escape(values.Get("extra_code")), + }, fields, formFields, detailFields)) + + if err != nil { + return err + } + + return tools.GenerateTables(output, values.Get("package"), []string{table}, false) + }) + + formList.EnableAjaxData(types.AjaxData{ + SuccessTitle: lgWithScore("generate table model", "tool"), + ErrorTitle: lgWithScore("generate table model", "tool"), + SuccessText: lgWithScore("generate table model success", "tool"), + ErrorText: lgWithScore("generate table model fail", "tool"), + DisableJump: true, + }) + + formList.SetFooterHtml(utils.ParseHTML("generator", tmpls["generator"], map[string]string{ + "prefix": "go_admin_" + config.GetAppID() + "_generator_", + })) + + formList.SetFormNewBtnWord(template.HTML(lgWithScore("generate", "tool"))) + formList.SetWrapper(func(content tmpl.HTML) tmpl.HTML { + headli := html.LiEl().SetClass("list-group-item", "list-head"). + SetContent(template.HTML(lgWithScore("generated tables", "tool"))).Get() + return html.UlEl().SetClass("save_table_list", "list-group").SetContent( + headli).Get() + content + }) + + formList.SetHideSideBar() + + return generateTool +} + +// ------------------------- +// helper functions +// ------------------------- + +func encodePassword(pwd []byte) string { + hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.DefaultCost) + if err != nil { + return "" + } + return string(hash) +} + +func label(ctx *context.Context) types.LabelAttribute { + return template.Get(ctx, config.GetTheme()).Label().SetType("success") +} + +func lg(v string) string { + return language.Get(v) +} + +func defaultFilterFn(val string, def ...string) types.FieldFilterFn { + return func(value types.FieldModel) interface{} { + if len(def) > 0 { + if value.Value == def[0] { + return val + } + } else { + if value.Value == "" { + return val + } + } + return value.Value + } +} + +func lgWithScore(v string, score ...string) string { + return language.GetWithScope(v, score...) +} + +func lgWithConfigScore(v string, score ...string) string { + scores := append([]string{"config"}, score...) + return language.GetWithScope(v, scores...) +} + +func link(url, content string) tmpl.HTML { + return html.AEl(). + SetAttr("href", url). + SetContent(template.HTML(lg(content))). + Get() +} + +func escape(s string) string { + if s == "" { + return "" + } + s, err := url.QueryUnescape(s) + if err != nil { + logger.Error("escape error", err) + } + return s +} + +func checkJSON(values form2.Values, key string) error { + v := values.Get(key) + if v != "" && !utils.IsJSON(v) { + return errors.New("wrong " + key) + } + return nil +} + +func (s *SystemTable) table(table string) *db.SQL { + return s.connection().Table(table) +} + +func (s *SystemTable) connection() *db.SQL { + return db.WithDriver(s.conn) +} + +func interfaces(arr []string) []interface{} { + var iarr = make([]interface{}, len(arr)) + + for key, v := range arr { + iarr[key] = v + } + + return iarr +} + +func addSwitchForTool(formList *types.FormPanel, head, field, def string, row ...int) { + formList.AddField(lgWithScore(head, "tool"), field, db.Varchar, form.Switch). + FieldOptions(types.FieldOptions{ + {Text: lgWithScore("show", "tool"), Value: "n"}, + {Text: lgWithScore("hide", "tool"), Value: "y"}, + }).FieldDefault(def) + if len(row) > 0 { + formList.FieldRowWidth(row[0]) + } + if len(row) > 1 { + formList.FieldHeadWidth(row[1]) + } + if len(row) > 2 { + formList.FieldInputWidth(row[2]) + } +} + +func formTypeOptions() types.FieldOptions { + opts := make(types.FieldOptions, len(form.AllType)) + for i := 0; i < len(form.AllType); i++ { + v := form.AllType[i].Name() + opts[i] = types.FieldOption{Text: v, Value: v} + } + return opts +} + +func databaseTypeOptions() types.FieldOptions { + opts := make(types.FieldOptions, len(db.IntTypeList)+ + len(db.StringTypeList)+ + len(db.FloatTypeList)+ + len(db.UintTypeList)+ + len(db.BoolTypeList)) + z := 0 + for _, t := range db.IntTypeList { + text := string(t) + v := cases.Title(textLang.Und).String(strings.ToLower(text)) + opts[z] = types.FieldOption{Text: text, Value: v} + z++ + } + for _, t := range db.StringTypeList { + text := string(t) + v := cases.Title(textLang.Und).String(strings.ToLower(text)) + opts[z] = types.FieldOption{Text: text, Value: v} + z++ + } + for _, t := range db.FloatTypeList { + text := string(t) + v := cases.Title(textLang.Und).String(strings.ToLower(text)) + opts[z] = types.FieldOption{Text: text, Value: v} + z++ + } + for _, t := range db.UintTypeList { + text := string(t) + v := cases.Title(textLang.Und).String(strings.ToLower(text)) + opts[z] = types.FieldOption{Text: text, Value: v} + z++ + } + for _, t := range db.BoolTypeList { + text := string(t) + v := cases.Title(textLang.Und).String(strings.ToLower(text)) + opts[z] = types.FieldOption{Text: text, Value: v} + z++ + } + return opts +} + +func getType(typeName string) string { + r, _ := regexp.Compile(`\(.*?\)`) + typeName = r.ReplaceAllString(typeName, "") + r2, _ := regexp.Compile(`unsigned(.*)`) + return strings.TrimSpace(cases.Title(textLang.Und).String(strings.ToLower(r2.ReplaceAllString(typeName, "")))) +} diff --git a/plugins/admin/modules/table/table.go b/plugins/admin/modules/table/table.go new file mode 100644 index 0000000000000000000000000000000000000000..d13ac681d99466bbadf0e03672fc7b8cdae0cfc9 --- /dev/null +++ b/plugins/admin/modules/table/table.go @@ -0,0 +1,193 @@ +package table + +import ( + "html/template" + "sync" + "sync/atomic" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/paginator" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter" + "github.com/GoAdminGroup/go-admin/template/types" +) + +type Generator func(ctx *context.Context) Table + +type GeneratorList map[string]Generator + +func (g GeneratorList) Add(key string, gen Generator) { + g[key] = gen +} + +func (g GeneratorList) Combine(list GeneratorList) GeneratorList { + for key, gen := range list { + if _, ok := g[key]; !ok { + g[key] = gen + } + } + return g +} + +func (g GeneratorList) CombineAll(gens []GeneratorList) GeneratorList { + for _, list := range gens { + for key, gen := range list { + if _, ok := g[key]; !ok { + g[key] = gen + } + } + } + return g +} + +type Table interface { + GetInfo() *types.InfoPanel + GetDetail() *types.InfoPanel + GetDetailFromInfo() *types.InfoPanel + GetForm() *types.FormPanel + GetNewForm() *types.FormPanel + GetActualNewForm() *types.FormPanel + + GetCanAdd() bool + GetEditable() bool + GetDeletable() bool + GetExportable() bool + + GetPrimaryKey() PrimaryKey + + GetData(ctx *context.Context, params parameter.Parameters) (PanelInfo, error) + GetDataWithIds(ctx *context.Context, params parameter.Parameters) (PanelInfo, error) + GetDataWithId(params parameter.Parameters) (FormInfo, error) + UpdateData(ctx *context.Context, dataList form.Values) error + InsertData(ctx *context.Context, ddataList form.Values) error + DeleteData(pk string) error + + GetNewFormInfo() FormInfo + + GetOnlyInfo() bool + GetOnlyDetail() bool + GetOnlyNewForm() bool + GetOnlyUpdateForm() bool + + Copy() Table +} + +type BaseTable struct { + Info *types.InfoPanel + Form *types.FormPanel + NewForm *types.FormPanel + Detail *types.InfoPanel + CanAdd bool + Editable bool + Deletable bool + Exportable bool + OnlyInfo bool + OnlyDetail bool + OnlyNewForm bool + OnlyUpdateForm bool + PrimaryKey PrimaryKey +} + +func (base *BaseTable) GetInfo() *types.InfoPanel { + return base.Info.SetPrimaryKey(base.PrimaryKey.Name, base.PrimaryKey.Type) +} + +func (base *BaseTable) GetDetail() *types.InfoPanel { + return base.Detail.SetPrimaryKey(base.PrimaryKey.Name, base.PrimaryKey.Type) +} + +func (base *BaseTable) GetDetailFromInfo() *types.InfoPanel { + detail := base.GetDetail() + detail.FieldList = make(types.FieldList, len(base.Info.FieldList)) + copy(detail.FieldList, base.Info.FieldList) + return detail +} + +func (base *BaseTable) GetForm() *types.FormPanel { + return base.Form.SetPrimaryKey(base.PrimaryKey.Name, base.PrimaryKey.Type) +} + +func (base *BaseTable) GetNewForm() *types.FormPanel { + return base.NewForm.SetPrimaryKey(base.PrimaryKey.Name, base.PrimaryKey.Type) +} + +func (base *BaseTable) GetActualNewForm() *types.FormPanel { + if len(base.NewForm.FieldList) == 0 { + return base.Form + } + return base.NewForm +} + +func (base *BaseTable) GetCanAdd() bool { + return base.CanAdd +} + +func (base *BaseTable) GetPrimaryKey() PrimaryKey { return base.PrimaryKey } +func (base *BaseTable) GetEditable() bool { return base.Editable } +func (base *BaseTable) GetDeletable() bool { return base.Deletable } +func (base *BaseTable) GetExportable() bool { return base.Exportable } +func (base *BaseTable) GetOnlyInfo() bool { return base.OnlyInfo } +func (base *BaseTable) GetOnlyDetail() bool { return base.OnlyDetail } +func (base *BaseTable) GetOnlyNewForm() bool { return base.OnlyNewForm } +func (base *BaseTable) GetOnlyUpdateForm() bool { return base.OnlyUpdateForm } + +func (base *BaseTable) GetPaginator(ctx *context.Context, size int, params parameter.Parameters, extraHtml ...template.HTML) types.PaginatorAttribute { + + var eh template.HTML + + if len(extraHtml) > 0 { + eh = extraHtml[0] + } + + return paginator.Get(ctx, paginator.Config{ + Size: size, + Param: params, + PageSizeList: base.Info.GetPageSizeList(), + }).SetExtraInfo(eh) +} + +type PanelInfo struct { + Thead types.Thead `json:"thead"` + InfoList types.InfoList `json:"info_list"` + FilterFormData types.FormFields `json:"filter_form_data"` + Paginator types.PaginatorAttribute `json:"-"` + Title string `json:"title"` + Description string `json:"description"` +} + +type FormInfo struct { + FieldList types.FormFields `json:"field_list"` + GroupFieldList types.GroupFormFields `json:"group_field_list"` + GroupFieldHeaders types.GroupFieldHeaders `json:"group_field_headers"` + Title string `json:"title"` + Description string `json:"description"` +} + +type PrimaryKey struct { + Type db.DatabaseType + Name string +} + +const ( + DefaultPrimaryKeyName = "id" + DefaultConnectionName = "default" +) + +var ( + services service.List + count uint32 + lock sync.Mutex +) + +func SetServices(srv service.List) { + lock.Lock() + defer lock.Unlock() + + if atomic.LoadUint32(&count) != 0 { + panic("can not initialize twice") + } + + services = srv +} diff --git a/plugins/admin/modules/table/tmpl.go b/plugins/admin/modules/table/tmpl.go new file mode 100644 index 0000000000000000000000000000000000000000..4b9ee86ebfb29abd07e2d99d260501437142b23a --- /dev/null +++ b/plugins/admin/modules/table/tmpl.go @@ -0,0 +1,426 @@ +package table + +var tmpls = map[string]string{"choose_table_ajax": `{{define "choose_table_ajax"}} + NProgress.start(); + let info_table = $("tbody.fields-table"); + info_table.find("tr").remove(); + let tpl = $("template.fields-tpl").html(); + for (let i = 0; i < data.data[0].length; i++) { + info_table.append(tpl); + } + let trs = info_table.find("tr"); + for (let i = 0; i < data.data[0].length; i++) { + $(trs[i]).find('.field_head').val(data.data[0][i]); + $(trs[i]).find('.field_name').val(data.data[1][i]); + $(trs[i]).find('select.field_db_type').val(data.data[2][i]).select2(); + } + let form_table = $("tbody.fields_form-table"); + form_table.find("tr").remove(); + let tpl_form = $("template.fields_form-tpl").html(); + for (let i = 0; i < data.data[0].length; i++) { + form_table.append(tpl_form); + } + let trs_form = form_table.find("tr"); + let pk = $(".pk").val(); + for (let i = 0; i < data.data[0].length; i++) { + $(trs_form[i]).find('.field_head_form').val(data.data[0][i]); + $(trs_form[i]).find('.field_name_form').val(data.data[1][i]); + $(trs_form[i]).find('input.field_canedit').iCheck("check"); + if (!(data.data[1][i] === pk || (pk === "" && data.data[1][i] === "id"))) { + $(trs_form[i]).find('input.field_canadd').iCheck("check"); + } + if (data.data[1][i] === "created_at" || data.data[1][i] === "updated_at") { + $(trs_form[i]).find('select.field_display').val("1").select2(); + } + $(trs_form[i]).find('select.field_db_type_form').val(data.data[2][i]).select2(); + $(trs_form[i]).find('select.field_form_type_form').val(data.data[3][i]).select2(); + } + $(".hide_filter_area.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_new_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_export_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_edit_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_pagination.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_delete_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_detail_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_filter_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_row_selector.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_query_info.ga_checkbox").bootstrapSwitch('state', true); + $(".filter_form_layout.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_continue_edit_check_box.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_reset_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_continue_new_check_box.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_back_button.ga_checkbox").bootstrapSwitch('state', true); + + let detail_table = $("tbody.fields_detail-table"); + detail_table.find("tr").remove(); + + NProgress.done(); +{{end}}`, "generator": `{{define "generator"}} + + +{{end}}`} diff --git a/plugins/admin/modules/table/tmpl/choose_table_ajax.tmpl b/plugins/admin/modules/table/tmpl/choose_table_ajax.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..98b21904ccfb7ef49bdf1712edae846df14f672b --- /dev/null +++ b/plugins/admin/modules/table/tmpl/choose_table_ajax.tmpl @@ -0,0 +1,56 @@ +{{define "choose_table_ajax"}} + NProgress.start(); + let info_table = $("tbody.fields-table"); + info_table.find("tr").remove(); + let tpl = $("template.fields-tpl").html(); + for (let i = 0; i < data.data[0].length; i++) { + info_table.append(tpl); + } + let trs = info_table.find("tr"); + for (let i = 0; i < data.data[0].length; i++) { + $(trs[i]).find('.field_head').val(data.data[0][i]); + $(trs[i]).find('.field_name').val(data.data[1][i]); + $(trs[i]).find('select.field_db_type').val(data.data[2][i]).select2(); + } + let form_table = $("tbody.fields_form-table"); + form_table.find("tr").remove(); + let tpl_form = $("template.fields_form-tpl").html(); + for (let i = 0; i < data.data[0].length; i++) { + form_table.append(tpl_form); + } + let trs_form = form_table.find("tr"); + let pk = $(".pk").val(); + for (let i = 0; i < data.data[0].length; i++) { + $(trs_form[i]).find('.field_head_form').val(data.data[0][i]); + $(trs_form[i]).find('.field_name_form').val(data.data[1][i]); + $(trs_form[i]).find('input.field_canedit').iCheck("check"); + if (!(data.data[1][i] === pk || (pk === "" && data.data[1][i] === "id"))) { + $(trs_form[i]).find('input.field_canadd').iCheck("check"); + } + if (data.data[1][i] === "created_at" || data.data[1][i] === "updated_at") { + $(trs_form[i]).find('select.field_display').val("1").select2(); + } + $(trs_form[i]).find('select.field_db_type_form').val(data.data[2][i]).select2(); + $(trs_form[i]).find('select.field_form_type_form').val(data.data[3][i]).select2(); + } + $(".hide_filter_area.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_new_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_export_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_edit_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_pagination.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_delete_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_detail_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_filter_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_row_selector.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_query_info.ga_checkbox").bootstrapSwitch('state', true); + $(".filter_form_layout.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_continue_edit_check_box.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_reset_button.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_continue_new_check_box.ga_checkbox").bootstrapSwitch('state', true); + $(".hide_back_button.ga_checkbox").bootstrapSwitch('state', true); + + let detail_table = $("tbody.fields_detail-table"); + detail_table.find("tr").remove(); + + NProgress.done(); +{{end}} \ No newline at end of file diff --git a/plugins/admin/modules/table/tmpl/generator.tmpl b/plugins/admin/modules/table/tmpl/generator.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..1ea086635501c0c592e1795f38ebb0bb772c695c --- /dev/null +++ b/plugins/admin/modules/table/tmpl/generator.tmpl @@ -0,0 +1,369 @@ +{{define "generator"}} + + +{{end}} \ No newline at end of file diff --git a/plugins/admin/modules/tools/generator.go b/plugins/admin/modules/tools/generator.go new file mode 100644 index 0000000000000000000000000000000000000000..d1046ef7f2607b03cfcb4dacea8705e956e86d10 --- /dev/null +++ b/plugins/admin/modules/tools/generator.go @@ -0,0 +1,489 @@ +package tools + +import ( + "bytes" + "fmt" + "go/format" + "os" + "path/filepath" + "regexp" + "strings" + "text/template" + + "github.com/GoAdminGroup/go-admin/modules/db/dialect" + "github.com/GoAdminGroup/go-admin/modules/language" + "golang.org/x/text/cases" + textLang "golang.org/x/text/language" + + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +type Param struct { + Connection string `json:"connection"` + Driver string `json:"driver"` + Package string `json:"package"` + + Table string `json:"table"` + RowTable string `json:"row_table"` + TableTitle string `json:"table_title"` + TableName string `json:"table_name"` + + HideFilterArea bool `json:"hide_filter_area"` + HideNewButton bool `json:"hide_new_button"` + HideExportButton bool `json:"hide_export_button"` + HideEditButton bool `json:"hide_edit_button"` + HideDeleteButton bool `json:"hide_delete_button"` + HideDetailButton bool `json:"hide_detail_button"` + HideFilterButton bool `json:"hide_filter_button"` + HideRowSelector bool `json:"hide_row_selector"` + HidePagination bool `json:"hide_pagination"` + HideQueryInfo bool `json:"hide_query_info"` + FilterFormLayout form.Layout `json:"filter_form_layout"` + + HideContinueEditCheckBox bool `json:"hide_continue_edit_check_box"` + HideContinueNewCheckBox bool `json:"hide_continue_new_check_box"` + HideResetButton bool `json:"hide_reset_button"` + HideBackButton bool `json:"hide_back_button"` + + TablePageTitle string `json:"table_page_title"` + TableDescription string `json:"table_description"` + FormTitle string `json:"form_title"` + FormDescription string `json:"form_description"` + DetailTitle string `json:"detail_title"` + DetailDescription string `json:"detail_description"` + + ExtraImport string `json:"extra_import"` + ExtraCode string `json:"extra_code"` + + Fields Fields `json:"fields"` + FormFields Fields `json:"form_fields"` + DetailFields Fields `json:"detail_fields"` + + PrimaryKey string `json:"primary_key"` + PrimaryKeyType string `json:"primary_key_type"` + + DetailDisplay uint8 `json:"detail_display"` + + Output string `json:"output"` +} + +type Config struct { + Connection string `json:"connection"` + Driver string `json:"driver"` + Package string `json:"package"` + Table string `json:"table"` + Schema string `json:"schema"` + Output string `json:"output"` + Conn db.Connection `json:"conn"` + HideFilterArea bool `json:"hide_filter_area"` + HideNewButton bool `json:"hide_new_button"` + HideExportButton bool `json:"hide_export_button"` + HideEditButton bool `json:"hide_edit_button"` + HideDeleteButton bool `json:"hide_delete_button"` + HideDetailButton bool `json:"hide_detail_button"` + HideFilterButton bool `json:"hide_filter_button"` + HideRowSelector bool `json:"hide_row_selector"` + HidePagination bool `json:"hide_pagination"` + HideQueryInfo bool `json:"hide_query_info"` + FilterFormLayout form.Layout `json:"filter_form_layout"` + ExtraImport string `json:"extra_import"` + + TableTitle string `json:"table_title"` + TableDescription string `json:"table_description"` + FormTitle string `json:"form_title"` + FormDescription string `json:"form_description"` + DetailTitle string `json:"detail_title"` + DetailDescription string `json:"detail_description"` + + HideContinueEditCheckBox bool `json:"hide_continue_edit_check_box"` + HideContinueNewCheckBox bool `json:"hide_continue_new_check_box"` + HideResetButton bool `json:"hide_reset_button"` + HideBackButton bool `json:"hide_back_button"` + + DetailDisplay uint8 `json:"detail_display"` + + ExtraCode string `json:"extra_code"` +} + +func fixedTable(table string) string { + if utils.InArray(keyWords, table) { + return table + "_" + } + return table +} + +var keyWords = []string{"import", "package", "chan", "const", "func", "interface", "map", "struct", "type", + "var", "break", "case", "continue", "default", "defer", "else", "fallthrough", "for", "go", "goto", "if", + "range", "return", "select", "switch"} + +func NewParam(cfg Config) *Param { + ta := camelcase(cfg.Table) + dbTable := cfg.Table + if cfg.Schema != "" { + dbTable = cfg.Schema + "." + cfg.Table + } + + fields := getFieldsFromConn(cfg.Conn, dbTable, cfg.Driver, cfg.Connection) + tt := cases.Title(textLang.Und).String(ta) + + pkey, ptype := fields.GetPrimaryKey() + + return &Param{ + Connection: cfg.Connection, + Driver: cfg.Driver, + Package: cfg.Package, + Table: fixedTable(ta), + TableTitle: tt, + TableName: dbTable, + HideFilterArea: cfg.HideFilterArea, + HideNewButton: cfg.HideNewButton, + HideExportButton: cfg.HideExportButton, + HideEditButton: cfg.HideEditButton, + HideDeleteButton: cfg.HideDeleteButton, + HideDetailButton: cfg.HideDetailButton, + HideFilterButton: cfg.HideFilterButton, + HideRowSelector: cfg.HideRowSelector, + HidePagination: cfg.HidePagination, + HideQueryInfo: cfg.HideQueryInfo, + FilterFormLayout: cfg.FilterFormLayout, + HideContinueEditCheckBox: cfg.HideContinueEditCheckBox, + HideContinueNewCheckBox: cfg.HideContinueNewCheckBox, + HideResetButton: cfg.HideResetButton, + HideBackButton: cfg.HideBackButton, + RowTable: cfg.Table, + Fields: fields, + FormFields: fields, + DetailDisplay: cfg.DetailDisplay, + Output: cfg.Output, + ExtraImport: cfg.ExtraImport, + ExtraCode: cfg.ExtraCode, + TablePageTitle: utils.SetDefault(cfg.TableTitle, "", tt), + TableDescription: utils.SetDefault(cfg.TableDescription, "", tt), + FormTitle: utils.SetDefault(cfg.FormTitle, "", tt), + FormDescription: utils.SetDefault(cfg.FormDescription, "", tt), + + PrimaryKey: pkey, + PrimaryKeyType: ptype, + } +} + +func NewParamWithFields(cfg Config, fields ...Fields) *Param { + ta := camelcase(cfg.Table) + dbTable := cfg.Table + if cfg.Schema != "" { + dbTable = cfg.Schema + "." + cfg.Table + } + + if len(cfg.Output) > 0 && cfg.Output[len(cfg.Output)-1] == '/' { + cfg.Output = cfg.Output[:len(cfg.Output)-1] + } + + tt := cases.Title(textLang.Und).String(ta) + + detailFields := make(Fields, 0) + if len(fields) > 2 { + detailFields = fields[2] + } + + return &Param{ + Connection: cfg.Connection, + Driver: cfg.Driver, + Package: cfg.Package, + Table: ta, + TableTitle: tt, + TableName: dbTable, + RowTable: cfg.Table, + Fields: fields[0], + FormFields: fields[1], + DetailFields: detailFields, + HideFilterArea: cfg.HideFilterArea, + HideNewButton: cfg.HideNewButton, + HideExportButton: cfg.HideExportButton, + HideEditButton: cfg.HideEditButton, + HideDeleteButton: cfg.HideDeleteButton, + HideDetailButton: cfg.HideDetailButton, + HideFilterButton: cfg.HideFilterButton, + HideRowSelector: cfg.HideRowSelector, + HidePagination: cfg.HidePagination, + HideQueryInfo: cfg.HideQueryInfo, + FilterFormLayout: cfg.FilterFormLayout, + HideContinueEditCheckBox: cfg.HideContinueEditCheckBox, + HideContinueNewCheckBox: cfg.HideContinueNewCheckBox, + HideResetButton: cfg.HideResetButton, + HideBackButton: cfg.HideBackButton, + DetailDisplay: cfg.DetailDisplay, + Output: cfg.Output, + ExtraImport: cfg.ExtraImport, + ExtraCode: cfg.ExtraCode, + TablePageTitle: utils.SetDefault(cfg.TableTitle, "", tt), + TableDescription: utils.SetDefault(cfg.TableDescription, "", tt), + FormTitle: utils.SetDefault(cfg.FormTitle, "", tt), + FormDescription: utils.SetDefault(cfg.FormDescription, "", tt), + DetailTitle: utils.SetDefault(cfg.DetailTitle, "", tt), + DetailDescription: utils.SetDefault(cfg.DetailDescription, "", tt), + } +} + +type Fields []Field + +func (fs Fields) GetPrimaryKey() (string, string) { + for _, field := range fs { + if field.IsPrimaryKey { + return field.Name, field.DBType + } + } + return "", "" +} + +type Field struct { + Head string `json:"head"` + Name string `json:"name"` + DBType string `json:"db_type"` + FormType string `json:"form_type"` + Filterable bool `json:"filterable"` + Sortable bool `json:"sortable"` + InfoEditable bool `json:"info_editable"` + Editable bool `json:"editable"` + Hide bool `json:"hide"` + FormHide bool `json:"form_hide"` + EditHide bool `json:"edit_hide"` + CreateHide bool `json:"create_hide"` + Default string `json:"default"` + CanAdd bool `json:"can_add"` + ExtraFun string `json:"extra_fun"` + IsPrimaryKey bool `json:"is_primary_key"` +} + +func Generate(param *Param) error { + t, err := template.New("table_model").Parse(tableModelTmpl) + if err != nil { + return err + } + buf := new(bytes.Buffer) + err = t.Execute(buf, param) + if err != nil { + return err + } + c, err := format.Source(buf.Bytes()) + if err != nil { + return err + } + return os.WriteFile(filepath.FromSlash(param.Output)+"/"+param.RowTable+".go", c, 0644) +} + +const ( + commentStrEnd = "example end" + tablesEnd = "generators end" +) + +func GenerateTables(outputPath, packageName string, tables []string, isNew bool) error { + + if len(outputPath) > 0 && outputPath[len(outputPath)-1] == '/' { + outputPath = outputPath[:len(outputPath)-1] + } + + outputPath = filepath.FromSlash(outputPath) + fileExist := utils.FileExist(outputPath + "/tables.go") + + if !isNew && !fileExist { + return nil + } + + var ( + tableStr = "" + commentStr = "" + tablesContentByte []byte + tablesContent string + err error + ) + if fileExist { + tablesContentByte, err = os.ReadFile(outputPath + "/tables.go") + if err != nil { + return err + } + tablesContent = string(tablesContentByte) + } + + for i := 0; i < len(tables); i++ { + lowerTable := strings.ToLower(tables[i]) + if !strings.Contains(tablesContent, `"`+lowerTable+`"`) { + tableStr += fmt.Sprintf(` + "%s": Get%sTable, `, lowerTable, cases.Title(textLang.Und).String(camelcase(tables[i]))) + + if commentStr != "" { + commentStr += ` +` + } + commentStr += fmt.Sprintf(`// "%s" => http://localhost:9033/admin/info/%s`, lowerTable, lowerTable) + } + } + + commentStr += ` +// +// ` + commentStrEnd + tableStr += ` + + // ` + tablesEnd + + content := "" + + if tablesContent != "" && strings.Contains(tablesContent, "/") { + replacer := strings.NewReplacer(`// `+commentStrEnd, commentStr, `// `+tablesEnd, tableStr) + tablesContent = replacer.Replace(tablesContent) + keep := `// example: +//` + keep2 := `, + + // ` + tablesEnd + replacer2 := strings.NewReplacer(keep, keep, keep2, keep2, + `// +// +`, "//", `// + +//`, "//", `// +// "`, `// "`, + + `, + +`, ",") + content = replacer2.Replace(tablesContent) + } else { + content = fmt.Sprintf(`// This file is generated by GoAdmin CLI adm. +package %s + +import "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + +// The key of Generators is the prefix of table info url. +// The corresponding value is the Form and Table data. +// +// http://{{config.Domain}}:{{Port}}/{{config.Prefix}}/info/{{key}} +// +// example: +// +%s +var Generators = map[string]table.Generator{ +%s +} +`, packageName, commentStr, tableStr) + } + + c, err := format.Source([]byte(content)) + + if err != nil { + return err + } + return os.WriteFile(outputPath+"/tables.go", c, 0644) +} + +func InsertPermissionOfTable(conn db.Connection, table string) { + table = strings.ToLower(table) + InsertPermissionInfoDB(conn, table+" "+language.GetWithScope("query", "generator"), table+"_query", "GET", "/info/"+table) + InsertPermissionInfoDB(conn, table+" "+language.GetWithScope("show edit form page", "generator"), table+"_show_edit", "GET", + "/info/"+table+"/edit") + InsertPermissionInfoDB(conn, table+" "+language.GetWithScope("show create form page", "generator"), table+"_show_create", "GET", + "/info/"+table+"/new") + InsertPermissionInfoDB(conn, table+" "+language.GetWithScope("edit", "generator"), table+"_edit", "POST", + "/edit/"+table) + InsertPermissionInfoDB(conn, table+" "+language.GetWithScope("create", "generator"), table+"_create", "POST", + "/new/"+table) + InsertPermissionInfoDB(conn, table+" "+language.GetWithScope("delete", "generator"), table+"_delete", "POST", + "/delete/"+table) + InsertPermissionInfoDB(conn, table+" "+language.GetWithScope("export", "generator"), table+"_export", "POST", + "/export/"+table) +} + +func InsertPermissionInfoDB(conn db.Connection, name, slug, httpMethod, httpPath string) { + checkExist, err := db.WithDriver(conn).Table("goadmin_permissions"). + Where("slug", "=", slug). + First() + + if db.CheckError(err, db.QUERY) { + panic(err) + } + + if checkExist != nil { + return + } + + _, err = db.WithDriver(conn).Table("goadmin_permissions"). + Insert(dialect.H{ + "name": name, + "slug": slug, + "http_method": httpMethod, + "http_path": httpPath, + }) + + if db.CheckError(err, db.INSERT) { + panic(err) + } +} + +func camelcase(s string) string { + arr := strings.Split(s, "_") + var res = "" + for i := 0; i < len(arr); i++ { + if i == 0 { + res += arr[i] + } else { + res += cases.Title(textLang.Und).String(arr[i]) + } + } + return res +} + +func getType(typeName string) string { + r, _ := regexp.Compile(`\(.*?\)`) + typeName = r.ReplaceAllString(typeName, "") + r2, _ := regexp.Compile(`unsigned(.*)`) + return strings.TrimSpace(cases.Title(textLang.Und).String(strings.ToLower(r2.ReplaceAllString(typeName, "")))) +} + +func getFieldsFromConn(conn db.Connection, table, driver, connName string) Fields { + columnsModel, _ := db.WithDriver(conn).Table(table).ShowColumnsWithComment(conn.GetConfig(connName).Name) + + fields := make(Fields, len(columnsModel)) + + fieldField := "Field" + typeField := "Type" + if driver == "postgresql" { + fieldField = "column_name" + typeField = "udt_name" + } + if driver == "sqlite" { + fieldField = "name" + typeField = "type" + } + if driver == "mssql" { + fieldField = "column_name" + typeField = "data_type" + } + + for i, model := range columnsModel { + typeName := getType(model[typeField].(string)) + isPrimaryKey := false + if columnKey, ok := model["Key"].(string); ok { + isPrimaryKey = columnKey == "PRI" + } + + head := cases.Title(textLang.Und).String(model[fieldField].(string)) + if comment, ok := model["Comment"].(string); ok && comment != "" { + head = cases.Title(textLang.Und).String(comment) + } + fields[i] = Field{ + Head: head, + Name: model[fieldField].(string), + DBType: typeName, + CanAdd: true, + Editable: true, + IsPrimaryKey: isPrimaryKey, + FormType: form.GetFormTypeFromFieldType(db.DT(strings.ToUpper(typeName)), model[fieldField].(string)), + } + if model[fieldField].(string) == "id" { + fields[i].Filterable = true + } + } + + return fields +} diff --git a/plugins/admin/modules/tools/template.go b/plugins/admin/modules/tools/template.go new file mode 100644 index 0000000000000000000000000000000000000000..542a33e61964a79267722f2a67bec5ca8c42ad83 --- /dev/null +++ b/plugins/admin/modules/tools/template.go @@ -0,0 +1,91 @@ +package tools + +const tableModelTmpl = `{{define "table_model"}} +package {{.Package}} + +import ( + {{.ExtraImport}} + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +func Get{{.TableTitle}}Table(ctx *context.Context) table.Table { + + {{if eq .Connection "default"}} + {{if eq .PrimaryKey ""}} + {{.Table}} := table.NewDefaultTable(ctx, table.DefaultConfigWithDriver("{{.Driver}}")) + {{else}} + {{.Table}} := table.NewDefaultTable(ctx, table.DefaultConfigWithDriver("{{.Driver}}").SetPrimaryKey("{{.PrimaryKey}}", db.{{.PrimaryKeyType}})) + {{end}} + {{else}} + {{if eq .PrimaryKey ""}} + {{.Table}} := table.NewDefaultTable(ctx, table.DefaultConfigWithDriverAndConnection("{{.Driver}}", "{{.Connection}}")) + {{else}} + {{.Table}} := table.NewDefaultTable(ctx, table.DefaultConfigWithDriverAndConnection("{{.Driver}}", "{{.Connection}}").SetPrimaryKey("{{.PrimaryKey}}", db.{{.PrimaryKeyType}})) + {{end}} + {{end}} + + info := {{.Table}}.GetInfo(){{if .HideFilterArea}}.HideFilterArea(){{end}} + + {{if .HideNewButton}}info.HideNewButton(){{end}} + {{if .HideExportButton}}info.HideExportButton(){{end}} + {{if .HideEditButton}}info.HideEditButton(){{end}} + {{if .HideDeleteButton}}info.HideDeleteButton(){{end}} + {{if .HideDetailButton}}info.HideDetailButton(){{end}} + {{if .HideFilterButton}}info.HideFilterButton(){{end}} + {{if .HideRowSelector}}info.HideRowSelector(){{end}} + {{if .HidePagination}}info.HidePagination(){{end}} + {{if .HideQueryInfo}}info.HideQueryInfo(){{end}} + {{if not .FilterFormLayout.Default}}info.SetFilterFormLayout(form.{{.FilterFormLayout.String}}){{end}} + + {{- range $key, $field := .Fields}} + info.AddField("{{$field.Head}}", "{{$field.Name}}", db.{{$field.DBType}}){{if $field.Filterable}}. + FieldFilterable(){{end -}}{{if $field.Sortable}}. + FieldSortable(){{end -}}{{if $field.InfoEditable}}. + FieldEditAble(){{end -}}{{if $field.Hide}}. + FieldHide(){{end -}} + {{- end}} + + info.SetTable("{{.TableName}}").SetTitle("{{.TablePageTitle}}").SetDescription("{{.TableDescription}}") + + formList := {{.Table}}.GetForm() + + {{- range $key, $field := .FormFields}} + formList.AddField("{{$field.Head}}", "{{$field.Name}}", db.{{$field.DBType}}, form.{{$field.FormType}}){{if ne $field.Default ""}}. + FieldDefault({{$field.Default}}){{end -}}{{if not $field.CanAdd}}. + FieldDisableWhenCreate(){{end -}}{{if not $field.Editable}}. + FieldDisableWhenUpdate(){{end -}}{{if $field.FormHide}}. + FieldHide(){{end -}}{{if $field.EditHide}}. + FieldHideWhenUpdate(){{end -}}{{if $field.CreateHide}}. + FieldHideWhenCreate(){{end -}}{{$field.ExtraFun}} + {{- end}} + + {{if .HideContinueEditCheckBox}}formList.HideContinueEditCheckBox(){{end}} + {{if .HideContinueNewCheckBox}}formList.HideContinueNewCheckBox(){{end}} + {{if .HideResetButton}}formList.HideResetButton(){{end}} + {{if .HideBackButton}}formList.HideBackButton(){{end}} + + formList.SetTable("{{.TableName}}").SetTitle("{{.FormTitle}}").SetDescription("{{.FormDescription}}") + + + {{if eq .DetailDisplay 1}} + detail := {{.Table}}.GetDetailFromInfo() + {{else if eq .DetailDisplay 2}} + detail := {{.Table}}.GetDetail() + {{end}} + + {{- range $key, $field := .DetailFields}} + detail.AddField("{{$field.Head}}", "{{$field.Name}}", db.{{$field.DBType}}) + {{- end}} + + {{if ne .DetailDisplay 0}} + detail.SetTable("{{.TableName}}").SetTitle("{{.TablePageTitle}}").SetDescription("{{.TableDescription}}") + {{end}} + + {{.ExtraCode}} + + return {{.Table}} +} +{{end}}` diff --git a/plugins/admin/router.go b/plugins/admin/router.go new file mode 100644 index 0000000000000000000000000000000000000000..d2b4edf16554a62277d41f496b411f87894d4c62 --- /dev/null +++ b/plugins/admin/router.go @@ -0,0 +1,154 @@ +package admin + +import ( + "net/http" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/trace" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/response" + "github.com/GoAdminGroup/go-admin/template" +) + +// initRouter initialize the router and return the context. +func (admin *Admin) initRouter() *Admin { + app := context.NewApp() + + route := app.Group(config.Prefix(), admin.globalErrorHandler, admin.traceIDMiddleware, admin.themeMiddleware) + + // auth + route.GET(config.GetLoginUrl(), admin.handler.ShowLogin) + route.POST("/signin", admin.handler.Auth) + + // auto install + route.GET("/install", admin.handler.ShowInstall) + route.POST("/install/database/check", admin.handler.CheckDatabase) + + checkRepeatedPath := make([]string, 0) + for _, themeName := range template.Themes() { + for _, path := range template.Get(nil, themeName).GetAssetList() { + if !utils.InArray(checkRepeatedPath, path) { + checkRepeatedPath = append(checkRepeatedPath, path) + path = "/assets" + path + admin.handler.AssetsTheme(path, themeName) + route.GET(path, admin.handler.Assets) + } + } + } + + for _, path := range template.GetComponentAsset() { + route.GET("/assets"+path, admin.handler.Assets) + } + + authRoute := route.Group("/", auth.Middleware(admin.Conn)) + + // auth + authRoute.GET("/logout", admin.handler.Logout) + + authPrefixRoute := route.Group("/", auth.Middleware(admin.Conn), admin.guardian.CheckPrefix) + + // menus + authRoute.POST("/menu/delete", admin.guardian.MenuDelete, admin.handler.DeleteMenu).Name("menu_delete") + authRoute.POST("/menu/new", admin.guardian.MenuNew, admin.handler.NewMenu).Name("menu_new") + authRoute.POST("/menu/edit", admin.guardian.MenuEdit, admin.handler.EditMenu).Name("menu_edit") + authRoute.POST("/menu/order", admin.handler.MenuOrder).Name("menu_order") + authRoute.GET("/menu", admin.handler.ShowMenu).Name("menu") + authRoute.GET("/menu/edit/show", admin.handler.ShowEditMenu).Name("menu_edit_show") + authRoute.GET("/menu/new", admin.handler.ShowNewMenu).Name("menu_new_show") + + authRoute.GET("/plugins", admin.handler.Plugins).Name("plugins") + + if config.IsNotProductionEnvironment() { + authRoute.GET("/plugins/store", admin.handler.PluginStore).Name("plugins_store") + authRoute.POST("/plugin/download", admin.handler.PluginDownload).Name("plugin_download") + authRoute.POST("/plugin/detail", admin.handler.PluginDetail).Name("plugin_detail") + } + + authRoute.POST("/server/login", admin.guardian.ServerLogin, admin.handler.ServerLogin).Name("server_login") + + formats := config.GetURLFormats() + + // add delete modify query + authPrefixRoute.GET(formats.Detail, admin.handler.ShowDetail).Name("detail") + authPrefixRoute.GET(formats.ShowEdit, admin.guardian.ShowForm, admin.handler.ShowForm).Name("show_edit") + authPrefixRoute.GET(formats.ShowCreate, admin.guardian.ShowNewForm, admin.handler.ShowNewForm).Name("show_new") + authPrefixRoute.POST(formats.Edit, admin.guardian.EditForm, admin.handler.EditForm).Name("edit") + authPrefixRoute.POST(formats.Create, admin.guardian.NewForm, admin.handler.NewForm).Name("new") + authPrefixRoute.POST(formats.Delete, admin.guardian.Delete, admin.handler.Delete).Name("delete") + authPrefixRoute.POST(formats.Export, admin.guardian.Export, admin.handler.Export).Name("export") + authPrefixRoute.GET(formats.Info, admin.handler.ShowInfo).Name("info") + + authPrefixRoute.POST(formats.Update, admin.guardian.Update, admin.handler.Update).Name("update") + + authRoute.GET("/application/info", admin.handler.SystemInfo) + + route.ANY("/operation/:__goadmin_op_id", auth.Middleware(admin.Conn), admin.handler.Operation) + + if config.GetOpenAdminApi() { + + // crud json apis + apiRoute := route.Group("/api", auth.Middleware(admin.Conn), admin.guardian.CheckPrefix) + apiRoute.GET("/list/:__prefix", admin.handler.ApiList).Name("api_info") + apiRoute.GET("/detail/:__prefix", admin.handler.ApiDetail).Name("api_detail") + apiRoute.POST("/delete/:__prefix", admin.guardian.Delete, admin.handler.Delete).Name("api_delete") + apiRoute.POST("/edit/:__prefix", admin.guardian.EditForm, admin.handler.ApiUpdate).Name("api_edit") + apiRoute.GET("/edit/form/:__prefix", admin.guardian.ShowForm, admin.handler.ApiUpdateForm).Name("api_show_edit") + apiRoute.POST("/create/:__prefix", admin.guardian.NewForm, admin.handler.ApiCreate).Name("api_new") + apiRoute.GET("/create/form/:__prefix", admin.guardian.ShowNewForm, admin.handler.ApiCreateForm).Name("api_show_new") + apiRoute.POST("/export/:__prefix", admin.guardian.Export, admin.handler.Export).Name("api_export") + apiRoute.POST("/update/:__prefix", admin.guardian.Update, admin.handler.Update).Name("api_update") + } + + admin.App = app + return admin +} + +func (admin *Admin) globalErrorHandler(ctx *context.Context) { + defer admin.handler.GlobalDeferHandler(ctx) + response.OffLineHandler(ctx) + ctx.Next() +} + +func (admin *Admin) traceIDMiddleware(ctx *context.Context) { + traceID := ctx.Headers(traceIDHeaderKey) + + if traceID == "" { + traceID = trace.GenerateTraceID() + } + + ctx.SetUserValue(trace.TraceIDKey, traceID) + ctx.SetHeader(traceIDHeaderKey, traceID) + ctx.Next() +} + +const ( + traceIDHeaderKey = "x-request-id" +) + +func (admin *Admin) themeMiddleware(ctx *context.Context) { + theme := ctx.Query(context.ThemeKey) + + if theme == admin.config.Theme { + ctx.Next() + return + } + + if theme == "" { + theme = ctx.RefererQuery(context.ThemeKey) + if theme == "" { + ctx.Next() + return + } + } + + cookieTheme := ctx.Cookie(context.ThemeKey) + if cookieTheme != theme { + ctx.SetCookie(&http.Cookie{ + Name: context.ThemeKey, + Value: theme, + }) + } + ctx.Next() +} diff --git a/plugins/example/controller.go b/plugins/example/controller.go new file mode 100644 index 0000000000000000000000000000000000000000..03aa13ba0093895f687dbc2fa1903e355f9b75f8 --- /dev/null +++ b/plugins/example/controller.go @@ -0,0 +1,384 @@ +package example + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/page" + template2 "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/chartjs" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/themes/adminlte/components/chart_legend" + "github.com/GoAdminGroup/themes/adminlte/components/description" + "github.com/GoAdminGroup/themes/adminlte/components/infobox" + "github.com/GoAdminGroup/themes/adminlte/components/productlist" + "github.com/GoAdminGroup/themes/adminlte/components/progress_group" + "github.com/GoAdminGroup/themes/adminlte/components/smallbox" +) + +func (e *Example) TestHandler(rawCtx *context.Context) { + page.SetPageContent(rawCtx, auth.Auth(rawCtx), func(ctx interface{}) (types.Panel, error) { + + components := template2.Default(rawCtx) + colComp := components.Col() + + /************************** + * Info Box + /**************************/ + + infobox1 := infobox.New(). + SetText("CPU TRAFFIC"). + SetColor("#3583af"). + SetNumber("100"). + SetIcon(``). + GetContent() + + infobox2 := infobox.New(). + SetText("Likes"). + SetColor("#6a7c86"). + SetNumber("1030.00$"). + SetIcon(``). + GetContent() + + infobox3 := infobox.New(). + SetText("Sales"). + SetColor("#d8cd68"). + SetNumber("760"). + SetIcon(``). + GetContent() + + infobox4 := infobox.New(). + SetText("New Members"). + SetColor("#6cad6e"). + SetNumber("2,349"). + SetIcon(``). + GetContent() + + var size = types.Size(6, 3, 0).XS(12) + infoboxCol1 := colComp.SetSize(size).SetContent(infobox1).GetContent() + infoboxCol2 := colComp.SetSize(size).SetContent(infobox2).GetContent() + infoboxCol3 := colComp.SetSize(size).SetContent(infobox3).GetContent() + infoboxCol4 := colComp.SetSize(size).SetContent(infobox4).GetContent() + row1 := components.Row().SetContent(infoboxCol1 + infoboxCol2 + infoboxCol3 + infoboxCol4).GetContent() + + /************************** + * Box + /**************************/ + + table := components.Table().SetInfoList([]map[string]types.InfoItem{ + { + "Order ID": {Content: "OR9842"}, + "Item": {Content: "Call of Duty IV"}, + "Status": {Content: "shipped"}, + "Popularity": {Content: "90%"}, + }, { + "Order ID": {Content: "OR9842"}, + "Item": {Content: "Call of Duty IV"}, + "Status": {Content: "shipped"}, + "Popularity": {Content: "90%"}, + }, { + "Order ID": {Content: "OR9842"}, + "Item": {Content: "Call of Duty IV"}, + "Status": {Content: "shipped"}, + "Popularity": {Content: "90%"}, + }, + }).SetThead(types.Thead{ + {Head: "Order ID"}, + {Head: "Item"}, + {Head: "Status"}, + {Head: "Popularity"}, + }).GetContent() + + boxInfo := components.Box(). + WithHeadBorder(). + SetHeader("Latest Orders"). + SetHeadColor("#f7f7f7"). + SetBody(table). + SetFooter(``). + GetContent() + + tableCol := colComp.SetSize(types.SizeMD(8)).SetContent(row1 + boxInfo).GetContent() + + /************************** + * Product List + /**************************/ + + productList := productlist.New().SetData([]map[string]string{ + { + "img": "http://adminlte.io/themes/AdminLTE/dist/img/default-50x50.gif", + "title": "GoAdmin", + "has_tabel": "true", + "labeltype": "warning", + "label": "free", + "description": `a framework help you build the dataviz system`, + }, { + "img": "http://adminlte.io/themes/AdminLTE/dist/img/default-50x50.gif", + "title": "GoAdmin", + "has_tabel": "true", + "labeltype": "warning", + "label": "free", + "description": `a framework help you build the dataviz system`, + }, { + "img": "http://adminlte.io/themes/AdminLTE/dist/img/default-50x50.gif", + "title": "GoAdmin", + "has_tabel": "true", + "labeltype": "warning", + "label": "free", + "description": `a framework help you build the dataviz system`, + }, + }).GetContent() + + boxWarning := components.Box().SetTheme("warning").WithHeadBorder().SetHeader("Recently Added Products"). + SetBody(productList). + SetFooter(`View All Products`). + GetContent() + + newsCol := colComp.SetSize(types.SizeMD(4)).SetContent(boxWarning).GetContent() + + row5 := components.Row().SetContent(tableCol + newsCol).GetContent() + + /************************** + * Box + /**************************/ + + lineChart := chartjs.Line(). + SetID("salechart"). + SetHeight(180). + SetTitle("Sales: 1 Jan, 2019 - 30 Jul, 2019"). + SetLabels([]string{"January", "February", "March", "April", "May", "June", "July"}). + AddDataSet("Electronics"). + DSData([]float64{65, 59, 80, 81, 56, 55, 40}). + DSFill(false). + DSBorderColor("rgb(210, 214, 222)"). + DSLineTension(0.1). + AddDataSet("Digital Goods"). + DSData([]float64{28, 48, 40, 19, 86, 27, 90}). + DSFill(false). + DSBorderColor("rgba(60,141,188,1)"). + DSLineTension(0.1). + GetContent() + + title := `

    Goal Completion

    ` + progressGroup := progress_group.New(). + SetTitle("Add Products to Cart"). + SetColor("#76b2d4"). + SetDenominator(200). + SetMolecular(160). + SetPercent(80). + GetContent() + + progressGroup1 := progress_group.New(). + SetTitle("Complete Purchase"). + SetColor("#f17c6e"). + SetDenominator(400). + SetMolecular(310). + SetPercent(80). + GetContent() + + progressGroup2 := progress_group.New(). + SetTitle("Visit Premium Page"). + SetColor("#ace0ae"). + SetDenominator(800). + SetMolecular(490). + SetPercent(80). + GetContent() + + progressGroup3 := progress_group.New(). + SetTitle("Send Inquiries"). + SetColor("#fdd698"). + SetDenominator(500). + SetMolecular(250). + SetPercent(50). + GetContent() + + boxInternalCol1 := colComp.SetContent(lineChart).SetSize(types.SizeMD(8)).GetContent() + boxInternalCol2 := colComp. + SetContent(template.HTML(title) + progressGroup + progressGroup1 + progressGroup2 + progressGroup3). + SetSize(types.SizeMD(4)). + GetContent() + + boxInternalRow := components.Row().SetContent(boxInternalCol1 + boxInternalCol2).GetContent() + + description1 := description.New(). + SetPercent("17"). + SetNumber("¥140,100"). + SetTitle("TOTAL REVENUE"). + SetArrow("up"). + SetColor("green"). + SetBorder("right"). + GetContent() + + description2 := description.New(). + SetPercent("2"). + SetNumber("440,560"). + SetTitle("TOTAL REVENUE"). + SetArrow("down"). + SetColor("red"). + SetBorder("right"). + GetContent() + + description3 := description.New(). + SetPercent("12"). + SetNumber("¥140,050"). + SetTitle("TOTAL REVENUE"). + SetArrow("up"). + SetColor("green"). + SetBorder("right"). + GetContent() + + description4 := description.New(). + SetPercent("1"). + SetNumber("30943"). + SetTitle("TOTAL REVENUE"). + SetArrow("up"). + SetColor("green"). + GetContent() + + size2 := types.SizeXS(6).SM(3) + boxInternalCol3 := colComp.SetContent(description1).SetSize(size2).GetContent() + boxInternalCol4 := colComp.SetContent(description2).SetSize(size2).GetContent() + boxInternalCol5 := colComp.SetContent(description3).SetSize(size2).GetContent() + boxInternalCol6 := colComp.SetContent(description4).SetSize(size2).GetContent() + + boxInternalRow2 := components.Row().SetContent(boxInternalCol3 + boxInternalCol4 + boxInternalCol5 + boxInternalCol6).GetContent() + + box := components.Box().WithHeadBorder().SetHeader("Monthly Recap Report"). + SetBody(boxInternalRow). + SetFooter(boxInternalRow2). + GetContent() + + boxcol := colComp.SetContent(box).SetSize(types.SizeMD(12)).GetContent() + row2 := components.Row().SetContent(boxcol).GetContent() + + /************************** + * Small Box + /**************************/ + + smallbox1 := smallbox.New().SetColor("blue").SetIcon("ion-ios-gear-outline").SetUrl("/").SetTitle("new users").SetValue("345¥").GetContent() + smallbox2 := smallbox.New().SetColor("yellow").SetIcon("ion-ios-cart-outline").SetUrl("/").SetTitle("new users").SetValue("80%").GetContent() + smallbox3 := smallbox.New().SetColor("red").SetIcon("fa-user").SetUrl("/").SetTitle("new users").SetValue("645¥").GetContent() + smallbox4 := smallbox.New().SetColor("green").SetIcon("ion-ios-cart-outline").SetUrl("/").SetTitle("new users").SetValue("889¥").GetContent() + + col1 := colComp.SetSize(size).SetContent(smallbox1).GetContent() + col2 := colComp.SetSize(size).SetContent(smallbox2).GetContent() + col3 := colComp.SetSize(size).SetContent(smallbox3).GetContent() + col4 := colComp.SetSize(size).SetContent(smallbox4).GetContent() + + row3 := components.Row().SetContent(col1 + col2 + col3 + col4).GetContent() + + /************************** + * Pie Chart + /**************************/ + + pie := chartjs.Pie(). + SetHeight(170). + SetLabels([]string{"Navigator", "Opera", "Safari", "FireFox", "IE", "Chrome"}). + SetID("pieChart"). + AddDataSet("Chrome"). + DSData([]float64{100, 300, 600, 400, 500, 700}). + DSBackgroundColor([]chartjs.Color{ + "rgb(255, 205, 86)", "rgb(54, 162, 235)", "rgb(255, 99, 132)", "rgb(255, 205, 86)", "rgb(54, 162, 235)", "rgb(255, 99, 132)", + }). + GetContent() + + legend := chart_legend.New().SetData([]map[string]string{ + { + "label": " Chrome", + "color": "red", + }, { + "label": " IE", + "color": "Green", + }, { + "label": " FireFox", + "color": "yellow", + }, { + "label": " Sarafri", + "color": "blue", + }, { + "label": " Opera", + "color": "light-blue", + }, { + "label": " Navigator", + "color": "gray", + }, + }).GetContent() + + boxDanger := components.Box().SetTheme("danger").WithHeadBorder().SetHeader("Browser Usage"). + SetBody(components.Row(). + SetContent(colComp.SetSize(types.SizeMD(8)). + SetContent(pie). + GetContent() + colComp.SetSize(types.SizeMD(4)). + SetContent(legend). + GetContent()).GetContent()). + SetFooter(`

    View All Users

    `). + GetContent() + + tabs := components.Tabs().SetData([]map[string]template.HTML{ + { + "title": "tabs1", + "content": template.HTML(`How to use: + +

    Exactly like the original bootstrap tabs except you should use + the custom wrapper .nav-tabs-custom to achieve this style.

    + A wonderful serenity has taken possession of my entire soul, + like these sweet mornings of spring which I enjoy with my whole heart. + I am alone, and feel the charm of existence in this spot, + which was created for the bliss of souls like mine. I am so happy, + my dear friend, so absorbed in the exquisite sense of mere tranquil existence, + that I neglect my talents. I should be incapable of drawing a single stroke + at the present moment; and yet I feel that I never was a greater artist than now.`), + }, { + "title": "tabs2", + "content": template.HTML(` + The European languages are members of the same family. Their separate existence is a myth. + For science, music, sport, etc, Europe uses the same vocabulary. The languages only differ + in their grammar, their pronunciation and their most common words. Everyone realizes why a + new common language would be desirable: one could refuse to pay expensive translators. To + achieve this, it would be necessary to have uniform grammar, pronunciation and more common + words. If several languages coalesce, the grammar of the resulting language is more simple + and regular than that of the individual languages. + `), + }, { + "title": "tabs3", + "content": template.HTML(` + Lorem Ipsum is simply dummy text of the printing and typesetting industry. + Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, + when an unknown printer took a galley of type and scrambled it to make a type specimen book. + It has survived not only five centuries, but also the leap into electronic typesetting, + remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset + sheets containing Lorem Ipsum passages, and more recently with desktop publishing software + like Aldus PageMaker including versions of Lorem Ipsum. + `), + }, + }).GetContent() + + buttonTest := `` + popupForm := `
    +
    + + +
    +
    + + +
    +
    ` + popup := components.Popup().SetID("exampleModal"). + SetFooter("Save Change"). + SetTitle("this is a popup"). + SetBody(template.HTML(popupForm)). + GetContent() + + col5 := colComp.SetSize(types.SizeMD(8)).SetContent(tabs + template.HTML(buttonTest)).GetContent() + col6 := colComp.SetSize(types.SizeMD(4)).SetContent(boxDanger + popup).GetContent() + + row4 := components.Row().SetContent(col5 + col6).GetContent() + + return types.Panel{ + Content: row5 + row2 + row3 + row4, + Title: "Dashboard", + Description: "dashboard example", + }, nil + }, e.Conn) +} diff --git a/plugins/example/example.go b/plugins/example/example.go new file mode 100644 index 0000000000000000000000000000000000000000..6f5411a7c70b283bd159c0a92a6710658d698b3c --- /dev/null +++ b/plugins/example/example.go @@ -0,0 +1,22 @@ +package example + +import ( + c "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/plugins" +) + +type Example struct { + *plugins.Base +} + +func NewExample() *Example { + return &Example{ + Base: &plugins.Base{PlugName: "example"}, + } +} + +func (e *Example) InitPlugin(srv service.List) { + e.InitBase(srv, "example") + e.App = e.initRouter(c.Prefix(), srv) +} diff --git a/plugins/example/go_plugin/Makefile b/plugins/example/go_plugin/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9be418c4bbc8d8a8862f08bc7ea6952003b6e9ba --- /dev/null +++ b/plugins/example/go_plugin/Makefile @@ -0,0 +1,4 @@ +all: build + +build: + go build -buildmode=plugin -o plugin.so main.go \ No newline at end of file diff --git a/plugins/example/go_plugin/main.go b/plugins/example/go_plugin/main.go new file mode 100644 index 0000000000000000000000000000000000000000..6596d7f70d17c5b4add73ee8a37516a2819993ee --- /dev/null +++ b/plugins/example/go_plugin/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + c "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/plugins" +) + +type Example struct { + *plugins.Base +} + +var Plugin = &Example{ + Base: &plugins.Base{PlugName: "example"}, +} + +func (example *Example) InitPlugin(srv service.List) { + example.InitBase(srv, "example") + Plugin.App = example.initRouter(c.Prefix(), srv) +} + +func (example *Example) initRouter(prefix string, srv service.List) *context.App { + + app := context.NewApp() + route := app.Group(prefix) + route.GET("/example", auth.Middleware(db.GetConnection(srv)), example.TestHandler) + + return app +} + +func (example *Example) TestHandler(ctx *context.Context) { + +} diff --git a/plugins/example/router.go b/plugins/example/router.go new file mode 100644 index 0000000000000000000000000000000000000000..cecb95eaf98524a283b85c19982f714122b4662b --- /dev/null +++ b/plugins/example/router.go @@ -0,0 +1,17 @@ +package example + +import ( + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/service" +) + +func (e *Example) initRouter(prefix string, srv service.List) *context.App { + + app := context.NewApp() + route := app.Group(prefix) + route.GET("/example", auth.Middleware(db.GetConnection(srv)), e.TestHandler) + + return app +} diff --git a/plugins/plugins.go b/plugins/plugins.go new file mode 100644 index 0000000000000000000000000000000000000000..67633ef42789f48df74eced60617b1a8bdf0427a --- /dev/null +++ b/plugins/plugins.go @@ -0,0 +1,494 @@ +// Copyright 2019 GoAdmin Core Team. All rights reserved. +// Use of this source code is governed by a Apache-2.0 style +// license that can be found in the LICENSE file. + +package plugins + +import ( + "bytes" + "encoding/json" + "errors" + template2 "html/template" + "net/http" + "plugin" + "time" + + "github.com/GoAdminGroup/go-admin/template/icon" + "github.com/GoAdminGroup/go-admin/template/types/action" + + "github.com/GoAdminGroup/go-admin/context" + "github.com/GoAdminGroup/go-admin/modules/auth" + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/db" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/menu" + "github.com/GoAdminGroup/go-admin/modules/remote_server" + "github.com/GoAdminGroup/go-admin/modules/service" + "github.com/GoAdminGroup/go-admin/modules/ui" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/plugins/admin/models" + "github.com/GoAdminGroup/go-admin/plugins/admin/modules/table" + "github.com/GoAdminGroup/go-admin/template" + "github.com/GoAdminGroup/go-admin/template/types" +) + +// Plugin as one of the key components of goAdmin has three +// methods. GetRequest return all the path registered in the +// plugin. GetHandler according the url and method return the +// corresponding handler. InitPlugin init the plugin which do +// something like init the database and set the config and register +// the routes. The Plugin must implement the three methods. +type Plugin interface { + GetHandler() context.HandlerMap + InitPlugin(services service.List) + GetGenerators() table.GeneratorList + Name() string + Prefix() string + GetInfo() Info + GetIndexURL() string + GetSettingPage() table.Generator + IsInstalled() bool + Uninstall() error + Upgrade() error +} + +type Info struct { + Title string `json:"title" yaml:"title" ini:"title"` + Description string `json:"description" yaml:"description" ini:"description"` + OldVersion string `json:"old_version" yaml:"old_version" ini:"old_version"` + Version string `json:"version" yaml:"version" ini:"version"` + Author string `json:"author" yaml:"author" ini:"author"` + Banners []string `json:"banners" yaml:"banners" ini:"banners"` + Url string `json:"url" yaml:"url" ini:"url"` + Cover string `json:"cover" yaml:"cover" ini:"cover"` + MiniCover string `json:"mini_cover" yaml:"mini_cover" ini:"mini_cover"` + Website string `json:"website" yaml:"website" ini:"website"` + Agreement string `json:"agreement" yaml:"agreement" ini:"agreement"` + CreateDate time.Time `json:"create_date" yaml:"create_date" ini:"create_date"` + UpdateDate time.Time `json:"update_date" yaml:"update_date" ini:"update_date"` + ModulePath string `json:"module_path" yaml:"module_path" ini:"module_path"` + Name string `json:"name" yaml:"name" ini:"name"` + Uuid string `json:"uuid" yaml:"uuid" ini:"uuid"` + Downloaded bool `json:"downloaded" yaml:"downloaded" ini:"downloaded"` + ExtraDownloadUrl string `json:"extra_download_url" yaml:"extra_download_url" ini:"extra_download_url"` + Price []string `json:"price" yaml:"price" ini:"price"` + GoodUUIDs []string `json:"good_uuids" yaml:"good_uuids" ini:"good_uuids"` + GoodNum int64 `json:"good_num" yaml:"good_num" ini:"good_num"` + CommentNum int64 `json:"comment_num" yaml:"comment_num" ini:"comment_num"` + Order int64 `json:"order" yaml:"order" ini:"order"` + Features string `json:"features" yaml:"features" ini:"features"` + Questions []string `json:"questions" yaml:"questions" ini:"questions"` + HasBought bool `json:"has_bought" yaml:"has_bought" ini:"has_bought"` + CanUpdate bool `json:"can_update" yaml:"can_update" ini:"can_update"` + Legal bool `json:"legal" yaml:"legal" ini:"legal"` + SkipInstallation bool `json:"skip_installation" yaml:"skip_installation" ini:"skip_installation"` +} + +func (i Info) IsFree() bool { + return len(i.Price) == 0 +} + +type Base struct { + App *context.App + Services service.List + Conn db.Connection + UI *ui.Service + PlugName string + URLPrefix string + Info Info +} + +func (b *Base) InitPlugin(services service.List) {} +func (b *Base) GetGenerators() table.GeneratorList { return make(table.GeneratorList) } +func (b *Base) GetHandler() context.HandlerMap { return b.App.Handlers } +func (b *Base) Name() string { return b.PlugName } +func (b *Base) GetInfo() Info { return b.Info } +func (b *Base) Prefix() string { return b.URLPrefix } +func (b *Base) IsInstalled() bool { return false } +func (b *Base) Uninstall() error { return nil } +func (b *Base) Upgrade() error { return nil } +func (b *Base) GetIndexURL() string { return "" } +func (b *Base) GetSettingPage() table.Generator { return nil } + +func (b *Base) InitBase(srv service.List, prefix string) { + b.Services = srv + b.Conn = db.GetConnection(b.Services) + b.UI = ui.GetService(b.Services) + b.URLPrefix = prefix +} + +func (b *Base) SetInfo(info Info) { + b.Info = info +} + +func (b *Base) Title() string { + return language.GetWithScope(b.Info.Title, b.Name()) +} + +func (b *Base) ExecuteTmpl(ctx *context.Context, panel types.Panel, options template.ExecuteOptions) *bytes.Buffer { + return Execute(ctx, b.Conn, *b.UI.NavButtons, auth.Auth(ctx), panel, options) +} + +func (b *Base) ExecuteTmplWithNavButtons(ctx *context.Context, panel types.Panel, btns types.Buttons, + options template.ExecuteOptions) *bytes.Buffer { + return Execute(ctx, b.Conn, btns, auth.Auth(ctx), panel, options) +} + +func (b *Base) ExecuteTmplWithMenu(ctx *context.Context, panel types.Panel, options template.ExecuteOptions) *bytes.Buffer { + return ExecuteWithMenu(ctx, b.Conn, *b.UI.NavButtons, auth.Auth(ctx), panel, b.Name(), b.Title(), options) +} + +func (b *Base) ExecuteTmplWithCustomMenu(ctx *context.Context, panel types.Panel, menu *menu.Menu, options template.ExecuteOptions) *bytes.Buffer { + return ExecuteWithCustomMenu(ctx, *b.UI.NavButtons, auth.Auth(ctx), panel, menu, b.Title(), options) +} + +func (b *Base) ExecuteTmplWithMenuAndNavButtons(ctx *context.Context, panel types.Panel, menu *menu.Menu, + btns types.Buttons, options template.ExecuteOptions) *bytes.Buffer { + return ExecuteWithMenu(ctx, b.Conn, btns, auth.Auth(ctx), panel, b.Name(), b.Title(), options) +} + +func (b *Base) NewMenu(data menu.NewMenuData) (int64, error) { + return menu.NewMenu(b.Conn, data) +} + +func (b *Base) HTML(ctx *context.Context, panel types.Panel, options ...template.ExecuteOptions) { + buf := b.ExecuteTmpl(ctx, panel, template.GetExecuteOptions(options)) + ctx.HTMLByte(http.StatusOK, buf.Bytes()) +} + +func (b *Base) HTMLCustomMenu(ctx *context.Context, panel types.Panel, menu *menu.Menu, options ...template.ExecuteOptions) { + buf := b.ExecuteTmplWithCustomMenu(ctx, panel, menu, template.GetExecuteOptions(options)) + ctx.HTMLByte(http.StatusOK, buf.Bytes()) +} + +func (b *Base) HTMLMenu(ctx *context.Context, panel types.Panel, options ...template.ExecuteOptions) { + buf := b.ExecuteTmplWithMenu(ctx, panel, template.GetExecuteOptions(options)) + ctx.HTMLByte(http.StatusOK, buf.Bytes()) +} + +func (b *Base) HTMLBtns(ctx *context.Context, panel types.Panel, btns types.Buttons, options ...template.ExecuteOptions) { + buf := b.ExecuteTmplWithNavButtons(ctx, panel, btns, template.GetExecuteOptions(options)) + ctx.HTMLByte(http.StatusOK, buf.Bytes()) +} + +func (b *Base) HTMLMenuWithBtns(ctx *context.Context, panel types.Panel, menu *menu.Menu, btns types.Buttons, options ...template.ExecuteOptions) { + buf := b.ExecuteTmplWithMenuAndNavButtons(ctx, panel, menu, btns, template.GetExecuteOptions(options)) + ctx.HTMLByte(http.StatusOK, buf.Bytes()) +} + +func (b *Base) HTMLFile(ctx *context.Context, path string, data map[string]interface{}, options ...template.ExecuteOptions) { + + buf := new(bytes.Buffer) + var panel types.Panel + + t, err := template2.ParseFiles(path) + if err != nil { + panel = template.WarningPanel(ctx, err.Error()).GetContent(config.IsProductionEnvironment()) + } else { + if err := t.Execute(buf, data); err != nil { + panel = template.WarningPanel(ctx, err.Error()).GetContent(config.IsProductionEnvironment()) + } else { + panel = types.Panel{ + Content: template.HTML(buf.String()), + } + } + } + + b.HTML(ctx, panel, options...) +} + +func (b *Base) HTMLFiles(ctx *context.Context, data map[string]interface{}, files []string, options ...template.ExecuteOptions) { + buf := new(bytes.Buffer) + var panel types.Panel + + t, err := template2.ParseFiles(files...) + if err != nil { + panel = template.WarningPanel(ctx, err.Error()).GetContent(config.IsProductionEnvironment()) + } else { + if err := t.Execute(buf, data); err != nil { + panel = template.WarningPanel(ctx, err.Error()).GetContent(config.IsProductionEnvironment()) + } else { + panel = types.Panel{ + Content: template.HTML(buf.String()), + } + } + } + + b.HTML(ctx, panel, options...) +} + +type BasePlugin struct { + Base + Info Info + IndexURL string + Installed bool +} + +func (b *BasePlugin) GetInfo() Info { return b.Info } +func (b *BasePlugin) Name() string { return b.Info.Name } +func (b *BasePlugin) GetIndexURL() string { return b.IndexURL } +func (b *BasePlugin) IsInstalled() bool { return b.Installed } + +func NewBasePluginWithInfo(info Info) Plugin { + return &BasePlugin{Info: info} +} + +func NewBasePluginWithInfoAndIndexURL(info Info, u string, installed bool) Plugin { + return &BasePlugin{Info: info, IndexURL: u, Installed: installed} +} + +func GetPluginsWithInfos(info []Info) Plugins { + p := make(Plugins, len(info)) + for k, i := range info { + p[k] = NewBasePluginWithInfo(i) + } + return p +} + +func LoadFromPlugin(mod string) Plugin { + + plug, err := plugin.Open(mod) + if err != nil { + logger.Error("LoadFromPlugin err", err) + panic(err) + } + + symPlugin, err := plug.Lookup("Plugin") + if err != nil { + logger.Error("LoadFromPlugin err", err) + panic(err) + } + + var p Plugin + p, ok := symPlugin.(Plugin) + if !ok { + logger.Error("LoadFromPlugin err: unexpected type from module symbol") + panic(errors.New("LoadFromPlugin err: unexpected type from module symbol")) + } + + return p +} + +// GetHandler is a help method for Plugin GetHandler. +func GetHandler(app *context.App) context.HandlerMap { return app.Handlers } + +func Execute(ctx *context.Context, conn db.Connection, navButtons types.Buttons, user models.UserModel, + panel types.Panel, options template.ExecuteOptions) *bytes.Buffer { + tmpl, tmplName := template.Get(ctx, config.GetTheme()).GetTemplate(ctx.IsPjax()) + + return template.Execute(ctx, &template.ExecuteParam{ + User: user, + TmplName: tmplName, + Tmpl: tmpl, + Panel: panel, + Config: config.Get(), + Menu: menu.GetGlobalMenu(user, conn, ctx.Lang()).SetActiveClass(config.URLRemovePrefix(ctx.Path())), + Animation: options.Animation, + Buttons: navButtons.CheckPermission(user), + NoCompress: options.NoCompress, + IsPjax: ctx.IsPjax(), + Iframe: ctx.IsIframe(), + }) +} + +func ExecuteWithCustomMenu(ctx *context.Context, + navButtons types.Buttons, + user models.UserModel, + panel types.Panel, + menu *menu.Menu, logo string, options template.ExecuteOptions) *bytes.Buffer { + + tmpl, tmplName := template.Get(ctx, config.GetTheme()).GetTemplate(ctx.IsPjax()) + + return template.Execute(ctx, &template.ExecuteParam{ + User: user, + TmplName: tmplName, + Tmpl: tmpl, + Panel: panel, + Config: config.Get(), + Menu: menu, + Animation: options.Animation, + Buttons: navButtons.CheckPermission(user), + NoCompress: options.NoCompress, + Logo: template2.HTML(logo), + IsPjax: ctx.IsPjax(), + Iframe: ctx.IsIframe(), + }) +} + +func ExecuteWithMenu(ctx *context.Context, + conn db.Connection, + navButtons types.Buttons, + user models.UserModel, + panel types.Panel, + name, logo string, options template.ExecuteOptions) *bytes.Buffer { + + tmpl, tmplName := template.Get(ctx, config.GetTheme()).GetTemplate(ctx.IsPjax()) + + btns := options.NavDropDownButton + if btns == nil { + btns = []*types.NavDropDownItemButton{ + types.GetDropDownItemButton(language.GetFromHtml("plugin setting"), + action.Jump(config.Url("/info/plugin_"+name+"/edit"))), + types.GetDropDownItemButton(language.GetFromHtml("menus manage"), + action.Jump(config.Url("/menu?__plugin_name="+name))), + } + } else { + btns = append(btns, []*types.NavDropDownItemButton{ + types.GetDropDownItemButton(language.GetFromHtml("plugin setting"), + action.Jump(config.Url("/info/plugin_"+name+"/edit"))), + types.GetDropDownItemButton(language.GetFromHtml("menus manage"), + action.Jump(config.Url("/menu?__plugin_name="+name))), + }...) + } + + return template.Execute(ctx, &template.ExecuteParam{ + User: user, + TmplName: tmplName, + Tmpl: tmpl, + Panel: panel, + Config: config.Get(), + Menu: menu.GetGlobalMenu(user, conn, ctx.Lang(), name).SetActiveClass(config.URLRemovePrefix(ctx.Path())), + Animation: options.Animation, + Buttons: navButtons.Copy(). + RemoveInfoNavButton(). + RemoveSiteNavButton(). + RemoveToolNavButton(). + Add(types.GetDropDownButton("", icon.Gear, btns)).CheckPermission(user), + NoCompress: options.NoCompress, + Logo: template2.HTML(logo), + IsPjax: ctx.IsPjax(), + Iframe: ctx.IsIframe(), + }) +} + +type Plugins []Plugin + +func (pp Plugins) Add(p Plugin) Plugins { + if !pp.Exist(p) { + pp = append(pp, p) + } + return pp +} + +func (pp Plugins) Exist(p Plugin) bool { + for _, v := range pp { + if v.Name() == p.Name() { + return true + } + } + return false +} + +func FindByName(name string) (Plugin, bool) { + for _, v := range pluginList { + if v.Name() == name { + return v, true + } + } + return nil, false +} + +func FindByNameAll(name string) (Plugin, bool) { + for _, v := range allPluginList { + if v.Name() == name { + return v, true + } + } + return nil, false +} + +var ( + pluginList = make(Plugins, 0) + allPluginList = make(Plugins, 0) +) + +func Exist(p Plugin) bool { + return pluginList.Exist(p) +} + +func Add(p Plugin) { + // TODO: 验证插件合法性 + pluginList = pluginList.Add(p) +} + +func GetAll(req remote_server.GetOnlineReq, token string) (Plugins, Page) { + + plugs := make(Plugins, 0) + page := Page{} + + res, err := remote_server.GetOnline(req, token) + + if err != nil { + return plugs, page + } + + var data GetOnlineRes + err = json.Unmarshal(res, &data) + if err != nil { + return plugs, page + } + + if data.Code != 0 { + return plugs, page + } + + plugs = GetPluginsWithInfos(data.Data.List) + page = data.Data.Page + + for index, p := range plugs { + for key, value := range pluginList { + if value.Name() == p.Name() { + info := pluginList[key].GetInfo() + info.CanUpdate = utils.CompareVersion(info.Version, plugs[index].GetInfo().Version) + info.OldVersion = info.Version + info.Downloaded = true + info.Description = language.GetWithScope(info.Description, info.Name) + info.Title = language.GetWithScope(info.Title, info.Name) + info.Version = plugs[index].GetInfo().Version + plugs[index] = NewBasePluginWithInfoAndIndexURL(info, value.GetIndexURL(), value.IsInstalled()) + break + } + } + } + + for _, p := range plugs { + exist := false + for _, pp := range allPluginList { + if pp.Name() == p.Name() { + exist = true + break + } + } + if !exist { + allPluginList = append(allPluginList, p) + } + } + + return plugs, page +} + +func Get() Plugins { + var plugs = make(Plugins, len(pluginList)) + copy(plugs, pluginList) + return plugs +} + +type GetOnlineRes struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data GetOnlineResData `json:"data"` +} + +type GetOnlineResData struct { + List []Info `json:"list"` + Count int `json:"count"` + HasMore bool `json:"has_more"` + Page Page `json:"page"` +} + +type Page struct { + CSS string `json:"css"` + HTML string `json:"html"` + JS string `json:"js"` +} diff --git a/plugins/plugins_test.go b/plugins/plugins_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a997a1ffb9ae239abf42fa953eb8d50e6ca252a0 --- /dev/null +++ b/plugins/plugins_test.go @@ -0,0 +1,7 @@ +package plugins + +import "testing" + +func TestLoadFromPlugin(t *testing.T) { + LoadFromPlugin("./example/go_plugin/plugin.so") +} diff --git a/template/chartjs/assets.go b/template/chartjs/assets.go new file mode 100644 index 0000000000000000000000000000000000000000..41a3599eca5893c53d16532e1e982b14dde23fed --- /dev/null +++ b/template/chartjs/assets.go @@ -0,0 +1,235 @@ +// Code generated by go-bindata. DO NOT EDIT. +// sources: +// assets/chart.min.js +package chartjs + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _assetsChartMinJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\xbd\x7b\x9f\xdb\x36\xb2\x28\xf8\xff\xf9\x14\xdd\x3c\x89\x86\x68\x41\x6c\x49\x76\xdb\x6d\xaa\xd1\x5a\x27\x8e\x27\x99\xc4\xb1\xc7\x76\x1e\x8e\x46\xb7\x07\x22\x21\x09\x63\x0a\x50\x08\xa8\x1f\x91\xf8\xdd\xf7\x87\x02\x40\x82\x92\xec\xe4\xec\xdd\xbb\x1b\xff\xd2\x22\x01\x10\x8f\x42\xa1\x50\x2f\x14\xce\xcf\x4e\xff\xeb\xe4\xec\xe4\xeb\x25\x2d\x75\xf2\x1f\x75\x72\xfb\x28\x19\x26\x03\x93\xb4\xd4\x7a\xad\xd2\xf3\xf3\xbb\xbb\xbb\x24\x33\xd9\xff\x51\x89\x2c\x17\x26\x2b\xce\xd0\xc9\xb0\x3f\x1c\x34\x9f\x7d\x2d\x85\x2e\xf9\x6c\xa3\x65\xa9\x4c\x89\xb7\xac\x60\x54\xb1\xfc\x64\x23\x72\x56\x9e\xe8\x25\x3b\x79\xf5\xdd\xfb\x93\x1f\x78\xc6\x84\x62\xff\x75\x72\x76\xfe\x5f\xa7\xf3\x8d\xc8\x34\x97\x22\xd6\x98\xa1\x6d\x24\x67\xff\x61\x99\x8e\x08\xd1\x0f\x6b\x26\xe7\x27\xec\x7e\x2d\x4b\xad\x3a\x9d\xc8\xd4\x31\xe7\x82\xe5\xd1\xa9\xcf\x5c\xc9\x7c\x53\xb0\xb1\xfd\x49\x5c\x51\xc2\x62\x94\x46\xbe\xda\xa6\x26\xfb\x75\xa7\x63\x7f\x13\xba\xca\xc7\xf6\x31\x66\x28\x8d\x35\x39\xd6\xc0\xa2\x90\x33\x5a\xbc\x5f\x72\x35\x6e\x1e\x53\xbd\xdb\x29\x56\xcc\x51\x02\x03\x37\xed\x55\xb1\x5e\x72\x85\xe3\x7a\x30\x68\x1b\x6d\x14\x3b\x51\xba\xe4\x99\x8e\x46\x99\x14\x4a\x9f\xb4\xda\xa8\xfb\x75\xc7\x45\x2e\xef\xc6\x0d\x1c\xd0\xb6\x64\x7a\x53\x8a\x13\x1d\xa3\x2a\xb5\xd9\x49\xc9\x7e\xdf\x30\xa5\x9f\x0b\xbe\xa2\xa6\xd8\xcb\x92\xae\xd8\xc8\x7f\x74\xc2\x62\x86\x39\x16\x68\x6b\x5b\x92\x44\xec\x76\xb1\x26\xd7\xcf\xcb\x92\x3e\x24\xeb\x52\x6a\x69\x9a\x4b\x54\xc1\x33\x96\x64\xb4\x28\x62\x8d\xd0\xa8\x60\xfa\x44\x91\xd3\x01\xa6\x64\x32\x1d\xb9\x66\xeb\x9e\x24\x49\x22\xd0\x96\x12\x19\x0b\x84\xd5\x6e\x17\x2b\x72\xda\xc7\xda\x7e\x6e\xfb\x85\xe3\x18\x91\xeb\x2d\xd4\xc1\x12\xba\x5e\x17\x0f\x31\xc7\x14\x55\x08\xa1\xaa\xaa\xbb\xc7\xed\xec\x9a\xe6\xf8\x41\x33\xf5\x78\xd9\x38\xce\x0a\x46\xcb\xf7\x7c\xc5\xe4\x46\xc7\x1c\x61\x4e\x14\xd3\xfe\xdd\xd4\x81\x52\x1d\x23\xcc\xaa\xca\x0e\x54\x10\x4d\xae\x23\xa5\x69\x69\x70\x86\xe8\x71\x54\xb0\xb9\x8e\xd2\x88\x89\xdc\x25\x94\x7c\xb1\x34\x29\x19\x13\x9a\x95\x11\x96\xc4\x54\x84\x39\x6a\x7f\xc8\x82\x6f\x78\x1a\xb3\x2e\x47\xe7\x43\xac\x82\xc2\xb6\x22\x57\xc0\x57\x07\xaf\xae\x74\xca\x46\xb7\xb4\x3c\xa1\x44\xb0\xbb\x93\xac\xa0\x4a\xd9\xe9\x28\x37\x99\x96\x65\x8c\xb6\x06\x49\x92\x1b\x37\x95\x44\x6c\x8a\x02\xdb\x24\x58\x59\x0a\xbe\x7b\x45\xd7\x2e\xb1\xdc\x08\xc1\xc5\xc2\x80\xd6\x26\x14\x54\xe9\x17\x54\x33\x72\x2b\x79\x7e\xd2\xaf\x6e\x84\xd4\x7c\xfe\x60\x7b\x18\x4e\x3e\x4b\x0a\xae\x34\x13\xac\x54\x13\x31\xc5\x8a\xb0\x24\xdf\x94\x80\x37\x23\x99\xcc\x65\xf9\x0d\xcd\x96\x71\x2c\xc8\xb5\x88\xb7\xd0\x76\xaa\x31\x17\x5c\x73\x5a\xa4\x2c\x71\x4f\x58\x6c\x56\xef\x34\x5b\xab\x54\xe1\x6c\x53\x96\x4c\x68\xf3\x9a\xbe\xa2\x7a\x99\xac\xb8\x88\x79\x8f\x25\x00\x41\xac\xec\x84\xdf\x94\x6c\x5e\x32\xb5\x8c\x7d\x57\x18\x31\x3d\x1f\xb1\x7a\xd0\xbb\x5d\xcc\x82\x91\xf5\x71\x93\x45\x8e\x21\x16\x4b\x6e\x36\xeb\x9c\x6a\x66\x26\x7d\x0f\x74\x4d\x45\x9d\x0e\xe4\xb9\xb6\x6d\x57\xdc\x67\x9a\x18\x88\x25\x42\xde\xc5\x68\xaf\x57\x80\x8d\xa4\x6f\x7a\x67\xe1\xdf\x40\x26\x16\x58\x9a\xe6\xf9\x3c\x3e\x15\x89\x6b\x65\xb7\x3b\x15\x09\xd7\x6c\xa5\x92\x82\x89\x85\x5e\x22\x8b\xb7\x6e\x75\x2b\xe2\x72\xa1\x62\x8a\x4b\xe2\xcb\xf5\x06\xb8\x20\xa7\x83\xd1\x5c\x96\xf1\xa8\xbc\x26\xfd\x51\xaf\x57\x22\x4a\xd4\xa4\x9c\x62\x9a\xdc\xd0\x4c\xf3\x5b\x36\x8e\x69\x72\xa3\xa5\xa6\xc5\xb5\xa8\xa7\xab\xd3\x89\x9b\x17\xe2\x0b\x20\x4c\x13\xcd\xb3\x8f\xb1\x46\xa6\xe6\x3e\x4a\x63\x53\x17\x51\x93\xa6\xc9\x29\x56\xc9\x5a\xae\x63\xb3\xca\x3b\x9d\x58\x26\x79\x49\xef\x2c\x14\x1d\xda\x48\x2c\xb0\xc6\xd1\xba\x94\x8b\x92\x29\x15\x21\x84\xfd\xe7\xbb\x5d\x5c\x0f\xdb\x2e\xed\xbd\x8f\x32\xb9\x5a\x17\x4c\xb3\x08\x61\xe1\xf1\x85\x9c\x0e\x10\xe6\xdd\x7a\xd8\x15\x82\xd6\x6a\xac\xd5\xb8\x4f\x08\xe1\x9d\x4e\x0b\x05\x06\xa8\xba\x59\x30\x20\x6c\xca\xd0\xbe\x70\x8a\xfc\xc4\xb8\xa9\x62\xc9\x82\xe9\x58\x23\x4f\x41\xf8\x6e\x17\x73\xb2\x75\x55\xa5\xa7\x83\x1a\x87\x4f\xfb\x18\xa6\x22\x9d\x4c\x71\xbd\x14\xd2\xad\xef\xb5\x49\xf6\xc3\x4e\x27\xd3\xaa\xc2\x2c\x51\xa6\x6a\xcc\x11\xc2\xbc\xb2\x9f\xb8\x95\xef\x56\x6d\xd0\xc7\x60\x75\xb1\x69\xb2\xde\xa8\x65\xcc\x51\x45\xf3\xdc\x52\x39\x66\xd0\xd1\x82\xa0\xd3\x39\xfc\xd8\x62\x10\x7c\x95\x24\x09\x43\xd5\x92\xaa\x90\xe8\x7f\xe2\x03\x5b\xe1\x75\xbf\x82\x15\xf7\x29\x48\x79\x08\x31\x80\x72\x6b\x9d\xc1\x77\xc1\x6a\xc0\x0d\x51\x20\xcc\x35\x52\xb2\x7c\x93\xb1\x38\x86\x81\x90\x6b\xbb\xd2\xe9\xbd\x79\x4d\x6e\x7c\x69\x84\x70\x1f\x79\x1a\xe5\x17\x1d\xaa\x5c\x63\xa6\x67\x66\xd9\xb4\x68\x98\x5b\x29\xa7\x83\xd1\xe7\x3a\xed\x0a\x9d\xc6\x00\xc1\x60\x6d\xb7\x16\x5d\xa5\xb4\x5c\xff\xd9\xf8\x4d\x0f\xd8\x6e\x77\xca\x3e\xb3\x60\xb9\x1f\x36\xa0\x97\x20\xbc\x5e\x3b\x76\xa5\x0a\xbb\x52\x05\xe2\x13\x31\x4d\x32\x2a\x32\x56\xc4\x68\xe4\x3e\x22\x93\xa9\x83\x41\x40\x85\x03\xe8\x06\x4b\xa4\x2a\xd9\x4a\xde\xb2\x83\x49\x76\x5d\xce\x99\x29\x16\x6b\x54\x55\xa3\xff\x72\x9c\xd8\xff\xf5\x71\x53\x7e\x2c\xd8\x79\x26\x0b\x59\x9e\xdc\xf6\x93\x41\xf2\x2c\x64\xc7\x16\x5c\x2f\x37\xb3\x24\x93\xab\xf3\xb0\xe4\x7f\x97\x8c\xe6\x2b\x16\x70\x67\xfd\x93\x7f\x6c\x3e\x7e\xa4\x27\xdf\x6f\xca\x8f\xac\xa0\x7f\x8d\x2b\xb3\xf0\x29\xc9\xb6\x9f\xf6\xf1\x20\x1d\xe0\x61\x3a\xc4\x8f\xd2\x47\xf8\x71\xfa\x18\x5f\xa4\x17\xf8\x49\xfa\x04\x3f\x4d\x9f\xe2\xcb\xf4\x12\x3f\x4b\x9f\xe1\xe7\xe9\xa0\x8f\xbf\x4a\x07\x03\xfc\x75\x3a\x18\xe2\x17\xe9\xe0\x11\xfe\x26\x1d\x3c\xc6\x2f\xd3\xc1\x05\xa6\x26\x77\x66\x72\x33\x93\x9b\x9b\x5c\x66\x72\xe7\xe9\xe0\xa2\xc2\x05\x89\xfa\x83\xe1\xa3\xc7\x17\x4f\x9e\x5e\x3e\x7b\xfe\xd5\xd7\x2f\xbe\x79\x19\xe1\xcc\x6c\xec\xc5\x64\x70\xd1\xd1\x53\xbc\xb4\x2f\xf1\xf0\x71\xbf\xa3\xd1\xf5\xf5\xe3\x69\xd7\x67\xe5\x26\xab\xc9\x20\x24\x36\xe9\xa8\xe1\x8c\x36\x06\xec\x66\x5b\x66\xe4\x08\x8b\x95\xc7\x3a\x29\x51\xa7\x63\x7e\x17\xee\x77\xe6\x7e\xa9\x61\xeb\xd0\x38\x4b\x97\x9e\xe0\xe8\x71\xf4\xdf\x51\x97\xc1\x37\xf0\xb3\xb0\x3f\x33\xd4\x35\xe5\xaf\x86\x17\x17\x63\x06\x5f\xa6\x51\x84\x52\xdd\x30\x40\xf3\x70\xf2\xbb\xc9\xc5\xae\xef\x18\x98\x45\xc3\x60\xd4\xab\xad\xde\x60\x0d\x3d\xc2\x2c\x18\xcc\x3a\xa8\x66\x11\xcf\xe3\x61\x72\x71\x71\xa6\x11\xee\xe3\xe1\xc5\x05\x6a\x9a\x5b\xed\x97\x3b\x5e\xec\x7e\xaf\x98\x3e\x37\x15\xa2\xf3\x41\xbf\x8f\xfb\x78\x10\x94\x9c\xed\x95\x1c\xf4\xfb\xb6\xc2\x41\xbf\x8f\xdc\x50\x6e\xc8\xf9\xff\x2a\x17\x33\x3a\xfe\x57\xfc\x2f\x75\x16\x4f\x7a\xdd\xe4\x5f\xf9\xb4\x8b\xe2\x2f\xd1\x78\xf2\x2f\x85\xa7\x5d\x48\x63\x9f\x4f\x8c\xc7\xa9\x49\x3f\x3f\xc8\x40\xe3\x7f\xa9\xb3\x7f\xa1\x2f\xce\xdd\xf2\x7d\x20\xe7\xff\x2b\x5e\xaa\x82\x8e\x77\xcb\xbb\xd9\x6e\xa9\x6e\x51\xd3\xb0\xfb\x6c\x9c\xe6\x6c\x71\xa4\xa1\x2f\x0f\x53\x5c\xbb\x9f\x69\xb6\x06\xc6\xad\xdf\x19\x3c\x0f\xca\xce\x1a\x9e\x08\x0f\x7a\x1c\x19\x26\x93\xc1\x9f\xae\x3e\x7f\xd4\x47\x5f\x0e\x86\x88\x5c\xf3\x9e\x38\x3b\x9c\x64\xd9\x7b\x84\x9f\xf5\x24\x1e\x20\xdc\x1b\x78\x22\x38\x91\x71\x1f\x61\x19\x5f\x9a\x3f\x8f\xd1\xb4\x99\x89\xbb\xfd\xc6\x0d\xaf\x42\x62\xd1\xd5\xe7\x4f\xfa\xe8\xcb\x27\xd0\x0e\x3f\x63\xc7\x5a\xc2\x8f\x6d\x3b\xfd\xba\x19\x11\x5f\x20\x2c\xe2\x47\xe6\xcf\x20\x6c\xe6\xd5\x7e\x33\x66\xd0\x03\x9c\x5c\x58\xa1\x41\x02\x89\x64\x5d\x7e\x3d\x30\xcc\x05\x19\x9c\x03\x0f\x8c\xd9\x19\x91\x98\x9f\x11\x69\x40\xd0\x1f\xc9\xab\x47\x23\xd9\xed\x22\x31\x91\xd3\x33\x32\xe8\xb1\x1e\xc7\xe6\xb9\x4b\x98\x5f\x53\xa2\x69\xf3\x63\x8b\xaa\x27\xe5\xf9\xf0\xe2\x02\x73\xa2\x93\x05\x3c\x09\xa2\x93\x19\x3c\x49\x52\x0f\xce\x72\xbe\x58\x91\x7a\x94\x2e\x85\x92\x58\x76\x15\x3a\x1f\x42\x7f\x4b\x5c\xe0\xcc\x37\x29\x4f\x09\x51\x9d\x4e\x9c\x11\xd9\x53\xb8\x20\xf4\x3a\xb9\x18\x67\xe7\xf1\xb0\x27\x7b\x0a\xa5\xd9\x39\x7c\x89\x4b\x22\x09\x21\x6c\x1c\xf3\x9e\x40\xe7\x59\x37\xe6\x57\x62\xfc\x24\xed\xa3\xd4\xa4\xf3\x71\x2c\x7a\xcc\xa4\x0f\xd3\x98\xf5\xb8\x79\x7a\x8c\x4b\xf2\xa4\x7f\x56\x76\x93\x0b\x84\x27\xfd\x5d\x89\x8b\xdd\xae\x8f\x69\x00\xd7\x77\x0d\xb7\x6e\x7b\x13\x5b\x11\x8d\x2b\xf8\x8d\x19\x1a\xeb\x98\x4d\xfa\x53\xcc\x26\x03\xf3\x67\x38\x35\x22\x8f\xfd\x04\x25\x2b\xba\x8e\x57\xc1\xba\x7c\xe3\xa7\xc9\x0d\xed\x5d\x7c\x8b\x6d\x4a\x53\xe6\x45\xb3\x76\x63\xfd\xe5\xa3\x27\xfd\xee\x23\x83\x29\x8f\x9e\xf4\x9b\x32\x5f\x87\xa0\x7f\x48\xd8\x3d\xcb\xcc\x36\x0a\x7c\x17\x16\x64\x78\x71\x61\xb7\x54\xbf\x83\xb2\xc9\xc5\xf4\xd4\x32\x73\x82\xb0\xc9\x93\xe9\x78\x1d\x77\x4d\x22\x4a\x57\xee\x01\x8d\xbc\x38\xf2\xc2\xa4\x0c\xa7\x66\x96\xba\x6c\xf2\x68\x0a\xf4\x85\x9a\xe7\xc7\xf0\x5c\xf3\x73\x24\x5a\xde\xcd\x8c\x48\x65\xc6\x3e\x0e\x35\x03\xad\x21\xbe\xf2\x43\x8c\x25\x56\xd8\x10\xdb\xa5\xba\xfd\xf3\xcf\xee\xf6\x3f\x7b\xe3\x1e\xf0\xb6\x4c\xb9\x01\xf9\x22\xe5\x06\xe8\xb3\x94\x4f\x86\x53\x4c\x53\xe1\xc5\xcc\xd7\x64\x7b\x9f\x46\x39\x2d\x3f\x46\xf8\xb7\x34\x2a\x40\x12\xc4\x1f\xd2\xa8\x64\x11\xfe\x35\x8d\x66\xc5\x26\xc2\xbf\xa4\xd1\xa2\x8c\xf0\xcf\x69\xb4\x62\x39\xdf\xac\x22\xfc\x53\x1a\xa9\x82\x6a\x16\xe1\xe7\x69\xc4\x58\x84\xdf\xa7\x91\x2c\x22\xfc\x2e\x8d\x64\x19\xe1\xaf\xd2\xa8\xa4\x11\xfe\x3a\x8d\x4c\xa1\x45\x84\x5f\xa4\x91\xa9\x59\x45\xf8\x6d\x1a\x71\x11\xe1\x7f\xa6\x91\xde\x94\xbf\x6f\x24\x57\x11\xfe\x26\x8d\x96\x3c\xc2\x6f\xd2\xa8\x94\x11\x7e\x9d\x46\xb4\x88\xf0\x8f\x69\x54\xb0\x08\xbf\x4a\xa3\x9c\x45\xf8\x87\x34\x7a\x60\x45\x21\x23\xfc\xd2\x88\xb3\x11\xfe\x3e\x8d\xb2\x65\x84\xff\x9e\x46\xb4\xfc\xa8\x22\xfc\x6d\x1a\x31\x1a\xe1\xef\x6c\x43\x8b\x08\xff\x23\x8d\xee\x96\x51\x85\x9f\x93\xed\x6b\x9e\xb1\x5f\x59\x1a\xcd\xfb\xf3\xcb\xf9\x3c\xc2\x54\x68\xfe\xfb\x86\xdd\x7d\xa3\x4d\x22\x65\xb3\xfc\x69\x84\xe9\xef\x1b\x9a\x46\xf3\x39\x14\xf8\x7d\x43\x57\xb4\x7c\xcb\xd2\xe8\xe9\x7c\x3e\xcf\x1f\x47\x98\xfe\xb1\xf9\x00\x35\x40\x81\x19\xe3\x0b\xf3\xed\xc5\xfc\x22\xcf\x22\x3c\xe3\xea\xf7\x8d\x79\x9f\xb3\xc7\xd9\xe3\x08\xcf\x0a\x9a\x7d\x4c\xa3\x3e\x3c\x89\xef\x59\xfe\x7a\x25\x45\x0e\xf9\xb3\x2c\x8f\x30\x74\x66\x6e\x7e\x6f\xf9\x7b\xa6\xd3\xe8\x92\x0e\x67\x6c\x18\xe1\xd9\x9b\x3b\x91\x46\xf4\x62\x48\x87\x34\xc2\xb3\x4d\x59\x3c\xdc\x49\x99\x1b\x18\xcc\x2e\x2f\x9f\x46\x38\xa3\xaf\xb4\xf9\xfa\x62\xfe\x8c\xd1\x7e\x84\xbf\xa7\xa5\xfe\xb0\x51\xae\xa3\x7d\x93\x22\xb3\xf7\xd4\x0c\x2c\x1f\x3e\x79\x36\x60\x11\xce\xde\xbd\x36\xad\x3d\x9d\x5f\xf4\xcd\x8b\x98\x17\xf2\x8e\x95\xa6\x92\x27\x8f\x9f\x5d\xb0\x1c\x12\x15\x2f\x3e\xc2\xe8\x2f\xcd\x78\xb2\x92\xaf\x94\x14\x69\x94\x67\x83\xc7\x8f\x4c\xc2\x03\x15\x1e\x38\xf7\xe6\xd3\xcb\x59\x84\xef\x6d\xea\xe5\x0c\x5e\x16\xef\x5f\x89\x37\x79\x1a\xcd\x2e\x2f\x9f\xf4\x4d\xc2\x2f\xf4\x21\x8d\xe8\x33\xf3\xcf\x64\x7f\x78\x69\x1a\x34\x3d\xbc\x5f\x7c\x08\x73\x3e\x2e\xe9\x47\x9e\x46\xb3\x7c\xf6\xf4\x89\xf9\x6e\x45\x17\x2f\x35\x35\xf5\xf6\xfb\x50\xf3\x7b\x7e\xcb\xe0\xf3\x8b\x8b\x27\xb3\xa1\xe9\xc1\x3b\x2a\x00\xfc\xf3\xcb\x0c\x2a\x7c\x97\x7d\x93\xa7\xd1\xb3\x67\x8f\x86\x59\x16\xe1\xfb\x0f\xb9\xfd\x1a\xf2\x94\x81\x7d\x1a\xb1\x67\xcf\x9e\x3c\xa5\xe6\xfd\x5b\xa8\xeb\x72\x3e\xcb\x2e\x4d\x5d\x3f\x99\xe1\x3c\xbe\x7c\x94\x43\x5b\x3f\x41\xaf\x87\xf3\xc7\xf3\xc7\x90\x09\x5d\xad\x5f\xff\xc9\xd2\x28\x63\xf9\x20\xc2\xf7\x6e\xe2\x9e\x3d\xee\xf7\xf3\x47\x11\xce\x9f\xaf\xd7\x6f\x01\x82\x83\xc7\xcf\xec\xbb\xfa\xf8\x60\xaa\x9e\x01\xd0\x72\xbe\x82\x9a\x9f\x3c\x33\xff\xe0\x1d\xaa\xae\xdf\x65\xbe\xb0\x93\x32\x60\xcf\xfa\xe6\x8b\x39\xff\x30\x2b\xb9\x41\xa3\xd9\xd0\xfc\x17\xe1\x79\xf1\xee\xb5\xc3\xd9\xf9\x9c\xce\xfb\x11\x9e\xcb\x0f\x4a\xff\xf2\x5c\xa4\xd1\x70\x78\x39\x83\x32\x9b\xef\x15\x07\x3c\xee\x43\x2d\x0b\xfa\x56\xcd\xde\x49\x33\x93\xe6\x5f\x84\x17\x4b\xa9\xb4\xab\xe5\xd2\x2e\x87\xc5\x7b\x40\xcd\xfc\xa9\x81\x97\x9f\xc6\x9c\xd2\x8b\x61\x3f\xc2\xd0\xeb\xcb\xbe\xf9\x17\x61\x0b\x39\x80\xeb\xe2\xc3\xcb\x1f\xee\xd2\x88\xe6\xf3\xb9\x99\x12\x18\x8c\x2f\xb6\x94\x82\x3d\xbc\xba\x73\xcb\x05\x12\xb4\x83\xce\x93\x67\xb3\xc7\x11\x7e\x9b\x73\x2a\xcc\x2c\x65\xf9\x45\x76\x91\x41\xc2\x42\xa6\xd1\x63\x33\xe5\xc3\x08\xf3\xdb\x77\x0f\x16\xdd\xcc\xd7\x0e\x41\xe6\x7d\xf6\xe4\x32\x8b\x70\x41\x6f\x5f\xbe\x2a\xd3\x88\x3d\x61\x4f\xe6\xd4\xbf\xff\xaa\x96\xf0\x45\x7f\x7e\x61\x92\xee\x04\xf4\xf5\x69\x36\x07\x0c\xf9\x71\x25\x45\xf6\xcd\x7c\x2e\x2d\x16\x53\xb3\x08\x7f\x33\xd0\xa6\x79\x7e\xc9\x9e\x44\xf8\x37\xbb\x4a\xdc\x00\x7e\xb3\x98\xcd\xdc\x6a\xff\xcd\x02\xc5\x8c\x77\x4e\xe7\x34\x1f\x46\xf8\x37\x80\x4b\xfe\xc8\xfc\x33\x05\x4c\x63\xcf\xfa\x8c\x3d\xeb\xc3\x5b\x98\xe7\x46\x3e\x7b\x92\x0d\x22\xfc\x9b\x43\xc7\xf9\x9c\xf6\x0d\x3a\xfe\xe6\xd0\x71\xd8\x9f\x0d\x29\xbc\x5b\xac\xb9\x7c\x9a\x31\x33\xb8\xdf\x2c\x42\x3e\x7d\x7a\x79\xf9\xec\x99\x79\x85\xba\xeb\x57\xa5\x9f\x17\x80\x64\xfd\xec\xb1\x21\x93\xbf\x41\x1f\xe7\xf3\x39\xeb\x47\xb8\xe0\x2b\x66\x31\xc1\x3e\x43\x43\x8f\x86\x59\xfe\x68\x18\xe1\xe2\xed\x4b\x18\x4d\xdf\x8c\xde\x2f\x39\x8f\x35\x2b\xfa\xc6\x74\xd2\xcc\xb4\xf9\xf8\xe7\x80\x1e\x3e\x79\x92\xe5\xa6\xa3\x3f\x9b\x66\x0d\x18\x7f\xb6\x8b\x6f\x46\x2f\x2e\xcc\x70\x7f\x5e\x6f\xca\xf5\x8f\x69\xf4\xec\xd1\xd3\x7e\x3e\x8b\xf0\xcf\x6e\x80\x8f\xb2\xd9\xa3\xa7\x83\x08\xff\x0c\xeb\xed\xe9\xec\xc9\xa5\xd9\x36\x7e\x56\xeb\xf2\xed\x02\x0a\xcc\xe9\x33\x53\xed\x3f\x61\x35\xe6\x03\xb3\x90\x7f\x86\x45\x06\x78\xf2\x74\x70\x71\x79\x11\xe1\x15\xcf\x85\xa1\xef\xb0\x50\x9e\x0d\x9e\x3d\xed\x47\x78\xf5\x56\x67\x1f\xe8\x0a\xa8\xf1\xdc\xc0\x6c\xc5\x95\x7e\x78\xa3\x1c\x3d\x66\x83\x08\xaf\x64\x96\x51\xf5\xd6\x26\xcc\x2e\x22\x2c\xe8\x2d\xfd\x8f\xf4\x8b\x29\x67\x34\x87\x34\xc0\xe1\x08\xbf\xcf\x0b\x9a\x99\x9c\x7c\x7e\x61\xc0\x63\x08\x90\xc5\x6e\x03\x0d\xf3\x96\x7f\x35\x4b\xa3\x27\xb3\x4b\x36\x7c\x14\xe1\x9a\x1a\xd1\x0b\x93\x0f\xaf\x1f\x60\x49\x3d\xb6\x09\x00\xa0\x9c\x3e\xed\xe7\x4f\x22\xbc\x7e\xcd\xfc\x1a\x63\x8c\x5d\x1a\x58\x9a\x24\x40\xa0\xcb\xf9\xec\xd9\x25\xbc\x1b\x30\xd0\x39\x63\x06\x48\xeb\xd7\xac\x06\x44\x3e\x7b\xda\x37\xf4\x65\x4d\xd7\xf4\x81\xde\x7d\xb3\x86\x31\xcd\xf3\x8b\x08\xaf\xbf\xfd\x7e\xbd\x99\xcf\x61\x40\x74\xf6\x2c\xc2\x6b\x56\x6e\xcc\x1c\x5d\x5e\x3c\x9a\x47\xd8\xa1\x61\xd6\xcf\x66\x11\x5e\x17\x9b\x55\x1a\xe5\x39\xed\xe7\x79\x84\xd7\xf2\xee\x55\x69\xd1\x88\x01\x3e\xb8\x39\x34\xe3\x35\xf0\xf8\x30\x63\x59\x46\x5d\xe2\x93\x27\x8f\x1e\x19\xdc\xb3\x23\xb4\x08\xf2\x46\x3d\xcc\x4a\x69\xb6\x2e\x43\x54\x0d\x59\x7d\xf3\xf0\x1a\xe8\xea\xe0\xc9\x33\x33\x01\x8a\xe6\xf9\x8f\x76\x73\xbb\x9c\x3d\xbe\x18\x3c\x8a\xb0\x5f\x08\xf4\xb2\xff\x74\x68\x4a\x88\xfc\xc1\x96\x98\x3f\xa6\x8f\x9f\xf4\x23\xec\x97\x06\xbb\x9c\x5d\x3c\x35\xaf\x6a\xc9\x8a\x02\xf0\xfb\xc2\xc0\x45\xf1\x97\x82\xa6\x11\xed\x5f\x0c\x87\xb9\x79\x2d\x6e\x59\x99\x46\x59\xdf\xfc\x8b\x70\xb0\x92\xd8\x2c\xc2\x80\x77\x4f\xe8\x05\xac\x7d\xb7\xaa\xfa\x97\x7d\xb3\x60\xdd\xa2\x72\x6f\x4a\xc8\x3b\x47\x62\x69\x84\x03\x04\x9d\x3f\x9d\x47\xd8\xaf\xb8\xc7\x4f\x2e\x87\x86\x94\x69\x43\x28\xf2\xe1\xec\xb1\x21\x4d\x9a\xbd\xb6\x78\x12\x61\xfd\x8d\xd2\x3f\xa6\x51\x7e\x39\x9b\xe7\x97\x11\xd6\x72\x45\xb5\x04\x0a\xf8\xe8\xf1\xd3\x08\x03\x9a\xf7\x59\x3f\xef\x47\xd8\x6d\x25\x8c\x5d\x0e\xcd\xb0\xfe\xf1\xad\x36\xb8\x9c\xb3\xd9\xa3\x08\xd7\xf4\x1e\x08\x91\x79\x53\x2b\xf9\xd1\xb1\x1e\x86\xd4\xf9\x05\x6f\xa6\xe1\x87\x3b\x8b\x46\x14\x96\x78\x05\x4c\xee\xfb\x46\xba\x7b\x6b\x58\xe1\xf7\xbb\x5d\xfc\x9e\x04\x26\x0a\x6f\xc1\xd9\x56\x98\x91\xd7\x60\xa4\x4a\x3e\xb2\x07\x15\x3f\x47\x98\xb7\x12\x5e\x5b\xb6\x59\x60\x60\x2e\x71\x09\x92\x92\x20\xfd\x91\xb8\xf2\x6a\xc1\x91\xe8\x76\xd1\xd6\xa4\x53\x52\x12\x36\x11\x53\x27\x2d\x79\x15\x14\x08\x4d\x8a\xf0\x89\x9c\xe2\x92\x94\x49\xc9\xd6\x66\xad\xc5\x0a\xbf\x9e\xa8\x29\x1a\x29\xb2\xa6\xa5\x62\xdf\x09\x1d\x3f\x9f\xd0\x29\x1e\x3c\x41\x58\x4f\xca\x29\x99\xa8\xeb\xeb\xc1\x93\x8e\x11\x92\xd4\xf5\xf5\x25\x3c\x0c\x2f\x2e\x3a\x6a\x5a\x79\xed\x43\x15\x23\xfc\x3e\xd1\x25\x15\x6a\x4d\x4b\x26\x34\x99\x18\x69\xbf\x8f\xfb\x53\x54\xab\xe7\xde\x4f\x74\xa2\xe5\x0f\x86\x05\xfa\x9a\x2a\x16\xa3\xda\x3a\xc4\x3a\x9d\x6d\x99\x32\xcb\x2e\x33\xcb\x2e\x33\xcb\x2e\x3f\x36\x2c\xb8\x1b\xc1\xd8\x30\xfa\xe9\xf0\xe2\x22\xb0\x02\xfd\xe0\x59\x72\x0e\xea\x10\xab\x75\x33\x42\xdf\x48\x4c\xd8\xb4\x91\xe9\xfa\xb8\x16\xe6\x4c\x46\xd7\xfc\x39\xe3\xa0\x3d\x66\xe3\x47\x4f\xfa\xe9\x00\x21\x2c\xc8\x9b\x58\x20\xac\x93\x92\x08\xd3\x19\x9d\x2c\x88\x30\xdd\xd1\xc9\x8c\x88\xc9\x70\x1a\x34\xfc\x8d\x55\xcc\xd6\x5a\x1c\x37\x5d\x54\x29\xbe\x10\x31\xdb\xed\xb6\x15\xd6\x2d\x7d\xcd\x77\x8d\xd6\x68\x5b\xa6\x7d\xbc\x48\xfb\x78\x96\xf6\x31\x85\x21\x79\x58\xb4\x45\x37\x8d\xc6\xda\xab\x69\xc9\xa3\x4e\x27\x86\x6f\xb5\x85\x94\xb6\x90\xd2\x16\x52\xa6\x12\x5c\x17\x86\xb2\x09\x25\xab\x58\x4f\x1e\x4d\x11\x42\x69\xcc\x88\xe9\xf3\x5e\xd3\x83\x0a\x21\x28\xc6\x12\x8a\x30\x6b\x7a\xfb\x47\x23\xe1\x45\xd6\x24\x05\xb6\xda\xe7\x3a\xee\xa3\x96\x61\xd1\x4f\xef\xcd\xbe\x84\x67\x90\xd5\x4b\x79\x0c\x26\x88\x4d\x9e\x82\x84\xd7\xa0\x7e\xd7\x24\x8d\xa0\x58\x27\x66\x93\x4b\x23\xf3\x69\x94\x5a\xdd\x52\x55\xcb\x70\x5d\x40\x0a\x61\x45\x3d\x2c\x09\x48\x83\x98\xfb\xcf\x86\xe6\x33\x8e\x52\x8e\xac\x5c\x69\xd2\x1e\x9b\x34\x81\x52\x81\xb0\xf4\x69\x20\x52\x4a\x94\x4a\x2b\x9e\xe1\x45\x2a\xf0\x2c\x95\x98\xa6\xaa\xaa\x2a\xd3\xb0\x91\x59\x2b\x30\xb3\x9d\xbc\x6c\x19\xda\xac\x06\x5a\x9f\x70\xa1\x34\x15\x19\x93\xf3\x93\x97\xc8\x4f\x7e\xa3\x81\xb6\xe6\x57\x6d\x41\x00\x96\x3b\x00\xc3\xa8\xb1\x41\x13\x36\xe6\xc4\xa0\x42\x1a\x29\x5d\x72\xb1\x80\xb4\x4e\x27\x56\x24\x16\x44\x23\x37\x81\x38\xfa\x6f\x93\x61\xd0\xb0\xd3\x89\xcd\x2a\x50\xbb\xdd\x85\xf9\x19\x4b\x83\x02\x66\x44\x83\xa7\x67\xe5\xc4\xe0\xa7\x41\x86\x20\x61\x38\x35\x68\x11\x24\x3c\x9a\x1a\x04\xb1\x5f\xbb\xa4\xc7\x53\xbb\x92\xd2\xa7\x56\x71\xf1\xec\x14\x5a\x88\xa1\x72\x57\xeb\xd5\xd5\xe3\x9d\xaf\x6f\x91\xba\x8a\x7c\xe2\x63\x68\xc4\x3c\x5d\x34\x89\x4f\xa0\xa1\x67\xd0\x90\x79\x7f\xda\x64\x5d\xba\x06\x91\xa1\x6d\x72\xb7\x33\x24\x71\xb7\x33\x58\x56\xeb\xfd\x17\x33\xc2\xdd\xf3\x2d\x2d\x78\x4e\x4e\x4f\x79\xb5\x60\xfa\x04\xde\xe2\x3d\x4d\x37\x24\x42\x76\xb9\x98\xc5\x76\x65\x69\x83\xe2\xbe\xb2\xda\xa6\xa3\x3b\x9d\x58\x27\x94\xdc\x83\x1a\x15\x61\x5d\x29\xf7\x95\xae\xed\xa7\x8b\x19\x4c\x4a\x55\x2e\x66\xef\x60\x5a\x8e\x36\x37\x8e\x35\x69\xea\xb7\xd5\x82\x86\xf6\xdf\xe5\x62\x46\xe3\x2f\xb6\x3a\x29\x2b\x7c\x62\x7e\x17\xee\x77\x06\xbf\xb6\xe9\x0a\xfd\x3b\x35\x25\x3f\x51\x10\xfd\x1b\xa5\x75\xed\x80\x3c\xba\x5a\xb2\xfb\xcf\xf5\x67\x13\x0c\xb7\xf9\xb6\x5a\xaa\xe2\x73\x5f\x85\xcb\x17\xec\x2a\x6d\x23\x06\x03\x02\x8a\x39\x01\x8a\x2c\xc8\x2c\x36\xcb\xcf\xac\xa3\x19\x2c\xb6\x06\xb0\x7e\xf4\x4b\x55\x98\xd1\x73\x18\x8a\xa8\xbe\x34\x3f\xd2\xfe\x04\x23\x5f\xaa\xe2\x48\x21\xf4\xef\xea\xf8\x20\x56\xfc\xde\xd2\x58\x6f\x5a\x01\x0b\xab\xa5\xf3\x5e\x83\xc8\x93\x72\x31\xc3\x92\x68\xf3\x6b\x5d\x0f\xdc\x20\x28\x61\x80\x86\xc9\x45\xca\x70\x49\x86\x67\x14\x4c\xa6\x22\xa1\x3d\x99\x50\x9c\x91\x38\x2e\xcf\x0a\x42\x7a\x83\x71\x99\xc6\x65\xb7\x40\xe7\xf1\xa0\x5b\x9e\x15\x08\x75\x07\xe8\x7c\x38\x52\x64\xd0\xcb\xb0\x48\x4a\xa0\x1e\xd9\x99\x48\xca\xae\x3a\x93\x49\xd9\x4d\x2e\xb0\x48\x16\x75\xf2\x02\x92\x17\x36\x79\x56\x27\xcf\x20\x79\x66\x93\x29\xa1\x67\x22\xa1\xdd\x78\xd0\xa3\xe8\xcc\x74\x00\x7a\x4e\x44\x4d\xe3\xaa\xac\x90\x82\x35\xf3\x25\xd8\xdd\xc9\x4b\x0b\x18\x03\x97\x8a\x16\xeb\x25\x3d\x30\xf7\x94\x8b\x99\x25\xf2\x76\x11\x55\xe0\xeb\xc0\xca\xe3\xe5\xce\xc8\xa0\xa7\x6d\xb9\x45\xc9\x1e\x54\x46\x0b\x16\xb0\x21\x75\x49\xcc\xc8\x3c\x4e\x1e\x9d\x69\x18\xeb\xb3\x33\x6d\x06\x37\x18\x9c\xe9\x24\x58\x53\x49\x49\xcc\xf6\x68\x76\x46\x66\xeb\x94\x6b\xfa\xfb\xe6\x33\x6d\x77\x5d\xdb\x82\x2d\xc0\xf8\x7e\xd8\x70\x58\xf9\xf0\xe2\xa2\xa7\x93\x12\xf6\x60\xfb\xbc\x80\x6d\xd8\x3e\xcf\x6c\x55\xa0\x34\x63\xa1\xf1\xe5\x87\x06\x97\xf0\x10\x7b\xb0\xe4\xb4\xfc\xf8\x99\x62\x3d\x5f\x4e\x51\xbd\x29\xc1\xc4\x7f\xb4\xe4\xa0\xa9\x90\xfd\x69\xd1\xba\xd2\x52\xea\x76\xb9\xb6\x7b\x92\x59\xe7\xdc\xb2\x2c\x7c\xd2\x9f\x92\x17\xb1\xf9\xe9\x32\xb3\x00\xdf\xc4\xdc\xb2\x23\xdc\xb3\x23\xdc\xb3\x23\xdc\xb0\x23\x41\x83\xbe\xb5\x66\x07\xff\x39\x68\xd2\x61\x93\x37\xab\x7c\x45\x34\xb9\x6e\x6d\x66\x5f\x53\x71\x4b\xd5\xdf\x4b\x9a\x73\x26\xf4\x6e\x77\x24\xf3\x0d\xd5\x9a\x95\xa2\xe1\x6a\x7f\x09\x1a\xf8\x0a\x58\x95\xd4\xb4\xd9\xf4\xe0\xdb\xe3\x05\x92\x1a\x76\xc9\x05\x4a\xdc\xdc\x24\x03\x94\x04\xc4\xae\xa9\xe4\xc7\x18\x6d\x5d\xb7\xff\x13\x72\xcf\x66\xb1\x6b\xd2\xff\xb4\xeb\x8f\xee\x76\xab\x2a\x0e\x2c\x5d\x5f\x84\x00\xd9\x14\x05\x21\x01\x77\xf6\xc1\x11\xc3\x16\xff\xd5\xe9\xec\xb3\x63\xde\xea\xdc\xaf\x69\xa5\x63\xfc\x1a\x7f\x28\x2d\xed\x20\xbc\x4b\x94\xeb\x61\x34\x09\x78\x80\x44\x6d\x66\x4a\x97\x71\x1f\x3f\x45\x9d\x4e\x04\xb5\x4f\x5b\x39\xbd\x27\x01\x0c\x7e\xda\xeb\xf9\x29\x21\xba\xd3\xf1\x35\x9e\xd8\x2e\xc0\xf7\x7f\xda\x1b\x07\xca\x5f\xc1\xc6\x19\x89\xcd\x6a\x06\x1e\x47\x9e\x73\xd9\x9b\xf9\x1f\x21\x1f\x75\x3a\x5c\xbd\xe4\x82\x6b\x16\x77\x43\x43\xe8\xef\x2d\x26\xf8\x57\x3b\xc5\x01\x13\xf9\x7d\x2b\xdf\xba\x18\x39\xcf\x28\xed\x3a\xf2\x77\xe2\x1c\x03\x1a\x76\xc8\xf7\xa5\xd3\xd1\x09\x13\xb9\xfa\x85\xeb\x65\x1c\x7d\x19\xa1\x31\x48\x28\x2f\x0b\x49\x75\xac\xc1\xa4\x98\xea\x73\x86\x7f\xfb\xdf\xaa\xe2\x8c\xa5\x5d\xdd\x0c\xe9\x9f\xa1\x40\xd1\xe9\x1c\x71\xfa\xb3\x3e\x45\xa8\xd9\x03\x9d\x87\x1a\x0b\x26\xec\x1f\x8d\x49\x06\x4c\x5b\x46\x6c\x33\x5b\x97\xc1\x32\xc4\xe7\xb1\x22\x9e\x53\xc7\x02\x19\xa1\x4d\x12\xd5\x1b\x8c\xe4\xb5\x11\xd9\x7a\x3d\xe4\xfc\xe9\x38\xd6\x46\x5c\x93\x68\xc4\x0a\xc5\x4e\x6c\x41\x23\xd4\x29\x90\xe6\x8e\x97\xe2\xf3\xd8\xe0\x0b\xb2\xb2\x60\x28\x46\x6a\x84\x15\xa1\xbe\xe1\x4f\xd4\x44\x27\xd2\x70\x71\x13\x39\x0d\xc6\xa3\x75\xe0\x6d\x67\x59\x7b\x30\xde\xe8\xdd\xee\x94\xed\x76\x7e\x30\xa7\x8d\xb0\xd6\xb8\x67\x98\x7e\x70\xd2\x07\x5b\x9b\x13\x45\xf9\x95\x18\x75\xbb\xdc\x40\x42\x12\x3d\xe1\xe0\x4f\x66\x7e\x64\x92\x53\x4d\x15\xd3\xdf\x89\x9c\xdd\x1b\x9e\xb4\x95\xb0\xdb\xc9\x84\xd7\x39\xf0\xd4\xb4\xe3\x57\x66\xd3\x6b\xf0\xdb\xd8\x7a\xb0\xd7\x13\xb6\xa2\xeb\x98\x59\x87\x0e\x00\xd4\x76\x6f\x29\x67\x25\x33\xc4\xc9\x2c\xb4\x7d\x41\x5c\x1b\x09\xa3\x96\xa8\x61\x6a\x49\xdf\xba\x76\x48\x18\x93\x44\x6c\x62\x44\xec\x29\x31\x8d\xdb\xc7\x7a\xd3\x64\x8d\xc0\xdc\xb8\x31\xea\x66\x6d\xf7\x06\x84\x90\x49\x74\x73\x03\xcb\xf7\xe6\x26\x02\x4f\x2a\xbb\x90\x23\x1c\x05\xf2\x48\x34\xb5\xa3\x7f\x3d\x6f\x11\x5c\xa1\x1b\xbc\x33\xd3\x03\x95\xb7\x99\x3b\x49\xd8\x44\x1b\x78\xf3\x89\x9e\x8e\x7e\x8a\x25\xea\x74\x7e\x8a\x15\x1a\x4b\x0d\xa6\x2b\x81\x52\x53\xc0\x74\x5f\x05\x35\x4b\xbd\x6f\xbc\xfd\x10\x33\x34\x66\xe9\x84\x19\x81\x4c\xd4\x33\x3b\x8f\x4f\x7f\x0a\xa1\x5d\x7b\xb2\xc5\x9c\x70\x23\x15\xa3\x64\xc5\xca\x05\x2b\x77\x3b\xa1\x01\x72\xe0\xda\x46\xfa\x23\x7a\x25\x47\xdd\x2e\xb5\x3d\xff\x29\x66\x44\x4c\xe8\x14\xa1\x4c\x0a\xcd\xc5\x86\xd5\xfd\x0f\xe7\x83\xa1\xba\x0a\x41\xfa\x98\x12\x59\xab\x43\xae\xe8\xa8\xdb\x15\x48\xc5\x72\x22\xa6\xde\x4c\x77\x08\x7f\xa5\x5b\x34\xca\x8d\x73\x6b\xfb\x98\x52\x5d\x05\x40\xa0\x3a\x20\x0e\xc7\x80\x2b\x2c\x70\xa5\x07\xae\x00\xe0\x4a\x34\x56\x1a\xdc\xff\xd2\x03\xf2\xbc\xa4\xea\xf5\x9d\x78\x53\xca\x35\x2b\xf5\x83\x5d\x85\x0c\x1b\xd1\x28\xf6\xd3\x20\x51\xd0\x85\x52\xef\xf1\xc4\x35\x1e\x44\x49\x84\x99\x47\x35\xc0\x24\x5e\xeb\x0e\x52\xde\xd4\x50\xb8\x1a\xf8\x3c\x8e\x60\xc7\x69\xa6\xca\x79\x2f\x62\x41\x4a\x30\x0f\x5b\xe0\x8e\x74\xa7\x23\xae\xf9\x08\x69\xa2\x27\xf5\xfe\xc4\xb1\xe8\x71\x64\x84\x70\xd1\x1d\x04\x5f\x1c\x42\x38\xd3\x21\x4b\xd8\x68\x11\x12\x2d\x7f\x5a\xaf\xbd\x32\xa8\xab\xad\x23\x71\x3c\xf0\x5b\xd4\x52\x9b\x3d\xca\x6e\x1a\x66\xc3\xc3\x39\x24\x1c\xa3\xc8\x78\xa3\xfd\x1e\x00\x74\x3b\x51\xfc\x0f\x06\xc4\xc8\x3c\xb4\x49\x91\x83\xdc\x89\xf9\x0e\x81\x01\xda\x4c\x42\xcc\xd1\x11\x4a\x82\xe7\xfa\x28\x51\x58\x1c\x4d\x0e\x7c\x6a\x1a\x18\x9f\xb2\xfd\xa5\xc0\x4d\xb7\xd6\x05\xd7\x66\xca\x1a\xfc\x65\x00\xf8\x9a\xb6\x30\x20\x27\x2c\x10\x75\x26\x6c\x3a\x32\x53\x20\xa6\xbb\x5d\x6c\x7e\x8e\xf5\xe1\x18\x8a\xaf\x74\xdb\x6e\x7d\xb0\x4d\x32\xb3\xf8\x5d\x9f\x31\x47\xa9\x7f\x8b\x22\x64\x76\x35\xc3\xa1\xde\xeb\x4f\x78\x20\x7b\x11\x9a\x7a\x2f\x72\xe7\x49\x6c\x05\xf9\x19\xcd\x3e\x2e\x4a\xb9\x11\xf9\xd7\xb2\x90\x25\x89\x40\x4e\x76\xba\xc1\x64\x80\x22\x57\x4c\x96\x39\x2b\x3f\x5b\x24\xb3\x99\xff\xfd\xe4\xc9\x13\x97\xe2\x36\x06\x45\xb6\x95\x4b\x60\xb7\x3c\x63\x6f\xf8\x3d\x2b\xde\x9a\xae\x00\x93\x6b\x4f\x18\x24\xeb\x82\xea\xb9\x2c\x57\xc9\x82\xe9\x17\x7b\xe5\x62\xa7\x81\x60\x05\x5b\x31\x11\x54\xc8\x6e\xe1\x75\x12\xad\xe4\x46\x81\xbf\x5d\x84\xed\xb3\xdc\x68\x43\x90\x0b\x9e\x7d\x8c\x70\xa4\xe5\x26\x5b\x5a\xbf\x6f\xf7\x02\x65\x9d\x33\xdf\x5c\x0a\x4d\xb6\x73\xba\xe2\xc5\x43\x1a\xfd\xed\x5b\x56\xdc\x32\xcd\x33\x7a\xf2\x23\xdb\xb0\xbf\xe1\x93\x26\xc5\xbc\x3c\x2f\x39\x2d\xfe\x86\x4f\x14\x15\xaa\xa7\x58\xc9\xe7\x11\x36\x48\x9c\x0e\x86\x58\xe9\x87\x82\xa5\x91\x90\xe5\x8a\x16\x11\x2e\xb8\x60\xdf\x32\x23\x02\xa5\x83\x64\x88\xef\xec\xa3\x41\x03\xd7\xff\xa5\xbc\x65\x65\x3d\x1a\x78\xfb\x6a\x6f\x42\xdc\xaa\xf9\x36\x66\xfb\x73\x85\xc2\xaf\x82\xf9\x09\xbf\x68\x92\xc3\xd2\x07\xe5\xb2\xa0\x04\x90\xab\xe7\xf7\x5c\x91\xe8\x3e\xf2\x49\x9a\x95\x14\x70\x95\x6c\x57\x32\x37\x43\x34\x62\xac\xd2\x11\x86\x3c\xc5\x32\x9d\x9a\xe5\x08\xc5\x57\x94\x0b\x4d\xb9\x78\xae\xd6\x2c\xd3\x76\xa6\x4f\x1d\xbe\x49\xf1\x2d\x8c\xb9\xf1\x83\x97\xe2\x6b\x33\x4b\x41\x8a\xe1\x00\x9d\x07\xab\x7d\x2f\x36\x0b\x2e\x9a\x59\x2f\x99\x5a\x4b\xa1\xf8\x2d\xab\x8b\x80\xa8\xdc\x42\x6c\x48\x69\xbe\x51\x4b\x79\xf7\x03\x17\xcd\x17\x39\x53\x59\xc9\x67\xe0\x82\x69\xdd\x80\x9b\x3d\xc6\xac\xc7\x25\x57\x66\x4f\x42\x95\x75\x2e\xf5\x59\x6b\x9f\x85\xaa\xa6\x86\xf6\xa7\x0b\x6d\x3f\x34\xe3\x2c\x79\x7e\x90\x3f\x77\xf9\xa5\xdc\x68\x76\xe8\xc7\xdf\xb4\x80\x55\xfd\xc2\x11\xa6\x24\xba\x89\xba\x6c\xe4\x68\x8a\x3d\x52\xe2\xf6\x25\xce\x54\x2c\xf1\x76\x42\xa7\xe9\xf6\x96\x16\x1b\x96\x4a\xb3\xed\xdf\x95\x5c\xd3\x59\xc1\x60\x6a\x26\x6c\x9a\x6e\x99\xd8\xac\x58\xe9\xd2\xb0\x19\x5a\x5b\xca\x9f\xd0\x29\x66\x44\x4d\x44\xad\xfa\x37\xbc\xc2\x9e\xee\x7c\x5b\x61\xb3\x03\xa6\x56\x78\xa8\xb0\xb2\x10\x72\xdf\x13\x5d\x55\x15\xaa\xaa\x78\x7b\x63\xe0\xb3\xb6\x3d\xd0\xe4\xfa\x54\x5b\x77\x64\xc7\xeb\x4b\x11\x21\x7c\x03\xd8\xe6\x4b\x44\x76\x3d\x47\xb0\x99\x00\xa6\xa6\xdb\x9b\x39\x2d\x8a\x19\xf8\x8c\x04\x68\x18\x55\x38\x78\x4b\x5b\x4d\x9d\x0e\xc2\x6a\x4f\x07\x55\xe5\x4d\x1c\x33\x6d\xed\x0d\x6f\xbe\xc3\x37\x9a\x0c\xcf\x66\x1a\x3f\x68\x72\xa3\xbb\x33\x8d\x6f\x35\xb1\xb2\x54\xf2\xe6\xf5\xbb\xef\xde\x7f\xf7\xf3\x37\x37\xdf\xfd\xf8\xf2\xbb\x1f\xbf\x7b\xff\x01\xdf\x69\x32\xd3\xe7\x83\xcb\x3e\x7e\x05\x4f\x43\xfc\x11\x7e\x1f\xe3\x77\xb6\x9a\xf3\x47\xf8\x8d\xab\xbb\x90\x8b\x41\x1f\xbf\x70\x6f\x06\x5e\xcd\xae\xf3\x75\xcb\x85\x1b\x0a\xac\xe5\x5d\x3c\x70\xb6\x8f\x79\x21\x65\x19\xbf\x01\xae\xc5\xf0\xb5\xfa\xdc\x3b\xcc\xc5\xfc\x8a\x0c\xc6\x83\x94\x5f\x91\xe1\x78\x68\x7e\x2e\xc6\x17\xe9\xa0\x8f\xce\x02\x59\xee\x75\xab\xf6\x89\xd9\xf9\x6d\x1f\x7e\x07\xd7\x71\x6b\xa0\x72\x86\xa9\xc1\x48\x5c\x71\xb0\x48\xe9\x2f\x05\x21\x7d\x30\x46\x80\x7b\xba\x40\xd8\x3d\xe9\x73\x81\x1a\xb7\x7b\x42\x48\xdc\xdf\x71\xd4\xe9\x30\xef\xfd\x8e\x59\xa2\x64\xa9\x6b\xcf\x71\xdd\x63\x08\xd9\xf3\x07\xa1\xa1\xe2\x79\xb0\x7e\x4e\xb9\xfa\x91\xfe\x18\xb7\x64\xbc\x50\x78\x0d\x79\xe5\xf7\x7b\xbb\xe2\x09\x8c\x86\xce\x54\x6c\x1a\xba\x0a\x38\xa6\xb7\x7b\x3c\x17\x14\x04\x42\x19\x1e\x1c\xe8\xb1\x2b\x23\x94\xf3\x2e\xbb\x0e\xf5\x0a\x3f\xd4\xcd\xd4\x16\x3c\x6f\xbd\x03\xbd\x65\xcd\xae\x4a\x80\x97\x82\x0d\xde\x88\x42\x76\x28\x0a\x38\xc1\x64\xc5\x45\xe0\x8d\x68\xfe\x62\x65\x00\xb4\xa2\xf7\x81\xdf\xa2\xf9\x8b\x55\xc8\x2e\x7e\xd3\xe2\xbd\xce\x62\x8b\x67\x41\x81\xef\xf6\x0a\x0c\x2e\xfb\xe7\xb3\x10\x4c\x7f\x78\x19\xea\xf4\xd7\x80\xdd\xb5\x0c\xcb\x00\x73\x2f\xfd\x84\x30\x39\x63\xe8\xdc\x70\x5f\x7a\x84\xd8\x19\x19\xf4\x31\xef\x76\x6b\x30\x35\x35\xbf\xdc\x03\x2b\x4b\xee\x7b\x3a\xb9\xc7\x82\xb0\xe4\xa1\xa7\x93\x07\xef\x93\x09\x18\xc6\xcf\x78\x57\x9c\x09\x7f\xbe\xcc\x4e\x96\xa6\x62\x18\x8b\x80\xed\x54\x57\xbd\xe4\xe2\x6c\xa6\x3b\x9d\x58\x75\xc9\x8d\x46\x78\x4b\xc5\xa2\x60\xa9\xc2\x39\xb7\x5a\x8d\x54\x86\x3a\xb2\x36\x65\x6e\x9a\xab\x57\x8f\xef\xd5\x10\x75\x83\x34\xdb\xbf\x61\x08\xea\xaf\x5a\x55\x19\x24\xea\x3e\x68\xf4\xe5\x8d\xee\xcd\x02\x74\xf8\x45\xb7\x3c\x27\x6f\x74\xf7\x06\x0a\x05\x4a\xb3\x03\x49\x0b\xbe\xc1\xd2\xfc\x32\x43\xb5\x7f\x81\x63\x6c\xd4\xfc\xca\x9e\x40\xb8\x34\x4f\xca\x3c\x15\xe6\x49\xf4\x24\xc2\x99\x7d\x52\x35\x64\x04\x21\x44\xee\x76\xc2\x5a\x92\xe8\x75\xd9\xe9\x14\x57\x59\xa0\x66\x3b\xba\x1c\xac\x3b\x6c\xe0\x89\xac\xc3\x21\xff\x27\x44\x1e\xa8\xa0\xf7\x68\xf8\xf4\xc9\x25\x36\x7f\x9f\x06\x05\xbf\x08\xd7\xa8\xde\xed\xbe\x70\x2c\x3a\xb2\x8f\x96\x37\x42\x63\xb3\x47\xa7\x26\xcb\xf0\x39\x63\xf7\xdb\x8d\x4e\xa2\x34\x8a\xc0\x1d\xde\xb2\x38\x63\xff\x50\x67\xd9\xda\xba\xd1\xfa\xfe\x24\xea\xfa\xfa\x02\xdd\x5e\x2d\x16\x63\x69\xd7\xa1\x22\x6c\x22\xeb\x4d\xc8\x9e\x43\x34\x29\x44\x27\x2b\x46\xd5\xa6\x64\xef\xd9\xbd\x11\xbd\x92\x3b\x9e\xeb\x25\xe6\x96\x28\x49\x84\xb0\xba\x86\x13\x54\x44\x21\x1c\xf8\x19\xff\xa4\xf7\x34\x3e\x24\x16\x44\x58\x79\xd7\x30\xaa\x44\xc0\x0f\x98\x85\x15\x11\xc9\x82\x96\x33\xba\x60\x5f\xcb\xa2\x60\x99\x3e\x48\xd8\xed\x26\xd3\x91\x00\xd6\xf1\xd4\x9a\x07\xa5\xab\x81\x1c\xaf\x60\x32\xc5\xb6\x38\x61\x08\xeb\x44\xd1\x5b\x66\xd8\x5a\x97\x34\xf2\x32\xb6\x3f\x85\xd1\x52\x61\x14\x38\xc3\x4b\x9c\xe3\x8d\x95\x45\x48\x7f\x54\x5c\x95\xa3\xa2\xdb\x35\xe2\x51\x4e\xf8\xa4\x98\x62\xab\x75\xcc\x3b\x9d\x53\x23\x8b\x7d\x88\x73\x84\x28\x01\xb8\x5a\x97\x84\xbc\x51\x3e\x41\x26\x48\x5a\xa4\x8f\x97\x24\xf7\x4d\x65\x57\xcb\x51\xd6\xed\xa2\x0d\xc9\x27\x99\xad\x91\x90\xcd\x6e\xf7\x21\xde\x18\x22\xd7\xaa\x6e\x83\xd0\x48\x1b\x4e\x4c\xcb\x92\xc5\x7e\x83\x9d\xd7\xe7\xc0\xce\x87\x23\x3e\x8f\xe7\xd7\x7e\x1c\xd6\xf3\xc1\x76\x7d\x0e\x5d\xb7\x27\x60\x4e\xe4\x44\x4d\x8a\xe9\x74\xa4\x40\xe4\xca\x58\xdc\xc7\xf3\x5a\x46\xa2\xcd\xfc\xfd\x7a\xb0\xea\x74\xe2\x0e\x28\xee\x8b\x0b\x58\x12\x03\x03\x3e\xae\x57\x08\x3f\x1f\xe2\xe4\x02\xa5\xb5\x32\x3a\xa0\x86\x31\xeb\x49\x74\x26\xd0\xb9\xe8\xca\xa6\xb9\xdf\x1d\xb1\x88\x19\x01\xf5\xd9\x82\xe9\xaf\xa5\xd0\x06\xe7\xa2\x61\x1e\x21\xe4\x67\x90\x19\x20\x30\xfd\xbe\xa4\x42\x19\xf9\x05\x92\xc0\xca\xf3\x96\x65\x1a\xc4\x24\xed\x70\x54\x27\x4b\x58\x15\xee\x23\x0b\xb9\x40\x17\xbb\x8f\xa1\xd6\x95\x04\x17\x0e\xb8\x19\x61\xc9\x5a\x72\xa1\xdf\x99\x55\x87\x97\x84\x25\x60\xb9\xe0\x52\xe0\xdc\xbc\xd0\x9c\x6f\xec\xb9\xa6\x0d\x89\x97\xbb\x5d\x1f\x9d\xdd\x69\x33\x0d\x59\xa7\x73\x70\xc6\x3a\x03\x94\xcd\x6a\xf5\x73\x8c\x70\xad\xae\xfe\xf6\xfd\xab\x1f\xbe\x5b\xd1\x05\xfb\xc6\x0a\x5c\xa0\xb8\x96\xbb\x5d\xab\x80\xb5\x39\xb4\x4a\x04\x2a\xbc\x1a\xbf\xc1\xe7\xa4\x30\xe2\x2f\x78\xe8\xeb\xc4\x59\x5b\x36\xe6\x39\x2f\xe9\x1d\x34\x14\x67\xb8\x97\x59\x30\x9d\x0f\xcd\xa3\x85\xd4\xf9\x10\xbb\x54\x9c\xd5\xc0\x03\x2e\x3f\xc4\x3d\xb3\xf7\xc5\x76\x37\xce\xd1\x6e\x97\x5f\x91\x3e\x42\x5b\x75\xc7\x75\xb6\x8c\x75\x32\x63\x0b\x2e\xde\x50\xbd\x8c\x11\xce\xd0\x36\x67\x73\xba\x29\x74\xaa\x13\x5a\x66\xa6\x53\x38\xc7\x7d\x6c\x76\x22\x9d\x64\x85\x54\xcc\x16\x1d\xcd\x4a\x46\x3f\x8e\x32\xaa\x58\xa4\x4b\x0e\x9b\x54\x94\xea\xc4\x08\x8e\xef\x65\xcc\xbb\x8e\xbf\x13\xf1\x06\x9d\xe5\x58\xf4\xe0\x3d\x93\x0a\xde\x11\xde\x74\xc9\x3b\x8d\x75\x62\x64\xc0\xff\x13\xe5\x3f\xd9\xd9\xd2\xc8\x5c\x06\xb3\x59\x1e\xa5\x05\x49\x2e\x06\x4f\xce\x72\x5c\x92\xbc\x57\xf8\x13\x13\x50\x4b\xf7\xa3\x46\x67\x25\xa6\xa4\x69\xc8\x25\x39\xc8\xf4\x14\x16\x3d\x8a\x0b\xbc\xe9\xcd\x34\xde\xf4\x5e\x01\x88\x20\xab\x4b\xb1\xe8\x29\xc8\x7a\xa5\xf1\xa6\x49\x57\x58\x74\xe1\x13\xbc\xe9\x06\xe5\x7b\x14\x8b\x2e\x94\xef\x9a\xf2\xdd\xd9\xe7\xa0\x6d\x06\x10\xa5\x66\x52\x97\x68\x5b\xda\xee\xbd\xfb\xe7\xdb\xf7\x83\x9b\xe1\x59\x6e\x10\xc8\xac\x2b\xde\x2b\xb1\xe8\x95\x78\x78\x06\xff\xbb\x0a\xaa\x4d\x97\x7c\xd4\x21\x1c\x74\x94\x86\x83\x36\xa0\x0c\x47\x8c\xa0\x46\x3f\xa7\x76\xc4\x28\x9c\x05\x18\x68\x2b\x05\x86\x18\xa6\xd8\xc1\x7d\x66\x40\x59\x29\x95\x82\xae\x04\xdd\x83\xc4\xff\xcd\xce\xd5\x5d\xa9\x91\xf2\xa0\xbb\xae\x73\x61\x77\x8c\x10\xf6\xff\x59\xc3\x18\x86\x8c\xff\x7f\x18\xa6\xc9\xfa\x7f\x65\x98\x61\xa5\x39\x55\xcb\x90\x0a\x58\x92\xd6\x5e\xb3\x75\x5b\xa2\xbd\x86\x51\xa5\x93\x39\x2f\x0a\xd8\x21\xac\x66\xe6\x17\x43\xda\xae\xfb\x9d\x8e\x61\xa5\x4a\xf9\xd1\xec\x07\xcd\x86\xf0\xf7\x7d\x8e\x0f\x14\xf4\xc9\x05\x06\xab\xd9\xfd\x35\x4b\x0a\x36\xd7\x3d\x0e\x6f\x57\x2c\x81\x58\x05\x5d\x78\x7d\xb8\x66\x89\x96\x6b\x9b\xf7\x70\x65\xda\xd3\x5a\xae\xba\x01\x77\xff\x9b\xdb\xe1\x02\x5a\x1d\x52\x4a\xb7\xce\x6c\x1b\x18\x6a\xc3\xae\x89\x5e\x9d\x68\x6b\xed\x41\xae\x5d\x00\x7c\x1d\x6e\x69\xff\xb4\x8a\x81\x63\xbb\xdd\x3f\x5a\x2c\x5f\x5b\x0f\x5b\x03\x34\xb9\xc7\x3c\x79\x00\x0a\x1f\xad\x78\x9e\x17\xcc\xee\x33\xcd\x39\x38\x96\xdc\x77\x79\x72\x8f\xce\x87\xa3\xfa\x33\x81\x59\xf2\x10\xcc\x8b\x80\x4a\x2a\xc3\xfe\x44\x74\xee\x02\x38\xc8\x53\x72\x7a\x2a\xc6\x75\x21\xe6\xda\x4a\x5b\xad\x9b\x8a\x46\x07\xfd\x09\xcc\x6e\xac\x6d\xcf\xf9\xcc\x18\x0c\x78\xff\xe0\xac\xfc\x7a\x53\x02\xee\x88\x31\x4b\xb2\xf5\xe0\x3e\x35\x3f\xc3\x7b\xec\xde\x1f\xec\xfb\x03\x16\x63\x0e\x19\x29\x87\x62\xfe\xfd\xc1\xbe\x3f\xe0\xc3\xce\x30\xd6\x80\x14\x2b\xb2\xad\x3c\x9c\x68\x68\x0b\x2a\x89\x72\xc8\x56\x63\x5f\x14\x81\xb9\xce\xa6\x82\x72\xd0\xb3\x9d\xc0\x70\xd6\x18\xa2\xea\xdd\x1c\xa2\x14\x84\x9b\x7b\x2b\x6b\xd2\x9f\xb6\xcb\x82\x77\xd2\x17\xb1\xaa\x19\x16\x64\x78\x2a\xc7\x09\x04\xa9\x9e\x1f\x96\x89\xd5\x80\x63\xa7\x56\x06\x47\x2e\xb3\x76\x80\xf5\x21\xca\x2b\x2c\x55\x62\x38\xb2\xe7\x05\x5f\x08\x28\x52\xbf\x91\x20\xc7\x17\xfb\x8a\x2a\x56\x40\x34\x18\x57\xd2\x27\x90\x76\xbe\x91\xd2\x0c\x9b\xea\xed\xb0\xa3\x6e\xb7\x80\xd9\xcd\x08\x35\x9c\xb6\xe9\x4d\x0b\x58\x50\xa1\x7d\xf7\xfd\x0b\x72\xed\xc0\x03\x88\x1b\x5e\xda\xa2\x07\xbc\xb6\xa7\x03\x64\x03\x78\x05\xf9\x26\x83\xd9\x54\x86\x89\xb5\xd9\x00\x22\x5e\x14\xc7\x73\x31\xd4\xc5\x3f\x32\xbd\x2c\xe5\x66\xb1\xdc\xed\x54\x02\x07\xbe\x61\x5c\xc1\x31\xcb\x50\x86\xca\x8c\xe8\xca\x7b\x2c\xa1\x99\xde\xd0\xe2\x2b\xc3\x3b\x70\xb1\xf8\x4a\xde\xff\x60\x56\x39\x25\xbc\x7b\x24\xef\xad\xa1\x04\xb8\x24\xe2\xd8\x87\xcf\x55\xc6\x84\xc6\x05\x11\xc7\x3e\x7d\xc1\x6c\xb6\x1b\x7a\xd3\xdd\xb1\x75\xf3\x1a\xa6\xc5\xa8\x0d\xd0\x60\xf2\x0f\xc8\x54\x08\xc8\x9c\x65\xd2\xc6\x2b\x80\x94\xdd\x6e\xd8\x10\x78\x89\x97\x01\x4d\xa0\x38\x00\x75\x8c\x2a\xd1\x25\x32\x69\xf4\xf5\xd5\x51\x9a\xc5\x59\xa0\x10\xd9\xde\xa7\x1c\x3f\xa4\x02\xdf\xa5\x12\x2f\x53\x85\x2d\xff\x9d\xd2\x8a\xb0\x51\xcd\x27\x19\xca\x08\x60\x14\xc1\x73\xf3\x64\xd8\xa7\x99\xc6\xa7\xfd\x70\x1b\x31\x5b\x59\x8f\x3a\xe2\x6a\x8a\x05\x7c\x57\x90\x7a\x50\x0c\xb7\x5e\x66\x1a\xbf\xda\xaf\xb9\x2b\xeb\x0f\xec\xfc\x39\xc6\xc5\x56\x7e\x24\x73\x2f\xa5\xfd\xf6\x4a\xe3\xfe\x41\x03\x7e\xa0\x6f\x2d\xb3\x1e\xd6\xed\x53\x5b\x45\x70\xf0\xd8\x07\x78\xec\xd5\x18\x80\x30\x34\xa4\xb3\xda\xd2\x4b\x6c\x48\x90\x6b\x3d\xe1\xd3\x2b\x56\x3b\xe2\xd7\xaa\xbb\xde\x00\xab\xda\x07\xa0\xa7\xae\x07\x23\x24\x88\xea\xca\xeb\xeb\x01\xe6\xb1\x40\x63\x45\x44\x2a\x89\x70\xb2\xe1\xb6\x90\xa9\xc2\x4b\x9e\x4a\x7f\x10\x54\xb2\xe6\xbc\x3e\xb4\xcb\x71\x2c\x4c\x7b\x62\x3a\x61\xd3\x2b\x8e\x10\x56\x9f\x2d\x72\x4d\x38\x0a\x8c\x8f\x94\xb5\x34\x8e\x6d\x3d\xa3\x8d\x42\x71\x25\x3b\x1d\xf3\xf1\x15\x1b\x21\xd1\xed\xba\xce\x5f\x1b\xa2\x3b\x91\xbd\xc1\xf4\x9a\x8f\x90\xec\xf5\x6a\xdd\xd1\x75\x7f\xb7\x93\x57\xbe\x92\xb1\x37\xd5\x82\x49\xdb\x3b\xf2\x94\x8c\x4c\xa2\xf5\x46\x2d\x23\x1c\xad\xe5\x3a\xc2\x91\x5a\xf2\xb9\x36\xbf\x20\x79\x47\x38\xda\x08\x9b\x34\x6d\x3a\x5b\x30\xcf\x26\xdc\xb8\x50\x60\xe3\xe6\xb1\x09\x8a\x62\xd5\x2f\x0c\xa5\xf1\x31\xfb\xc4\x43\xac\x71\xe4\x3f\x8a\xb0\x59\x40\x73\xbe\xd8\xd4\x86\x88\xd0\x2c\x31\xc0\xd6\x82\xb1\x6d\x62\xb8\x4c\xd8\xb4\xaa\x10\x2e\x59\x13\xac\x87\x91\xeb\x5a\x2f\x19\xdd\x48\xf1\x82\x6a\x1a\x75\x33\x50\xc0\x09\xa2\x27\x6c\x7a\xd4\x50\x62\x23\x76\xfc\x95\xf6\x6d\x9c\x96\x26\xb4\x96\xf3\x22\x02\x6b\x0c\x0b\xbc\x2c\x8f\x80\xa2\xee\xa4\x26\xd7\xdb\x63\xd6\xf0\x09\x9f\x9a\x89\xe4\x53\x17\x0c\x06\x21\x2c\xab\xca\xc6\x30\x6a\xac\xf2\x6c\xdf\x91\xc0\x37\x05\x02\x2f\xdf\x77\x6c\xe0\x4d\x0f\xc0\xdf\xc3\xbb\x1d\x30\x34\xea\x0d\x4e\x09\x91\x9d\x8e\xf0\x3a\x16\x08\x08\x20\xea\xf0\x32\xbb\x5d\xbc\x0f\x5b\xa7\x9d\x31\x80\x34\xdd\xf3\xaf\x75\x1f\x82\x8e\x2e\x59\x68\x77\x10\xec\xee\xe4\x1d\xd3\xfe\x8c\xc1\xe7\x9c\x8b\x58\x02\xe1\x73\x26\x7c\x0a\x1c\x9e\xf5\x05\x20\x84\x88\x7d\x83\xbc\x0c\x02\x95\xb1\x36\x78\xa5\xb3\x55\x38\x20\xd6\x9d\xca\x99\x3f\x6d\x62\x76\x0f\x7b\xe6\xe5\x47\x99\xb3\xe0\x44\x4b\xad\xcd\x78\xb7\xa4\xb9\xbc\x7b\x2b\xa5\x76\x1e\x7e\x8d\x4e\x04\x0e\x74\xb0\x64\x29\x95\x6e\xd9\x34\x36\xed\x25\x3c\xfa\x84\xdd\x5e\x8f\x63\xd1\x9c\xda\xd1\x78\xd0\x47\x18\x26\x23\xf0\x0a\xf9\x32\x42\xa0\xae\x14\xd6\xd7\x2d\xe8\xab\x01\x0c\x4a\x05\xd1\x58\xb8\x25\x3c\x67\x44\x93\x6b\x17\x1d\x0e\x54\x52\xab\xf5\x46\xb3\x1c\x76\xc6\x58\xe3\x3d\x2f\x87\x45\xdb\xf6\x38\x37\x30\x31\x9f\xf9\xc5\xf0\x33\x20\x3a\xf3\xfe\x1c\x6b\x43\x20\x34\x50\x06\x1b\xf6\x0c\x47\x96\xdc\x47\xd8\x46\x58\x0b\xc8\xc2\x8a\xed\xab\xe3\xb6\xd5\x88\x13\x3e\x8e\x7a\x51\x97\xa7\x51\x54\xbb\x4d\x58\x2f\xb6\xc7\xe0\xc5\xb6\xf5\xbe\x46\x6b\xd0\xee\x8a\x89\x9a\x92\xd0\x16\x34\x61\x5d\xf3\xbd\xea\xf2\x29\xda\xed\xfa\x5e\x0d\x28\xac\x0e\x08\x9c\x98\xe6\xba\x2b\xac\x18\x82\x85\x53\x09\x11\x61\xb6\x88\xae\x70\x9b\x53\xa8\xf5\xbd\x6f\xed\xd8\x19\xa8\xac\x52\x8e\x3f\xa1\x38\x4c\x45\x45\x18\x96\x64\xce\x62\x8e\xb0\x22\x91\x95\xd2\x7a\x33\x79\x0f\x22\x43\x32\x93\xf7\xef\xf8\x1f\x86\x31\xa5\x64\x65\x96\x51\xb4\xa6\xb9\xe1\x6a\x22\x84\x4b\x97\x62\xbf\x89\x70\x04\x7d\x8e\x10\xde\xde\xa7\x05\x7e\x48\x33\x3c\x93\xf7\xe9\xb2\x22\x6d\xef\xe2\x66\x79\x0b\xaa\xf9\x2d\xdb\xed\x34\x78\x99\x80\x97\x02\x83\xa5\x6c\x16\xae\x23\xec\x86\xb5\x4e\x39\xde\xca\xf9\x5c\x31\xfd\x6b\xaa\xb0\x7d\xfa\x60\xd8\x0e\xd9\x44\xbd\x20\xa7\x03\xb3\xa4\xe2\x7a\x53\x8a\xb5\x59\xe6\xec\xba\x6f\x90\xed\x94\xef\x76\xa7\x3c\x51\x35\xde\x23\x14\x2b\x4c\x31\x4f\x34\x2d\x17\x4c\x23\x54\x12\x88\x91\x01\x2a\xe3\xda\x40\x0c\xa1\xb8\x3c\x1f\xf7\x75\xc1\x99\xd0\xa0\xe3\x44\xa3\x92\x48\x23\xfb\x31\xa1\x7f\xed\x69\x2b\x1c\x16\x75\xd2\x87\x9e\x06\xe9\x31\x23\xa7\x7e\x46\xb7\xf7\x69\x89\x1f\xd2\x02\x40\x92\x55\x95\x0d\x7a\x93\x83\xeb\xe3\x5c\x77\xe3\x65\xa7\x53\xc2\x23\xc2\x1b\x02\x0c\x80\x4b\x33\x72\xa6\x19\xe5\x16\x80\x9b\xce\xb1\xc5\x80\x74\x51\x35\xc1\x45\x54\xa7\x13\xcf\x7b\x84\x5a\xa4\xe9\x96\x4e\x81\xb8\x30\x49\xb6\x78\xb7\xac\x95\x89\xdb\xfb\x34\xd4\x05\x17\xbd\x1c\x9d\xcf\xcf\xb8\x53\x45\x0a\x84\x1f\x5a\xf9\x59\x6f\x83\xce\x17\x67\xdc\xeb\x27\x05\xf2\x4c\xc2\x0c\x96\x66\x50\x76\xd0\x3f\x03\xff\xd5\x66\xcd\xdc\x1c\xb1\xf6\xc3\x9a\xc4\xca\xe1\xce\x8a\x96\x0b\x2e\x22\x84\x29\xd9\xb0\x58\xd6\x8c\x3d\x44\x5f\x03\x60\xfe\x62\x91\x6a\xb7\xbb\x35\xcc\xb7\x2f\x64\x99\xd6\xa6\x94\x7d\x77\xc5\x0a\xb2\x1f\x1d\xc3\x71\x48\x06\x43\x6a\x97\x5f\xb6\xdb\xd5\xcf\xbc\x59\xa8\x40\x47\x4d\x41\xd5\x38\x0a\xa8\x4f\xe1\x01\xa6\x66\x3c\xca\xad\x05\x7a\x64\x2d\x14\x2e\xa3\x5e\x36\x23\x43\x9e\x21\xb7\x57\xb8\x5f\x3f\x61\x66\x4d\x58\x30\xf7\x0a\xff\xe0\x27\x0e\x0b\x33\x76\xda\x00\x48\xed\x01\x08\xcb\xba\x80\x03\x8e\xda\x07\x0e\x28\x04\x60\x7b\x08\xbe\x84\x56\xc3\x72\x1e\x63\x2d\xc2\x31\x8f\x70\x1c\xfb\xa6\x53\x01\x50\xae\x1b\x4a\xa5\x79\x07\x9c\x06\x7f\xbb\x06\x5b\x33\xff\xf1\xb2\x22\x05\xe8\x34\x32\x29\x34\x13\xfa\x08\x81\x69\xa0\xfd\x09\xaa\xc2\xf6\x09\xd0\x28\xeb\x11\xe6\x70\xde\x5b\x1c\x96\x26\xc9\xe1\xbc\x07\xa6\x27\xaa\x59\x78\xf6\x32\xeb\x29\xfb\x09\xc2\xcb\x30\x5d\x8c\x03\xcf\x84\xec\x5c\xa0\x74\xd9\x53\xf5\xe2\xc9\xc8\x8c\x35\x91\x86\x32\x4c\x71\x11\x4a\xaa\xcb\x56\xf6\x12\x97\x36\xdb\x82\x09\x21\x9c\x75\x3a\xa7\xcb\x4e\x27\x86\x72\xd9\xf9\x10\x21\x7c\x08\xa9\x86\x96\x3f\x1c\x6c\x39\x9f\xb4\x00\x19\x6c\x1e\x60\x4f\xef\x25\xb6\xb5\x2a\x5f\x2b\xad\x88\x1e\x49\xbf\x7b\xd0\x33\x81\xa5\xdb\x61\xd4\x99\xc0\x3a\xc9\xf4\x7d\xd2\xb2\xe9\x08\x70\x76\x83\xbf\x08\x4b\x6b\xf6\xec\x74\x62\x43\x46\xdd\x9b\xab\xac\xd3\xa9\x13\x2c\x38\x21\x0a\x63\x58\x82\xd0\x6e\xb4\xbe\x8f\x70\xab\x18\x51\x90\xe8\x37\xe2\x5b\x76\x78\x8e\xe2\x74\x30\xd2\xe5\x43\xcd\x66\x6d\x17\x4c\x9f\xac\xa9\x52\xfc\x36\x38\x93\xa4\xc9\x69\x1f\x9f\x0e\xaa\x6a\xe4\x78\x04\x9a\xe7\xdf\xdc\x32\xa1\x7f\x70\xac\x61\x1c\x69\xf0\xda\xb2\xb1\x34\x11\xae\x03\xcd\x1a\x21\xf9\x73\x25\xab\x8c\x82\x35\x05\x6d\xc3\x33\xc5\x0d\x6d\xbb\xdb\xe3\x56\x1d\xeb\x61\xb6\xb2\x4e\x87\x27\x2b\xf8\xfa\xfc\x7f\xc5\xff\xca\xbb\x28\xfe\x57\x62\x7e\xc6\xeb\xfb\x2f\xce\x1b\xf3\xf7\xb8\x2b\x26\x83\x69\xea\xc2\x9c\x36\x31\xa9\x5a\x3c\x4c\x64\x37\xc9\x88\x8b\x13\x3d\xde\xde\xa7\x3a\xb9\xc7\x0f\xa9\x4e\x1e\xaa\xd4\x6d\xf5\x41\x64\xa9\x7d\x72\x6b\xfe\xea\x52\x16\x05\x2b\x53\x89\x73\xaa\x69\xaa\xf0\x8d\x92\xa5\x66\x39\xe0\x04\x36\xfb\xd8\x4d\x46\xb3\x25\xcb\x5f\x31\x4d\x13\xfe\x2e\xa3\x05\x33\x4b\xb5\xec\x74\x0c\x6b\x5a\x26\xf4\x9e\xab\x4e\x87\x76\x3a\x75\x7c\xc1\x7a\xa5\x96\xc9\x4d\xc9\x6e\x59\xa9\x2c\x26\xaa\xb1\x62\xa9\x84\xaf\x4f\x1b\x96\x36\x56\x96\x2c\xf0\x79\x2c\x93\x1b\xb5\xa4\x25\xcb\x5f\xaf\x4d\x87\x55\x83\xd8\x6a\xd2\x9f\x62\x49\x8e\xc8\x0e\xc2\xd0\xde\xb7\x54\x2c\x98\xe1\x0a\xfc\xb3\x61\xf2\x4d\x85\xc1\xd2\xb0\xed\xf4\xa4\x21\xca\xee\xa5\x2b\x51\x20\xe3\x8a\xa4\x90\x46\xcc\xa5\xc9\x92\x57\x55\x55\x35\x39\x7d\x93\xdc\x04\x15\x0d\x96\xe0\x3b\x16\x2a\x6d\xfd\x1e\x01\xd6\xd1\x77\x00\xc6\x9f\xb9\xe2\xb3\x82\xbd\xb0\x9e\x9f\x06\x86\x0a\xb6\x05\xf0\x8d\xf5\x1c\xa1\x26\x7d\xcc\x6b\x4b\xf1\x48\x5f\xf1\x51\xb7\xeb\x45\x88\x2d\x30\xc6\x29\xb7\xf3\x53\x56\x44\x4d\xf4\x14\x9b\x6e\x15\xa6\x5b\x59\x45\x3e\xb2\x18\xd2\x18\xa6\x58\xa2\xa0\xd6\x62\xa4\xaf\x48\x16\xd4\x75\xc2\x48\x39\xd1\xd3\x11\x4b\xd4\x47\xbe\xde\xed\x6c\x9c\x30\x8d\xaa\x60\x48\x6f\x8e\x6c\xca\x93\x29\x4c\xda\xdf\x75\xcc\xb0\xf3\x4f\x7d\x5e\x32\x8a\x75\x72\xb3\xe2\xe2\x8d\xa5\xb8\xb5\xb1\x53\x7a\x14\x7e\x67\x05\x7f\x16\x84\x84\x36\xef\xca\x08\xcf\x5c\xb8\x89\xb2\x4a\x65\x2c\x50\xa7\xe3\x64\x97\xad\x73\x6b\x4d\x35\x0e\x8f\x52\xa4\x1c\x5b\x50\x28\x23\x19\x82\x56\x24\x90\x6f\x5e\x1c\x9d\x89\x63\xc7\xbe\xf7\xe5\x8d\xfb\x08\x61\x7e\x90\xfa\x10\xa1\x83\x83\x5a\xba\x15\x5e\x78\xdc\xb8\x5f\x25\xf7\x3d\x91\xdc\xa3\xb4\x8f\x95\xb7\xb2\xdb\xf4\x87\x9e\x48\x1e\xf6\xcd\xec\x6d\x77\x1d\xd9\x72\xd4\x51\xe0\xa2\x53\xc5\x76\x9f\x3c\xa1\x9f\xf6\xc1\x2b\xff\x87\xb3\x52\xfe\xc9\xac\x58\x95\xae\xe8\x74\x4e\x0f\x27\x47\xee\x9d\x1a\xc8\x2c\x8a\x7f\x0d\xd1\x9f\xdf\x48\x2e\x74\x2c\xcd\xf6\xa6\x62\x86\x33\x34\x5a\x5e\xd1\x71\x5c\x92\xc9\x9f\x4d\x64\x51\x4d\x31\x25\x4b\x94\x2e\x09\x21\xd4\xb0\xb3\x7f\x69\xfe\x0b\xd0\x0c\xe0\x32\x08\xdc\x76\x04\x67\x5f\x31\x38\x96\x80\x21\x46\x29\x25\x1c\x08\x15\x2e\x49\x04\x9c\x05\x1d\x47\x5c\xfc\x0a\xa3\x8c\xd2\x88\x8b\x0f\xf6\xd1\xaa\xf9\x49\xed\x51\x7f\xf2\x29\xd9\xe4\xf3\x0b\x7c\xd4\xf6\x9c\xf3\x6b\x9c\x36\x5e\x2c\x1a\x0e\x7b\x68\xb4\x8d\xdd\xfa\x16\x76\x7d\xcb\x0a\x4e\x62\xa0\x3d\xda\x20\xf7\x69\x83\x22\x12\x0e\xc5\xb8\x85\xcc\x62\x85\x05\x2c\xe4\x58\xe3\x46\xc8\xd9\xea\x49\x39\x8d\xe5\x84\x4e\x61\x7d\xa9\xcf\xc3\x97\x39\xf8\xf2\x0a\xe1\x06\x07\x64\x72\x8f\xa5\x5b\xa0\x31\xc4\x59\x86\x13\xf1\x49\xed\xe7\xdc\xe9\x9c\x16\xe3\xc9\x34\x55\xe0\x76\xff\x9a\x59\x8f\x68\x95\xda\x81\x7d\x6e\x62\xec\x9c\xec\x76\xd1\x7d\x04\xb0\xa9\xab\x1c\x03\x11\x72\x87\x7b\x5e\xf8\xe7\xd3\x01\x06\xdf\xb1\x46\xf7\xe1\x2d\x12\xe3\xf8\xcf\x66\xa4\xad\x24\xf1\xa4\x80\x4e\xfa\xee\x58\x12\xb0\xb9\x06\x1a\x86\x2e\xf3\x4e\xc7\x48\x83\x1f\xf9\xfa\x10\x27\x79\x1b\x66\xda\x7f\x6e\x21\xe8\x30\x13\xa5\x93\x69\xe5\x0b\xfe\x35\x08\x3c\x44\x6e\xc1\xff\x35\x30\x98\x95\x4f\x6b\x7d\x15\x6a\x8f\x28\xec\x21\x76\xd8\x1a\x80\x23\x66\xd6\xc3\x6b\x04\xf1\xee\x03\x44\x33\xd8\xd5\xd8\x78\x34\xa2\xfb\x63\x37\x28\x77\x14\x65\x74\x15\xb8\x28\x61\x70\xca\x49\xeb\x71\x93\x6b\x18\x87\x1f\x75\x38\x66\x33\xa1\xce\x73\x3e\x2c\xff\xe2\x93\xe5\x03\xe8\x98\x6f\xef\xc3\xaf\x62\x5b\x12\xdc\xf4\x03\x8a\x60\x44\xd6\x63\xa5\x1e\xda\xa5\xaa\xca\x11\xb7\xe7\x56\x67\xf7\x96\x2d\xbe\xb9\x5f\x1b\x16\xcd\x9e\x5e\xd8\x19\x56\x2d\x1e\xa7\x8e\x57\x43\xf1\xfa\x7e\xc7\x56\xbb\x2f\xd1\x18\x7d\x71\x8e\xf0\xfb\x4f\x7c\xc5\x35\x2d\x78\xb6\x73\x31\xb9\x77\x5c\x2c\x59\xc9\xf5\x6e\x23\x14\xd3\xbb\x58\xce\x0a\xfe\xfb\x86\xc5\x27\xbd\xf1\xa4\xdf\x7b\x36\xb5\x7f\x21\x5c\x2b\x32\xd5\x06\x01\x79\xf6\xf8\xc9\x38\x8a\xba\x1a\x39\x3e\xf2\xb9\x65\x77\x4e\xf9\x6e\xe7\xcf\x5a\x18\xa9\x75\x32\x98\x7a\xea\x3f\x48\x86\x67\x6c\xe4\x5d\x80\x48\x17\x62\x3b\xf2\xc9\xa3\x29\xda\x82\x1d\x7f\x7d\x1f\xa5\x8d\x92\xd1\xa4\x7c\x19\xa5\xfa\x9c\x0c\xfa\xb5\xd2\x89\x9d\x85\x4e\xc2\x7b\xdd\xd9\x56\x58\x90\x9f\x62\x66\xe4\x4c\x31\x6e\x1f\x78\x4b\x19\x56\x04\xdc\xe6\xc5\x98\x93\xeb\xef\x41\xc3\x89\xf5\x84\x4d\xf8\x74\x8a\x52\x46\xae\xf5\x84\x4d\xd3\x18\x91\x6b\x1d\x1c\x78\xd2\x27\x72\x7e\x22\x91\x41\x3a\xd2\x55\x10\x7c\xa3\x7f\xc4\x29\xf7\x9b\xd6\x81\x76\xd3\xad\xad\x96\xeb\xd4\x4c\x2f\x68\xc5\x52\x83\x0e\x56\x11\x06\x89\x05\x9b\x43\x5a\x78\x4c\xee\xbb\x83\x3a\x40\xef\xf7\x03\x9b\xdb\xf3\x31\xd6\x1c\x53\xab\xff\x5c\x7a\x60\xfa\x89\xc2\xa3\xa7\x7f\xb4\xf4\xbf\xd0\xbf\x46\x59\x6b\xc5\x1b\xeb\x30\xd0\x75\xfe\x03\xb8\x96\x85\xc0\x71\xa0\xeb\xfd\x08\x42\x05\xeb\x4b\x6f\x76\x20\x1a\x5c\x32\xc1\x0d\xef\xde\x9a\x87\xdd\x31\xb8\xef\x9d\x8f\x2a\x76\x87\xc8\x46\x07\xea\x57\x6e\x64\xb4\x46\xff\xca\xf1\xa0\xef\xee\xbf\x10\xf6\x6b\x30\x28\x32\xfb\x8b\x46\x86\x19\x30\x78\x26\x3c\x9e\xbd\x67\x66\x13\x30\x23\x93\x46\x52\xa3\xa5\x88\xff\xf6\x9d\x80\x10\x1d\x27\xa6\x23\x27\xf0\xe1\x89\x5a\xb3\x8c\xcf\x39\xcb\xd3\x93\xe8\x6f\x5d\xd1\xfd\x5b\xf4\x37\x23\xf8\x44\x51\x13\x0d\xd5\x9f\x2e\xfa\xbe\x76\xa6\xc5\xcc\x7b\xd5\x86\xc7\x84\xde\xb2\xf8\x7b\x67\x1c\x76\x6a\x0b\x16\xbc\x20\xcc\x91\x3d\x6b\xc4\xdd\x51\x23\xe1\x0f\x15\x7d\x5f\x3b\xdf\x62\xe6\x1e\x10\xb6\xf0\x48\xa3\xa8\x0e\x5e\xe4\x0d\xeb\xe4\x0b\x60\x61\x02\x86\xf2\x67\x76\xd4\xfb\x90\x9c\x5a\xcb\x99\x24\x86\xd9\xab\x55\xf8\xf2\x4a\xc1\x59\x5a\x43\x96\x09\x1c\x6e\xae\x8f\x01\xd2\x4e\x27\xae\x5f\xd8\xd1\x03\xda\xa6\x08\x25\x86\x30\x9b\x06\x06\xa8\xf9\x98\x77\x3a\x1f\x62\x8a\x6c\xfe\x84\x7f\xe9\x69\xfe\x74\xbf\x20\xad\x19\x3d\x33\x6b\x25\x5c\x99\x00\x82\x1b\x9d\x15\x0c\x8a\x06\xbe\xa3\x5f\xb5\xb4\xc0\x2b\x2e\xac\xfa\x26\x15\x15\xd1\x5e\x22\x82\xd4\x5e\xcd\xcb\xfe\x06\x87\x25\x91\x2d\xd6\x35\x6f\x8d\xaa\xef\x17\x46\x26\x56\x21\x8e\x8f\xab\xcb\x03\x3d\xf9\xb7\x6d\x15\x3c\x18\xb2\x35\x2b\x61\x5f\xd6\xc9\x5a\x2a\x38\xe0\x19\x46\x53\xf8\xcc\x07\x70\x68\xf4\x17\xd6\x9c\x2c\x36\x15\xa0\x4e\x47\x27\x33\x79\x6f\xc9\xfc\x5e\x6d\xff\xd9\xaf\xad\x3e\xb0\x01\xfc\x52\x7d\x28\x76\xcc\x53\x8d\x0d\x93\xaf\xd3\xfa\xae\x16\xe1\xf0\x08\x14\x53\xce\xb1\xdb\x99\x95\x7a\xee\x94\x77\xea\xcb\xf4\x7c\x89\x2a\x6c\xfd\x0b\xb6\x1f\xf8\xb8\x71\x5a\x07\xa2\x08\x26\x8e\x6e\x90\x26\x4c\x9a\x08\xa9\xcb\x87\xda\xfe\xa8\xe5\xba\x51\x4d\x69\xe7\x90\xe4\x1c\x8f\xcc\x6c\x84\x99\xce\x3f\xa9\x70\xc6\x73\x3b\x2f\x61\x01\x4f\x71\xdc\x03\xf8\x3b\xc1\x60\x83\x32\x9e\x58\xc1\x6f\x18\xec\xe1\x40\x11\xc5\xcd\x04\x80\x15\x77\x45\xef\x9d\x24\x32\xfa\x29\xe6\x30\x3f\xbb\x5d\xcc\x81\x42\x75\x3a\x66\xd4\x26\x6d\xda\x23\x36\xc9\x6c\xf7\x60\xf5\xe2\xc9\x52\x96\xfc\x0f\x29\x34\x2d\xc6\xde\xb0\x91\x0a\xef\x39\xec\x3e\xeb\xd6\x9f\x81\xec\xef\x5a\xea\x74\x3e\xb0\x58\xb6\x92\xe2\x3a\x12\xb3\x0a\xd5\x79\x2c\x91\x1b\xed\xdc\xce\x7a\x5f\x98\x8f\x34\xf6\x98\x6c\x71\x18\x21\xef\x22\xd7\xfa\xc2\xd2\x9f\xfa\x13\x8b\xf5\x0e\xdb\x0d\x07\x48\x14\x88\x93\x77\xb8\x20\x14\x9e\x9a\xf0\xf9\xc9\x1d\x51\x58\x27\x4b\x42\x71\x6b\x90\x5b\x45\x57\x2c\x2d\xb1\xd4\x4b\x56\xa6\x45\x95\xda\x84\xc2\x25\x94\x81\x8c\xfe\x2b\xdb\x3f\x88\x12\x80\xb9\x71\x09\x08\xe3\x0e\x6d\x61\xdf\xeb\x63\xb3\x37\xf6\xdd\xce\xd8\xf7\xfb\x62\xbf\x6a\x7a\xd7\x62\x94\x85\xd9\x7c\x9b\x73\x15\x86\xff\x03\xf1\xc4\x70\xb9\x75\x24\x20\x11\xeb\xf1\xa4\x0d\xb5\x69\x3a\x69\xc3\x24\xc4\xe0\xdf\x0f\xd0\x65\x32\x1d\xb5\x1c\xbc\x9d\x9f\x55\x9b\xc6\x82\x17\x92\xa7\xb3\x5b\x47\x65\x4b\x42\x01\xd5\xca\xc4\x5d\x75\xe3\xac\x1c\xbb\x1d\x4b\xee\xb0\xb7\x6f\x98\xb7\x25\xfe\xd5\xe4\x36\x00\xc7\xcc\xe3\x84\x05\xb4\x72\x80\x5e\x56\xe4\x27\x7b\x93\x14\x45\xa3\x62\x47\x54\x63\x6e\xc2\x19\xc9\x76\xbb\x25\x2e\x93\xf9\xa6\x28\xde\xf1\x3f\xd8\x6e\x27\x2c\x93\x4c\x6b\x06\xb8\xe8\x74\x7e\x67\xb1\x80\x11\xee\x76\xc1\x81\x95\xef\x8f\x2c\x13\xa7\xad\x6e\x0f\x9f\x18\xb9\x3b\x23\x2c\x79\xf8\xdc\x56\xb3\x07\x81\x70\x68\xe3\xd8\x5a\x8b\x48\xd3\xd1\xb1\xb5\x15\xa6\xce\x55\xb1\x74\xab\x3b\x28\xc0\xc3\xb5\xe0\x4c\x8a\x69\xcd\xa8\xdc\x61\x30\x36\x91\x0c\x97\x9e\x7a\x64\xb5\xd9\x08\x3b\x13\x05\x71\xd5\xf6\x6c\xf3\x38\x23\xbe\x30\x4a\x7d\x97\x8a\xba\xed\xa2\x36\x45\xd9\xaa\x5b\x9d\x35\x78\x6a\x9d\x2c\xeb\xf6\x0e\xfb\xea\x56\x61\xd3\x59\xcb\x3d\x99\x0a\x1d\x43\xe5\x3f\xee\x41\x13\xb8\xf0\x3d\x44\x23\x96\xdc\x93\x02\xb3\xe4\x81\x64\xd5\xbd\x86\x9b\x6f\xa2\x82\x3e\xc0\x39\xe9\xad\x9b\x96\x74\x7b\x7c\xb9\x58\x26\xb2\x5f\x55\x08\x82\x9e\xfd\x9d\x91\x2d\xcd\xf3\xaf\xe4\xbd\x27\xcd\x33\x79\xcf\x14\xb8\xb5\xc1\x13\x99\x4c\x91\xe1\x6f\x5c\xf7\x09\x0b\xf0\x07\x2e\x16\x5a\x4b\xc5\xb5\xbd\x83\xc6\x3f\xee\x76\x76\x05\x79\xee\x85\xf8\x87\xdd\xae\x6f\x6f\x14\x7a\x60\xa5\x22\xf5\xd3\x6e\x77\x10\x50\x68\xb2\xfd\x23\xed\x63\xb8\xf2\x48\xa3\x2d\x4b\xdc\x53\x55\x4d\x2b\xec\x7a\xe6\xdd\x54\x2a\x6c\x55\xde\xf5\x18\x1a\x55\x07\x94\x1b\xfb\xf2\x8d\x17\x45\xda\x1b\x58\x47\x0a\xee\x36\x5c\x56\x1f\x59\xe1\x78\x80\x2a\xec\x9d\x4a\x6a\xb4\x0f\x20\xc0\xeb\xc7\x70\xf4\xbc\x7e\x6c\x86\xcd\xfd\x46\x8a\xfd\x65\x56\xa1\x7f\xa9\xde\x8f\x15\x62\x98\xef\x44\x5a\xfd\x71\x62\xe7\xd3\x2f\x32\x23\x70\xb3\x9e\xb3\x6c\x60\x4a\x78\x4f\xd6\xf8\x7b\x54\x53\x78\x2c\xcd\x91\x2a\xd0\x33\x06\xbe\x1c\x31\x1c\x80\x42\xfb\x1e\x1d\x2e\x56\x8c\x3b\x21\x5a\xab\x72\x67\xf2\x3e\x95\x78\x2d\x55\x2a\x9b\x11\x37\xcb\x37\x95\x09\x57\xdf\xd6\xaf\x31\xf2\x4c\x6d\xc3\x54\x34\x01\x5a\x6c\xe0\xbb\xff\xb0\x98\xb5\x59\x29\xc3\x01\x79\x18\x23\xab\x2e\x15\xa6\xd8\xb7\x86\xb6\x59\x6a\xed\x94\xa8\x4d\x6a\xbd\xe5\xa9\x26\xcd\x20\xa1\x2d\x48\x9b\xc4\x70\xa7\xfb\x11\x52\xee\xc1\x98\x69\x9f\x1b\xdd\xe9\xd6\xf7\x20\xe5\xb0\x66\x9e\x8b\xfc\xbd\x5c\xa7\x22\xc9\xa4\xc8\xa8\x06\xd3\xa8\x69\xf3\xb9\xc8\xbf\xb2\x6b\x4b\xfa\xac\x02\xf9\x27\x5a\x3f\x95\x08\xd7\x6a\xce\xd4\xf6\xa4\x7e\x8f\x10\xbe\x65\xa5\xe6\x19\x2d\x9a\xfa\x25\x6a\xaa\x0b\x01\xac\x8e\xd5\x5d\x55\x7e\xbd\x22\x20\x14\xbe\x3a\x20\x63\xcd\xc7\xa3\x7f\xf8\x62\xf8\x93\x0e\x4d\xc9\x8c\xcd\x65\xc9\x7e\x00\xfc\x83\xe5\x11\xbc\xc3\xe5\x6c\x0e\x61\x97\xa4\xd8\xbf\x6f\x8a\xc1\xd4\x39\x14\xee\x74\x4e\x07\xe0\x82\x13\xa4\x25\x39\x57\xeb\x82\x3e\x8c\x75\xaa\xbb\x70\x91\xc9\x6e\x37\xc0\xb9\x0f\xae\x31\x2f\x19\xfb\x83\xc5\xdb\x86\x9a\xa7\x0c\x07\xe4\x32\xe5\xd8\x93\x37\x89\xe9\x2d\xe5\x85\x11\x11\x7e\x71\x16\xc1\x3a\xc1\x15\xa6\xf8\xf6\x2b\x79\xff\xca\xdb\x76\xd5\xf9\xf0\x7c\x89\x97\x36\xc9\x17\x39\x1f\x56\x08\x6f\xc8\xc1\xa1\x78\x89\x46\x1f\x58\xbc\xc1\x7f\xb0\x58\xa0\xe6\x48\xdc\x5e\xb9\x86\x75\x49\x37\xf8\x2e\x55\x78\x99\x52\x7c\x9f\x4a\xbb\x85\x3c\xa4\xd2\x50\x6e\xa8\x6c\xef\x9a\xcc\x63\xeb\xf0\xc0\xa7\xca\xad\x40\xd9\xda\x1d\xbd\x75\x53\xb6\x56\x49\xa7\xc3\x92\x36\x3c\xb0\x6c\x04\xf3\xd6\x98\x51\x5a\xd7\xc1\x92\x10\x40\xcd\x17\x9f\xa9\xdb\xd5\x51\xc5\x85\x47\xbe\x0c\xe1\x1c\xe1\xdf\x59\xdc\xec\x71\x78\xee\x92\x8a\xfa\x29\x83\x27\x60\x2f\x5c\xe2\x31\x0a\xa5\x8f\x32\x83\x3c\x0e\xf8\x8e\x80\xa3\xe3\xd3\x1e\x00\xa8\xdf\xb8\xf0\x4d\xf8\xb4\x4b\x04\x16\x95\x4e\x1e\xba\x84\xc7\x8e\x04\xe8\xe4\x1e\xde\x1c\xe9\xe0\xb1\x27\x17\xe6\xd1\x13\x84\x2a\x9e\x23\xfc\x3d\x73\x9b\xbd\x5d\xee\xae\xab\xe6\xf3\x79\x72\x87\xe7\xa6\xd6\x79\xb2\xb4\xc5\xda\xcb\xdf\x16\x0d\x8c\x19\x8e\x6f\x9d\x5b\x64\x30\xbb\xf1\xdc\x32\x05\x80\x79\x36\xb9\x6b\x6a\x75\x5b\x33\xe4\x76\x4d\xed\x4e\x62\x30\x8f\xce\x37\x26\xb9\xab\xf0\x3f\xe2\x32\xb0\x94\xb4\xbc\x26\x61\x8d\x8d\xda\xc8\xc9\xc3\xbe\x18\xd9\xc4\xed\x41\x76\x1c\x4b\xb3\x90\xab\x6a\x64\xc3\xef\xfe\xc6\xb6\x34\xfb\x7d\xc3\x4b\xe6\x0f\x46\x02\x96\x56\xa5\xbd\x69\xac\x4e\xac\x8f\x15\x0f\xaa\x03\x6b\xb4\xdb\x28\xab\x63\xf6\x67\x9f\x77\x34\x1e\x8c\x17\x27\x07\x26\xfb\x15\xbd\xe7\xab\xcd\xca\x60\xd1\x81\xbc\xc9\x5a\xc2\xcc\x6e\xa7\xbd\x9b\x03\xb8\x0d\x7b\x9f\x08\xbc\xef\xde\xf1\x29\x17\x08\x76\x2e\x50\xca\x51\x55\x71\xf5\x5c\x6b\x30\x14\x07\x23\xec\x57\x2e\x34\xf1\x3f\xd9\x09\xbb\xd7\x4c\xe4\x47\xc1\xd4\x88\xe3\x86\x54\x36\x27\x4b\xdb\x6f\xf6\x9c\xe9\x6e\x07\xe1\x62\x9c\xe6\xe1\x1f\x8c\x6c\x9b\x58\x36\xa9\x0d\x75\x93\xcb\x3b\x11\xe1\x3a\xaa\x4d\x1a\x06\xc3\xb1\x5e\x64\x22\x77\x89\x9b\x75\x64\xb5\xd5\xac\x04\x5b\x96\x4b\x76\x97\xa4\xba\x9c\x1c\xa2\xd5\x07\x55\xbb\xf4\x83\xca\x5d\xfa\x66\x7d\x58\x7b\xc1\x68\x5d\x18\x58\x4c\x97\x2e\x37\x3a\x48\xad\xb0\xe6\x44\x93\x6b\x7b\xd2\x98\xe8\xdd\x0e\x02\x5e\xd5\xe1\x9a\x39\x39\x3d\xbd\x65\x9d\xce\xd6\x39\x3a\xa4\xa7\xfd\x2a\x58\xe2\xbc\xbe\xac\x31\xb1\x9e\x1e\x47\xfd\x18\x0c\x3e\xb0\xf0\x6a\x24\xc1\x8f\xf8\x91\xc0\xf7\xd6\xd9\x2e\x37\xe4\x7b\xb7\x13\xd8\xdd\xcd\xba\xb1\x87\x53\x5e\xcf\x14\x2b\x6f\x1d\xbf\x51\x53\x9f\x9c\xc5\x12\x8d\xf6\xc4\xc8\x20\xe2\xd9\x48\x5c\xe9\x84\xe6\x39\xcb\x7f\x94\x39\x53\xad\x58\xf0\x8d\xfd\xbc\x29\x30\x11\xd3\x91\xb2\xde\xba\xe6\x87\xed\x76\x3c\xd6\xde\x2d\xaf\x32\xeb\xaf\x89\xc9\xa1\x12\x69\xfb\x14\xe7\x32\xdb\xac\x98\xd0\x78\x9b\x2d\x79\x91\x9b\xb1\xa7\xa7\x7d\xac\x36\x33\x5d\x32\x00\x1b\xc2\x2a\x88\x15\xf7\x57\x20\x00\x1a\x76\x79\x70\xdd\xe9\x27\x21\xf2\x09\x18\x30\xd2\x1f\xb1\x2b\xed\xa6\xa6\x0d\x05\x66\x4f\xb0\xb7\x33\x27\x6c\x0a\x3e\xc0\x5b\xee\x4f\x51\x7e\x72\xd8\xb2\x3d\x5e\x18\xa4\xeb\x28\xf7\xf7\xea\x5a\x33\x13\x84\xc1\xa8\x43\xb4\xf1\x20\xea\x8d\xf3\x80\xd9\x0f\x4d\x35\xd2\xa7\x84\x50\xde\xe9\xc4\x94\x13\x8d\x15\x0f\xee\x89\x75\xc6\x46\xfe\x29\xef\x23\x1b\xfc\x93\xc5\x7b\x0e\xdd\x05\x07\xc3\x73\x60\x18\xab\xa1\xae\xcc\x7c\x5b\x54\x32\x50\x57\x6d\xa8\x53\xd2\x70\x4b\x35\xf9\x56\xa1\xd7\xda\x48\xb8\xd0\x60\x57\xad\xe4\x4e\x47\x98\x4e\x38\x2f\x1f\xc3\xbc\x5a\x9b\x8d\xe2\x7f\xb0\xe3\xe8\x0c\x81\xeb\xcd\xde\xe0\x3c\xd3\xde\x9a\xcd\xc1\x0a\x10\x62\x2f\xd5\x52\xca\x91\xbb\xde\xd5\xfc\x88\xdd\x8e\xc2\x91\xc8\x60\xb2\xca\x7a\xb2\x54\xb8\x7d\x9b\x9d\x42\x59\x5d\xd6\x6e\xf7\x49\x5f\xa5\x12\x7a\x1a\xe1\x12\xd4\xde\x89\x0f\xd3\x54\xc5\x1a\xd3\x96\x41\x3c\xab\x11\x1a\xbc\x8d\x72\xae\x32\x29\x84\xf5\x49\xf4\xb5\xd8\x28\xee\x21\x07\xa1\x78\x73\x0d\x27\xde\xef\xcd\x51\x97\xa8\xa0\x43\x55\x2b\x56\xcd\xf2\x73\x73\xcb\xac\x4b\xbd\x8f\x0b\x9b\x64\xfa\xde\xcc\xcc\x71\x5b\xfb\x3f\xd8\x44\x27\x86\xaf\x9e\x9a\xed\xc9\x3c\xe0\xed\x7d\x2a\x0c\x63\x58\x11\xe7\xe3\xe4\xa5\x0d\x93\x9b\x72\x2b\x23\xa4\x0c\x5b\xdf\xa8\x54\xe3\xfb\xb4\xd6\x93\x8b\xb1\x80\x80\x63\xf8\xa1\x49\x93\x63\x69\x83\x90\x55\x60\x71\x34\x18\xa2\xf1\x01\x1a\xf8\xdb\x02\x8d\xc4\xea\xdc\x8d\xeb\xa7\x0f\xd3\x60\x8a\xf7\xbd\x4b\xf5\xe1\x4c\x7a\x1a\x1c\x4b\xf0\x87\x31\xcb\x14\xf6\xc9\x9c\x7f\x6e\x9f\x6c\xc9\xe5\x7f\xbe\x55\xd6\xc6\x32\x83\x02\x16\xf8\xf6\xa2\xee\x4f\x39\x35\x58\xf3\x8f\xb0\x06\xe3\xe7\xda\xde\x62\xcf\xe2\x68\xe9\x1c\x44\x41\xa1\xdb\xce\x72\xde\x97\x10\xe4\x3c\xf9\xc2\x9d\x85\x20\x5b\x7f\x05\xf1\xd6\xeb\x6d\x1d\x0f\x26\x9d\x99\x66\xeb\x44\x98\x94\x7b\x61\xa6\xf6\x23\xf5\x1c\x88\xfd\xc0\x39\x1e\x57\x55\x85\xeb\xa2\xa4\x7e\xda\xed\xa2\x59\x21\xb3\x8f\x11\xe6\x8d\xa7\x28\x09\x9e\x4d\x81\xc6\x65\x1d\x6b\x1e\xcb\x20\x7a\x2a\xf8\xe8\xd5\x43\x08\x6d\x34\xb1\xf6\xdc\x3d\xaa\xcc\xd0\xb8\x11\x60\x7c\xfc\x49\xdd\xf2\x5f\x44\x7e\xe5\x7b\xbf\xdd\xf3\x98\xed\x76\x43\x14\xba\x8a\xfb\xa6\x3c\x20\xf7\xdb\xf2\x42\x86\xf5\xdf\xb0\x21\x0d\x01\x21\x0f\x79\xc7\x86\xcb\xb7\x33\x6a\x6f\x24\xac\x41\xbf\x7f\xc7\xb0\x21\x5d\x3e\xcf\x5f\x51\x3d\x9a\xf8\x8e\xf8\xc1\x4f\x8f\xb9\x47\x08\x1b\x13\xf4\x8b\x58\xa0\x31\x73\xcb\xbf\x99\x79\x8d\x52\xb8\x2e\x3a\x48\x71\x84\xae\x51\x6e\x02\x98\x76\xbb\x6d\xad\x5d\x0e\xed\xc2\x62\xcf\x27\xc3\x59\x1d\x27\x7a\x4a\xc4\x44\x4f\xed\xbd\xd9\x5e\xc4\xb2\x14\xd7\x1d\xba\x69\x46\x84\x4f\xfb\x9f\xe4\xa1\x5d\x64\xbc\xa3\x7c\x74\xd3\x49\x9d\x7c\xb1\x2e\xe5\x3d\x77\xda\x3a\xff\x42\xb6\x95\xc1\xf6\x2d\x05\x9e\x36\x15\x1c\xe7\x0c\x9e\x24\xc7\x96\xe6\xa5\x05\xaf\x26\x86\x26\x2d\xb9\xbd\xb8\x45\xc6\x75\xc4\xd6\xe3\x6d\x06\x0b\xed\xd3\x6d\xba\xa8\x99\xa1\x8b\xe4\x28\xf6\xdd\xc8\xea\x6e\x64\x75\x37\x32\xd7\x0d\xce\x11\x74\x40\x20\x6c\x6a\xf0\xb7\xe3\x7f\x56\x58\xf8\xc4\x56\xff\x67\x22\x44\xe0\xb6\xbf\xc7\xf6\x07\x4c\x60\x73\x93\xb5\xbd\x83\x3a\x67\x31\x33\xc2\xd2\x2d\x2d\x4f\x36\x7c\x5f\x57\x51\x07\x10\xb6\xb4\xf9\x2b\xaa\xd8\x1b\x17\xff\x32\xfd\x8d\x99\x77\x9e\xd5\x09\xff\x64\xf8\x85\x5c\xd5\xaf\x39\xaf\xe3\xd9\xcd\x81\x7b\xee\x5b\xd6\x79\x60\x7e\xf0\x82\x37\x07\x11\x7b\xb5\x9b\xdd\x10\x0f\xfa\x67\xb1\xee\x91\x01\x42\x67\x75\x50\x02\x88\xa3\x76\x76\xa3\xcf\x39\xc2\x6b\xbe\x77\x27\xb1\xfd\xac\x07\xa7\x18\x8e\x7e\xd2\x1d\xe0\x15\x27\xdb\x82\x0b\x46\xcb\x54\x93\x6b\x8d\xcd\xda\xfd\x4e\xfc\x73\x43\x73\x78\x3f\xb3\x29\xaf\x37\xda\x27\xf5\xb4\xe9\xc5\x10\xb9\x92\x41\x4e\xac\xcf\x49\x72\x81\xae\x06\xe3\xe4\xe2\x4c\x9f\xe9\xb4\x97\x5c\x9c\xc5\x3d\xff\x41\x6f\xe0\xbf\xf9\x7a\x33\xe3\x99\xab\xbe\x69\xa0\x4e\xb5\x83\x34\x59\xdd\x41\xd3\x4a\x90\xdd\x6e\xe6\x4c\xa7\xa6\x1d\xf3\xd5\xd0\x7e\x55\x77\xee\x9f\x1b\xb3\xab\xfa\x86\x5a\x63\xb1\xe9\xbd\xb8\x69\xec\x4c\x37\x3d\x0c\xcb\x1c\xb6\xe7\x87\xd6\x34\x79\x16\x40\xe4\x9f\x1b\x2e\xc2\x46\xc3\x66\x5d\x4e\xd8\x68\x7b\x94\x41\x91\xc3\x56\x0f\x46\xda\x1e\xed\x3b\x2e\x20\x04\xe3\xa0\x89\x33\xa3\xcf\x5e\x69\xe4\x9b\xf7\xf9\x35\x26\x34\xb9\xd0\xb6\xcf\x87\xb1\xd5\x55\xcc\xf4\x99\x0e\xe6\xee\x9b\xfb\xb5\x4c\x3d\xc6\x8e\xfb\xe9\x01\x7e\x0e\x50\xdd\xa0\x2f\x0b\x68\x3d\x1e\xa4\x83\xde\x01\x5a\x36\xad\xfb\xc2\x73\x6e\x23\xdc\xeb\xab\xe4\xc2\x8c\x7d\xaf\x81\x21\x4c\x13\x02\x40\x0c\xf7\xeb\xf3\xb9\x35\xa2\xf1\xd2\xe2\xd9\x35\x19\x8c\x75\xda\x8b\x1b\x67\xd6\x41\x2f\x1c\x97\xc1\x2f\x57\x36\x2c\xe2\xe7\x29\xe8\xa6\x2f\xd6\xcc\x4f\x03\xae\x76\xc5\xe9\x7e\xba\x9f\x37\xd4\x6d\xa0\x59\x50\xa5\x2d\x5a\xfb\x71\x2f\x0c\x1b\x9a\xf4\x9f\x5e\xe0\xe4\x51\x03\xc9\xc3\x72\xeb\x83\x72\x16\x8c\xb6\x64\x48\xda\x92\xc1\x60\x78\x51\x73\x7a\x7b\xe0\x5d\x70\x03\x34\xcc\x70\xf2\xf8\xc2\x74\xb9\x9b\x5c\x9c\xad\xb9\x05\xa4\x4b\xad\x5c\xed\x5f\xd1\xac\x75\xd3\xf3\x20\x79\xda\x1f\x5c\x5c\xd6\x4a\xb8\x33\x7d\x16\xc7\xac\x6b\x00\xd6\x63\xee\xab\xd7\x1b\xfd\xf9\xcf\xea\xa5\xe0\x3f\xed\x32\xd4\x1d\x54\xcd\x80\xfc\xe7\x2e\x1a\xe2\xde\xc7\x7e\x12\x6c\xe3\x83\x6e\xcc\xce\xc8\x20\xb9\x18\x5e\x20\xdb\x8b\xb3\xe4\xa2\xbd\x66\x0e\x0a\x99\xf6\x86\xcd\x18\xe5\x46\x64\x6e\x15\xad\x78\xe2\x87\x00\xa9\x66\x6a\xeb\x19\x71\x49\xc1\xb8\x9e\x26\x17\x4f\x86\x17\x98\x93\x61\xf2\xb4\x06\xb7\xbe\x1a\x9c\xf3\x31\x83\xb5\xab\xaf\x86\xf0\x0c\x43\x4e\x2e\xce\xb9\x69\x3d\x79\x7a\x61\x32\xcc\xab\xcb\x1a\x26\x43\x9f\xf7\xec\xd1\xd3\x8b\xd4\xa7\x3e\x69\x92\x2f\x1f\x3f\x7a\x7a\x11\xc2\xa8\xee\xb5\x9f\x56\xd7\x77\x3f\x20\x33\x9f\x80\x90\x07\x63\xb2\x2b\xa6\x9b\x5c\x54\xf8\x9e\x93\x28\xb8\x5e\x2e\xc2\x33\x4e\xb6\x33\x29\x0b\x46\x45\x5a\xef\x2c\xfc\x3a\xb9\x18\x1b\x09\x05\xe2\x77\x1c\x84\x51\x8c\xf5\x6e\x77\x0f\x17\xa9\x8b\x04\xbc\xac\x3a\x9d\x5f\x0c\x77\x79\xdf\xc4\x8f\x94\x9d\x8e\xb4\x79\x63\x99\xac\xf8\x3d\x04\x97\x0c\xef\x25\x49\x59\x85\xed\xa5\x19\x4d\xb3\xba\x1b\xb3\x9e\x46\x67\xdc\xeb\x2e\x6f\x78\x3b\x44\xf6\xbe\xc7\x2a\x9b\xf0\xe9\x48\x90\x9f\x59\x0c\x57\xe2\x41\x40\x15\xb3\x7b\xcb\xd5\xb4\xf1\xab\xb0\xb9\x26\x11\x4b\x2c\xa6\x68\x64\xef\x7a\xa1\x99\x0e\x23\x13\xdf\xcc\x0d\xd7\x35\x17\xbb\xdd\x8c\x3b\xc9\x6e\xb7\x73\x76\x13\xe5\x02\x50\xdf\x30\x0a\x01\x8f\x57\xa6\x84\x7d\x9e\xee\x76\x2b\x9e\xd8\xcd\xd5\x15\x02\x9d\x1f\x09\x94\x91\x2f\xa8\x66\x89\x90\x77\x31\x84\x5e\xcc\x19\x48\x08\xfd\xfa\xba\xae\x7c\x63\x43\x61\xb8\xeb\x7b\xb4\xd4\xb4\x08\x3f\xd7\x89\x2f\xe1\xbf\x28\xa4\x5c\x93\xd3\x53\x9d\x98\x07\x97\x66\x55\x4f\xee\x0a\xa1\xc4\xf0\x2d\xeb\xfa\x12\x30\x33\x76\xa2\x7c\x41\x49\x44\x53\x68\xc5\x15\x53\x9e\x2b\xb3\x20\xd9\xbf\xea\xca\xa6\x56\xa1\x99\x33\x50\x48\xb9\xab\xa4\x84\x2f\x87\xb6\x22\xb9\x11\x52\xf3\xf9\x43\x7c\x3a\x68\x3c\xeb\x84\xef\xe2\x44\xd8\xde\xc1\xd5\x09\x3d\xe1\xe0\x85\xa9\x29\xe1\x07\xda\x53\x23\x9f\x41\x38\x0e\x32\x42\xb8\xd4\xba\x5f\x8a\x03\x10\x21\x53\x1c\x60\xd8\x25\xca\x3c\xb7\x81\x05\x99\x0d\xc2\xb0\x00\x61\x4c\x1e\x40\xaa\x85\x30\x6c\x8a\xaa\x2a\xa3\x22\x63\xc5\x5e\x0c\xe6\x91\xf6\x63\xb6\x91\x6a\x78\xf6\x31\x98\x6a\x84\x75\x83\x63\x03\xf3\xd2\x00\x05\x55\x50\x38\x14\x9b\x20\x78\x34\xd1\x3d\xe6\xe1\x21\x08\x6b\x86\x8d\xa5\x79\x33\x50\xc3\x60\x5a\x87\xbe\x51\x30\xb2\x9b\x41\x95\xe6\x49\xdb\x63\xc5\x85\x3d\xa2\xef\x9b\x56\xa7\x84\x94\x9d\x4e\x4c\x77\x3b\x7e\x25\x10\x3e\xad\xb3\x50\xed\xfb\xe9\x27\x46\x4e\x49\x69\x3d\xf7\x58\xd3\xdb\x3e\x1a\xf1\xab\xfe\xb8\x55\x4a\xa5\x71\x41\xf8\xb9\xf8\x72\x88\x0b\x42\x3b\x9d\xe2\x7a\x30\x1e\xf6\x8a\xb4\x00\x1f\x11\xb7\x4a\x9a\x13\x85\x03\x1c\xa8\xe9\x0b\xd8\xb9\xc3\xda\xcc\x78\x44\xac\x70\x69\xf2\xaa\x3b\xca\xf7\x83\x5d\x37\x88\x6a\x04\x93\x36\xe6\x4e\x9a\x4b\x41\x04\xbb\x3b\x79\x63\xd3\x1b\xe5\x9f\x76\x26\xee\x92\xa9\x94\xe1\x92\xfd\x27\xe5\xe0\x0c\x5f\xf9\x01\x86\x93\x30\x8e\x4a\xa6\xa2\x34\x2a\xd9\x7f\x22\xcc\x0f\xda\xfe\xb4\x57\xba\xee\x76\xc1\x21\x78\xc2\xa6\x31\xaa\x6a\xbf\x8d\x3a\x94\x7f\x84\xb7\xb0\xec\x9d\x6e\x07\xfb\x79\x4d\x07\xec\x11\xb6\xe0\x4a\xa3\x90\x6f\x8d\xf0\x5c\xf8\xc2\x66\xb2\xfd\xb3\x99\x6e\xff\xac\x65\xfd\xf4\xb0\x66\xfe\x40\x9e\x5f\x71\x0f\xed\xfb\x56\xee\x75\x73\xb1\x00\x1a\xdd\xeb\x26\xae\x7a\xab\x97\x4d\x14\xef\xfd\xe8\xdc\x78\x2f\x4a\x78\x24\xc5\x9b\x52\x2e\x4a\xa6\x6c\x1c\xf0\x4e\x27\x92\xe2\x6b\xb9\x5a\x1b\x59\xd8\xa7\xcc\x05\x3c\x55\x08\x1f\x80\xc4\x86\x17\x29\x64\xa9\x52\xab\x1f\x8b\xe0\x2d\xc2\xeb\x3a\x54\x7a\x3a\xf1\x69\x51\x10\x20\xdf\xbc\xb5\x03\xec\x47\x53\xbf\x95\xd4\x75\xb9\xeb\x98\xda\x95\xdd\x47\x38\x7a\xa8\x2b\xb3\x07\x90\x71\x64\xc3\x01\x45\x38\xd2\x4c\x28\x03\x05\x88\x61\x72\x1c\x42\xaa\x05\xa2\x00\x72\xc1\x08\x61\x77\xe5\xbe\xb4\x5d\x6b\xe9\xb6\x2e\x9a\x6e\xeb\xc9\x7f\xdc\xef\x57\x95\x17\x98\x8f\x96\x30\xf9\x6a\x29\xef\x82\x5c\x95\xd6\x60\x03\xc4\x68\xed\xe6\x15\xbe\xb5\xa7\x4c\x3c\x1c\xdc\xce\x1e\xe1\xb0\xca\x0a\x2f\x79\xce\x8e\xd7\xa9\xe5\x5f\xad\xd1\xa3\xad\xdd\xfb\x00\x61\x8d\xc8\xb0\xd3\x55\x05\x91\xdc\x61\x03\xbf\x3d\xd8\xc0\xfd\x45\x91\xa0\x2a\x21\x3a\xd8\xae\xec\x3c\x79\x7b\x80\xbf\xac\xc2\xfb\xea\xd8\xe8\x17\xde\x71\xc7\xdf\x6d\xb3\x7f\x6f\x0c\x23\xfb\xf5\x79\xcb\xe9\x82\xe9\xe0\x7e\x98\x1f\xe9\x8a\xa9\x58\x07\xba\x1e\x1e\x28\x98\xb4\x61\x2b\x6c\x03\xe2\xe0\xd6\x9f\x6d\xb5\x7f\x18\xe0\x81\x23\xe9\xf5\x43\xa3\xf8\x43\x2c\x92\xa6\x79\xd4\xe9\x84\xaf\xbb\xdd\x84\x4f\xf7\x34\x4c\xda\xfa\x2a\xd9\xdb\x53\x34\xda\xed\x98\xd3\xa0\x4b\x64\x0d\x2a\xd5\x8d\x9d\x29\xe6\xce\x9f\xee\x3b\x59\x3a\xff\x0b\x2c\xf6\x42\x56\x84\x81\xee\x9c\x2f\x7e\xed\x83\xd4\x8a\x4d\x03\x17\x8a\x35\xfe\x49\x84\xa1\x11\x4f\xbe\xb0\x67\x5e\x61\x6f\xf3\x19\xfc\xd0\x99\x82\xe3\xad\x2b\x69\x88\xc4\x17\x21\x4e\x55\x81\xf2\x99\xdb\xd8\x11\x2d\x0d\xd2\x64\x5a\x03\xd5\xe1\x04\xdc\xbf\xf2\xbc\xae\xa2\x15\x91\x3c\xe8\xd1\x71\x45\xf1\x64\x8a\xc5\xa7\xee\x34\xb2\xd6\x2d\x11\x1a\xb4\x1a\x0b\xc0\x44\x4c\xd8\x74\x3a\x02\x96\xd5\xb3\x40\x9d\x8e\x8f\x54\x9d\xd8\x1d\xa9\xf6\xa3\x74\x5b\x4c\x02\xd7\x7c\xa1\x2a\x70\xeb\x0a\x06\x6f\x98\x5d\xbd\x64\x22\x8e\x63\xbb\x0b\xd5\x10\xac\x10\xb6\x69\x36\x10\xce\xe1\x98\x0f\x6e\xea\x0c\xb1\x19\x0b\x33\x4c\x49\x74\xd8\x98\x55\xd7\x35\xef\xa0\xb1\x53\x7b\xa0\xc0\x94\x34\xcc\x89\x0d\x40\x02\xd0\x29\x49\x73\x82\x78\x54\x5e\x93\xfe\xa8\xd7\x2b\x7d\x07\x0a\xa2\x26\x25\x2c\x85\xe8\x8b\x88\x10\x52\x34\x97\x0e\x35\xd7\x49\x99\x5c\x37\x3c\x28\x63\x38\x40\x00\x5d\x92\x24\x8e\x83\x3c\x44\x5f\xd8\xa6\xe0\xfb\xaa\x8e\x9a\x3c\x29\xac\x13\xdb\x92\x48\xf3\x68\xd3\x73\xc2\xcd\xda\x8d\x0b\xc0\x9d\x25\x60\x75\xde\xe9\x2c\xeb\x99\x42\xdb\xa5\x77\x7f\xc8\x71\x86\x69\x50\xf3\x32\xf1\x8c\x5b\x95\x77\x3a\x79\xcd\x24\x8e\x63\xd3\x00\x59\x02\xad\xb9\xe1\x71\x8e\x35\x2e\x70\x66\x38\x40\xe8\xf8\x12\xa1\x54\x9b\x12\x59\xed\x4b\x1c\xb0\xbf\xd0\x05\x50\x8d\xec\x4d\x4e\x78\x47\x92\xbd\xa3\xaf\xbd\x5a\x02\x0d\x2f\xff\x14\xc6\x07\x66\xa3\x13\x5e\x1f\x23\xa4\x36\x02\x53\x43\x38\x31\x07\xdf\x37\xbf\xe9\x37\x06\xae\x3b\x7e\xc4\x1e\xe3\x66\x07\x0e\xe8\xc0\x55\xaf\xf6\xf4\x3b\x96\xa4\x09\x25\x62\x38\xb5\x31\x83\xa3\xc2\x61\x22\xbd\x37\x89\xde\x90\x65\xdd\x19\xc4\x58\xa5\x12\x33\x91\xa7\x62\x2c\xd3\xf0\xa6\xcc\x57\xfc\xc8\x92\xd4\xc9\x4d\x7d\x08\xb2\x75\xfa\xd1\xc5\x80\xf3\xc7\x51\xad\x3f\xb1\x38\x38\xba\xe2\xee\xbb\x98\x48\x77\x26\x12\x1d\x39\x6d\xf5\x91\x1f\x0a\x87\x16\xf5\xb1\x22\x91\xd9\xa8\x6c\x48\x52\x91\xac\x64\xee\xa2\xb8\x3b\x8f\x6e\x23\xbc\x58\x8b\x1f\xf3\xd7\xbe\xf7\x21\xc0\x80\xeb\x07\xbd\x2a\xeb\xdb\xd4\x0a\xd2\x85\x53\xab\x85\x0d\xbe\x02\x72\x0f\x2d\x8a\x66\x2d\x58\x3b\x78\x46\x74\x02\xc1\xc9\xd4\xa4\x98\xe2\x5f\xe3\x0c\x75\x3a\xb1\xda\xed\x5c\xfc\x96\x17\x3a\x66\x88\x10\xf2\x42\xc7\x19\x82\xd0\x55\x5d\x92\xd5\xf4\x85\x85\xe7\xf8\x3f\x33\x9b\x89\xd2\x34\xfb\xc8\xf2\x1a\x1a\x61\x60\x98\x4e\xa7\x31\xe5\xd8\x82\xc1\x49\xfa\x43\xaf\x02\x50\xd5\xc7\xe6\xc7\xd0\x8e\x9a\xa1\x9e\x70\x93\x6c\x7e\x4c\x72\x70\xa8\xbd\xae\xa1\xd9\x09\x85\xd9\x09\x21\xf2\xd0\x2b\xaa\xb3\x25\x17\x0b\x77\xe0\xd5\xce\x75\x34\xa3\x65\x84\x3c\xe6\xc5\x81\xb5\xcb\x08\x84\x30\xaf\x40\x67\xcc\x46\x78\xdd\xdf\xed\x4e\xcd\xc3\x55\xbf\x3e\x3c\x64\x8b\xd4\xcb\x71\x53\x14\xc1\x31\x6b\xde\x0a\x1e\x05\x56\x56\x8e\x83\xf8\x10\x70\x80\x08\x4b\xc2\x41\xb8\xca\x3e\x2a\x38\xea\xe1\x9e\x81\x5a\x6e\x6d\x04\x89\x54\xe1\x5b\xfb\x40\xdd\x09\xd2\xb2\x22\x02\x17\x44\xd9\x63\xda\x19\xa1\xf6\x61\x79\x10\xab\xc7\xf6\xec\xdf\x5f\x6c\x75\xc2\xf3\x2a\xf9\x62\xcb\xdc\x2f\xb7\xf0\xdf\xed\x38\x28\x15\xaa\x7f\x57\x10\xc3\x49\x20\x08\x06\x1f\xdc\x1e\xb0\xd9\x93\x2e\xf2\x30\x42\x02\xb7\x17\xe2\x6d\x27\xc5\x34\x15\x78\x92\x4d\x53\x55\x11\x3e\xda\x90\xf8\x93\x83\x42\x93\x6c\x4a\xde\xf0\x58\xe2\xa5\x69\x6d\x33\x29\xa7\x44\xe1\x8d\x91\x14\xd7\xe4\x05\x8f\x37\x98\x82\xff\xec\x26\xb9\x71\x9e\xec\x3e\x71\x10\x46\x37\x7e\xbd\x8f\x83\xee\xca\xa6\x63\xc6\x33\x8e\x42\x67\x5f\x23\x11\x05\xa7\x9d\x12\x88\x59\x18\xc6\xe9\x7c\xee\xaa\x76\xb1\xfb\x6f\xe0\x04\x60\xbe\x7f\xb9\x5c\x10\x10\xb5\x1e\x9f\x35\x2f\x06\x18\xcf\x26\x3a\xb1\x53\x97\xf0\x7c\xfa\xc9\x8c\x89\x3b\x18\xed\x0f\xa0\x8e\xbc\xed\xee\x78\x29\xef\xcf\xf5\x1e\x0c\x38\x11\xdc\x20\x10\x39\x0f\x28\x21\x05\x90\x14\x8d\xdf\x72\x7f\x47\x17\x1b\xeb\xf4\x80\x43\xd2\x9e\x19\xfe\xe1\x53\xcc\x70\x9b\x17\xce\xf4\xbd\x75\x3e\x08\xee\xf9\xaa\xf5\x3a\x16\xa9\x0d\x19\x7d\xbd\x0e\x2e\x58\x0b\x90\xdd\xee\x2c\x0b\x77\xa6\xda\xab\x8c\x0c\xea\x91\xfd\xa2\xd6\x6d\xc1\xde\xee\xe5\xf8\x93\xf0\x6a\xae\x9b\xfa\x82\xaf\x81\x57\x55\x51\x4d\xdb\x45\x6c\x68\xbe\x17\x07\xe9\xad\x80\x29\xed\xac\xbc\xa4\x77\xef\x40\xb5\x73\x90\xfc\xb5\xdc\x88\x76\x32\x13\x46\xe0\xb4\xf5\xbc\x5b\xd2\x32\xec\xce\x17\x99\xb5\x3f\xb7\x3e\x70\xc6\x64\xfe\x07\x8b\x51\x15\xbe\xb4\x54\x0a\x98\x41\x9c\xc6\x1a\x12\x23\x1d\x88\x1a\x2e\xac\xe9\x47\xc0\x07\x05\x61\xc0\x6f\x1c\xb1\x25\xef\x78\xcc\x1c\xa6\x60\xb8\x20\x84\xe6\xb9\xbb\x5b\x41\xc5\xc8\x71\x09\xdf\xd9\x08\x03\x6e\x76\xed\x04\xea\x2a\xac\xf2\xb0\x33\x6e\x5b\x6f\x77\xcb\x3b\x3d\xb8\x6d\x33\x46\x58\x92\xe0\xcc\xb8\x8d\x19\xa1\xc7\x2c\x8d\x4a\xfb\x24\x52\x0e\xc7\xf7\xe1\x62\xb8\xef\x5e\x90\xef\x63\xe1\x9f\xf1\x6b\xee\x1c\xe2\x21\xd0\x4b\xf2\x10\x14\x79\x68\x15\x79\xb0\x0e\xf4\x3c\x29\x83\x22\x65\xab\x48\x69\x8a\x14\x10\x14\xc0\xdd\x42\x87\x33\xf3\xe6\x3e\x90\x71\x61\x0f\x0d\x21\xbc\x24\x3c\xb9\x0d\x92\x29\x56\xb8\x34\xf2\xc5\x3d\xc0\xc2\xc5\xac\x30\x8f\x2f\x65\xf9\x5d\x1e\x2b\x84\x79\xf2\x70\x3c\x0f\x5c\x5d\xcb\xe3\x79\x25\x04\x7f\x38\x9e\x97\x99\xbc\xdb\xe3\x79\x4b\xb8\x39\xae\x06\x70\x4b\xf5\x69\x6f\x3c\x34\x48\x5f\xdf\x91\x38\x69\xa6\x74\x5a\xd5\x8b\xec\xc8\x57\x7b\xb1\x0d\x9a\xcf\xa0\xbd\xa0\x03\xfa\xd8\xd7\x96\xc4\x4e\xf4\xb4\x32\xbc\xd3\x6b\xbd\x64\x76\xd8\xfb\x3a\xc3\x16\x0e\xd7\xc1\xa5\xcc\xa6\x6f\x41\x31\xf6\xc8\x9a\xfa\x94\x0a\x88\x58\xec\xc5\x70\xc7\xd6\x3a\xd2\x86\xaa\x9b\x9c\x29\x5d\xca\x87\x03\x15\x5c\xb8\x56\x6a\x5a\xd0\xe9\x64\x2c\x6e\x5e\x61\x05\x82\xe2\xd3\x2d\x97\x4e\xc7\x90\x78\x53\x2b\xd5\xf4\xeb\x25\xcb\x3e\x1e\x43\xfd\x16\x82\x1b\x81\xd6\x5e\xf0\x13\xdb\x07\x50\xf1\xfd\x14\x73\x34\xd6\x8e\x00\x1d\xf3\x2e\xdf\xbb\x5b\xd8\xba\x15\xda\xbb\xc6\xeb\x8b\x94\x47\xc7\x2e\x0e\x63\xe1\xc5\x61\x70\xcf\x2d\x61\x13\x31\xc5\x7c\x22\xa6\x64\x7b\x9f\x2a\xfc\x90\xea\x89\x9a\x56\x81\x24\xcb\x51\xea\xfa\x62\x45\xf7\x58\x7b\x70\xc4\x06\x1e\x1e\x18\x08\x9b\xd1\x87\xa0\x43\x08\xf3\x4e\xc7\xf5\x95\xab\x6f\xee\x41\xe7\x34\x2b\x58\xcc\x51\xa7\x63\x7e\xe0\xfa\x79\x37\x4e\x8e\xaa\x16\x75\xf9\x33\x0a\x16\x42\x19\x6b\x8f\xb2\xee\xfb\xf7\x0f\x6b\x06\xb7\xc7\xb9\x64\x00\xd0\xb1\x42\xa8\x9a\x6d\x78\x91\xbf\x2e\x7f\x02\xd4\xa8\x9b\x3f\xd4\x56\xb3\x3d\x52\xc5\x5a\x33\xe9\x2e\x75\x3e\x1d\x8c\x58\xbb\x67\xbc\x45\x4d\xfd\xc2\x34\x12\x8d\x63\x95\x4e\x0d\x9f\x0e\x4f\x70\xf7\xcd\x69\xdf\xc0\xb1\xc9\xf6\x99\x40\x97\x4b\xa6\x1e\x44\x16\x74\x12\xcb\x4e\xe7\x6b\x43\xa3\xb8\xe7\x27\x42\xc5\xd1\xa7\x08\xaf\xa3\xfe\x35\xf6\x29\xb3\x44\xe5\x9a\x7d\x0f\xf8\x64\xb7\x4f\xe4\x87\xe8\x76\x23\x93\xaf\xe2\x7d\xf4\x35\x2c\xd5\x28\x50\xa5\xb8\x4b\x65\xdf\x32\x25\x8b\x5b\x56\xc6\x02\xb7\x3c\xe2\xac\x9d\xc0\x6f\xb4\x0d\x73\xef\x52\x2a\x18\xc2\xa1\x4e\x00\x6f\x5b\xec\x2d\xbe\xa9\xa3\xf4\x04\xac\xac\x07\x73\x4a\x0d\x13\x5b\x3a\x26\x36\xbc\xa9\x8a\x80\x18\x6b\x43\xa6\x79\x81\x67\xb7\x13\x89\x0b\xb6\x86\x37\x44\x5f\xf7\x3b\x1d\xe1\x41\x39\xd1\xbd\x81\xd5\x8c\xc1\xb5\xc8\x75\xc7\x51\x5d\x82\x48\x5c\x7f\x6e\x66\x6e\x49\xa4\xf5\x38\x5b\x92\x0f\xb1\x9c\xe8\x29\x1a\x73\x18\x1c\x83\xc5\x69\x00\x17\x83\xd5\x0e\x33\x94\xfe\xd4\x2e\xf1\xba\xe6\x2d\x9a\x22\x2e\xeb\x4d\xc9\x57\xdc\xc8\xff\xad\xdc\xe6\x8a\x6c\x54\x3b\xab\x67\x93\x72\xba\xdb\x6d\x3a\x1d\xf3\x70\x65\x98\xe0\xe0\x72\x2e\x06\x77\x06\x34\xc3\x2b\xba\x7a\x4a\x32\xb2\x34\x62\x5b\x6e\x64\x36\x88\x2c\x9c\x43\x9c\x82\x0d\xc9\xd0\xa8\x19\x5b\x5e\x51\x40\x34\x8e\x97\xa8\x3a\xd2\xa7\xbd\x90\x76\x6e\x52\xa4\x97\x2f\x94\x91\x48\x28\x91\x3e\xfc\x93\x13\x30\x0a\x22\x0d\x76\xfc\x40\x67\xac\x30\x9c\x47\x46\x24\x21\xc4\xc8\x1b\x0d\x3d\x73\x57\xf7\xe5\x78\x83\xe7\x30\x96\x9c\xf4\xf1\x86\x88\x51\x7e\xb5\x19\x75\xbb\x39\x9a\x93\xbc\xcb\xf1\x72\x92\x4f\x09\x5c\x35\x9a\xed\x76\xd2\x82\x2d\x2e\x26\xf3\x29\x9e\x23\x3c\x29\xa7\xa9\x72\x69\xcc\xa6\xd5\x04\x6e\x59\xed\xcd\xcf\xde\x50\xee\xfd\x50\x1e\x5a\x43\x39\xe8\x20\x88\xd7\x78\xe9\xb4\x4c\x7d\x5c\x10\x31\x2a\xaf\x8a\x51\xb7\x5b\xa2\x8c\x94\xa6\x8b\x84\x4d\xb2\x29\xa6\x46\x32\xd9\xde\xa7\xbe\x93\xcb\x49\x7f\x8a\x33\x84\x1f\xea\x2e\x2e\x27\x03\x93\x52\x77\x91\x56\xfb\x08\xf2\x57\xfa\xb8\x05\x46\xe5\x7b\xf6\x90\x52\x08\xc6\xf3\xe0\x5f\x4b\x12\x3d\x44\x15\x69\x31\xbd\xa6\xbb\xfb\x23\x0a\x6f\x77\xb3\x77\xb2\x09\xb8\x8c\xad\xdb\xcd\x50\x4e\xb2\x2e\xc7\x1b\xc2\x26\xf9\x14\x17\x46\xf4\x0a\x46\x54\x68\x23\x58\xc1\x61\xaa\x66\x50\x90\x58\x9a\xc4\x7a\x5c\x45\x05\xa1\x01\x0c\x2e\xee\xf3\x05\x21\xdb\x5e\xaf\xc6\xa9\xe7\x5d\x1c\x01\xfc\xdc\x47\x10\x45\x4a\x4f\x2b\x88\x8f\xfe\x4e\x83\xc7\xc5\xa1\x2d\xd7\xf1\xa1\xf2\x60\xd7\xc7\x0a\x44\x29\x83\xa5\x75\x9c\xab\x8f\x3c\xde\x9a\x1d\x37\x7d\xc5\x63\x01\xb2\xa4\xd5\x7a\xa4\x9e\x61\x56\xfe\x8b\x0a\x2b\xec\x62\x51\x60\x7b\x33\x31\xaf\x3c\xb7\x0c\xf1\xbc\x5e\x96\x72\xe5\x07\xbe\xaf\xc8\xe1\x13\xe6\xda\xb5\x37\x0e\xba\x95\x2d\xc7\x3f\xd2\x1f\x53\x59\x3b\xdf\x8b\x4e\xa7\x96\x0f\xeb\x2f\x04\xc4\x80\x8c\x85\xd3\xc7\x10\x8a\x75\xfb\x0e\x50\xed\xef\x00\xd5\xed\x3b\x40\xb5\xbb\x03\x14\x2b\xf2\x91\x5b\x0a\xb3\x0f\x51\x37\x1c\x5a\x14\x70\xa8\x01\xfd\x4f\xab\x06\xfe\x91\x8b\x57\x74\xff\x6c\xb3\x21\xf0\xc2\x90\xd7\x00\xfc\x60\x41\xb7\xf3\x0e\x5a\x32\x47\x87\x3a\x1d\x0d\x6a\x2d\x4b\x5f\x82\x7b\xfb\x81\x81\x3f\x60\x1e\x71\x41\x20\x20\x65\xc3\xa0\xd5\x13\xc8\xdd\xd4\x07\xd3\x68\x6f\xc0\xce\x08\x04\x5d\xf9\x64\xc4\xbf\x15\xbd\xf7\x99\x3f\x7e\xf3\xf7\xe7\xad\xcc\x0a\xc3\xb7\x4b\x28\x94\x57\x87\xac\x1b\x64\x33\xc8\xe6\x78\xc5\xc5\x0b\xb8\x05\x20\x4f\x85\x49\xf2\x2f\xb2\xb2\x5c\xe2\x4f\x8a\x95\x10\xcf\x58\xc5\x28\x8c\x07\x23\xc6\xec\x93\x3d\x80\xaa\xe5\x98\x7f\xb2\xff\x55\x15\x97\x76\x79\x6f\xf0\x1c\x2f\xf0\x3a\x08\x9e\xde\x70\xf6\x0b\x22\x27\x9b\x29\x9e\x93\x85\x47\x69\xbc\x26\x8b\x49\xe9\x9e\x4f\x7f\x8d\xe7\x68\xb7\x5b\x5e\xaf\x77\xbb\xfc\x6a\x5d\x19\x0a\xb1\x21\xfd\xd1\xe6\xca\x60\xdf\x2a\xb6\xc1\x45\x8e\xe3\x7b\x86\x35\x5e\xe0\x02\xe1\x53\x85\xd0\xa8\xdb\xdd\xb8\x38\xce\xb6\x0e\xda\x1b\x8c\x36\x56\x2d\xbf\x81\x5b\xf6\x57\x31\x42\xdb\x3f\xab\xcb\x29\x22\x7d\x08\x61\x83\x69\xcf\x8b\xc2\x16\x82\x68\xf2\x07\xfc\xdc\x31\x02\x83\xb9\x3f\x57\xfe\x17\xb9\xe6\x1a\x36\xbf\xc6\xaa\x31\xa1\xa8\x40\x63\x6b\x7d\x78\x5f\xdf\xb2\x72\x5e\xc8\xbb\x38\x38\x74\xe8\xb7\xbd\xe7\xc2\xf6\xf0\x73\x1d\x04\x3e\xcd\xa1\xbc\x61\xca\x1c\x0b\x29\x6b\xdd\x47\x4d\x43\xeb\x40\xaa\xa6\xea\x94\x8f\xa3\xa8\xcb\xeb\x1d\xf6\xa5\x2c\x6d\x53\x72\x62\xa3\xaf\x4d\x51\x1a\x45\xee\x5e\x0b\x61\xca\x8a\x63\x65\x45\x53\xb6\xaa\xbc\x04\xf5\x27\xec\xf1\x88\xb5\x14\x0c\xec\x98\x26\x87\x79\x3b\x86\xde\xed\x22\x77\xfd\x61\x04\xfc\x72\x56\xf0\x75\x6b\xed\x80\x4d\xcb\x46\x15\x6d\x5d\x00\x1e\x1b\x8e\x56\xcb\x35\x28\x12\x5c\xf4\x7d\x52\x47\xd1\xb1\xd7\x97\xcc\x35\x4a\x19\xe1\x44\x10\x49\x5c\x54\x32\xe6\x0e\xae\x72\x7f\x5e\x55\xd8\x50\x12\xb2\xaa\xe2\xef\xe3\xda\xc2\x08\xf7\x86\xe1\x7d\xcd\x67\xcd\x0e\xee\x3b\xf9\x0b\x02\xd6\x08\x70\x14\xbb\x03\x96\xbc\x39\x11\x23\xd7\xa9\x4c\x98\xc8\x5d\xc3\x02\x9e\x67\xfe\xac\xbd\x75\x81\x81\x2e\x08\xfb\x52\x55\xb1\x57\x19\xd4\xfa\x01\xec\xd4\xcd\x01\x3a\x21\x84\xaa\x66\x42\x2a\x88\x2c\x71\x54\x6e\xd2\x36\x02\xa1\x25\x79\x62\x4f\xeb\x22\xeb\x4b\x66\x27\xe0\xa5\x14\x1c\xcd\x35\xe2\x28\x2e\x4d\xf9\x5a\x8d\xb5\xdb\x19\xde\x46\x07\x1a\x2c\xc3\x6f\x39\x9b\x9a\xbd\xa1\xcb\xc6\x8d\x11\x5e\xb4\x30\x14\xd8\x3d\xda\xe0\x17\x0c\x5b\xf7\x17\x9c\x91\x72\x94\x5d\x95\xdd\x02\x78\x8a\xba\xe3\x72\x92\x4d\x47\xda\x99\xbc\xc6\xd4\xdf\xa5\x91\x36\x9f\xa3\xca\x31\x25\xa3\x2c\xbc\x10\x2b\x43\x74\x92\x4d\x83\x52\x0b\xa6\xfd\x25\x14\xa1\x0d\x79\x1c\xd9\xba\xa3\xb4\x46\xbc\x51\x68\xc8\x72\x72\xc2\x51\xa6\x42\x31\x3d\xf6\xf7\xfd\x1b\x19\xe7\x45\x4b\xa4\xf4\x46\x3f\x23\x33\xef\x15\xda\x2b\x01\x80\xe4\xd0\xc5\xe0\x68\xd1\x3e\x83\x02\x93\x73\x20\x67\x82\x42\x58\x5f\x93\x7e\xa7\xa3\xaf\xc4\x41\x17\xf7\x42\x40\x33\x72\x58\x64\xa2\xa7\x23\x05\xa7\x52\x6c\xdb\xa0\x7f\xa8\x35\x8c\xc7\xb5\xfd\x7b\xf6\x3d\xf7\x66\x05\xbe\x18\xae\xcc\xb6\xde\x1f\xa7\x03\x08\x4f\xe9\x63\x53\x5a\xe2\xea\x9d\x77\x4a\x7a\xe7\x1f\x9b\x60\x9e\x2e\x6e\x27\x06\x56\xa9\x9e\x12\xeb\xe8\x13\x99\xba\xa2\x0a\x55\xb1\x68\x49\x92\x20\x02\x21\xec\xd8\xcb\x9c\x88\x16\x21\xc4\x2a\x29\xe9\x1d\x91\x35\x1f\x08\x51\xf5\x0d\x27\x11\x8c\x58\x1c\x1f\xf1\xff\xc3\xf1\x2a\xa6\x6b\x87\xa7\x63\xd1\x39\x3f\x33\x3c\xc5\xb4\x1b\x61\xad\x4d\x6b\xc6\xe9\x8c\x41\x30\x56\xaf\xcd\x90\xcd\xa9\x53\xef\x74\x77\xca\xb0\x02\x73\x1f\xe1\x58\x55\x9f\x45\xce\x7d\xb6\xd9\x15\xde\x2f\x65\xf2\x0e\x15\x26\x09\xcf\xb1\x46\xd5\x67\x10\x3b\x8c\xec\xf6\x57\xea\xdf\xab\x9c\x99\xea\x3f\xf5\x0d\x66\xa4\x01\xe0\xe1\x5a\xf1\xeb\x9a\x10\xc2\x2c\xdf\xd8\xde\x77\x30\x25\x1a\xee\x72\x61\x46\x08\xb5\xb6\x4d\x71\x4c\xeb\xde\xe9\x2c\x75\x6c\x1d\x38\x4a\x6f\x95\x7b\xcb\x63\x43\xb5\xea\xe8\xcb\xa2\xad\x4c\x59\x92\x6c\x0f\x5c\x8d\x4e\x45\x58\x9d\x0a\xd6\x08\xe7\x44\x8e\x27\xba\x1b\x7d\x2b\x6f\xe1\xe6\x84\xa5\xfd\xd5\x38\x8a\xa6\xe9\x04\x7e\x8c\xf0\x7d\xa0\x79\x69\x93\x01\xbc\x44\x78\xbe\xef\xf2\xe6\x16\x94\x9a\xe8\x29\xc2\x0b\x92\x79\xea\xf3\x23\x5d\xd5\xc6\x89\xd8\xb0\x7d\xe0\x92\xd1\x5a\x4e\x1c\x4b\x64\x64\x33\x8f\x57\x8b\xc0\x21\xa6\x7e\x26\x05\x36\x30\xdb\x3b\x36\xf4\x96\xc7\x0b\xeb\xe7\xb8\xa8\xa7\xad\x6d\xe2\x3f\x4a\xd4\xec\x5e\x74\x7c\x8e\x1a\x57\xb3\x60\xaa\x5a\x73\x51\x36\xde\x9f\xa7\x70\x8d\x54\xbd\x69\x37\x7e\x7f\xc1\x2d\x59\xad\x89\x52\x8e\x2c\x28\xa6\xeb\x7e\x1e\x4e\x15\xf8\x90\xc8\x3f\x9b\x06\x85\x46\x70\xa5\x4c\x5b\xeb\x45\x71\x0b\xb8\x10\xe8\x1b\xa1\xda\xf5\xc3\x48\xda\xb7\x3c\x96\xb8\xe8\x74\x8a\xa6\xc3\x0d\xe3\x08\xe9\x37\x75\xe0\xc8\x4e\x27\x3e\x02\xf7\x0c\x21\x0c\xac\xee\xbb\xd0\xf8\xe4\x9c\xc5\xb4\x9f\x33\xd4\x5a\x89\x2d\x3b\x55\xed\x5e\xda\xb6\x5e\x1d\xb1\xe8\xa1\x8a\x8b\xac\xd8\xe4\xec\xc8\x1a\x3f\x65\xbb\xdd\x7b\x0e\x9e\x5c\x81\x46\xff\xa6\x19\xd5\x0b\xae\xcc\x20\x72\xc7\xa9\xd4\xb2\xbb\x97\x7b\xdf\xc3\x6d\x77\xfb\xbe\x23\x7e\x03\x3d\x82\x51\x0c\x0b\x54\xf3\x8e\x66\x0b\xb5\xcf\x7b\x60\x70\xe7\x0f\x3b\x9d\xd3\xf7\x3c\x66\xc8\xef\xe6\x87\xd5\xf9\xdd\xa8\x5d\xe7\x8d\x0a\x58\x07\xdb\x51\xcf\x91\x10\xd1\x76\xe4\xaa\x99\x0c\xd3\xb1\xd1\xa7\x7b\xcd\x5b\xfd\xde\x3a\x84\x4d\x4f\x85\xeb\xdb\xc1\x54\x4a\xb4\xdb\xc9\xca\x9f\x65\x04\x82\x11\x74\xc9\xdb\x34\x82\x8e\x72\xec\x09\x20\x58\xb5\x15\xd3\xff\xc3\x8f\xfa\x40\x79\x4d\x6b\x0e\xc5\x83\xef\x3f\x6d\x22\xf1\xab\x69\x54\xf3\x4c\x41\xf5\x0e\xbc\xad\x8e\x99\xfc\xff\x73\x0d\xd8\x41\xec\x29\xca\x3f\x2b\xae\x84\x5c\x93\x73\x45\x6f\x12\x46\xe2\x9a\x8f\x59\x72\xc3\x85\x62\xa5\xae\xab\xe4\x58\xf4\x38\xd6\x28\x15\x57\xe0\xce\xe8\xe0\x56\xe7\x0b\xcc\x7b\xa2\x39\x10\x50\xab\x49\xc0\xc9\x4f\x9a\x2f\xac\x5e\xac\x8f\x25\xaa\xf6\x2b\x87\xb9\x22\xa7\xfd\x23\x74\xb3\xad\xa4\xb2\x94\x0c\xb6\x35\xe6\xdc\xdc\xbc\x2f\x9b\x0f\x77\xe1\x83\x31\x75\x09\x03\x56\x3e\x74\x7f\xa3\xa3\xb2\xd7\x43\x7a\x52\x4e\x89\x9e\x94\x3d\x36\xb5\xde\x9e\x85\xbd\x2d\x49\x8f\x4a\x08\x90\x5f\x22\x65\x4a\x18\xb2\x25\xf6\x77\xec\x91\xa8\x75\x88\x9d\x4e\x11\xcb\xda\x0c\x81\x45\xd2\x68\xf3\x31\x37\x42\xc0\xa6\x6d\x62\x51\x86\x85\xc3\xb5\x2d\x6e\x2f\xb7\x5e\x76\xd5\x3e\x64\x8f\xb8\x0c\x06\x92\xa7\x21\x7e\xa1\x66\x33\x8c\xf3\xe8\xfa\xe6\x03\xe4\x81\x12\x9d\xb7\xcd\x77\xf6\x3c\xad\x45\x88\xa0\x58\xe5\x2e\x73\x7c\x63\x44\x91\x06\x51\x69\xb9\x80\x08\x27\xcd\xad\x21\xd0\xf4\xfe\x6c\xba\xd5\x5d\x6f\x1b\x21\xbe\xf5\x34\xf0\x3b\xae\x7a\xb9\xae\x6d\x95\xfb\xa3\x3e\xb6\x26\x9a\xbb\x44\x07\x75\x1d\xef\xac\xdf\xc9\xf1\x5a\xfa\x61\xc1\x66\x78\x9f\x68\x12\x6c\xfe\x47\x47\x84\xf7\x47\xde\x1b\xd6\xf5\xfe\x24\x54\xab\x0b\x7b\xdf\xf6\x0f\xbe\x45\x55\xf5\x03\x4f\x1c\x5b\x07\xba\x81\x1f\x78\x02\x47\x91\xcd\x4e\x7c\x84\x07\x05\x9d\xe8\x61\xa1\xfd\x12\xce\x11\xe5\x9b\xb6\x23\x8a\xeb\xd6\x7d\xcb\x8f\xe2\xa1\xf5\x16\x1c\x64\xf9\x94\xb7\x48\xe8\x80\xea\xdc\x12\xb5\x94\x85\xe6\xeb\x37\x2e\x94\x60\xa3\xf4\x33\xbc\xff\x43\xca\xab\x46\x5f\x53\xca\xb5\x8a\xbd\x8b\xfe\x14\x37\x8a\x1b\x5f\xb4\x5a\x52\x65\x15\x30\x35\x2f\xfd\x5c\x5b\x1c\xb8\x47\x9d\x8e\x7f\x7e\x00\xf9\xd1\x56\x77\x4c\xa9\xba\xdf\x55\xef\x48\xd4\xdc\xfc\xc3\x43\x4f\xef\xe3\xe1\x67\x6b\x6f\xef\x4e\x47\x80\x8f\x93\x73\x45\x1d\xc3\xdb\x8d\x96\x70\x25\x81\xbb\x2e\xf4\x9b\xf6\x44\x36\xaf\x6f\xe5\x46\xd7\x67\xaf\x5c\xa3\xdf\x35\xf1\x70\x9a\xb8\xed\x7c\x4f\xea\x6c\x8b\x66\xe0\x3c\xb5\xad\x1a\xc7\xd2\xee\x3f\xde\xbd\xfe\xd1\xc5\x0f\xe7\xf3\x07\xef\x66\x29\xc8\x77\xd6\x93\xb6\x71\xa9\x16\x46\xe4\x83\x06\xbf\x13\xba\x48\xac\x86\xf4\xa5\x2c\x57\xd4\x46\xf5\xc0\xdf\xd9\x60\x2e\xf6\xba\x02\x51\x81\x0a\xc7\x80\x02\x0a\x78\xf6\xed\x25\x27\x5b\xa7\x30\xd6\xe4\xfa\x83\x3d\x7a\x1a\x45\x5d\x8d\xe1\xb6\x56\x9e\x05\x8a\x22\xd0\x23\x38\x48\x47\xfd\x68\x74\x68\x7a\x68\x82\x64\x4a\xb8\xd3\xc9\x79\x88\x12\x0d\xde\x81\xfe\x6a\x89\x41\xb3\x7d\xd5\x6a\xf5\x3a\x26\x38\x9f\xf4\xa7\x56\xdf\x8f\x70\x90\xc8\x6b\xda\xe0\x73\xd1\x28\x66\x57\x03\xd6\x7b\xbc\xdb\xb1\xeb\x01\x1b\x5c\xc0\x35\x60\x24\x52\x19\x67\x42\xf3\x39\xcf\x22\x84\x15\x39\x12\xde\xaf\x56\x80\x5e\x3f\x1a\xb3\xc9\xd0\x55\xd8\x63\x13\x5f\x77\xda\x3c\xf6\x58\xdd\x9f\x51\xd3\x1d\x74\x3d\xe8\x74\xf4\x29\x69\x9d\xf4\x43\x10\x08\x5f\xf7\x5a\x69\xfb\x3e\xf4\x95\x37\x78\xbc\xd1\xcd\x98\x15\xb8\xec\xb4\x61\x61\x36\xd7\xde\xe0\x2c\xa8\x8c\x22\x3c\xec\x23\xdc\x47\xb8\x20\x5b\xe1\x6e\x73\x4f\x25\x5e\x71\xc1\x57\x9b\xd5\xcb\x92\xc2\x40\x5f\xf0\x05\xd7\x2a\x2d\xf1\xca\x86\x5b\xd8\x4f\xdf\x0f\x9c\xe1\x78\xd4\xa2\x45\x1c\xe0\x40\x9c\x72\xd8\x82\x30\x60\xb1\xc0\x05\xaa\x70\x21\x17\xb4\xe4\x7a\xb9\xfa\x6b\xa8\x71\x5e\x1f\x00\x1f\xf4\x71\x30\x98\x37\x06\x07\x1b\xe8\x0c\x6c\x64\xa3\xa1\xfd\xb9\x30\x3f\xe3\x97\x3c\x71\x38\x98\x64\xb4\x28\xec\xad\xc0\xb6\x49\x50\xde\x42\x98\xdc\x9f\x39\xd9\xda\x5e\x6a\x56\xaa\xf4\x25\x0f\x62\x97\x7d\x75\xe0\xfc\xd8\x1a\x1d\x58\x69\x56\xf4\xfe\xbd\x79\xf9\x81\xaf\xb8\x0e\xc2\xdd\xb6\xa2\xa2\xf8\xcf\x6c\x5c\x1e\xeb\xe7\x65\xaa\x80\x48\x16\xc8\x6a\x20\x2d\x46\x9d\xf3\x6e\xcc\xc6\xfd\x74\x60\xe3\xda\xdc\xac\xe8\xfd\x0f\x2e\xa3\x75\x5d\x53\x78\x08\x12\xb8\x28\x89\x20\xd8\x11\xf8\xb9\xae\xe8\x7f\x64\xe9\xc4\xf8\x7c\xfc\xf9\x80\xb1\x9f\x0a\x53\xc9\xbb\x5d\xa4\x27\x7c\x6a\x2b\x03\xfe\xcc\xec\xf5\x3c\x0c\xf2\xca\x50\x0a\x2a\xd2\xda\xea\x44\x89\x9c\xf4\xa7\xb8\x24\x72\xa2\x7a\x83\x29\x2e\xdc\x75\x50\xea\xba\x26\xb0\x6d\x5d\x5a\x70\x8f\x81\xbd\x8a\x68\xd2\x9f\x3a\x43\x83\x0d\x4f\xc0\x78\x11\x0b\x33\xaa\xfe\xa8\xb9\x39\x1b\x6e\xa9\x95\xf6\x7a\xa6\xd8\x75\x4c\x4f\xe4\x14\x61\xd5\xed\x42\x2d\xea\x4c\x4c\x91\xa1\x58\x05\x96\x58\x9d\x0b\x84\x8b\x5a\x4f\xb1\xaf\xcd\xf3\xa8\x76\x7c\xea\x02\x17\x5d\x03\x2d\x3e\x8f\xd9\xd5\xb0\xd1\x75\xdb\xbe\xba\x80\x5c\x83\x11\x07\xa7\x02\x0e\xf1\xd2\x5c\x14\xcb\xde\x60\x7a\xda\xdc\x96\xdc\x5c\xe2\x24\xdc\x74\x79\x5a\x72\xce\xdb\x77\xd0\xd5\xab\x59\xe2\x41\xe3\xdb\xf0\x5a\xc7\xa2\x7d\x0f\x13\x0b\x8f\x70\xe8\x2b\x06\xc7\x11\xeb\x01\xa8\x89\x86\x19\x60\xd7\xb2\x3e\xf2\x59\x1d\x6b\xa1\x8a\x25\x04\x6a\xb1\xd3\xd5\x77\x17\x1a\x62\x5e\xaf\x44\x75\x3d\x18\x87\x97\xac\x96\x3d\x8a\xce\x63\x05\xa1\x1a\x80\xbb\x30\x9d\xfa\x85\x03\xcc\x33\x0c\x01\x7a\xfa\x29\xed\x09\x4c\x11\x76\x77\xc9\x41\x07\x39\x74\xb0\x2e\x08\x37\x46\xc9\x89\xee\x0e\x9a\xb3\x9c\x75\x66\x89\x5d\xa0\x1f\x3b\xc0\xb4\xec\x9a\x99\xac\xf6\x8a\x99\xa4\x7a\xcd\xfe\xc2\x8f\x5d\xb9\xf6\x7d\x2c\x70\xbf\x09\x9a\xcf\x45\xfc\x7d\x2c\xb1\x9f\x5d\xd4\x3c\x85\xbe\x0b\xee\x3a\x7a\x1e\xa0\x22\x07\x0f\xa3\xb8\x24\xb2\x27\x30\x27\xe5\x79\xb0\x18\xcb\x73\x8e\x10\xce\x88\x1a\x65\x57\xfd\x11\x5a\x76\xbb\xd8\xdd\xde\x69\x21\xa6\xba\xcb\x33\xee\xe6\xae\xa1\xd5\x0a\xf7\xd1\xa8\xb8\xa2\xa3\xa2\xdb\x45\xe0\xb2\x12\xe2\x74\x31\x45\xf8\x13\x15\xa1\xfa\xa0\x29\xb8\x0b\x46\xb8\x0e\x5f\x75\xda\x77\xf7\x0d\xa7\xa7\x03\xec\x7c\xee\xcd\xe3\x8c\x2d\xb8\x78\xae\x7f\x63\xa5\x84\x57\xb0\xa5\xa6\x11\xd0\xb2\x08\x2f\x4a\x9a\xb1\xb4\x8f\x17\x25\xcf\xd3\xb0\xae\x82\x0b\x17\x88\x77\x00\x61\xbb\xbf\x82\x03\x94\x26\xc7\xbc\xbd\x16\x5f\xd7\xc1\x8f\x5d\x12\xd0\x43\xf3\x62\x6a\xb6\xd4\x2b\xbd\x84\x17\x5b\x4d\x1d\x52\xb8\xae\x19\x32\xe1\x34\x67\x93\x09\x67\x12\x83\x81\xd8\x73\x9b\x2f\xa8\x5a\x1a\xa2\xd3\xbc\xbd\xb6\x25\xfa\x38\x38\xd8\x99\x0e\x2a\xac\xb9\x0e\x63\x7a\x19\xe6\x95\xdd\x1b\xd6\x04\xb7\xe2\xa8\x3f\xf6\x76\xa7\xc7\x55\x05\xdd\x50\xe9\x76\xc5\xc5\x5b\xbf\x3d\xf6\xcd\x36\x58\xbf\x5d\xf4\xf1\x8a\x97\xa5\x2c\x7d\x7d\xef\x74\x29\x3f\x3a\xf0\xf4\x83\x14\x3b\x98\xa0\xb1\x47\x38\x00\x29\xdd\x68\xf9\xee\xe3\xff\xcd\xde\xbb\x36\xb9\x6d\x63\x8b\xa2\x7f\xa5\x5b\x95\x68\x08\x0b\x62\xab\xbd\xcf\xcc\x3e\x87\x32\xac\xf2\x23\x4e\x9c\x38\x89\x63\x3b\x33\x49\x54\x2a\x1d\x36\x09\xb5\x30\x61\x03\x0a\x09\xb5\xa5\x48\xfc\xef\xb7\xb0\x16\x00\x82\x14\xd5\x76\xf6\xde\xf7\xc3\xad\xba\x5f\xba\x45\x10\x00\xf1\x5c\xef\x87\xd8\x84\xbf\xdf\xfa\x8a\xa0\xad\xf4\xd3\xca\x9c\xc3\xe9\x3f\x45\xdc\x20\x2a\x6b\xd1\x60\x70\xb7\x2a\x93\x43\x4d\x01\x4c\x9b\x1f\x69\x21\x6e\x65\x32\xc8\x6c\xb8\xd0\xac\x54\x55\xf5\x0c\xcb\xd0\x55\xb3\x5a\xab\x8f\xa0\xd4\x7c\x9e\x66\xbf\xe7\xa5\xda\xc0\xea\xda\xdf\x76\xdc\xe5\xed\x4d\x1a\x3d\xfe\xfb\xdf\xe9\x45\xf3\x67\x12\xff\xe7\xdf\xc9\xc0\xd7\x74\xe3\x7d\x6c\x1d\x66\x4b\x05\xb1\xd7\xe0\x30\xc6\xf6\x4c\x79\x0f\x5e\xff\xf3\xb4\xaa\x39\x6f\xed\x9a\xa1\xbb\xef\xd9\xfa\x6d\x9f\xe0\x4f\xb6\x82\xd3\x70\x76\x40\x8d\xb3\xaf\xbb\x4b\x6d\x57\xe8\xb6\xf3\xf3\xa5\x46\xad\x64\xf5\x2f\xa1\xd7\xd1\x00\xa3\x68\x0f\x08\x24\x29\x0c\x5f\xa4\x2b\xb3\x03\x64\x38\x1c\xb8\x3d\x74\x9e\xd1\xc0\x77\x97\xf0\x14\xfa\x59\x6b\xf6\x74\xd0\x1c\x6c\x57\xd9\xac\xe5\xf3\x76\x69\xd7\x45\x19\x0d\x86\xdb\xee\xc9\x38\x13\xef\x11\xfe\x4d\x10\x3a\x0a\x62\x27\xa3\x37\x12\x06\x4e\x36\xbf\x67\x7a\xce\x17\x23\x91\x98\x7f\x63\xd1\x10\x41\x3f\xf4\x7b\x75\x59\xa4\xc5\xad\x5a\xd9\x23\xc9\xca\x42\xcd\x69\xf5\x44\x4d\xab\x11\x93\xce\x85\x4b\xcf\x03\x58\x59\x91\x45\x9f\x23\xd7\xbf\x7b\x42\x9e\x22\x21\x69\x09\x8c\x40\x70\xc4\xa9\x1c\x5f\x13\xc8\x41\x81\x41\x13\x20\x56\x18\x4d\xcd\x33\x97\x39\x3e\x95\xec\x9a\x8f\xff\xe1\x8c\x1e\xd1\x8e\x04\xde\xbc\x52\xa5\x01\x53\x2e\x96\x67\x24\x20\x8f\x20\x50\x91\x33\x0f\x9b\xb3\x71\x45\xd3\x71\x46\x12\x70\x25\xb1\xf9\xfc\x5a\xcd\xaf\xc9\x38\x23\x57\x8f\x93\x28\x1b\xf7\xf4\x6d\x70\xe3\xd5\x63\x9a\x8d\x98\x7a\xc2\x67\x45\x32\x2e\x68\xf6\xa4\x1a\x97\xc7\x63\xf6\x34\x1d\x95\xc4\x67\x47\x0a\xf2\x71\x7c\x21\x42\x7d\x54\xec\xe1\xe9\x0c\x57\xc2\x82\xd3\x20\xf5\xee\xaf\xa2\xf1\xf7\xd5\x2e\x06\xa1\xeb\x78\xe2\x19\xc3\x57\x3c\xc2\xbc\x5c\x98\xef\x17\x32\x12\xb8\x14\x04\x2e\xd6\xce\xaf\x91\x8e\x0d\x08\x23\x33\xfc\xef\xd0\xee\x35\x79\x24\x82\x04\x57\x23\x97\xf9\x26\x48\xb6\xe3\x37\x0e\xed\x7e\x65\x63\x7c\x61\x96\xd6\x06\xe5\xc6\x50\xb9\x97\x4d\x01\xb8\x06\x01\xd7\x65\x38\x6b\x7f\x16\xf5\xcc\xbe\x4f\x9a\x7a\x7a\x86\xaf\x13\x4d\x22\x05\xbc\x35\x4a\x33\x7e\x69\x82\x53\x76\x04\x1b\x9a\x1c\xaa\xed\x86\x97\xce\x0b\x46\xe4\x4c\x83\xb6\xce\x3c\xa0\x47\xcc\x27\x5c\x60\x3a\xbe\x38\xd6\x51\xc7\xaa\x6a\xb0\x1b\xb5\x69\xb5\xb0\xae\x54\x61\x11\xe4\x17\x09\x0b\x30\xcf\x48\x58\x82\x01\x05\xc3\x12\x1b\x7b\xb1\xe5\x22\x83\x69\xe7\x2b\x9f\x34\xc7\xe5\xff\xc0\x6c\x20\x3e\x69\x0e\xd6\x76\x09\xbe\x5b\x5d\xf8\xb4\xde\xad\x52\x7b\x0a\x3e\x74\xa6\x62\x8b\x9f\x9f\xce\xc8\xbe\x79\xd3\x9d\x98\x2d\x7f\x77\xf2\x05\xf0\x02\x6b\xad\x89\x41\x3f\x0e\xa9\xb6\x87\x28\x64\x77\xc8\xed\x55\x28\x53\x79\xcb\x5b\x45\x00\x20\x0c\x58\xc2\x0a\x06\x3f\xbc\x11\x92\xbf\xd6\xfc\x0e\xad\x05\x5d\xdc\x1c\xf3\xd1\xfe\x52\xc3\x87\xb5\x4b\xe1\xe0\xb3\x66\xe9\x2d\x17\xe6\x4b\x0a\x25\x6f\x79\xa5\x3f\xf0\x9d\x7e\x91\x66\x6b\xde\xf8\x62\x35\xb0\xa8\x3d\x70\x07\x93\x3a\xd3\x09\x53\x51\x37\xae\x56\xdb\x8a\x97\xdf\x77\xa7\x0e\x85\x9d\xf5\x59\x56\xdb\x5b\x33\x10\x9e\x9f\x54\x6f\xde\x74\xdb\xc0\x8a\x75\x26\x84\x78\x07\x64\x6c\xbe\x2c\x6b\xcf\x2c\x4f\x75\x0a\x6c\x6e\x05\x53\xce\xcf\xb9\x62\x81\xe3\x55\x57\xc7\x30\xf5\x76\x44\x0c\xc8\x56\xa7\x0c\xe4\x1d\x7b\x78\x34\xdd\x64\x68\x4a\x46\x79\x33\x6b\xa7\x20\x00\xbb\x4a\xe2\xdf\xa4\xbb\xf0\x4d\xba\x43\x7f\xad\x70\xea\xcd\xeb\xb0\xb8\x53\xaf\xd5\x4d\x58\x4c\x42\x23\x7c\x07\x96\xeb\x8e\x39\xa2\x81\x7a\x07\x37\xd2\x44\x53\x37\xb4\x84\xd3\xd6\x58\x12\x41\x5b\xdf\x04\xd7\x54\xb3\x38\xde\x79\xe7\x8f\x48\xd3\x73\xb6\x8a\x84\x72\xf6\x47\xc4\xe9\x39\x63\x47\x42\x05\xfb\x23\x12\x0f\xb4\x97\xec\x8f\x48\x3e\xd0\x1e\xec\x29\xff\x40\xab\xad\xbb\x74\x97\xfc\x01\x3a\xbc\xd0\x32\xf3\x17\xc3\xb4\x06\xc6\x99\xbf\x44\x9c\xd4\xa1\x29\x6b\x67\xd7\x91\x6f\x46\x3b\x4d\x34\xc6\x0c\x7b\xab\xc2\xae\xd2\x1a\x1d\x2c\x5a\x76\x9e\x86\x1b\x1d\x0e\x53\x12\xda\x7b\x62\x3f\x4e\xe6\x59\xb2\xf3\x7e\xc7\x0d\xa7\x5c\xb0\x09\x64\x4c\x71\x94\xca\x93\x0c\x8c\xff\x05\x2b\xe7\xc5\x22\x6e\x52\xce\xc7\xcd\x54\x30\x1b\x2e\x08\x49\x43\x6d\x15\x1c\x3f\x42\xd3\xe3\x31\x52\x2c\x60\xa1\x41\x14\x44\x5a\x96\xa9\x66\xb1\xff\x88\x14\xc8\x4f\x71\x39\x15\xfd\xc3\xca\x6a\xea\x30\x0d\x5c\x3b\xb2\x94\x33\x5a\x34\x60\x5e\x87\xd0\xf6\x78\x44\x68\xaf\x03\x98\x6d\xca\x10\x13\xe8\x16\x00\x36\xe5\x16\x27\xe8\x36\x2c\x3f\x1e\x27\xf0\x75\x20\x2b\x3a\xde\x6b\x00\x19\xea\xc0\x07\xa1\xad\x83\x6c\x5c\xe2\xa6\x61\xab\x26\xb3\x91\x69\xe4\xf4\xe8\xed\x6c\x41\x33\x1d\xef\xb0\xd3\x44\xc7\x7b\xfc\x45\x8e\x47\xed\x1b\xcd\x17\x75\x3b\x1f\xcd\xe1\x73\x81\x90\x6d\x87\x1e\x4b\x11\x39\xfc\x14\xb5\x46\x15\xbe\xa5\xe0\xc1\xb7\x20\x0f\xc4\x33\x03\xad\x62\x5b\xce\x57\x31\x15\x57\xe9\xdd\xa6\xe0\x06\x51\x4c\x65\xdc\xfe\x20\x95\x0d\xba\xd5\xf8\x60\xd1\x2c\xa7\xb2\xc1\xdb\x27\xe1\x53\x3e\x81\xc7\x85\xe9\x18\x91\x1b\x60\x26\x79\x8a\xac\x64\x2f\xba\x93\xa7\xb8\xce\x0d\xf9\x3d\xd7\x2f\xc5\x1d\x86\x3a\xaa\x60\xe4\xd5\x49\x09\xb0\x2d\xa7\x15\x03\x24\x28\xbb\x7b\x6b\x33\x20\x8e\x04\xa6\x3e\x11\xb1\xb3\xeb\x44\x3a\x66\x24\x20\x01\x8a\x70\x96\xa8\xf2\x74\x1b\xc1\xfc\x0c\xc7\xf8\xd2\xbf\x82\xef\xe6\x5c\xf3\xf2\x4e\xc8\x6e\x39\x0c\xb3\x53\x66\xa9\x83\xe7\x3c\x92\xb4\xd9\x44\x90\x6c\x90\xbe\xaf\x32\x48\x40\x65\xbf\xfb\x7c\x2b\x8a\xdc\x5e\x8a\x66\xe5\xe3\x9b\xa0\x18\x4c\x42\xed\xa7\xc3\xea\xde\x0b\xa1\x7a\x22\x5b\x0c\xcb\x54\xc6\xcb\x4c\xc9\x7b\x5e\xe2\x75\xfb\xa0\xec\xc5\x4a\x67\x3f\x88\x48\xba\xf3\x45\x12\xfb\xd3\x7c\x38\xb4\x14\x76\x63\x7b\x91\x16\xd9\xb6\x48\x35\x7f\x13\x52\x4e\x50\x21\x3b\xff\x0a\xc6\x79\xb6\xa9\x72\xcc\x82\x21\xc0\x63\x27\x86\x38\x1e\x07\xe6\xe7\x00\x7c\xc2\x2a\xb5\x2d\x33\xa0\xd0\xdd\x7a\x3c\x17\xb0\xb4\x7e\xb0\xdd\x23\x49\x68\x0a\x1e\x06\xbd\x93\x0e\xda\xe1\xb4\x5e\x09\xb4\xe8\x5b\xd9\xff\x30\xe0\x57\xe1\x83\xbb\x63\x67\xbd\xf8\xa6\xde\x18\x3a\x90\xb3\x5b\xaa\x6a\xaa\xbb\x07\x35\xe2\xd6\x08\xba\x31\x91\x26\x49\xcb\x6c\xda\x1d\x51\x76\x29\x9d\x2f\xab\x23\xe7\x38\x0d\x58\x4b\x26\xcc\x53\x9b\x7c\x93\xd4\xcb\xf1\x99\x18\x43\x75\x90\xc6\x7c\x50\xb6\x42\x33\xc2\x56\x79\xdd\x9a\x6a\x17\x7e\x05\x2f\x3d\xf8\xea\xbd\xcd\xfd\x80\xaf\x55\xc9\x77\xd0\xb9\xf6\xdd\xd0\x86\xdd\x65\x73\x91\xe7\xb5\x87\x74\x2e\x9d\xeb\xc4\xe7\x64\x75\x49\x7b\x92\x28\x08\x3e\xef\x61\x21\xc5\xd4\xb0\x93\x26\xcf\xab\xab\x65\x96\x39\x64\x29\x26\x34\xc4\x6f\xe1\x23\x72\x16\x41\x81\x65\x4e\x26\x75\x1f\xcc\xea\x5d\xc7\xfe\xd5\x58\x66\x69\x51\x7c\xa3\xd4\xef\x27\xa6\x31\x53\x6e\x91\x1e\x06\x0c\x7c\x5b\x6c\x0d\x2c\x37\x78\xa3\x43\xb7\xfe\xd4\x18\xca\xcf\xf5\x82\xce\xb9\xdf\xa5\x10\x46\x79\x9c\xe6\x3e\x37\xe8\xd6\x19\x90\xba\x17\xe2\x1d\xea\x13\x80\x77\xda\x59\xa7\xca\xc0\x0d\x21\x84\x55\xe7\x86\xd0\xd4\x19\x58\x27\xe0\x36\x81\x30\x5f\xd4\x27\x70\xef\xcc\x08\xda\x5d\x41\xef\xe6\xc9\xc2\x81\x17\x00\x1a\x2a\xcc\x09\xd9\x7b\x64\x7b\x2b\xfb\xcd\xba\xe5\x92\x97\xa9\x86\x5a\x16\xb0\xf4\xd9\x33\xb5\x50\x78\xaf\x1b\x8b\x0e\xdd\x58\xe4\x68\x44\x2a\x06\xb9\x8d\x2d\x21\xc3\x7e\x8a\x44\xec\x24\x77\x74\x6e\x45\xae\x54\x52\xbd\xa0\x36\xb8\x18\xa6\x0e\xc2\xc6\x5f\x40\x62\x64\x6c\x4a\xc0\x2b\xdc\xda\xcb\x48\x7a\x4d\xa8\x1a\x8f\xa9\x1c\x8f\x09\xae\xe1\x67\xae\xc6\xf9\xba\x1d\x40\x70\x0e\xc6\xf7\x2f\x6f\x7f\x6d\xdf\xe5\x39\x84\x72\xea\x48\xe1\x42\xcf\x99\xe5\x76\x1a\xd1\x13\x81\x1d\x10\xcc\xae\x17\x43\x95\x56\xa8\x35\x75\x45\x61\x38\x26\xa6\xa6\x28\xc6\x5a\x8a\xca\x12\xf2\x06\xe9\x5e\x06\xb9\x35\xd4\x53\x56\x1d\x8f\xf2\x09\xbb\x3e\x1e\x2f\xbb\x80\xaa\x13\xd1\xae\x2d\x66\x50\x4d\x7a\x43\x0c\x4a\xf5\xc6\xa3\xae\x88\xd0\x9c\xad\x0d\xfc\xe2\x95\xcb\xa4\xb3\x65\xeb\x78\x2d\x6e\xd7\xa6\xc4\x66\xff\x58\xb1\x1f\x74\xe4\x7c\xc6\xa1\xd6\x38\xa7\x13\xda\x00\x45\x32\x4d\x99\xcb\xbe\x32\x6b\x8a\xaf\x64\xb2\xba\x8a\x40\x5c\x99\x8f\xfe\xf1\x14\x13\xab\x43\x49\xe4\x6b\xc7\x7f\x4f\xae\x41\x8b\x1f\xc0\xcc\xf1\x17\x22\x42\x81\x37\x19\xfb\xac\xbc\xe3\x5f\x4d\x21\xc8\xb3\x5d\x1e\x38\xbf\xbf\x2b\x25\xc1\xcb\xaf\x89\x49\x9e\x3f\xca\x47\xdb\x47\x5b\x42\x33\xf6\x5a\x37\xfa\x62\xb4\x1c\xa8\xdc\x2f\x53\x14\x75\xa7\x3b\xfa\x07\xb9\x4a\xa9\x19\x54\x4f\xed\xf2\xaa\x30\xaf\xc6\xcd\xab\xed\x15\x18\x3d\x67\x21\x4f\xe4\xab\x57\x34\x23\xe0\x22\xd9\xde\x92\xac\x7e\x90\x44\xe9\xbd\x0f\x9f\x38\xbd\x01\x61\xd1\x7f\xfa\x5f\x09\xed\x2b\xaf\x4e\xa2\xcd\x52\xce\x6c\x96\xb9\x89\x4b\xfe\x32\xa9\xa9\x0f\x2b\xe5\x8c\x54\x0f\xa8\x29\x92\x56\xcb\xa4\x50\x6b\x56\xd5\xe8\x0c\xdd\x3e\xbf\xb0\xa7\xed\x73\x6a\x4e\x79\xea\x3e\x9c\xb2\x5f\x05\xf0\x8f\xad\x5d\x04\xf3\xee\x59\xc4\x4f\x11\xaf\xcb\xf1\xc2\xbe\x10\x51\x45\x46\xa9\x21\x60\xfa\x10\xae\x6b\xea\xaa\x19\x5a\xda\x11\x7c\xed\x5b\xea\x0c\xb3\x56\xa2\xac\xcc\x2c\x8b\xb4\xd2\x89\xa2\x78\x1d\x92\x8a\xda\x73\x61\x38\xf4\x9e\x9b\x53\xb0\xc7\x8f\xa4\x3b\x9d\x34\x63\x5f\xe9\xee\xd5\x23\x74\xcd\x7c\xb6\x81\xcc\xdc\x35\x9f\xa0\x20\xb3\x3e\x05\x5e\xbd\x20\x63\x54\xb0\xcd\x26\x49\xfe\xc8\x8a\x46\x47\xeb\x47\x2e\x69\xf6\xd4\x4f\x35\x74\xb4\x0d\xe6\xec\x38\x8e\x51\x41\xea\x20\xd9\x4d\xab\xe3\xb5\xef\x38\x0f\x3b\xc6\xe5\x6a\xf7\xeb\x96\xdc\xf2\x37\xa6\x57\xf0\x16\xb3\x67\xd0\x71\xf0\x82\x2a\x9a\xd3\x35\xa9\xcd\xdb\x75\x2a\xf3\x82\x7f\x8f\x6c\x9f\xd9\xff\x90\x7e\xf2\xf4\xa1\x05\x20\xda\x33\x88\x40\x4f\x85\xcf\xa5\xa5\x9b\x9a\x6c\x9f\x2e\xcd\xa7\xee\xe4\xa0\x09\x08\xae\xa6\x7f\x2c\x0a\x3b\xd4\x6a\x13\x3e\xda\x24\xd8\xf5\xe9\x74\x4e\xa3\xe9\x41\xd4\x09\xab\x1e\x45\x0d\x63\xe5\x55\x9b\x69\x4d\x5d\xbe\xe0\xa4\xac\x1b\xdf\x03\x5a\x30\x48\x64\xd5\x3e\x0c\x34\x63\xa0\x7f\xc2\xd0\xd4\x18\xc4\x08\x03\x21\x98\x93\xd0\x4d\x32\xec\x86\x50\xa2\xef\x41\x4b\xbf\x32\x21\x63\x9b\x8f\x75\xcd\x94\xcd\xf1\x7d\x5a\x4b\xb5\x8e\xfa\xf8\xda\xc6\x4f\x80\xa8\x09\x93\x69\x31\xcb\x66\x51\xce\xe4\xa3\x06\xec\x8b\x47\xc1\x4a\xe7\x4c\x3c\xf2\xf0\x7f\xcb\xe4\x23\xee\xa8\xdc\x01\x70\x06\x66\xf4\xd5\x6c\xeb\x76\x22\x19\x70\x99\x63\x59\xee\x08\x62\xd3\x89\xcb\xc2\xf4\x98\xfa\xaa\x57\x8f\x0d\x0b\x16\x12\xbe\x1e\x70\x46\xf9\xb8\x1c\xa5\xe4\x91\x72\xb9\x9b\xec\x8f\x71\x09\xd6\x5b\xaa\x4d\x0f\x37\xcd\xb6\xe3\x75\x6f\xb3\xb5\x69\x86\xb7\xc1\xd9\xaf\xe1\x8c\xae\x1e\x03\xc6\x76\x0f\xd3\x70\x4e\xde\x28\xc8\xad\x85\x9f\x1a\x98\xa9\xb9\x2e\xa8\x64\xe1\x90\x0c\xc5\x2e\x46\x69\x53\x60\x49\x74\x39\x4a\xeb\xba\x7b\x33\x4e\x02\xaa\xdb\x93\x89\x31\x75\xc2\x6b\x11\xba\xcf\x07\x4b\x46\x3b\xd5\x08\x6d\x9f\xf6\x9e\x66\x1f\xd4\xa6\x53\xa9\xd5\xa8\x6c\x2f\x69\x9b\xf5\x38\xa9\xd8\x6a\x6a\xd9\x9a\xd3\xb6\x36\x8b\xec\xe9\xcd\xb3\xd4\x60\x2f\xb2\x72\x6f\x3c\xae\x6a\xdf\x0b\x0b\xb1\xcd\xb5\x49\x74\x73\xfd\xb8\x35\xaa\x75\x71\x7e\xad\x41\x5d\xa0\xef\xb5\x39\x71\xed\x13\x86\x10\xab\x45\xf5\xca\x26\xf8\xed\xc8\x03\x3d\x4a\xb2\xaf\xeb\x7e\xc6\xfe\x94\x69\x7a\x90\xea\x07\xe6\xa9\x87\x88\xa7\xfc\x01\x92\x37\x22\x75\x17\xf5\xf4\x08\x02\x00\x02\xfa\x2a\x68\xdf\x7b\xd6\x80\x2f\x14\xea\xa1\x91\xac\xc5\x8b\x53\xe1\xa3\x05\x0f\x87\x91\x64\x3f\x08\xf0\x53\x80\xed\x0e\x04\x1e\xe8\x4c\xac\xee\x36\x5b\x4b\x8d\xe0\xb8\x24\x95\xde\x92\xdb\xdb\x5c\xf5\xd4\x0b\xc3\x56\xea\x5d\x22\xe8\x89\x56\xc9\xe9\x07\xa8\x62\x60\x68\x67\xcd\xf6\x2c\xb5\x8c\xf1\x48\x30\x6c\x01\xbd\xa3\x3b\x36\xa1\x37\x56\xfb\x9e\xb2\xc9\x34\x05\x5b\x34\x0c\x5d\x9a\x31\x3d\x4f\x2d\x73\x42\xd7\xac\xe5\x94\x63\x56\xfb\x95\x6a\x9c\x0a\x21\xce\x9a\xa1\x41\x98\xa1\x89\xd1\x92\xd8\x80\xbe\x79\xbe\x80\x3f\xc7\xe3\x01\xe2\x0d\x1d\x6a\x7a\x9b\x25\xf3\x45\x4d\x57\x6c\x1d\xa8\x87\xe9\x2d\x33\x5c\xfb\x17\x51\x46\x8e\xc7\x5f\xa3\x8c\xc0\x08\xe0\x47\x13\x0c\x26\x73\xbc\x97\x8b\x09\x73\x07\xb1\x7a\xe8\x17\xd1\x1d\xb4\x32\x7f\xa3\x5b\xf6\xab\x8e\x04\xdd\xa2\x53\xc7\x36\xbe\xcd\xe8\x2d\xbd\x23\x74\x33\x62\x2b\x84\x67\x17\xbd\x55\x32\x42\x37\x6c\x35\x55\x68\x78\x70\x0b\x5e\xb1\xe6\xd7\x86\xd0\x20\x10\xc7\x2d\xdd\x11\x7a\xd3\x3c\x6f\xe8\x0d\xa9\x3b\xd9\xbc\xbf\x8d\x5a\x39\x17\xcd\x09\xba\xcd\xc0\xee\xd3\xda\x3f\x3c\x46\x13\x65\x88\x23\xfb\xd4\x86\x90\x45\x63\x45\x09\xf1\x6b\x6d\x84\x49\x14\x9a\xcf\xc5\x5c\x2d\x16\x53\xe1\x38\xc3\x09\x95\x98\x2d\x36\x92\x4d\xfc\xa2\x25\xb3\xa1\x52\x7e\x5c\x45\x3b\x42\xf7\xac\xf2\x8f\x37\x84\xde\x33\xcd\x9e\x46\x96\x44\x55\x73\xbd\x30\x1c\x95\x25\x54\x2b\x7c\xac\x9b\x3c\xf7\x40\xcf\xdd\x47\x13\x82\x24\xdd\x7d\xc4\x0d\x0f\x62\xe9\xba\xfb\x68\x49\x3c\x69\x77\x1f\xed\x09\x66\x37\xac\x12\x65\x3b\xac\x0c\x41\x7b\x12\x01\x41\xb7\xd5\x60\x0e\xd7\xda\x97\x81\x96\xec\x87\xf4\x07\x53\x01\x5e\xbc\x52\x25\x54\x04\xbf\xfc\x2e\x86\xee\x86\x7a\xc0\x2b\xe8\xd3\xeb\x4c\x8e\x47\xfd\x94\x7b\xfc\x3d\x93\xdb\xa2\x48\xbc\xeb\x40\xeb\xf3\x7c\xae\x9d\x75\x77\xf8\x95\x97\x3c\x13\x77\x69\xd1\x03\xa6\xda\x02\x3c\x83\x77\xd8\xf5\x58\x93\x20\x4f\x61\x20\x00\x1c\xe9\x47\xdc\xd1\x57\x6e\x78\xff\xd6\x90\x6e\x23\x94\xe6\xcd\x7e\x31\x65\x36\xa2\x23\x9d\x40\x26\x6a\xc8\x78\x07\x83\x08\x57\xc2\x0d\x26\xd2\xe3\xae\xee\x98\x5c\x85\x8a\xe8\x69\xc7\x7f\x38\x18\xf3\xec\x7a\xcc\x13\x6e\x3e\x00\xf9\xe9\xa0\xeb\x36\x0c\x3f\xdd\x24\x5b\x6a\x1a\x58\xa7\x0a\x52\xb7\x9f\x83\xf0\x2c\x1a\x54\x57\xbc\xa3\xa8\x7c\x32\x81\x08\xbd\x33\x9e\x40\x8c\x30\xfe\x74\x32\x33\x9c\x52\xe8\xe5\xd9\x23\x93\x81\x9d\x85\x54\x1a\x81\xc3\xbe\xe8\xf8\xe7\xdb\x14\x8f\x3e\xf6\xef\xa7\x3d\xd4\xff\x82\x4f\xbe\x19\x41\xe0\x61\x8f\x3e\xe7\xa6\x10\x1c\xce\x79\xd7\xa5\x5e\x36\x91\xdf\xcf\x05\x07\x88\x20\xe5\xee\xa9\x9f\xba\x62\x9c\x3e\x34\x1a\x49\xe8\xa1\xb2\x71\xaa\x70\x18\xce\x62\x8b\x10\xb0\x4c\x97\x54\xd5\x81\x95\xf8\x59\xf1\x8b\x15\xbd\x88\x5e\xc6\xcb\xea\x2e\xd3\x9b\xaa\xc9\xf7\x66\x10\x99\xea\x94\x1b\x5e\xcc\x94\x57\x7d\x1c\x5e\xca\x78\xdc\x31\x53\x04\xb5\x23\xab\x66\x55\x4b\x68\x32\x4a\x13\x03\xdc\x4d\x71\x47\x96\x90\xfa\x30\xe2\x17\x27\xd2\xe5\xe2\x91\x7c\x5a\x3e\x52\xb3\xf2\x4a\x26\xc5\x95\x4a\x8a\x47\xea\x49\xf9\x48\xce\x8a\x2b\x99\x94\x57\xaa\x0e\x99\xe9\xb6\x3e\xd2\xad\x80\xe5\x6b\x1d\xb9\x03\x0a\x8c\x4b\xc6\xf4\xec\xf2\x52\x7b\x50\xd1\xaf\x21\x76\x9e\x1c\x13\x8f\xa1\xbf\x0e\x35\x6a\xbd\xc7\x18\x4c\x13\xfc\xb6\x83\x0d\xb7\x63\x79\x0e\x28\x08\x68\x28\xb2\xb4\x66\x0a\x22\xbc\x59\xdb\xff\x82\xf1\xce\xfc\x69\xe6\x6e\x86\xf3\x4c\x8c\xca\xd9\x75\x32\x31\x5c\x33\xf0\xee\x34\x37\xd8\x7f\xcb\xaa\x87\xec\x26\x56\x6c\x1b\x37\xd6\xb9\xb3\x6d\x1c\x9a\xc3\x4e\xe8\x2d\x5b\x5d\x3d\xa6\xed\x30\x32\x76\x3f\x7e\xd1\x91\xa4\x1a\x62\xc0\x19\x5c\x76\x47\x77\xf4\x86\x2e\xe9\x9e\xde\xd3\x8f\xf4\x7b\xfa\x3b\x7d\x4f\xdf\xd2\x97\x10\xe8\xdf\xd2\x91\x29\xb9\x63\x9b\x88\x3b\x32\x96\xde\x33\xf7\x7b\xbc\xa6\xdf\xb3\xbb\xf1\x2d\x7d\xcf\x36\x11\x48\xfc\xc9\xe8\x96\xbe\xf4\x7a\x15\x88\x06\x78\x61\xba\x6a\xe8\x50\xd7\x1b\x90\xe2\xef\xad\x2a\xe6\x25\xb4\xb7\x1f\x18\xdf\xd2\x7b\x76\x37\xba\xa5\xdf\x33\xa8\x36\x5a\x37\xfd\x38\x9b\x31\xd7\x8b\xa5\xcb\xf7\xcc\xfe\x1c\xaf\xe9\x47\x18\xd1\xef\xd0\x23\x70\x09\xa3\x5b\xfa\xd6\xa9\x7f\x9a\x9e\xbc\xa9\x99\xeb\x0a\x39\x8a\xdf\x9d\xc6\xe8\x2d\x74\x80\xfd\x8f\x6f\xe9\x1e\x46\xf4\x91\x61\xbd\x70\x48\x40\x54\xa3\x5b\x8b\x33\xc3\xf5\xbd\xe2\xa2\x8c\xfc\xd4\xae\x1e\x8f\xe2\xbf\x13\xdf\xf6\xe7\x28\x25\xcd\x19\x0f\xe3\x19\xa4\x04\x7d\x0c\x20\x54\x09\x8e\xaf\x13\x2c\xf6\x14\xd0\x0b\x42\xea\x66\x41\xbd\x6e\xcb\xad\xe5\xfd\x68\x5d\xfb\x41\xef\x1f\x1e\x34\xcc\xd1\xcd\xfe\xea\xf1\xff\x9b\x23\xde\xc3\x76\x7d\x64\xfb\xf1\x3a\x5c\x7c\xfb\x71\x88\x74\xb3\x63\x93\xe9\x0e\x4c\x38\x76\xcd\xb7\xcf\x5f\x8f\x1d\x21\xa0\xdb\x6b\xec\xcd\x21\xf3\x37\x18\x98\xa7\xac\x8a\x1b\x9b\x5b\x50\xf0\x66\xb0\x54\x6d\xfb\x72\x43\x34\xc7\xde\x84\x9d\x6e\xed\x13\x98\x1b\xd3\x95\x7d\x7a\xde\xee\xe7\xf6\xa4\x18\xfb\x9a\xde\xb0\x7f\x8b\x88\xd3\x1d\x2d\x09\xf5\x91\xfa\x6f\x86\xc3\x68\xc9\xe0\x32\xde\x50\x41\x68\x31\xdb\xb3\x8f\xec\x77\xf6\x96\x2d\x93\x7b\xf6\x3d\x7b\xcf\x5e\xb2\x25\xcd\x6d\x46\x2b\xbd\xbb\x4e\xf6\x54\xef\xaf\x93\x7b\xaa\x77\x8f\x93\x8f\x54\xef\x1f\x27\xdf\xd3\xdd\x75\xf2\x3b\xdd\x5f\x27\xef\xe9\xee\x71\xf2\x96\xee\x1f\x27\x2f\x5d\xd2\x6a\x4c\xe7\x97\xa8\xd0\x92\x3e\x3d\x35\xa4\xcf\x02\x4b\xfd\xd0\x30\x7f\x4b\xdb\x73\x49\x56\xb4\x6f\x72\xc9\x6d\x1d\xe2\xcc\x96\xad\x59\x46\x79\xdb\xd0\xec\x8e\xe6\x6d\xc6\xe8\x33\x60\xae\x87\xb4\x1e\xc4\x2a\x6b\xb7\x5f\xd5\x4c\x02\xae\xea\xc0\xd7\xd2\xab\x27\xac\xe8\xaa\x08\xad\xe2\x33\x2f\xc7\x5a\x3b\xdb\xfe\xbc\x66\x15\xdd\x1a\xe0\x2b\x51\xfa\x6e\xa0\xeb\x68\x4d\x6f\x59\x3e\x1b\x9b\x89\x6f\xd8\xf8\x2b\x73\xc6\x3a\xc8\xf6\xce\x31\x68\xbd\x10\x94\xbe\xa0\x3f\xb2\xc1\x9d\xc8\xf3\x82\x0f\x42\x70\xaa\xc8\xc7\x06\x80\x22\x90\x33\x28\xf8\x97\x67\x3b\x51\x61\x50\x34\x33\x50\x88\x12\x41\xfa\x00\x28\xb6\x37\x60\xe5\xb3\x1b\x3b\xa8\xa9\x9a\xeb\x83\xed\x7e\xed\x69\xb7\x25\xd3\xef\x19\x5a\x04\x43\xa1\x61\x42\xe2\x5d\x7d\x0a\x38\xff\x47\x7a\xeb\x87\x9e\x66\x8e\x3d\xb0\x73\x15\xc0\x21\x75\x06\x0e\x29\x0b\x87\x0c\xb7\x34\xfd\xc8\x3e\x0b\x0a\x8d\x56\xf5\x83\x2b\xf9\x29\xb8\xa9\xc8\x3d\x3b\x85\x9a\xe3\xbf\x3c\xdc\xfb\xcf\x1b\xae\x1f\x6c\xff\x82\x37\x8b\x5d\xdb\xf1\x0e\x87\x51\x23\xeb\x2b\x66\x3f\xa2\x48\xd6\x8b\xf8\x8a\xe1\x30\xfa\x91\xb9\x23\xe6\xd3\xa7\x3f\xb3\x5f\x09\x89\xc3\xa9\x85\xc7\xf4\xa6\xb1\xae\xdb\x3d\xb9\x41\xd0\xbc\x64\xe5\x7c\xb7\xa0\x7b\xb6\xc4\xab\x32\xfd\x2c\x58\x3d\xfd\x1d\x4d\xfa\x5a\x4c\xe2\x8e\x8c\xac\x5e\xd6\x02\xe4\xf7\x8c\x9f\x15\x61\xec\x08\x7d\xcb\xde\x87\x32\x89\x97\xec\xd7\x68\x4f\x66\x7b\x6f\xd2\xee\x59\xbc\x97\x56\x08\x8a\xe8\xa0\xb0\x47\x33\xf0\xe2\x41\xd0\xdf\xf6\xf5\xb1\x29\x3b\xc4\x2a\x4a\x67\xd1\x3d\xfb\x9d\xbe\x60\xfe\x36\xcf\xd0\xcf\x86\x31\x96\x1d\x8f\x06\xb2\x6f\x66\xe3\x97\x8f\xde\x8e\xde\x5e\x3d\x4e\x82\x23\x92\xcd\xc6\xcf\x3a\x54\xf2\xd5\xe3\xb1\x30\x15\x93\x93\x37\xd8\xf8\xa4\xdf\x93\x2e\xfb\x7b\x4c\xba\xc5\x66\x3c\x10\xe2\xf8\xc5\x23\x06\x0e\x7c\xd1\x47\x98\x43\x74\x3d\x7e\x49\x1e\xbd\xbd\x7a\x4c\xa8\x8e\x4f\xfc\x84\x1a\xa8\x0c\xde\x03\x1d\x47\x20\x83\x62\x9f\xd9\xfe\x2b\xb3\xeb\x92\x3d\x43\xae\xc0\x3c\xd9\x78\xe0\x1f\x47\x2f\xc6\x00\xaa\x68\xc5\xee\xc7\x48\x3a\x4d\xab\x8f\x42\x67\xeb\xe8\x47\x72\xc8\xd2\x8a\x3b\x08\x99\xa8\x31\x13\x57\x8f\x31\x96\xe5\x14\xde\xd8\xf3\x08\x6f\x6a\xdb\xea\x7b\xdb\xca\xae\x42\x52\x8d\x99\x6c\xb7\xb2\xae\x04\xe6\x45\xbd\xb5\xd6\xf3\x15\x58\xdb\x29\x8b\x1a\xe5\xc8\x29\x52\xac\x24\x45\x8c\xbc\x78\x1b\x91\x66\x33\x5b\x38\x12\x75\x7d\xe7\x32\x4b\x3a\xcf\xb0\x0d\xba\x6d\x25\x7b\xba\x52\x52\x27\xef\x6d\x43\x49\xab\xc0\x1f\xac\xb0\x4f\x1e\xbb\xf2\x9d\xb6\x68\xf3\x05\xf5\x97\x34\xf9\x1e\x7e\x1b\x76\xdc\x9c\xe0\xe4\x47\x0a\x39\xf9\x0a\xfc\xd0\xfc\x9e\x7e\x5c\x78\x2f\xac\x64\x5b\x7b\x6c\x7b\x57\x9f\x85\x57\x96\x9f\xf7\x28\x53\x5b\x94\xd9\x95\x17\x8b\x55\x64\x30\xdb\xa9\x91\xbe\x53\xad\xbb\x53\xee\xdd\x31\xec\xf2\xda\x0c\x73\x6e\x1b\x1c\x27\xe6\x61\x0c\x47\x8b\xa7\x99\x60\xae\x9d\x05\x35\xf6\x05\xe8\x14\x6c\x5f\x84\x8a\xfa\x1c\x30\xeb\x92\x06\xcd\x94\x84\xf3\xde\x0b\x70\xbb\x74\x18\x5d\x79\x24\x5f\xd5\x75\x40\x44\xa4\x4c\x8f\x2a\xcc\xa0\xda\x81\x6b\x2d\xbe\xd6\x39\x10\xb9\x69\x39\xf4\x29\x66\x6a\x16\x15\x76\x4a\xc0\xc7\xc1\x0c\x46\x15\x49\x22\xff\x34\x4e\xa9\xbf\xb9\x72\x56\x30\xef\xdb\xd2\x5c\x5c\x09\xbd\x78\x97\xbd\x31\x2b\xaf\x1e\x93\xa4\xdd\x33\x70\x23\x24\xf0\x88\x71\x1f\xc7\x02\x5f\xc7\x7f\x1c\x1e\xba\xdf\xb6\x8b\x7f\xf6\xd3\xa3\xe6\xd3\x41\xbf\x88\xc0\x48\xd2\x94\x1e\x9a\xd3\x5a\xd0\x5d\x92\xd5\x6d\x3a\xee\x59\xc9\xd3\xae\x7c\x1e\xc2\x70\xb5\xe5\xef\xb8\x3b\x27\x49\x1c\x83\x74\x28\x3e\x22\xbe\xdd\xe6\x93\x1d\x38\x1e\xc3\x15\x39\xa0\x11\xad\x35\x9d\x06\xce\xc1\x1a\xd4\xfa\x0b\xed\x8c\xa5\x91\x9b\x48\xdc\x81\x16\x2d\xdd\x88\xed\x09\x59\x27\x6b\xa7\xeb\xad\xa9\x2d\x1b\x85\x1d\x59\xc8\x51\xbb\x64\x6c\xc0\x83\xfb\x0c\xa5\x51\x28\xe0\xd7\x8d\xf9\x40\x27\x87\x69\xc2\x6b\xfc\x8a\x00\xb8\x24\x2d\x5c\x52\x5e\xb2\x6b\xa5\x6f\x98\x6b\xb8\x4a\xef\x31\x4d\xcd\x4a\x14\x05\x84\x7d\x02\xd3\x44\xf3\xf4\x8e\x67\x10\xb0\x83\x2a\x8c\x3c\x5d\xf2\x4a\xab\xd2\x09\xf8\xde\x38\xfe\x27\x14\xe6\x9e\x35\x9c\x32\x94\x2f\x2a\x50\xce\x9a\xe3\x74\xdd\xcc\xa4\x97\x64\xac\x84\xcc\x31\x11\x4e\xc4\xd9\x53\x8e\x42\x59\x88\xf1\x40\x50\x56\xce\x26\x5e\x00\x21\xce\x12\x03\x92\x90\x86\x69\x73\x60\x0e\xd7\xf8\xeb\x52\xe4\x9f\x1c\x3e\x0a\x69\xf4\x0e\x33\x28\xb7\x4c\xa5\x41\x88\xd7\xb1\x9e\xe6\xf1\x39\x19\x90\x8d\x33\x4a\x53\xef\x74\xe0\x9d\x29\x0f\x56\x93\x3f\x1c\x0a\xa4\x21\xc0\x5a\xd6\xee\x91\x6c\x86\xef\x34\xfe\x54\xc6\x88\x04\x70\xe7\x6c\x23\xb4\xc4\x7e\x03\x46\x7f\xd5\x3a\x12\x1d\x9e\xd4\xf5\xd4\xf0\x59\x4c\x9c\xf2\xa8\x32\x06\x17\xef\xb7\xa9\x5e\xa3\x55\xba\xba\xe7\x1f\x54\xa4\xe3\x1d\xd5\xf1\xde\xf5\xf1\x41\x45\x3c\xde\x51\x8e\x25\x38\x16\xa8\x1e\x1c\x16\x0c\xb7\xe2\x76\xd9\x90\x78\x18\x9f\xc1\xe7\xc5\xab\x20\x12\x56\x15\x06\xa9\xad\x16\x53\x11\x77\x5c\xc2\x87\xc3\x32\x3a\xec\x12\x1d\xef\xae\xe9\x3e\xd1\xf1\xfe\xba\xa6\xf8\xfc\x18\x9f\x1f\xd7\x54\x13\x2a\x1a\x3f\x47\xdf\x42\xdb\x26\xda\xb7\xd1\xb6\x91\x36\xad\x0e\x0e\x31\x37\x6c\x38\x5e\x9a\x90\x4d\x0f\x38\xdd\x2e\x2b\x7e\xca\xf8\xf6\x33\xeb\x35\xa9\xeb\x46\xae\xd6\x15\xc3\x5a\xc3\x20\x4e\x51\x81\xe7\xef\x37\x48\x01\x65\x8d\x89\xe7\x64\x78\xbe\xbb\xf9\x3f\x2a\x26\x43\xb9\x9d\x6a\xcb\xed\xe0\xfe\x55\x6d\x00\x99\x3e\xd0\xe1\x24\xbc\x30\x18\xa9\x38\xe0\xb7\xc3\xe4\x1f\x3d\x46\xbe\x05\xfb\x45\x47\xdc\x1a\xf6\xd2\x8a\x8c\xab\xab\xc7\x34\x73\x85\x68\x8d\x92\x92\x51\x7a\xf5\x98\xae\x59\xce\x4a\x92\x44\x6b\xf7\x16\x48\x3a\x6c\x91\xbb\x32\x0b\x27\x6d\x93\x82\x65\x0c\xd2\x34\xd9\xab\x21\x82\xab\xd1\x9a\x35\x64\x7a\x69\x2e\x88\x7b\x87\x7b\x2c\x5a\x47\x5c\xb8\x23\x5e\xd0\xb5\xeb\xf1\x83\x8a\x32\x9a\x13\xdf\x0b\x54\xf3\x47\x1b\xf6\xf2\x8c\xd6\x1a\x61\x5d\x1b\x43\xb5\x01\x5d\xa0\x17\x32\x50\x45\x06\x10\x23\x40\x7b\x53\x39\x1c\xfe\x86\x71\x93\x7c\x34\x29\x1e\xfa\x64\x20\xec\x09\x7c\x34\x3a\xdd\x9c\x42\x9d\xcf\xbd\x81\xa0\x17\x00\x2f\x5f\xf0\xd1\x06\x9e\xab\xa1\x5d\x0d\x91\x15\x20\x8d\xe6\x85\x85\x41\xa2\xc1\x21\xc1\xbb\x02\x0d\x2d\xfc\xb3\x06\x0b\x0a\xff\xe8\x2c\x90\x7c\x81\xb5\x17\x21\x94\x73\x40\x45\x13\x8a\xec\x93\x05\x52\x10\xcf\x56\x0e\x87\x3f\x69\xc3\xb8\xe2\xbd\xd7\x8d\x68\xbf\x8b\x29\x1b\xd3\x06\x6b\x6b\x27\x7c\x48\x0b\xb8\x5f\x6e\xe3\xba\x38\xc9\x47\x4c\x79\xc5\x23\x61\x0d\x23\x53\xc3\xb8\x78\x43\x4a\xcc\x72\x06\xd4\x27\x5e\x0c\x56\x05\x1c\xe3\xd5\xe3\x69\x68\x2b\x31\x8b\x8a\x11\x4b\xdd\x91\xfe\x35\x12\xe8\x28\x3d\x1c\x9a\xf2\xb0\xdd\x23\xfb\xaa\xb1\x37\x32\x74\x93\x69\xab\xd5\x06\x07\x75\x80\x89\xfc\x02\x92\x36\x5d\xf0\x5f\x31\x41\x02\xde\xf8\x9c\x7a\x8e\x62\x5b\xb3\xd3\x60\x39\xb6\x03\xb5\x49\x2a\xa4\x18\x52\x47\x97\x94\x96\x20\x29\x6a\xa6\xc3\x7c\x25\xec\xbc\xe2\x25\x5a\x33\x15\x49\x9a\xd2\x82\xd0\x9c\x7d\x23\x6c\x64\x56\x9a\xb1\x62\x9c\xc2\xed\x6e\xca\x72\xa8\x5a\x1a\xb2\x62\xcb\x02\x02\x78\xfc\xbd\x4e\xbe\x47\xcd\x1a\x4c\x6a\xed\x26\x95\x37\x93\xca\xc2\x49\xd5\x18\x87\xa9\xa0\x9c\x96\x64\xca\x21\xe4\x26\xac\x18\x9d\xd0\x09\xad\x1c\x60\x77\x78\xb1\x77\x65\x02\x5e\xe9\x67\x11\x95\x10\xb8\xa6\xcd\x33\x39\x2e\xb2\xcd\x3a\x65\x74\xbd\xa8\xf1\xd0\xf5\x29\x83\x03\x2a\x07\x93\x65\x75\x68\x39\x8a\x45\x96\xf4\xb0\x4f\x0e\x31\xd8\x47\x7b\x98\xed\x93\x87\x34\xa4\x5e\x16\xe9\x9e\x97\xbd\x89\xbc\x4e\xec\xa8\x87\x43\x47\x47\xfd\x79\x3c\x4e\x30\xf3\x54\x29\x72\x53\x6c\xfe\x43\x69\xb3\xab\xed\x71\x63\xcc\x00\xc6\xd8\x2f\xad\x78\x80\x65\xfa\x71\x36\x3f\xfc\x99\x48\x8a\xe1\xe0\xc9\x41\x9f\xce\x4f\x37\xf3\xe3\xee\xc9\x4e\xa8\xae\xa9\x69\x3d\xc2\x18\x30\x91\x6f\x6e\x67\x8f\xaf\x45\xa7\x73\x3b\x7d\x4e\xea\x7a\x91\xcc\x7b\x2a\xe0\xab\xfa\x9c\x76\xae\x8f\xbe\xf3\xaa\x56\xcc\x29\x6c\xeb\xb7\x52\x0b\x63\xae\xae\x74\x27\xaa\xd1\x00\x93\x0f\x0e\xc0\x82\xa6\x07\x9c\x8a\x33\xe0\x14\x82\x48\x2d\xa6\xe9\x5c\x2e\x20\x8b\xae\xc8\x8f\x47\x3d\x1c\x62\xd2\xcc\x4b\xc8\x01\x6a\xcd\x4b\x52\xcf\x7f\xab\xfa\x9c\x3c\xaa\x51\xb6\xbd\xe2\x51\x4f\x98\xb4\x10\x9d\x5b\x6d\x65\xa3\x46\x27\x08\xbd\xea\xe5\x5d\xba\xc3\x18\x6c\xbd\x49\x0d\xce\x7c\x7a\x42\x02\xe0\x74\x16\x12\x38\xfb\x44\x7e\x15\xff\x67\xe2\x85\x47\x61\x32\xe2\x3f\x20\xc2\x03\x9b\x0f\x06\x0b\xc3\x98\x19\x16\x83\x41\xdc\x6c\x3d\x9f\x2c\x08\x39\xac\x0d\xb5\x0e\xce\xb2\xa9\x8c\x06\x3e\x0e\xc9\x80\x6a\xd2\x44\xaf\x3a\xcc\xdf\xef\xef\x6e\x54\x11\x6b\xf5\x1e\xec\x8a\x3e\xa4\xb7\x8b\x64\x80\xe2\xcf\x01\x6d\x42\x3c\x27\x97\x13\xba\xac\x20\xc4\x74\xa2\xe9\xb2\x54\x0a\xa3\x87\x57\x89\xa0\x4d\x8c\x13\x49\x0d\xd7\xfe\x21\x2d\x6f\xb9\x4e\x14\x55\xf7\xbc\x2c\x45\xce\x13\xc5\x9e\xfe\x21\xa2\xb9\xa2\x71\x1c\xeb\x85\x05\x9d\x3e\x6c\x9d\xe4\x1f\x2f\xde\x96\x6a\xb7\x8f\x2a\x7a\x40\xfb\x9c\xb7\x98\xc5\x7b\x9f\x44\x48\xd3\x47\x3e\x31\xac\x58\x50\xf7\x3b\x5e\x42\x46\x69\x67\xd1\x33\x9f\x2c\xcc\xdb\xcb\x09\xa1\xe6\xf3\x11\x26\xe5\xfc\x16\x42\xa6\x62\x44\xf1\x33\xa1\xce\x82\x0c\xb7\x15\x66\xb8\x15\xab\x48\x99\x65\xfb\x4d\x44\x15\x90\xc3\x84\xae\x75\xa4\xbc\x3b\xc3\x4f\x66\xed\x15\x99\x29\x09\x9d\x9b\xdf\x89\x02\xa3\x21\xaa\xa9\x80\x50\xe3\x5c\xff\xf8\x51\xba\x69\xbc\xe4\x18\xb0\xa6\x09\xa7\xf4\x8e\xaf\x0a\x9e\xc1\xb5\xe9\xad\x17\xe9\xd8\x2e\xf7\x7c\xb2\x30\xe0\x1e\xc3\x68\x22\xf0\xf8\x71\x95\x44\xed\x3e\x82\x57\x11\xec\x3f\x5d\xa7\x95\xfb\x56\x29\x23\x4d\x62\x1b\x21\x1b\x92\xcf\xab\x8f\xf2\x3b\xbe\x87\x00\x91\xf0\x92\x1a\x72\xdb\x33\x50\x11\x7c\x5c\xab\x32\xbd\xe5\x90\xcc\xde\x3d\x30\x15\x11\x42\xe6\x7c\xc1\x44\xb3\xea\xdc\x6f\x88\xb6\x1b\x72\x39\x21\x61\x7a\xea\xef\x7a\x92\x81\x1f\xc2\x93\x75\x4d\x97\x1b\xb3\xfd\xe6\x60\x59\x23\x0e\xf4\xd3\xbf\x81\x53\x01\x3e\xfa\x1a\x8e\x17\xff\x78\xf1\x9e\x6b\xba\xcc\xfd\x3a\x55\xc9\xd7\x10\xc9\x10\xa6\xf0\xc2\x35\x66\x4f\x83\x8f\xb6\x8e\xa1\x29\x8f\x5d\x41\xa4\xc8\xf9\xb3\xa8\xfe\xca\x59\xd4\xad\x93\xe7\x16\xf2\x5b\x1c\x44\xcf\xd9\x73\x74\x83\x9d\xb8\x6c\x26\xae\x82\x89\x57\xed\x99\xa6\x8e\x90\x28\x99\x9c\xf3\xc5\x34\xd7\x51\x49\x0c\x08\x14\xd5\x7b\x1f\x0f\x09\x42\x89\x47\xe5\x59\x4a\xc5\x7e\x51\x35\x5f\xac\x82\x2f\xa6\x6e\xa9\xcb\x9a\x41\xf8\xbb\x32\x5e\x03\xd8\x27\x7a\x5d\xaa\x8f\xb0\x3c\x5f\x95\xa5\x2a\xa3\xc1\x3b\x9e\x6d\xc1\x0e\xf4\x22\xe7\x9a\x67\x9a\xe7\xc9\xc5\x60\x34\x8f\xe3\xb8\x5c\xc4\xff\x56\x42\x46\x83\xf1\xd3\x01\x19\x99\xbf\x23\x4d\xa6\x25\x26\xcd\x27\x94\x33\x1e\x55\x34\x3d\x1e\x25\xa1\x65\x8c\x0b\x68\xca\x7f\xc6\xa1\x73\xa6\x64\xa4\xdc\xe9\xb7\xa9\x04\x5b\x41\x0e\x69\x89\xb7\x6c\xfa\x2b\xcc\xbf\x6c\x0c\x43\xff\x1b\xd3\x0e\x17\xda\x4d\x7e\xad\x23\x97\x25\x76\x38\x34\xf7\x84\x70\xc6\xe7\xb6\xe8\x4b\x67\x01\xb7\x08\x74\x48\x1c\x61\xaf\xe7\x4a\xa8\x64\x7e\x2a\x61\x2e\x6e\x7d\xc9\x98\x99\x01\x04\x7e\x6c\xc0\x4f\x61\xc0\x8f\x68\xda\x2b\x69\x93\x29\x16\x64\x6a\x43\xd0\x7d\x67\xe0\x59\x45\xd3\xe1\x30\x9d\xeb\x05\x2d\x09\xa9\xeb\xee\xda\x98\x13\xf1\xda\x05\xb1\x22\x64\xfa\x93\x30\x2f\xf0\x5c\x7c\x67\xc8\x33\x45\xab\xe1\xb0\x32\x57\x37\x6d\x96\xb6\xac\xed\xd9\x7c\x10\x7c\xe1\xc9\xe6\x71\xb8\x60\x71\x5a\x14\x06\x9e\xcc\x1c\x44\x82\x33\x43\x05\x99\x1d\x38\x04\xfc\x74\x18\xc4\xf9\x0c\xdb\x67\x27\xa3\x4b\x3e\x0d\x0d\x0d\x08\xfe\x0b\x20\xd0\xc2\x3f\x1c\x6c\x77\x54\x1e\xfc\x85\x3d\xd8\x32\x0f\x0c\x5d\x52\x67\x08\x1b\xc9\x24\x0d\x2f\x7d\x07\xc0\x7d\x8d\x98\xf8\x10\x04\x25\xbb\x9c\xd0\x26\x8a\xd8\xe5\xa4\xf6\xc7\x30\xa8\x63\x68\xa7\xe6\x31\x0c\x3b\x66\x88\x25\xff\x44\x97\x76\x75\x13\x05\xb2\x79\xf8\x6d\x20\x81\xb5\x29\xf5\x6f\x69\xd8\x77\xf0\x79\x49\x43\xf8\x90\xe4\x86\xcd\x9b\x09\x98\xbc\xa0\xc1\x41\x31\x6f\x24\x99\x49\x78\x23\x5d\xae\xf5\xdf\x7c\x36\x75\x3d\xd3\xa3\x4c\x47\x9c\x24\x9c\xfe\xe4\x4b\xf1\xd6\x0e\xd2\x3c\xdd\x68\x5e\x56\x60\xe5\xd5\xc4\x2a\xfb\xb6\x15\x86\x4a\x32\x83\x2e\x7c\x62\x4b\x4f\xa0\x88\x08\xf0\xab\x04\x2f\x4d\x83\x5e\x30\xbe\xb1\xef\x45\x77\x8d\x0a\x73\x0d\x61\x8d\x61\x7f\x13\x6d\x47\xca\xa5\x1b\xd3\xe5\xc4\xe5\xe1\x46\x63\xe9\x01\x63\xe6\x60\xa8\xd5\x85\x9e\x15\x20\x0a\x21\xf6\xe4\x35\x23\x95\x01\xc8\x68\x2e\xa4\xea\x64\xbc\xe7\x12\x32\x91\x40\x00\x4f\x48\xb8\x90\x1b\xc2\xbc\x49\xb7\x20\x23\x1e\x7b\x72\x08\xf8\x34\x0b\x47\x14\x19\x0e\x15\xa6\x27\x56\x41\xf4\xd1\x0b\xe5\x15\xd7\x90\xb4\x8b\x43\x5e\x17\xb3\x10\x22\xac\x05\x99\xff\x7c\x6a\x36\x3f\xe6\x13\x30\x87\x72\x8d\x86\x38\xa3\xd5\xe9\x90\xa4\x61\xbb\x0d\x98\xd6\x86\x1a\x53\x0b\x5a\x32\x8b\x57\x2d\x88\xb6\xd9\x35\x0b\x56\xc9\xa8\xa4\x29\x15\xb4\x3a\x1e\x83\x28\xd5\xdb\xa2\xb8\x44\x8d\x74\x74\x69\x20\x24\x39\x1e\x2b\x94\xd7\x47\xbe\x4d\x65\x38\x58\x57\x93\x90\xe1\xd0\x90\x7f\x80\x1a\x28\x10\xac\x8a\x56\x88\x16\x3b\xf6\x09\x48\x3a\x46\x4d\x28\xba\x0b\x21\x2f\xb4\x21\x42\xcc\x2d\x3c\xd4\x84\x9a\x1f\x35\x21\xc1\xf5\xab\x3a\x3b\x37\x15\x53\x22\x58\xb0\x9f\x3d\x71\xed\x52\x6b\x4e\xde\xec\xb4\xb0\x3b\x0d\xa2\x0b\x62\x90\x84\x90\x5b\xee\x55\x20\xc2\x86\x6d\x5d\x9b\x0b\xd0\x04\x6e\x6d\x7a\x2c\x9b\x44\x70\xcc\x12\x42\x1e\x67\x1d\x8f\x91\x2f\xec\x8d\x6a\xeb\x36\xa0\x33\x1c\x4d\x9a\x02\x6d\x0a\x42\xb3\x04\x41\x42\x84\xd2\x89\x74\xb8\x1c\x10\x42\x08\xb7\x28\xd7\x0e\xc4\x6c\x00\x5f\xd4\x0d\x69\x49\x08\xe5\xf6\xf2\x14\x92\xd9\x28\x44\x5f\xbd\x7d\xff\xfa\xcd\x8f\x3f\x1c\x8f\xd7\x7c\x7c\xfd\xbf\x68\xe6\x6f\x15\xf7\xe1\x7e\x87\xc3\x4b\x73\x4d\xe3\xea\x77\xb1\x19\x0e\xe1\x4e\xfb\x75\x58\xf7\x9c\x49\x0d\x35\x67\x3c\xd1\xb4\x62\x1c\x98\x3c\x5b\x20\x68\xc9\xfe\xa9\xa3\x8a\x2a\x42\x0b\xf3\x2b\x85\x8c\x35\x5c\x5f\x64\xac\xbc\x8a\xca\x51\x41\xe8\x9a\x15\xf8\x6b\x9a\x31\x51\xfd\x90\xfe\x10\x65\x64\x36\x49\x32\xba\xb6\x8f\x6b\xf3\xb8\xb6\x5b\x95\x33\xf9\x28\x03\xc7\x30\x67\xa4\x7d\xd8\x94\xfc\x5e\xa8\x6d\x95\x1c\x76\x49\x15\xef\xc6\xf9\xa3\x28\x8d\x77\x63\x15\xef\x30\xff\xeb\x1e\x4b\xf6\x63\x15\xef\x49\x4d\xa5\xa1\x0e\xb0\xea\x68\xdb\xad\x8a\x25\xb6\x6a\xb0\xfd\x79\x6f\x94\x62\x2a\x98\x4d\x7a\x0e\x9b\x55\x44\x13\xc3\x09\xbb\x22\x17\x4c\x9d\xa6\xb4\x64\x99\x59\xb7\x09\x5a\x6f\xa0\xbf\x02\x07\x7f\x05\xb1\x8a\x2a\x96\xd2\x94\x95\xae\x92\x1a\x5d\x13\x8a\x5e\x24\x65\xc3\x77\x96\xf1\x6e\x9c\xc6\xbb\xa9\x98\xab\x05\x78\xf9\xe9\x59\x54\xc6\xfb\x71\x1a\xef\xc9\x95\x4e\x26\xb5\x34\x2f\xaa\x59\x39\x7b\xa9\x23\x31\x57\xe3\xeb\x05\xb9\x64\x0c\x1f\x16\x66\x05\x6d\xe9\x08\x9e\xaf\x1e\x27\xf8\x68\xfe\x2d\xda\x5e\x18\xed\x70\x90\x41\xa8\x49\x3b\x17\x70\x1c\x0f\xe7\x63\xde\xad\xd9\x64\xba\x7e\x22\xc7\xd7\xd3\xd1\x68\x4d\x0a\x96\xb9\x3a\x6b\x33\x9d\x62\x38\xcc\x86\xc3\xe8\x83\x8e\xf8\x7c\xbd\xa0\x13\x5a\x48\x32\x13\xf3\xf5\x82\x89\xf9\x7a\x74\xbd\x60\x93\x24\x52\xe6\xf7\xe2\x0a\x2a\x54\xb6\x1c\x9f\x6c\xa4\xf5\x8d\xfa\x18\x29\xfa\x98\x8c\xfc\x53\x45\x1f\x13\x5a\x3e\x61\xff\xe7\x78\x8c\x52\xf6\x1f\x57\x8d\x1b\x76\x49\x28\x74\xaf\x1e\xa5\x8f\xa0\x0f\xfb\x9d\xca\x3e\x13\x88\xde\x8d\xe0\xb2\xed\x81\xd2\x38\x9e\x04\x33\x07\xfd\x1e\x4d\x4f\x66\x5d\xb2\xc9\xb4\x7c\x22\xc0\xa1\xe6\x00\x5c\xa5\x81\xc9\xa9\xab\x59\x9a\xb9\x5f\x56\x1d\x70\x73\xd8\x25\x82\xee\x93\xa2\x66\xd5\x54\x81\xbb\x53\x24\xe0\x08\x5e\xfd\x07\xad\xe2\x6c\x73\xbd\x63\x62\x2c\xf1\xe7\x9e\x15\x63\xf9\x88\xcf\xcb\x05\x04\x7a\x31\x75\xcd\x81\x15\xae\xee\xe3\x1d\x13\x23\xac\xfb\x78\xcf\x8a\x91\xad\x5b\xd7\xc0\x34\x35\xe7\x77\xdb\x45\xb1\xa7\xb1\xeb\x81\x74\xe2\x41\x9b\xd5\x69\xcc\x70\xb3\xfd\x80\x1c\xe3\x6a\x93\xca\xaf\xd3\x0d\xba\x70\xe8\x2e\xa8\xfa\x5d\x6c\x0c\x99\x39\xb8\x53\x52\x69\x25\x31\x21\x5a\x9c\x6d\x6f\x44\xf6\x5a\x6a\x5e\x6e\x14\x4a\x08\xbf\x57\x39\x27\x70\xb7\xa6\x81\xb7\xa4\x9c\xe9\x79\x23\xcf\x5d\x24\xda\x05\x29\x57\x6c\x02\x06\xf3\x2e\x2a\xf9\x93\x0a\xee\x50\xca\xf4\x1c\x10\xdd\xda\xa0\xef\x94\xda\xc8\xa7\x66\x52\x6a\x74\x4d\xab\x71\x24\x21\xc2\x3b\xf9\xb2\x5a\x50\x1e\x6b\x8c\x46\x42\x68\x8a\x8b\x5d\xc6\x0e\x82\xc4\x3b\x2c\xdb\x87\x65\x7b\x28\x7b\x6c\xea\x19\xd0\x61\xeb\x3c\xde\xbb\xe7\x3d\x15\x2c\xad\x79\x9c\xa5\x9b\xe7\xfc\x4f\xc1\xcb\xb7\x4a\x48\x5d\x0d\x87\x3d\x89\x0c\xa8\x3d\x48\xb4\x64\x5f\x6b\xe0\xe4\x5d\x64\x8b\x9e\x88\xf0\xa3\x91\x20\x29\x1c\x28\x03\x1b\x84\xb9\x5c\xc3\x21\x34\x13\xa3\x6b\x10\x1a\x54\x10\xca\x13\x98\xd3\x14\x82\x0a\xc1\x7c\xb6\xd2\xfe\xa2\x68\x93\x40\xbd\x91\xb6\xc2\xc9\xb9\x0a\x7b\x8a\xa6\x41\xde\xb0\x9c\xd0\xd2\x76\xf3\xd8\x77\xf3\xb8\xbf\x9b\xc7\xbe\x9b\xc7\xa7\xdd\x10\x9b\x3a\xc1\x1f\xa5\xdb\xe0\x28\xf9\x4c\x23\x3a\xde\x8d\xc4\xa3\x88\xc7\xbb\xb1\x46\x00\xac\xe3\x3d\x96\xec\xc7\x1a\x00\xb0\xef\x60\xf3\x79\x1d\x38\x29\x34\xd8\x59\x88\x27\xf1\xdf\x67\x3a\xde\x27\x3c\xde\x27\x36\x38\xb0\x7d\x71\xed\xcb\xc5\xd3\xc9\xcc\xfc\xd7\xf1\x3e\xf8\xdc\x5d\x0f\x96\x83\x0f\xc2\x7a\xec\xf1\xc7\xbe\xa6\x95\x29\xe5\xb8\xd8\x7b\xfc\xb1\xaf\x69\xca\x60\xbe\xca\x5c\xa8\xd2\xfc\x36\x7b\x2e\x0c\x06\xbc\x95\x51\x05\xd7\x90\x66\xe6\xb7\x01\xa6\xc2\x60\xc0\x5b\x43\x56\x15\x4d\xc6\x56\xb3\x60\x19\x5d\xb7\xd6\x70\xd7\xbd\xc2\x7a\xd6\x97\x35\xd2\x40\x17\xf6\x54\x8f\xf4\x88\x8f\x85\xe1\x75\x40\x9a\x6f\xb0\x17\x67\xba\x0e\x04\xf9\x9a\x3d\x0d\xcc\x52\xf4\x4c\xf7\x05\x7f\x75\xe6\x27\xbb\xb7\xc5\xd6\x0b\x9c\xf4\x98\x83\x2e\xe4\x95\x2a\xdf\xe8\x32\x28\xad\x31\x67\x89\xc1\xae\x86\x09\x6e\x7d\xbd\xf3\x69\xdd\xe9\x72\xd4\xdb\x65\xb0\x25\x37\xed\x7b\x34\x1d\x14\x1a\x02\x3a\xf3\xe1\x70\x50\xea\xc2\xc6\xba\x8d\x20\xcd\x6c\x2a\xef\xd3\x2a\xae\xf4\x1e\x12\x16\xcf\x85\xcb\x79\x63\xb8\x4e\xb4\xc3\x18\xe4\xa2\xe4\xd0\x31\xa4\xfc\x0d\xde\xbf\x2d\x85\x2a\x85\xde\xb7\xaa\x2c\x28\xd8\x4c\xb8\x3a\xe1\x3b\xca\xe9\x40\xdc\x6d\x54\xa9\x53\xa9\x07\x10\x5b\xa9\xe4\xf7\x1f\xf8\x4e\xbf\x74\x75\x58\x08\x89\x97\x76\x1e\xde\x0e\x9d\x0f\x87\x91\x97\xb0\x9d\xb4\xa5\xed\xe9\x9c\x1f\x05\xc0\x92\xf9\xf5\x22\x24\x9d\xf7\x81\xdb\xc9\x20\x95\xb7\x78\x33\xf4\xec\x70\xc3\xf5\x47\xce\x65\xf2\x8d\xa6\x99\xba\xdb\xa4\x25\x4f\x9e\x6b\x2a\x55\x79\x97\x16\xe2\x4f\x9e\xfc\x4b\xd7\x89\xaf\xe4\xe5\x5d\xfa\x69\x18\x0d\x5a\x90\xe1\x50\x3f\x69\xdc\x39\x51\x93\x65\x7b\x0b\x4e\x4a\xd3\xab\x6e\xef\xe8\xbd\x8c\x0e\x40\xd4\x26\x9a\x72\x99\x27\x9c\x66\x6a\x0b\xf9\x58\x0b\x05\x46\x39\x30\xe3\x44\xd5\xfe\x68\xdb\xda\x5f\x0a\xac\xff\xa5\xab\x39\x1c\x46\x7c\xac\x47\xd7\xe4\x4b\xc1\x0c\x8a\xb0\xed\x9a\x4f\x7d\x6c\xa5\x4f\x76\xa9\x93\x0d\xcd\x6f\xed\xf4\x9c\x0c\xd0\x7c\xd4\x7c\x43\xc1\x17\xaa\x9a\x09\x30\x70\xb7\xe4\xde\xc1\x4d\xaf\xa4\x6e\x71\x8a\x60\x7e\x59\xcd\xf6\x12\xfd\xcc\xa0\x93\x35\x74\x92\xe3\x20\xb7\x76\x58\xab\xae\x96\xd1\xdb\x0a\x3e\x30\x06\xbf\x17\x69\xf0\xb9\xd2\x7d\xae\x68\x12\x84\x3b\x3d\xa4\x1d\x41\x0e\x5d\x6c\x71\x04\xab\x1a\xd3\xfa\xac\x90\x2f\xca\x47\xac\xa0\x5b\xf3\x27\x63\x13\x43\x85\x4f\xb3\x27\xeb\xe1\x30\x8d\xca\x88\xcf\xf3\x2f\x8b\xc5\x5c\x2e\x08\xd8\x35\x41\x66\xe3\x7c\x3c\xa6\xdb\xf1\x78\x9a\x7f\x69\x9a\x7d\xc9\x7c\x56\x88\xed\x93\x7c\x38\x8c\x4c\x47\xa4\xff\xb3\x76\xe2\x1a\x4f\x70\xed\xc4\x50\xf4\xd6\x69\x88\xc0\x99\x9a\xde\xb0\xcb\x6b\xba\xb4\x89\xb3\x00\xe6\xee\x99\x61\x21\x6f\x8e\xc7\x22\x52\x74\x47\x37\x64\x38\x34\x17\xa6\x34\x4f\x84\xde\xc3\xdb\xcb\x9b\xe3\x71\xc2\x4c\x61\x45\x37\xc4\x54\xad\xa0\x6a\x90\xb2\xc3\x90\xe9\xeb\xa9\x7e\xc2\xf2\xe9\x68\xa4\xc9\x1d\xe3\x73\xfd\x65\xba\xa0\x77\x40\xa7\x1c\x8f\xd1\x86\x65\xd1\x1d\xcc\xf6\x86\x15\xd1\x06\x6d\xb9\xcc\x38\x18\x63\xcb\xe1\x70\x0f\xda\xc9\x25\xc3\xcf\x6c\xa8\x22\x33\x9d\x08\xcf\xff\x2e\x87\xc3\x7b\xa8\x71\x8b\x12\xbc\xe6\x58\x2f\x61\x19\xb4\xdb\x7f\x3c\xdd\xa9\x3f\x07\x84\xd8\xe9\x82\xb3\x0c\xdd\xb1\x4d\x97\x09\x5f\x0e\x87\x67\x3a\xcd\x1f\xe8\xf4\xb6\x39\xf7\xdf\xcb\xde\x58\xf2\x15\xbf\x85\xfc\x67\x7e\x91\xac\x07\x75\x98\xee\xc5\xa7\xf2\xf8\x28\x23\xc3\x5b\x18\xc0\x06\x84\x8d\xa1\x58\x2a\xcf\x29\xda\x60\xf3\x71\x1c\x57\x5e\x2f\x17\x30\xe2\xbf\x9f\x50\xd5\xb6\x93\x30\x7a\xa0\xa3\x25\x21\x78\x95\x23\x80\x56\xd1\xa5\xcb\xa0\x32\x5f\x78\x85\xd6\xe5\xa5\x8e\x97\x66\xea\xee\xa8\xa5\xb0\x1c\x65\x9f\xea\xde\x4e\xcc\xf0\xa4\xe3\x6b\x30\xb0\x1a\x0e\x2f\x25\xf0\xdc\x86\xfd\x02\x2e\x57\x21\x97\x3b\x25\x6a\x34\x9a\x36\x6f\x7a\x5e\xa8\x2f\x19\xa7\x62\x38\x8c\xaa\x11\x53\x64\x5a\x3d\x55\xa6\x5a\xf5\x25\x77\x15\xab\xf1\xd8\x67\x45\x36\x75\x0f\xed\x5b\x5c\x43\xcc\x9a\xca\x66\x7c\x01\x11\x96\x97\xff\xbc\x97\xd1\xbc\x3d\x1f\xdc\xde\xaa\x5e\xa0\x90\xa9\xa9\x77\x46\x00\x7e\xd1\x44\x57\x6b\x85\x2d\x60\x9c\x16\x28\x95\xc3\x28\x05\x7c\x74\x3d\x4d\x9f\x30\x81\x81\x0a\xfc\xae\xcc\xd3\x2f\x15\xf8\xc9\xc3\x85\x10\x71\xa5\xd5\x66\x56\xb8\xfb\x21\xcd\xcd\xb4\x2e\xfd\x76\x98\xfc\x4b\x9c\x57\x94\x8e\xaf\xc9\x97\xca\xc2\xe1\x9a\x50\xce\x4a\x66\xdb\xa7\x90\xa6\x86\x24\x51\xc9\x52\x5a\x58\x69\x42\xc4\x59\x4a\x0c\xc8\x12\x75\xfb\xac\x97\xc3\x61\xff\x27\xca\xb0\xfb\xaa\x06\x0a\xbf\x7c\x92\xce\xca\x91\x4a\x4a\x0a\x07\x62\xb5\x2d\x8a\x37\x4a\x6d\x86\xc3\x09\x26\x29\x2a\x19\x63\x6a\x7c\x4d\x60\xf5\x9a\xd3\xf8\xbe\x4b\x42\x09\x73\x80\x1b\xcd\xd2\x70\xc8\x67\xe7\x38\x61\xde\x44\x58\x73\x8a\x6d\x48\x43\x64\xb3\xe7\xa7\xac\x0a\xc4\x3b\x25\x8a\x77\x6c\x4e\x1f\x03\x61\xf9\xbc\xfa\x52\xba\x5d\xa8\x70\x17\xca\x98\xcb\x7c\x9a\x36\x97\x6d\xcd\xf8\x3c\x35\xd5\x0a\xf6\x56\x46\x2d\x33\xcd\x83\x75\x59\xc6\x9b\x3b\xa0\x9b\x49\x92\xd1\xcd\x75\xb2\x36\x37\xfe\x25\x64\x03\x83\xc0\xfe\xad\x25\xac\x60\x01\xd3\xf1\x35\x2e\x60\x19\xc3\xcd\xb1\xe0\xb8\x26\x54\xb3\xc2\xb0\xab\x66\xa1\x32\xb6\x36\x8f\x75\xf5\x24\x35\x6c\xc6\x5f\xe8\xa8\x30\xdb\x02\x9d\x34\x7a\x79\xbb\x78\x49\x90\xb1\xe0\x6d\x40\x8b\x9c\x98\xe3\xa2\x35\x53\x50\x62\xad\x05\x5f\xa4\x9b\xf7\x16\x79\xb4\x0b\xda\x96\x86\x37\x0f\x5a\x19\x9e\x98\x6d\x62\xc1\xb7\x4a\xc8\x76\xe7\xbe\xa4\x95\xda\x46\xb7\x6c\xe5\x02\xdb\xb8\x66\x50\xe8\xa2\xe0\xa7\xfa\xb2\x9d\xc6\x9d\x0f\x87\x9d\x1c\x81\x9a\x5c\x32\x76\x92\x37\xb0\xbe\x4f\xcb\x8b\x17\xb2\x93\x66\xf9\xb0\x5c\x82\x55\xc9\x72\x09\xd7\x89\xf2\xb4\x12\xf2\xf6\xab\xd5\x8a\x67\xba\x4a\xee\x9c\x97\xe1\xbf\xe8\xad\x4d\x66\x8b\x63\xfb\x86\x4a\xb3\x53\x3f\xd0\xad\xc8\x93\x7f\x53\x51\xfd\xb0\x2d\x8a\x1f\xcb\x9f\x65\xce\x57\xc9\x17\x54\x54\x20\xa5\x4a\x7e\xa5\xa2\xc2\xef\x25\x3f\x53\x51\xbd\x12\x52\x68\x9e\xfc\x42\x57\xf0\xe3\xc7\xf2\x25\x26\x4f\x4c\xfe\xa0\x60\x50\xdc\x14\x7c\x47\xb5\x7a\xcb\x4b\xc3\x40\xa4\xb7\x3c\xf9\x9a\x6a\xe5\xe3\x7c\x26\xbf\x35\x49\x74\x7e\xa2\x3c\xcd\xd6\xc9\xb7\x74\xe9\xb2\x83\x7f\xf5\xc7\x36\x2d\x12\xad\x69\x56\x28\xc9\x13\xae\xe9\xf2\x8e\x97\xb7\xbc\x4c\xa4\xa6\xf0\x2b\x51\xf6\xc7\xeb\x55\x52\xf9\xd7\xaf\x57\x49\x0a\xba\xe1\x4d\xc9\xb3\x54\xf3\x3c\x39\x85\x85\x21\x51\x6d\xae\x94\x2a\x78\xfc\x31\x2d\x65\xa4\x47\x7f\x4b\x2e\x06\x7f\x1b\x89\xd1\xdf\x06\x17\xa2\xba\x68\x7a\x89\x2f\xde\x16\x3c\xad\xf8\xc5\xb6\xe2\xa6\x86\x84\x1a\xb2\xd2\x3c\xcd\xff\x46\x6a\x6a\xcd\x3c\x70\x91\xbe\xe3\xfb\xa4\xd0\x74\x99\xa5\x1b\xa1\x2d\xd1\xa7\x69\x6e\x03\xaa\xaf\xb5\x59\x42\x3b\xa8\x24\x07\xfe\xc7\x4e\x77\xab\xa9\x56\xaf\x94\xd4\x68\x86\x91\x7c\x01\xd3\x4a\xab\x6d\xc9\x0d\xb9\x9f\xfc\xaa\x5b\xc1\x68\x92\x9f\x35\xc5\x20\x17\xe0\x93\x95\xfc\x62\x56\x8b\xa7\xe5\x0b\x60\x05\x92\x3f\x34\x98\xf5\x80\xa0\x21\xf9\x4e\xd3\xa5\xa8\xe0\xf7\x6b\x09\x69\xa1\xbe\x36\xb5\xc5\x06\x7e\xff\xa6\xe9\x56\xfa\xa7\x9f\x34\x5d\x56\x9a\x6f\x36\x1c\xac\xad\x3f\xa8\xe4\x5b\x4d\x97\x37\x20\xb6\x78\xb1\x2d\xef\x4d\x89\xe6\xb4\xe4\x32\xe7\x25\x0c\x84\x73\x9a\xe6\xf9\x3b\x73\x31\x79\xfe\x8e\x67\xfa\x6d\xaa\xd7\x89\xe0\x66\xb8\xea\xf7\xed\x26\x91\xfe\xe7\xf3\xbd\x59\x1e\xc5\xe9\xb2\x0c\x0b\x2a\x4e\x97\x28\x1e\x7a\xee\x88\x58\x4e\x0b\x51\x69\x2e\xe1\x0c\x7e\x75\x6f\x4e\x45\x52\x70\xba\x95\xa7\xc5\x19\xa7\xcb\xd4\x3c\xff\x2c\xc5\x1f\x5b\x9e\xac\x39\x5d\xb6\xb3\x9d\x27\x7f\x08\xba\x4c\xb5\x4e\xb3\xb5\x33\x10\xf8\x4e\x74\x6d\x08\x68\xb5\x29\x84\xe4\x30\xc7\x64\x2d\xc3\xc7\xef\xad\x8c\x2a\xc9\x25\x5d\x62\x98\x72\x94\xe3\xbc\xc0\x20\xf5\x28\xce\x49\x56\x68\xfc\xf2\x36\x2d\xb9\xd4\x3f\xa8\x9c\x27\x39\xa7\x2e\xf5\x76\x72\x0b\xbf\xdf\xf1\x22\xd5\xe2\x9e\xbb\x14\xac\xc9\x0e\x8a\xbf\xc7\xbc\x8e\xef\xcd\x71\x59\x9a\xd5\xd5\x42\xa6\xef\x21\x14\xc5\x9e\xd3\x6a\xbb\x31\xdc\x63\x05\x13\x7e\x03\x0b\xc0\x4b\x6b\x4d\x94\xdc\x9b\xea\x69\xfe\x73\xc5\x73\x68\xfe\x91\x83\xbb\x93\x3d\x45\x67\x62\x72\xf0\xd1\xe0\x62\x30\xd2\xa3\xc1\x66\x77\x31\x18\x09\x73\x86\xff\xd8\xf2\x0a\xb2\xcd\xbf\x2a\xd3\x3b\x9e\x68\xaa\xd7\xa5\xd2\xba\xe0\x86\xed\xca\xf9\x8d\xda\xca\x8c\x27\x82\x2e\xb5\x7a\xc3\x57\x1a\xa2\x6f\xbd\x00\xe1\x40\x22\xed\x49\x7c\x6f\x30\xc1\x57\x32\x4f\x14\x5d\x9a\x55\xfe\x25\xa9\xe8\x72\x83\xc7\xce\x1c\xa6\xe4\x56\xfa\xc3\xd5\x12\xf2\x25\x1b\xe9\xce\x58\xbb\xfc\x4e\x52\x4c\xb0\x85\xaa\x89\xe4\x4f\x41\xb5\x7a\xe3\x8d\xa7\x92\x77\xe6\x28\xf1\x34\x07\x96\xfd\x83\x82\x34\xb3\xc9\x1b\x4e\xb5\xfa\xf0\xee\xf9\x9b\xe4\x2b\xf7\xeb\x85\x2a\x25\x2f\xab\xe4\xb5\x29\x70\x69\xb2\xfe\xe4\xf6\xc6\x25\xaf\xb8\xbb\xc4\xc9\x3f\xcd\x69\xca\xf3\xaf\x21\xf5\xda\x73\x4e\xdf\xbe\x4e\x6e\x34\xfd\xf0\xec\xe7\x64\xa9\xe9\xdb\xd7\xe6\xc7\x5e\x53\x97\x9f\x21\xb9\xd7\xf4\xdd\xb3\x97\xcb\xb7\x5f\xbd\x5b\xbe\xfc\xea\xeb\xe4\xa3\xa6\xdf\x3c\x7b\xf3\x6a\xf9\xf6\x75\xf2\xbd\xa6\x3f\xfd\xfc\xec\xdd\x87\xaf\xde\x99\xa7\xdf\x35\xfd\xf0\xaf\x1f\x97\x1f\xbe\x79\xfd\xee\xe5\x7b\x53\xf0\xde\x10\xfd\xb7\xd7\x93\xe4\xad\xa6\x95\xb8\x95\xc9\x4b\x4d\xa5\xc8\xf8\x0f\xdb\xbb\xe4\x85\xa6\xcb\x55\x9a\x69\x55\x9a\x0d\xfd\x51\x03\x6c\x86\xf9\x3f\xd3\x34\x2d\xee\x54\xa5\x01\x66\x54\xc9\x07\xf7\xfc\xaf\xb5\x2a\x78\xf2\xce\x5c\x5e\xc8\x96\xf0\x4c\xe6\xdf\xa7\x3b\xbc\x5e\x6f\x0c\x64\x79\x97\xe6\x22\x95\x55\xf2\x95\x79\x78\xc9\x6f\x4b\xce\xab\xe4\x35\x40\x4b\x08\x82\xf3\xb6\x48\x33\x5e\x25\x7f\x6a\x73\x1c\x9f\xc9\xdb\x82\xbf\x2a\xd5\x1d\xc2\x8f\x57\x9a\xe6\xa2\xd2\xa9\xcc\xb8\xbd\xa0\xf6\xc4\xff\xd3\xc0\x20\x53\xf7\xa5\x58\xad\x92\xe7\x9a\x2e\x3d\xef\x09\x5d\x24\xff\x72\x15\x9e\x37\xe2\x84\x65\x21\xee\x04\xc6\x20\x4a\x7e\x80\x64\x5b\xfa\xfa\x1f\xef\x52\x79\xcb\x93\x7f\xc3\xd7\xdf\xe9\xe2\x19\x2a\x93\x93\x9d\xf4\xb6\x3b\x2d\x99\x47\x72\x23\xa9\x35\x20\x6f\x97\x2f\xcd\x41\x32\xa0\xe8\x3d\x92\x41\xc9\xc7\x4e\x41\x95\x7c\x0f\xb6\x36\x60\xda\xed\xcb\x7e\x97\x35\xb1\xb9\x98\x7f\x94\xed\x94\x45\x41\x4e\x7a\xcc\x50\x84\x59\x0f\x40\x7f\xc6\x5c\xa6\x22\x3b\x46\x26\x6c\x5e\x23\x30\x21\xef\x04\x97\x31\x94\x6e\x2d\xaa\x57\xaa\xfc\xb0\xdf\x84\x41\x9b\x6c\xbd\xc6\x32\x54\x54\x81\x29\x43\x93\x28\x15\xbe\xdf\x54\xa3\x41\x13\x43\x58\xdd\x1a\xc0\x50\xf6\x18\x68\xda\xfe\x4f\x2c\x24\x50\x6f\x31\x8d\x4e\x03\x90\x0c\x44\x3e\x10\xf2\x42\x0f\x87\x03\x97\x2a\x19\x9e\x6b\x12\x09\x08\x3d\xcf\x78\xec\x3f\x28\x48\x68\x53\x0f\x53\x07\x11\xbc\xc8\x41\x30\x02\x0b\x35\x1a\xc4\x83\x51\x65\x5d\x27\x4e\x0c\x89\x70\xe1\x73\xc5\xab\x0b\xa9\xf4\xc5\x3a\xbd\xe7\x17\x02\x8c\x89\xbc\xe6\xf2\xa2\xba\x10\xf2\x42\x1d\x8f\x91\x9a\x57\x0b\xa6\xe9\x39\x22\x5c\xe9\x6e\x50\x1f\x60\x9f\xe7\x62\xb6\xd3\x36\xdb\x72\x72\xa8\xa9\x7d\x40\x93\x5a\x3b\xc5\x05\x99\xda\xfc\x8d\x68\xbc\xdc\xce\x0b\xdd\x95\xd6\x87\x9a\x58\x4e\x9a\x7c\xd4\xc2\xab\xb4\x25\xc3\xb0\x62\x3a\x1a\xc4\x03\x02\x5e\x27\x1b\xb5\x89\x0c\x2d\x3c\xd7\x90\xe1\x24\x4b\x75\x24\x89\xb5\x95\x32\x75\x52\xc6\xe7\x62\x11\xb6\x2a\x59\x6a\x5b\x15\x2c\x6d\x6a\x4e\x7d\x36\xbd\x8a\x2a\x5a\xd0\x92\x40\xd0\x32\xde\x1d\x35\x99\xba\xa4\x74\x80\xe7\x86\xc3\x30\x49\x1d\xd6\xf6\x2f\x41\x26\x9f\x52\xc8\xc3\xe3\x4e\x34\x34\xf0\xd6\x72\x98\xac\xcb\x3f\x57\x84\xd0\xb4\x36\xeb\xa8\xdb\xf1\xad\xe0\x10\xcc\xf5\xa2\xde\xca\x73\xe7\xd2\x1e\x14\x81\x07\xc5\x26\x9c\x86\xb3\x32\x05\x95\x3f\x1f\x0e\x43\x83\x1b\x39\x1c\x42\xf1\x4e\xcf\xe5\xa2\x11\x87\xc2\xa3\x79\xdf\xba\x86\xbe\xe9\x4a\xcf\xc5\x82\xd4\x40\x36\x3f\xc3\xc4\xda\x70\xd6\xfa\xb2\xad\x37\x09\x67\x2a\xa8\xf8\xa3\x8c\xde\x08\x3a\xb0\xa9\xdd\xab\x01\x18\xf7\x41\x4d\x47\xa6\xba\x6a\x5f\x09\x3a\x70\x65\x03\x5b\x67\x83\x51\xe9\x5d\x15\x3c\x2c\x74\x60\x8b\x5d\x2d\x8c\x20\xe0\x2a\xfd\x22\xa8\x4b\x1b\xe8\x92\xda\x9b\xbb\x9a\xbf\x83\x25\x2c\x05\x37\xe7\xa6\x33\xd4\xb0\x9f\xf6\xe8\x16\x75\x9a\xe7\x51\x1c\xc7\xda\xc5\x84\x37\x64\x76\x34\x70\x1b\x32\xa0\xda\x40\x8d\x3b\x75\xcf\x7b\x6a\x35\x1b\x07\xf5\xd2\x3c\x7f\xd1\x7c\xf4\xe1\x5e\x69\x77\x8c\xd0\xdc\x27\xd7\xff\x8c\xb6\x6e\x0a\xd0\xd0\xc5\xf7\xff\x8c\x76\x76\x75\xa1\x19\x10\x4f\x9f\xd5\x0a\x17\x8f\xb8\x40\x69\x38\xe8\xee\x89\x5e\xc2\x29\x3f\x99\x1a\x1d\x34\x0f\x03\xe8\xc2\xce\xf3\xa1\xf6\x6e\x7a\xfe\xd4\x60\x4b\x9c\xe8\x43\x0d\xed\xfc\xdc\x31\xc2\x66\x30\xd1\x87\x5a\xd9\xc3\x61\x63\x99\xb9\x3d\x7f\x78\x3b\x5b\xdb\xdf\xb3\xa1\xd8\xc7\x03\x7b\xda\xd3\x41\xb3\xab\xd8\xfa\xfc\xc6\xf6\x34\xf6\x5b\x8b\x6d\xcf\xee\x6e\x4f\x53\xb7\xbf\x58\xa3\x27\xbd\xd0\x14\xcd\x62\x1a\x10\xce\x3d\x08\x57\x4c\x1c\x8f\x12\x96\xd3\xde\xc2\xbd\xc3\xdf\x9c\x4c\xc5\xf1\xa8\xe2\x06\xa3\x73\x72\x3c\x2a\xc6\x0c\x98\xc7\xd1\x0e\x87\x3c\x16\xf9\x4c\xc6\x4b\xbe\xe3\x19\xe8\xfb\x38\x49\xbe\x8d\x38\xfd\xec\x4f\xb4\xdb\x1a\x18\x4f\xcc\x4c\xb0\xa8\x35\x93\xcc\x9c\xb9\xe9\x4f\x91\x98\xbb\xf4\xa7\x23\xb9\xa0\xf3\x05\xe8\xec\xe7\x7a\x11\x09\x42\xe1\x2d\xaa\x39\xfd\xcb\xba\xef\xcb\x1a\x15\x00\x68\xcc\x34\x99\xf2\x27\xbd\xe0\xc8\x49\x62\x79\x23\x9f\x12\xac\xb7\xe6\x9c\x2f\xd0\x7f\x35\xa4\x7f\x88\x17\x06\x87\x47\xd7\xae\x5d\x6d\x8f\x70\x47\xc2\x86\xc8\xc6\xf4\x85\x8c\x3c\xc8\x48\xbb\x04\xc5\xdf\x06\x7f\x1b\x69\xcb\xc6\x1b\x82\x22\xbd\x70\x87\x82\xe7\x17\x86\xc3\x07\x0c\xea\x93\x7e\xd7\x96\xf2\xfb\x20\xfb\xf0\xc2\x52\x48\xa1\xd9\x7c\x51\x63\xba\x91\xfe\x40\xdc\x53\xbb\xe6\xaf\xa5\xc0\xd8\x0a\x20\x1f\xc3\xa6\x2a\xb6\xec\x68\x63\xcd\x5a\x45\x1a\xd0\x89\x8a\x97\xb6\x57\x5b\x99\x6a\x3a\x10\xb2\xd2\x69\x51\x0c\x02\x27\x0d\x39\x53\x2d\x73\xdb\x48\x7b\xd3\x2f\x49\x92\x93\x77\xe0\x93\xe8\x7a\x76\xf9\xf5\x9d\x9b\x7e\xce\x2b\x5d\xaa\x7d\x30\xca\xa0\xe2\xa0\xd2\x6a\x33\x38\x33\xb0\xad\x6c\x86\x46\xd3\x7a\xd9\x5d\x10\xc9\xe4\xf1\x78\xa8\xa7\x1d\xe3\x49\x1d\xf8\x43\xda\xdd\x9d\x3a\x6b\xc7\x9f\xc0\xac\x8f\xce\x39\x95\xd4\x07\x29\x5f\x80\xa0\x52\xc6\x99\x61\x38\x0a\xb0\x60\xf6\x96\x8f\xf6\xc7\xa4\x16\xf2\x3e\x2d\x84\x4d\xcf\xf3\x45\x14\x24\x26\x23\x2e\xdb\xd9\x52\x15\x39\x66\x82\x0c\xde\xb6\xf2\x28\xe2\x19\x22\x75\x67\xfd\x0e\x62\xd5\xea\xb0\x05\x59\xa1\x68\xda\x22\x65\x6c\x6f\xf6\xf7\xe9\x5e\x93\x76\xb0\x50\x5c\xb7\xf7\x3a\xd5\xfc\xc5\xda\xf0\x3d\x18\x68\xb9\xee\x3d\x26\xa1\x66\x64\x38\xd4\x36\x15\x15\x95\xec\xbb\xc8\xa7\x27\x18\x0e\x9b\x4c\x05\x0e\x47\x1c\x6a\x43\x71\xf6\x99\x1a\x9a\x5b\xdf\x0a\x6f\xf4\xcc\x03\x2c\x24\xca\x5a\xc9\xec\xa7\x4d\x34\x50\xc8\x12\x6f\x8d\xd0\x9f\xc9\xb8\x41\x59\x62\xae\x17\xfe\xb4\x4a\xa6\x5d\x77\x10\x56\xb4\xdd\x97\x0c\xfb\x6a\x12\x01\xcc\xf5\x62\x3a\x06\xf3\x57\x1f\x59\xd7\x30\x1a\xf6\x63\x22\x08\xda\x1c\xf9\x93\x7c\x79\x7d\xc9\xcc\x89\xeb\x0a\xe4\xc3\xbb\x09\x21\x9a\xdb\xfe\xdd\x7e\x40\x36\x20\xb3\x1b\x50\x20\x64\x2f\x19\x9f\xa7\x0b\x5a\xb0\x77\x66\x6e\x65\x2c\xf2\x05\x95\x64\xda\xd8\xbd\x3a\xd1\x37\xce\x33\x29\xbd\x6f\xec\x1b\x19\xf9\x2d\x2a\x69\x41\x2b\x52\xb7\x65\xde\x8a\x4a\x83\x03\xe6\x8b\xba\xff\x18\xb4\x69\x64\x7f\x82\x21\x78\x9d\x68\x9d\x63\x6f\x95\xd9\x36\xad\xe2\x71\xa5\xee\x38\xe0\x30\xb7\x0f\xb1\xc8\x61\x65\xfd\x13\x21\x84\x4c\xc3\xb3\x18\xa1\xb6\x3e\x00\x00\x9d\xb7\xa0\xb9\x87\xb7\x69\x69\xc8\x95\x46\x92\xfd\xae\x23\xc9\x3e\x1e\x61\x5f\xf4\xcc\x9a\x60\x1f\xea\x44\x83\x58\xba\x69\xf2\xa6\x57\x35\x85\xa3\x03\xab\xe5\xef\x90\xa7\x72\x5b\x87\x92\x2e\xb4\x67\x46\xe3\x6b\xef\x33\xd7\x96\xf3\x45\x15\x95\x68\x5d\xdc\xb2\xc4\xbf\x0e\x2d\xf1\xaf\xa9\x33\x97\xbf\x9c\x84\x26\xfc\x5f\x75\xd4\x90\x3b\x8c\x0f\x6d\x48\x7f\x88\xe1\x7c\x70\x0e\x42\x51\xc4\xfd\x1b\x53\x4c\xec\x6b\x82\x27\xf7\xd9\x4e\x54\xc7\x23\x0f\x1f\x44\xf8\x30\xd8\x0d\x9a\x8f\xbe\x6e\x2d\x9e\x0d\xfd\x7e\x3c\x62\x44\x30\x3d\xd3\x09\xba\x4c\x1e\x8f\x3e\x30\x1d\x84\xf9\x77\x0e\xd8\xa4\x13\x20\x65\xb0\x1b\x24\xe7\xc2\xb0\x0c\xf6\x03\x6b\xf1\x0e\x29\x1e\xb3\x75\x5a\x3e\xd3\xd1\x84\xc4\x5a\xbd\x51\x1f\x79\xf9\x22\xad\x78\x84\x51\x6e\x03\xe5\xec\x9f\x1d\xa3\x56\x7b\xce\xc1\x27\xcc\xe5\x8e\x3d\xd4\xe0\x9a\x62\x19\x9e\xef\x22\xde\x82\x41\xdc\x31\x39\xfd\x36\x94\x2b\x3d\xc7\x94\xcf\x66\x0d\xb1\x66\x72\xa8\x6b\xf0\x16\xc5\x47\xb3\xb6\x54\x31\xb3\x3f\x98\x1b\x1a\x0e\x46\x1f\x8f\x9f\xf6\x95\x4e\xdb\x42\x16\x00\x78\x32\x60\xd7\x9b\x48\xe2\x1c\xc0\x10\x2d\x99\xdd\x15\x5a\xb0\x3e\xeb\xa9\x0b\x0d\xce\xe7\x03\xf4\xd6\x58\x0e\x92\xc1\x12\x14\x1f\xcb\x41\x1d\x95\x54\x11\x9a\x19\xb6\xdf\x0f\x7d\x5a\xcd\xcb\x05\x33\x7f\x8e\x47\x4d\xd3\xb9\x5e\xb0\xea\x8c\x88\x02\x53\x07\x94\x35\xe5\x14\x02\xaf\x67\xf3\x62\xb1\x30\x04\x1f\xc5\xb3\xe8\x8f\x5d\x9f\xb0\x41\x31\x01\xcb\x63\x36\x17\x96\xa9\x64\xad\x73\xf7\x95\x8c\x14\x4e\x2a\x5a\xe9\xb9\xb2\x47\x36\x18\x67\xb8\x40\xc5\x99\x05\xea\xb1\x57\xf4\x6e\x28\x7e\x41\xe0\xe8\x0a\xc6\x9b\x85\x61\x90\x35\x3f\x12\x0c\x4f\x38\x87\xc3\x38\xd8\x41\x54\xa9\x48\xd3\xd2\x60\x2a\x31\xe7\xde\x33\x78\x71\x3c\x1a\x8a\xf1\x78\xe4\xd3\x74\xae\x16\x2c\x85\xf1\xf6\xad\x59\xa5\x23\xf3\xd2\xad\x1d\xaf\x29\x18\x22\x14\x73\x8d\x2b\x67\x16\xaf\x1d\x00\xb5\x7f\x62\x10\x10\xb5\xd2\x11\xa7\xf3\x9d\x0f\xec\xc7\xf1\x60\x52\x57\x82\x7b\x91\x82\xea\x32\x80\x1c\xaf\x02\x9e\x2d\xd2\xe6\xfe\x9a\x95\x75\x7b\xc5\x74\x00\x2d\xe6\x0b\x97\x6f\xa9\x62\x61\x9a\x55\xea\x3c\x58\xfe\x89\x42\x8a\xef\xd3\x0d\x7d\x2e\x1b\xa7\x00\xf7\xa9\x7f\xb5\x56\xfe\x9f\xd2\x91\xc3\x8e\x90\x06\xeb\x36\x1e\x11\xfa\x4f\x88\x47\x82\x06\xbb\xcf\x25\x38\x01\x08\x62\x96\x1b\x3f\xf3\x8d\x0c\x82\xe5\x38\xb4\x5d\xa0\x37\xcd\xd4\x6b\xc8\xa4\xa1\x37\xd0\x1f\xc4\x91\xc8\x3f\xc8\x6e\x3e\x77\x8b\x92\x00\xe5\xf5\x84\x32\x6e\x2f\x08\x33\x6b\x05\xbf\x08\x05\xd0\x42\x75\x0d\x7f\x31\xd4\xba\x81\xf0\x48\xb0\xb9\x45\x68\x65\x45\x28\x5b\xef\x0c\xef\x7b\x61\x36\xa8\x13\x5b\xdd\x8e\x05\xf6\xae\xae\x5c\x9d\xce\x48\xad\x28\x17\xfa\x30\xc3\x39\xd3\x87\x79\x05\x7d\x40\x9d\x6e\x1f\x7e\x46\xc0\x87\x5f\x58\x78\x78\xa6\x2b\xfb\x16\x7a\x53\x8d\x0f\x7a\x5f\x1d\x3b\x2e\x0b\x45\xcf\xf4\xe7\xd8\xa4\xad\x4b\x51\xd9\x8a\xc2\x6d\x6b\x21\x92\xb7\x7a\xc1\x6c\x6d\x4e\x06\x2c\x7b\x1d\x16\x1d\xba\xab\x8f\x0d\x5c\x8e\xfe\xf6\xea\xbb\x77\xb5\x3d\xd4\x0d\xbe\x6e\x24\x10\x70\x4a\xc1\xb7\x67\x3e\xf7\xf2\xb3\x78\x30\xd2\x74\x30\x58\x2c\x88\x6f\xfb\x4c\x8a\xbb\xd4\xa3\x76\xec\x24\x80\xb3\xff\x92\xd1\xff\xfd\xe2\xa0\xeb\x18\xe2\x45\x00\xc2\x8b\xbf\x38\xf0\xfa\xff\xba\xbe\xff\xaf\xef\xbb\x53\xad\xb2\xf5\x06\x61\xd1\x60\xc4\x17\xf4\xc1\xf1\x58\x71\xc6\xc3\xa3\x19\x3f\x34\x04\x27\xe2\x70\xdf\x6f\x7f\xcc\xbf\x1d\x8c\xb8\xfb\x74\x97\xea\x69\x61\x5b\x91\x4f\xdb\x5f\x77\x4a\x80\x7a\x8c\xed\xda\x83\x71\xd2\x45\xe8\x3f\x8e\x63\xb8\xbc\x30\xfb\xb4\x08\x69\x28\x03\x71\xe0\xe3\x48\x49\xe6\x96\xb2\x6a\xf3\x1a\x9d\x33\x61\x93\xb1\x88\x0e\xc4\x91\xc3\xe1\x25\x07\xfb\x1f\x77\x63\x85\x05\x3c\xe8\xc0\xd7\xa5\xde\x5a\xe6\x8c\x8e\x68\x96\x18\x6c\x5f\xd9\xc4\x2a\x55\x48\xe6\x36\x83\x13\x04\x02\x43\xa3\xd4\x1e\xb3\xb5\xd9\x41\x34\x11\xc0\x1c\xc0\xe4\x6d\x59\x8e\x41\x3e\x8d\xf3\x71\xeb\xdd\x37\x32\x2a\xd1\xbf\x98\xb4\x5e\x69\xfb\x4a\x52\x7d\xe6\x8d\xc7\xa2\x67\x6b\xec\xf4\xd9\x57\xb7\xf8\xaa\xf6\x5c\x53\xc1\xd0\x39\xce\x2d\xec\x73\x09\xce\xaa\x9c\x0c\x87\x95\x55\x49\x14\x84\x16\x35\x84\xda\x68\x2d\x69\x77\x35\x35\xae\x66\x3b\x3b\xc4\x5c\x9b\xf1\x72\x1c\x6f\x48\xda\xda\xa2\x83\x6d\x63\xc6\x7c\xab\x17\xb5\xbd\xf3\x3f\xa4\x77\x3c\xf7\x11\x33\x90\x66\x87\x78\x13\x81\xcd\xfb\x17\xd5\x3a\x2d\x79\x6e\xc8\x69\x7a\x70\xb0\x22\xa9\x28\xb8\x56\xf3\x95\xd8\x71\xf0\x5b\xff\xb7\x8c\x7a\xc0\x09\xe4\x73\xb0\x1e\xed\xa0\x1e\xea\xa1\x12\x0f\x2d\xe7\xd5\xb6\xc3\xaa\xac\xd9\xd7\xc2\x9c\xc7\x13\x87\x4d\xb1\x8a\x04\xf8\x5c\xe6\x3a\x32\x3b\x45\x8e\x47\x09\xcf\xbf\xe2\xa3\x13\x29\x4c\x3c\x37\x59\x47\x95\x39\x07\x07\x15\xdb\x29\xb1\xcb\xeb\x29\xb8\x4b\x57\x54\x30\xeb\x34\x1b\x91\xc4\xea\xf8\x3a\xac\x87\xa6\x82\xa6\x84\xd4\x1d\x7f\x3d\x4e\x94\x21\xf9\xca\x20\x7d\x86\xaa\x4f\x9a\x72\x2a\x30\x8c\x87\xf7\x56\xf7\x0b\xa9\x1e\x58\xba\xc6\x19\xe0\xe7\x88\x93\xd9\x77\xc2\x90\x78\x36\x7c\x39\x95\xa4\x65\xdf\xfc\x6f\xd9\xf1\xc3\x75\x37\x49\xb6\xae\x6f\xa3\xff\x6a\x94\x7a\x02\xd5\x4e\x36\xf4\x15\x43\xaa\x03\x5d\x60\x2f\x2b\x72\xa8\x58\x33\xda\x3f\x04\x32\x92\xe1\xe6\x8b\x8e\xeb\x4f\x8b\xe1\x68\x82\x52\x0c\xd6\xea\x9e\x97\x03\x73\x2b\x30\xf2\x5e\xa4\x68\x63\x30\x5a\x59\xca\xe5\x0b\x73\xfe\x0c\x2f\x44\x1d\xe7\x43\x6d\x40\x4e\xe7\x87\x30\xc8\x5c\xa4\xbb\x41\xe0\x94\xf8\x6b\x9b\xd3\x72\x21\x53\x5b\x0c\x94\x3e\x1e\x41\x06\xf1\x85\xf4\x42\x08\x4d\x5c\x3e\x3c\xde\x2c\xe5\xcf\x6d\x7e\xc0\x1f\xd9\xc0\xf5\xe4\x42\x98\x5d\x67\xc0\x51\xcc\xc4\x9c\x2f\xc6\x72\xce\x17\x89\x29\x1d\x9b\xb2\x60\x63\x7e\xe9\xb0\x58\x2e\xd0\x66\x13\xf6\x2c\x75\x78\x72\xca\x3b\x39\x8f\x51\x96\xfb\x0e\x6c\x71\x06\x20\xde\x05\x51\x90\x7c\xa1\xee\x36\x05\x87\xe4\xd4\x8b\x16\xad\xfa\xc7\x5f\xf8\x98\xef\xed\x6d\xa9\x6e\x4b\x5e\x55\x27\xbd\x7d\x27\x3d\x71\x32\xd8\x4a\x6b\xe3\x34\xb8\x74\xae\xd6\x1f\x85\xcc\xd5\xc7\xe1\xb0\xef\x5d\xae\xb2\xad\x41\x86\x81\x0f\x7d\xa8\xfa\x30\x3d\x0f\x87\x3d\xce\xdb\x9a\xb9\x96\x71\xa3\x6c\x79\xbe\x7f\x6d\x40\x7b\x02\x02\x32\x1f\x0a\x42\x33\x8c\x83\x82\x62\x33\x30\x8b\xb2\xae\x68\xf8\x40\x3c\xd5\xfd\x9b\x64\x87\x9a\xfe\x24\x59\xc8\x12\xc0\x78\x3a\xdc\x23\xf0\x33\x55\xf4\x9b\x6c\x79\xde\xba\x0e\xcd\x29\x21\x04\xb5\xb5\x8e\x60\xfe\xf6\xc4\x9a\xa0\xa3\x7b\x70\xba\x15\x43\x3e\x23\x6d\xfb\x83\x0c\x1d\xca\xbf\xc6\xe8\x2c\xec\x27\x69\xaf\x5c\x8f\xfe\x1c\x6d\xbe\x2e\x44\x75\x91\x16\x25\x4f\xf3\xfd\x85\x90\x17\xdb\x8a\xc7\x17\x10\xf5\xf1\xe2\xa3\xd0\xeb\x8b\xd7\x2f\x2f\xfe\x36\x18\x55\xb1\xc8\x47\x83\xbf\x5d\xdc\x6d\x2b\x7d\x71\xc3\x2f\xac\x34\x98\xe7\x17\x28\xc0\xbe\xd0\x6b\x7e\x81\xd3\x31\xff\x4c\x95\x92\x6f\x2b\x9e\xc7\x03\xe2\xd1\x2c\xef\x02\x3f\x1b\x02\xaa\x8d\x97\xa8\x6c\xc7\x55\x9c\x5a\xf1\x7e\xaa\x57\xaa\xbc\x63\x12\x05\xcb\x02\xac\x45\xde\xda\x52\xe0\x51\x3d\x4a\x94\xbe\x76\x9c\x66\x7f\x6c\x45\xc9\x5d\x6f\x8a\x96\x71\x5a\x6d\x78\xa6\xdf\x99\xc3\x0a\xf1\xd1\x86\xc3\xc2\x6e\x04\x5d\xb3\x6c\x38\xcc\x5c\x78\xd9\x1c\x9f\x30\x6c\x30\xea\x9e\x73\xf6\x6f\x47\xdf\x66\x7a\xc7\x0a\xfb\x13\xb7\x31\xc3\x27\xcc\xe7\x99\xe3\x83\x4d\xe6\xb9\xb6\x34\xb1\xa3\xd4\x4b\xfb\x1c\x8c\x05\xa9\x97\xa0\xc0\x56\xc1\x58\x63\x6c\x6e\x75\xd5\xcb\x3b\x6e\x19\x44\x5f\x02\x71\x57\x2a\x2b\x94\xc6\xb2\x1b\xb5\xe3\x4d\x8d\x6c\x5b\x96\x5c\xea\x97\xfc\x5e\x64\x98\x57\x0a\xbf\x18\xb6\xf0\xd0\xaf\x55\xba\x4c\x33\x2d\xee\x79\xf3\xad\x22\xad\x34\x98\x93\xb5\xab\x15\xd6\xb8\xac\x32\x17\xc2\x8e\x0a\x62\x7a\x7d\xdf\x1d\xad\x95\xea\xb8\x6a\xf0\xd8\xee\x2b\x54\x81\x7f\x90\x58\xf8\xc5\xa6\x54\x3b\x11\xb4\x5b\xae\x45\x9e\x73\xf9\x5a\xe6\x22\x0b\x8a\xd1\x58\x0f\x50\xb0\x9b\x80\x83\x4b\xd5\x4b\x51\x19\xdc\x9f\xb7\xbe\xe6\x73\x3f\xb5\x86\x90\xab\x77\xbc\x12\x7f\x72\x26\x22\x0c\x8c\x65\x4a\x2d\xc7\x34\x28\xe1\xd5\x80\x10\x5a\xc6\xf8\xfb\x25\x87\x7c\xd6\x13\x42\x7f\x93\x73\x19\x8b\x7c\xc1\x24\x38\x26\xcf\xa2\x34\xc6\xa5\x89\x24\x1d\x64\x16\xba\x0e\xe8\x2f\xe0\x22\xda\xbc\xd9\x58\x48\x39\xa0\x7f\x48\x73\xfe\x83\x13\x0e\xf7\xc1\x4d\x6b\x38\x94\x6e\x18\x84\x24\xce\x0c\x95\xe3\x6d\x7e\x95\x8a\x82\xe7\x17\x5a\x5d\xe0\x2d\xbb\xc0\x80\xa6\xe6\x3a\xfe\x4d\x5f\xd8\x8b\x70\x61\xe7\x7b\xb1\x2a\xd5\x1d\x5c\xda\x5b\x71\xcf\xe5\x85\xd0\xfc\x0e\xf5\xc2\x17\xc1\x11\x3c\x21\x12\x0f\xc1\x4b\x48\x27\x26\xa4\x4e\x85\x7c\x16\x94\xf2\xda\x67\x29\xb1\x21\x8f\x25\x0d\xcf\xb9\xa3\xd7\x1d\x88\xfc\x22\xd2\x64\xc6\x87\x43\x35\x53\x89\x9c\x89\x2b\x99\x60\x56\xb8\x73\x3c\xf6\x83\x2c\x76\xc8\x61\xeb\x87\xb8\xeb\x87\xd8\xea\x13\xae\xba\xb5\x1d\xed\x48\xd8\x5e\xa2\xdc\x41\xb0\x81\x5a\x8f\xd0\xc6\x1f\xa5\xe4\xd5\x46\xc9\x4a\xdc\xf3\x99\xb6\x87\x27\x22\xc9\x9e\x47\x3a\xa8\x94\x77\xee\xa9\xe9\xe0\x46\xc8\x1c\xed\x56\x21\xb6\x5f\x1f\x3a\x77\x1f\xab\xfb\xe0\x63\x4b\xd6\xee\xc1\xe3\xcc\xdc\xb1\xe6\x31\xb9\x34\xf8\xf3\x78\xec\x43\xbc\x3f\xae\x56\x55\x56\x72\x2e\x5f\x58\x8c\xa8\xc1\x94\x39\x95\x59\xcf\x5b\xe8\xf7\x27\x0e\x61\xb7\x72\x51\x5b\xde\xdf\x7d\xff\x0f\x1b\x1a\xcf\x02\x5d\x07\x4d\x11\xae\xd6\x95\x36\x78\xd0\xd5\x4d\xc1\xe1\x03\xea\xdb\xf7\x76\xd5\x60\x42\x69\x5c\x6e\xa5\x84\x34\xc7\xa6\xc2\xcc\x93\xbb\xe2\x4f\xfe\x1c\x76\xe0\x65\x99\x7e\x74\x69\xc0\xb5\x3b\x90\xbc\x4e\xc2\x9a\x28\x97\x0b\x1f\x5a\xec\x2c\xe4\x88\x74\x71\x1d\x0d\x55\x6b\xc7\x6d\x28\xda\x9e\x0b\x60\x68\x9f\x10\x8a\xa7\x4c\x34\xe8\xa8\x6d\xb2\x1b\x41\x88\x2a\x5a\x61\xf8\xd2\x33\x50\x9a\x1a\x7c\xd6\x3d\x12\xc7\x63\xbb\xd3\x6e\xa3\x88\x4c\x6d\x8c\x6a\xc6\x58\xda\x44\xb5\xb6\xe8\xc8\x94\xe1\x4f\x74\x68\x29\x8e\xc7\xc8\xd5\xb7\xb5\xa9\xaf\xec\xaa\x52\xd1\xc6\x57\xed\x69\xee\x79\x24\x68\x01\xda\x69\xd1\x3d\x9e\x16\x62\xd2\x83\xf9\x97\xa4\xb5\xa1\x38\x65\xac\x24\x02\x59\x3a\x17\x34\x05\x73\x03\x11\x00\x3a\xd1\x40\x61\x43\xd2\x89\x18\x2d\xc7\x23\x42\x6a\x2e\xab\x6d\x69\x4d\x39\xbe\x49\xef\xf9\xeb\x97\xe6\x7a\x7f\xdb\x8e\xc6\x18\x28\x01\x22\xab\xf0\x3a\x68\x83\xc0\x79\x4d\x48\x7d\xb3\x15\x45\xfe\x63\xf9\x33\xc0\x52\x6b\x14\xf2\x89\x68\x9e\xd6\x1c\x06\x9c\xdc\xf0\xa7\x62\x1d\xe5\x40\xc9\xf3\x6d\xc6\x23\xf7\x3d\x8c\x48\x64\x30\x11\x21\xf4\x50\x3b\x3e\x68\xbe\x98\x82\xb3\x17\xab\x9c\x29\x5f\x27\x38\xcb\x5d\xba\x69\x4b\x99\x0d\xf9\x4f\xa5\xd7\x2f\x28\x36\x40\x77\x73\x5a\x59\xc9\xb8\x8b\xa5\xef\xe1\x34\xa7\x79\x93\x53\x69\x16\xb0\x36\x49\x35\xf3\xb9\x37\x2c\x03\x94\xa3\x14\x65\x36\x28\xd3\x5c\xa4\xc5\x1b\x01\x59\x06\x4c\xc5\x2c\xd5\xfc\x56\x95\x7b\x53\x15\x4b\xeb\x1a\x04\x1e\xdf\x46\x15\x0d\x74\x08\x55\x70\x41\x52\x66\xc8\x24\xd4\x86\xa4\xe6\x68\x17\xec\xbb\x08\xa5\x4f\x54\xc4\xf0\xad\x40\x54\xdc\x64\x03\x18\x0e\x7f\x95\x51\xf3\x48\x4b\xc2\x18\xfb\x55\x46\x22\xce\x03\xb5\x55\x50\x83\x85\x6f\xa8\x9a\xa7\x0b\x76\x39\xb1\x01\x60\xc0\xd3\x53\xac\xa2\xd4\x10\xb4\x72\x38\x94\xf3\x74\x81\xa2\x5b\xc6\x0a\x92\x31\xf3\x8c\x61\x21\x32\x43\x66\x58\xad\x34\x5a\x44\x15\x84\x44\x07\x91\x27\x29\xca\x43\x0a\x88\x07\xae\x21\x48\x34\x22\x54\x5d\x13\x2a\xe7\x19\xa0\xf9\xac\xce\x62\x03\x6d\x41\x04\x50\xc3\xca\xa8\xe0\xbc\x1d\x8f\xd6\xa0\xd0\x70\x71\xf8\x5a\xa2\x0d\xcf\xd7\x3c\x76\x21\xcd\x00\xe4\x50\xcf\x49\x11\xfa\x35\x44\xdf\x79\xae\x76\x08\x98\x08\xa9\xad\xf3\x80\x25\xa6\x30\x18\x7f\x8f\xd8\xcd\x91\x86\x70\x48\x81\x15\x9d\x4a\xeb\x06\x2e\xe6\x72\xc1\x0c\x43\x49\xcd\x1f\x66\xd0\x0d\x54\x60\xbc\xdb\x7b\x7f\x48\x52\xdf\xb5\x60\x5d\xa5\x92\x75\x5e\x6b\xfc\xd8\x30\x2b\x80\x08\x4d\x81\xc4\x94\x43\xd8\x09\x4e\x34\x18\x9d\x18\xde\x21\x88\xf3\x6a\x18\x17\xee\x52\xe9\x0a\x2a\xc7\x02\x72\xf3\x77\x48\x48\x1e\x57\x98\x6b\x97\xc4\xe6\x4d\xf4\xb3\x8c\x06\xe0\x25\x35\xa0\x03\x98\xcc\x80\x00\x18\xbf\x53\xf7\xfc\x67\x59\xf2\x15\x2f\xb9\xcc\x9a\x0e\x4e\x22\xbe\xfb\x59\x99\xfb\x02\x79\x90\xdd\xa4\x12\x51\xd7\x4c\x4f\xdd\x8c\x9e\x0a\xcf\x1e\x36\x11\x2b\x91\xec\x0e\xa5\x74\x20\xff\x60\x4f\x0f\x13\xc6\x58\x4b\x7a\x01\xaa\xbf\x78\x69\x7b\x27\xc4\xf7\xd6\xbf\x1a\x92\x9c\xc0\xa8\xd0\xec\xee\x64\x7f\x50\xc7\xdf\xde\x16\x17\x5c\x06\x24\x5c\x10\x53\xf6\xfc\xb2\x50\xc9\x26\xa1\x87\xac\x7c\xa2\xa6\xb2\x31\x6b\x50\xcc\x9c\x1e\xe7\x98\x08\xe8\xa6\x35\x58\x1f\xcc\x5e\x79\x4d\x62\xa0\x2d\x01\xde\x13\x7e\x0d\x87\x95\x8b\xb9\x9b\x62\x5e\xf8\xde\xb9\xd3\xde\x8f\x10\x6a\xed\xe8\x53\x5a\x35\xfa\x49\x97\x60\xd9\xeb\x2a\xd3\x86\x8e\x32\x2d\xe0\x78\x30\x85\xff\x8f\xc7\x09\xd5\x71\xdf\x5d\xaa\x20\x0c\x28\x2a\xd6\xd8\x60\x30\x52\x36\xbd\x76\x15\xdf\x63\x4c\x62\x73\x9f\x2a\x3b\x20\x17\xa0\x19\x9a\x34\xd6\x8d\x24\x7c\xb0\x54\x3a\xf6\xde\xa9\x18\x17\x42\xfe\xee\x70\x8e\x85\x42\xf6\x1a\x3f\xf3\xac\xaf\xb5\x26\x4d\x09\x3d\xb4\xb5\x0c\x1f\x00\x58\xc3\x71\x0d\x4b\xca\xba\x65\x79\x90\x2e\xa6\xed\x84\xb5\x41\x00\x69\xec\x31\x6c\x8c\x9f\x75\x16\xa8\x25\xa1\x7d\x9f\x1c\x0e\xdb\xd5\x14\xa9\xdb\xf3\x02\x86\xcd\x86\x54\xb5\x06\x38\xad\xf5\xf1\xd1\x26\xbb\x7b\x60\x4e\x20\x07\xfa\xcb\xf7\x7e\x92\x11\xfe\xdb\xa8\x73\xbe\x69\x64\x63\x34\x1e\x4e\x0e\x8b\x20\xe1\x6a\x43\xb7\x91\x19\x2c\xd8\x2d\xc3\xc3\xc1\x93\x7f\xe1\x17\x91\x12\x3d\xa5\x5d\xb8\x1e\x10\xa7\x2f\xeb\x0d\x63\x8d\x3a\x33\xe1\x59\x33\xca\x1b\x4e\x5f\x74\x65\x1e\xa2\x57\xe6\xd1\xcd\x01\xfb\x6d\xe4\x29\x0e\x20\x05\xbe\xe6\x31\xde\x60\x83\x16\x20\xee\x7e\x60\x38\xc5\x7b\xd9\xdc\xcb\x7e\x49\x60\x2f\xf5\x44\x79\xdc\x4b\x12\x79\x71\x92\xd5\x90\xb4\x48\x15\x1e\xf0\xfd\x98\xfe\xc2\x56\x6a\x3e\xcc\x81\x65\x01\xf9\xd3\x16\x05\xb3\x90\x30\x61\x2b\x5b\xfc\x0c\x6f\xb1\x37\xb0\x7c\xde\xb0\x2c\x30\xd7\xa3\x18\xe8\xb0\x9f\xd5\xc2\x71\x0f\xe8\xe1\x4e\xe5\x3c\xd1\xb4\x31\x02\x04\x83\x9d\x6e\xde\x8d\xce\x7c\x5b\xe0\xf5\x54\x5c\x8a\x9f\x70\x27\xc5\x7e\xca\xe9\x1f\x26\xa1\xa9\x1a\x1c\x88\x3e\xfc\x38\xd5\x4f\x44\x60\xbc\x76\x68\x4e\x68\x22\x6a\x34\x50\x0d\xcf\xb0\x36\x64\xde\xa5\x1c\x0e\x41\xb2\x9c\x06\xd6\x6d\x53\xd1\x1e\xba\x3f\xbf\x8a\xb8\x70\x66\x77\xe9\x2e\x1a\x09\xcb\x6c\xfc\x78\xcf\xcb\x55\xa1\x3e\x46\x84\x96\xa4\xe6\xf1\xf2\x4e\x48\xeb\xc9\xc6\x4a\xb3\xd4\x78\x6a\xdf\xa4\x7b\xb5\x85\xcb\x2f\x8f\xc7\x6f\xa3\x14\x8f\x9d\x6e\xae\x0f\x69\xea\xda\x81\xa2\xf9\x61\x2f\x2f\xda\xd9\x0d\xb0\xe7\xb1\x92\xac\x06\x77\xff\x39\xa0\x83\xa5\xc8\x0d\xda\xc6\xd7\x56\xb4\x34\x1c\xf2\x78\x09\x27\xe7\x9b\x54\xe6\x05\xc8\x09\x9b\xb7\xc0\x62\x70\xcf\x0e\xd4\xed\xf1\xf7\x25\x02\x79\xe0\xd4\x60\xab\x01\x3d\x9c\x39\x2e\x5f\x73\x77\xa9\x0d\x85\x86\x8c\x91\x4f\x3f\xd5\x4a\x2e\xef\x49\x6c\x60\x17\xa1\xe6\x13\x36\x31\x7c\x1a\xd6\x7e\xc2\x26\x53\x1e\x4a\xf3\xbe\x85\xac\xcb\x3b\x77\xc1\xe5\x70\x18\x90\xe9\x8c\x41\x3c\x0b\xa4\x6e\xc1\x6a\xca\x53\x8b\x8d\x31\xa7\x21\x1d\xc3\x85\x75\xe1\x32\x74\xec\x13\x14\x10\x03\xf7\x78\x58\xa9\x21\x56\x3c\x53\x64\xf6\x00\xd8\xa2\x33\x9b\x69\x17\xc9\x2f\x75\xb0\xfd\x5d\x60\x38\x70\x62\xf9\x40\xf6\x8e\x7b\x70\x79\x76\x0f\x5c\x77\x9f\xba\xc1\x9e\x9c\x44\x6a\xe5\xcc\x35\x33\xb4\xcb\x68\x24\x49\xf7\xb0\x46\x92\x8a\x99\x8e\x1c\x36\x05\xcc\x9c\xc8\x9a\x24\xba\xe7\xbe\xc3\xb4\xcf\x8c\xab\x26\x75\x67\x21\xce\x88\x0c\xfa\x6e\xf4\xc1\x50\x9c\x89\xb4\x49\xeb\x35\x85\x2e\x79\x67\xaa\x53\x58\xad\x13\x2e\xba\xb5\x5a\x6e\x50\x0a\xdc\xea\x42\x7c\xb7\x74\x07\x16\xa2\xa9\xf9\x8e\x0d\x1f\x7a\xd2\x65\x38\xcf\xa6\x47\x83\x26\xe5\x69\x86\x24\x1c\xd6\x19\x49\x97\xd5\x25\x9d\x5c\xa4\xe1\x30\x4a\x6d\x20\xf0\x99\x0e\xd8\xfb\xcb\x40\x74\x03\x91\xc8\xc1\x42\x34\xd2\x24\x89\x6c\x3a\x09\x42\x7f\x91\xd1\xc1\xb3\x5c\xc4\xe6\xfa\xe8\x8c\x09\xd8\x0b\xcc\x47\x77\x22\xf7\x71\xa0\x16\xa5\x3f\xdc\x67\x84\xac\x59\x4f\xe5\xa9\x2f\xb3\x26\xad\x3d\x72\x24\x30\x47\x85\x6f\x79\x4b\x97\xe0\xa6\xeb\xe6\xa6\x3b\xf0\xe1\xa0\xcf\x99\x45\x33\x9d\x9e\x87\x3d\x4d\x60\x1b\x7b\x77\x01\xcf\xa0\x5b\x45\xc3\x8f\x18\x76\x2e\xfe\xd3\x40\x17\xc3\x5d\xc1\x13\xe6\x44\x69\x20\x12\x71\x3c\x80\x79\xe1\x2f\x2f\x09\xba\x79\xa8\x6d\xbf\xb0\x11\x86\x8e\x3e\x20\x98\xbc\xa3\x95\xb5\xe3\xc4\x1e\xb9\xcd\xc9\x51\xe1\x02\x9d\x38\x06\xc5\x5d\xe9\xce\x25\x6e\x62\x13\x1b\x06\x44\x0f\x87\x97\xca\x51\xe4\x20\x01\x33\x00\x4f\x05\xc1\x81\x1e\x4e\x25\x72\xe2\xe9\xd4\x33\xf2\x4b\xb0\xea\x6f\xad\xd3\x49\xea\xc3\x07\xb7\xd4\x36\xfb\x9c\xad\xe5\xec\x53\xc9\x4f\x3c\x65\x21\xfc\xda\x8c\xaf\xa7\xe2\x29\x9b\x4c\xc7\x63\x41\xda\x5b\x1a\xf1\xb9\x58\x9c\xdd\xaf\x70\x5c\xed\x29\xf6\xd3\xb4\x90\x13\x4b\xc7\xcb\xac\x10\x1b\xd8\x9a\x06\xc1\x55\x16\x8c\x69\x07\xc6\x90\x3a\xe9\x85\x62\x0f\xc3\x7c\x5c\xa5\xca\x40\x09\xc8\xb2\x85\x19\x5d\x61\x7d\x25\x44\x86\x9c\x4d\x12\x05\x3f\xc6\xf8\x6c\x13\x23\xd9\x1a\xf0\x30\xb3\x78\x39\x51\x36\x63\xa7\x2d\x87\xf4\x8b\xb6\xa2\x56\x1b\xe8\x49\xab\xcd\x18\x9e\x5c\xa6\x25\xfb\x1e\x9f\x66\x0e\xb3\x27\xca\x96\x8c\xdc\x2b\xc3\x3d\x84\x40\xd6\x42\x28\x48\x7c\x45\xab\x0e\x98\x7d\x10\x9d\xb8\x29\x87\x4e\x7f\xd5\x33\x24\x6d\x5e\xa9\xf2\x7b\x95\xf3\x53\x2b\xf7\x1f\x79\x6c\x30\x45\xd5\xc4\x40\xef\x41\xb4\x6a\xa6\x30\x1f\x13\x06\x9f\x4d\xe6\x90\x8c\xa7\x8d\x82\xda\xd7\xb2\x85\x40\xe7\xba\xf1\x14\x70\x72\x11\x6f\xbd\x15\xca\x32\x50\x6a\x81\xad\x42\xd5\xb5\xb7\xed\xc2\xc8\xec\x73\xbd\x60\x68\x1e\x04\xc1\x65\x40\xbe\x32\x5f\x38\xd6\x12\x0b\x03\x22\x18\x9e\x51\xa9\x87\xbf\x77\x68\xd4\x8b\x0f\xfb\xf0\x01\x98\xf9\x84\x1b\x32\xd1\xf3\xf5\x0e\xa1\xba\x81\x25\x9c\x2e\x37\x69\x59\xf1\xdc\x7c\xd4\xc2\x9f\xe4\xf2\xba\xb6\xd6\x65\x9e\xd5\x6a\x01\x05\xa7\x0d\x74\x9e\x40\x5e\x3b\xe8\xf0\x10\x2c\x2f\x84\x4b\x82\x82\x41\x0d\xdb\xd8\xbe\xbb\x2f\xd4\x56\x76\x3b\x7e\xf8\x9a\xdb\xab\x5d\x9f\x88\x18\x3e\xb5\x61\x98\xe7\xce\x7b\x39\x4d\x5b\xe2\xc0\x93\xcd\x77\x47\xe7\x46\xa9\x82\xa7\xc1\xc9\x11\x31\x2e\xfc\xec\xd2\xfd\x4a\x2e\xb9\xfd\x55\x57\xbe\x1b\x18\x95\x28\x84\xde\x23\xc5\xd3\xff\x15\xdb\x8e\x5d\xf2\x5a\xab\xdb\x5b\x9c\x6b\xd8\xd4\x71\xdf\x2d\x0d\xae\x39\x2d\x97\xfd\xe5\xee\x14\xb7\xfb\xb0\x73\x3e\xd3\xa4\x4d\xa0\x75\x07\xde\x21\xd5\xf8\x6c\x50\xad\xd5\xc7\x41\x32\x58\x8b\xdc\x50\x40\xbd\xc4\x5b\xc5\x54\x8b\xcc\xb2\xb6\x4b\xde\x18\xb5\x8a\xbc\x01\xd5\x14\x8c\x19\xfb\x3f\x4e\xbd\x22\x59\xd1\x83\xc5\x65\x09\xaf\x09\xf5\xe2\x03\xcc\xb2\x1a\xd2\xa9\x60\x19\x2f\x9d\x1b\x06\xa9\xd7\xe0\x53\xef\xd6\xf1\xfc\x54\x2f\xaf\x49\x6d\x66\xf6\x59\x75\xad\x3b\x5b\x57\x1e\xd7\x87\x1c\x3c\x7c\x00\x4e\xcd\x3d\xc0\x79\xc4\xd4\xa9\x6e\x95\x20\x4b\x61\xb8\x68\xf6\x03\x11\x09\x52\x31\x05\xed\x49\xed\x2b\x74\x93\x73\x62\x78\x22\x9b\x9d\xd3\xa5\xb3\x69\xe4\x9b\xa8\x35\xa4\xa9\x95\x92\x98\x0d\x43\x9a\xa2\x5f\x5a\xed\x29\x8c\x73\x42\xc8\xa9\x97\x61\xb6\xac\xa0\x31\x6b\x6f\x47\x7c\xf1\x87\x76\x24\xa3\x57\xc9\x95\x1c\x22\x3f\x39\x28\x03\x2f\xad\x95\x08\x80\x30\xd0\x27\xd8\x08\x8d\x27\x08\xdb\x79\x5a\xfa\x45\xfa\x4d\xce\x75\x2c\xf2\x45\xad\xd5\xf3\xb4\xe2\xff\xf8\x5f\xaf\xef\xd2\x5b\xe7\x7d\xdf\xd2\x8d\x63\x3c\x57\xad\xcc\x64\x7e\x7e\xf7\x06\xab\xd4\xe1\x68\x7b\x64\xfb\x5e\x88\x03\x52\x64\x37\x07\x2a\x59\x24\xa9\xc2\xb4\xb8\x69\x9e\xb7\x82\x08\x45\x90\x35\x8c\x50\x43\x97\x31\x55\x53\xe5\xeb\x72\x8c\xb3\x20\xec\x46\xf4\xb6\xf2\x01\x1a\xe4\x82\xd4\x56\xae\xec\xad\xe1\x2c\xea\xe3\xb1\x82\x90\x6a\xbf\x30\x41\xdd\xef\x5f\x99\x34\x74\x79\x5b\x34\x80\x19\x6e\xbf\x0d\x12\x52\xa3\xd0\x09\xd8\x6a\x19\x69\x83\x6b\xfb\x55\xf0\x36\xe9\xc2\xb4\x62\x8d\x28\xd1\xd9\x7b\x79\xed\xbc\x79\x53\x7b\xb1\x11\x24\x9e\x50\xd1\x00\x19\x99\x01\x4d\x4d\xcf\x8d\xd1\xc9\x84\x36\x5a\x7d\x2a\x1b\xc5\x67\x05\x4f\x39\xc7\x46\x66\xc8\x1c\xbb\xd2\x2d\x8b\x15\xd5\x6d\xd1\x7c\xa6\xa6\x22\x16\xd5\x33\x5b\x39\xf2\xb6\x68\xb3\x34\x22\x09\x8f\x08\x26\x03\x69\x8d\xa5\x6e\x1f\xd3\x87\x36\xde\x66\xa3\x6e\x9b\xf1\x80\xbf\x79\xb3\x32\xc1\xd9\xee\xdb\x58\x81\xaa\x2a\x27\x28\x85\x78\x71\x10\xc0\xaa\xeb\x8b\x2d\x66\x83\x8a\x43\xfc\x67\xe8\x67\x10\xc6\xff\x87\xcb\xec\xac\xdf\xbd\xa7\x71\x3f\x3a\x9b\x4f\x16\x2d\x20\x09\xec\xae\x07\x35\xf3\xc1\x72\x30\x92\xa3\x81\x6d\xd2\x0c\x67\xb0\x88\x08\xa1\x29\x9b\x40\x4a\x5d\xe7\xc3\xf9\xa4\xc4\xb0\x95\x15\xd3\xf3\x74\xe1\xe9\x73\x73\x10\x7a\xbe\x5d\xb5\x3f\x1c\x7c\xd6\x2c\x24\x9f\xcb\xd1\xa0\xf5\x41\x1f\xc3\x80\xb6\x5b\x3a\x4d\x06\xa9\x0d\x8e\x7b\x06\xa6\x56\x81\x00\xbc\xc5\xae\xa0\x21\xd6\xf1\x38\x5f\x18\x8c\xdc\xa9\xdb\x0b\xa4\x9b\x16\x40\xc9\x83\x56\xb9\x23\xff\x70\x14\xbc\xa8\x43\x87\x9e\x1e\x61\x24\x10\x1a\xa7\x0e\xf3\x83\x1f\xd4\x85\xed\xf0\x62\xa5\xb6\x32\xbf\x48\xf5\x05\x74\x19\xc6\xe0\xe9\x7e\xd3\x2e\x46\x22\x61\x31\xe6\x62\xe1\x47\x51\x13\x32\xbd\xd4\x20\xab\xc1\xb4\x9d\xce\xfe\x4c\x36\xc2\xc7\x66\x65\x2b\xa8\x47\xea\x36\xf8\xec\x44\x32\x0f\x4d\xca\x62\xe7\x79\x8e\x54\x32\x5c\xed\x9e\x5e\x7b\x42\x3d\x40\x10\x1e\x07\x41\xc0\xe0\x98\x56\x9f\xe1\x47\xdb\x41\xe2\x6d\xac\x6e\x80\x8c\xf0\x6f\xf0\x20\x10\x38\x9b\x15\x48\xf8\x69\xc9\xc4\x4c\x27\xe8\x60\x31\x4d\x3d\xc7\xef\x4c\xc0\x82\x2b\x96\x52\x05\x0c\x82\xc1\xf8\xb4\xc9\x6a\x86\x85\xbd\x2d\x4a\xdf\xc2\xe0\xfd\x16\x44\xed\x23\x91\x0e\x50\x23\xd1\xb4\xe4\x9b\x22\xdd\x9f\x88\xac\x0c\x87\xc8\x9e\x9e\x08\xfe\x8f\xc7\x96\x4d\x86\xd5\x06\x34\xb6\xdb\xe8\x72\x48\x3c\x53\x7d\x46\xe6\x05\xb0\x66\x00\xb8\xa3\x9b\x12\x58\xc4\xcb\x35\x8c\x1b\xea\xe0\x52\x39\x3e\xe4\xb3\xa4\x5f\x41\xdf\x34\xaa\x8e\x47\x69\x18\x5d\x79\xcb\xf3\x96\xd1\x09\x15\xf5\xc9\x77\xda\x6b\x74\xb0\x47\x35\x91\x6c\xbe\xf0\x7e\xdd\xaa\x66\x02\x28\x47\x3c\x33\x29\xe3\x56\x69\x30\x5f\xd0\xc2\x0c\xca\xda\x2a\x58\xca\xfc\x4e\x6d\x2b\xae\xb6\x1a\x72\x61\x59\x9d\x69\x54\x22\x39\x7a\x96\x57\xac\x70\x23\x2b\x83\x8c\x32\x36\xc8\x0a\x91\xfd\x8e\x02\x64\xd3\xc1\x4c\x04\xb2\xf3\x04\x12\xb4\x07\x46\x9c\x40\x8c\x7c\x0d\x0e\x7f\x01\x83\x2f\x42\x25\x81\xb9\x88\x3f\x45\x2a\x56\x12\x8e\x0f\x9d\x6b\x5a\x52\x01\xf6\x3a\x38\xde\xed\x26\x1c\xae\xfd\x7e\xab\x04\xa9\xa0\x3b\x2e\xb7\x4d\xf9\xf1\x88\x9d\xbe\x30\xd5\x83\x4e\x89\x59\x17\xad\xa3\x92\x4a\x42\xa3\xe2\x78\x84\xd4\x7e\xc2\x43\x82\xd2\x8c\xee\xf4\xce\x96\xe0\x1f\xdf\x99\x5d\x46\x0b\x97\x8c\x4c\x2b\x40\xb8\xdf\x46\xdf\xca\xd8\xd9\xad\x21\x89\xa0\xfb\x95\x4d\x84\x50\xae\xd8\x65\x90\xe7\x0b\xcc\x21\x4f\x20\xe0\x87\xb5\xa8\x2e\xee\xb8\x5e\xab\xdc\x85\x0d\x11\x77\x1b\xdc\x2b\x9e\x27\x17\x5c\xe8\x35\x2f\x2f\xa4\xba\xb0\x89\xce\x9c\x79\x34\xc2\x4b\x55\x5e\xa4\xf2\x42\x48\x67\x1b\x7a\x21\xa4\xe6\xb7\x65\x8a\xb1\xe5\xd3\xea\x62\x53\xaa\x7b\x91\x83\x1d\x75\x6d\x95\x6f\x68\x9b\x67\x63\xf5\x0b\x5e\x45\xdf\x4a\x7a\x70\xc1\xc8\x92\x30\x71\x1e\x57\x18\x5c\x35\xd9\xe9\x9a\xfa\x69\xf7\x57\xf9\x4d\xd6\x3e\x66\xde\x99\x2a\x2b\x5d\xd3\xd2\x46\x7c\xe9\xaf\xf1\x4c\xd6\xf4\x9e\x43\x72\xc7\xfe\x0a\x83\xff\x88\x1f\xc7\xd7\x83\x9a\x1a\x1e\x1c\x78\xea\xde\x6a\x3f\x49\xf7\x25\x5e\xf6\xd7\x40\xd2\x96\x3d\x3d\x3c\x43\x37\x55\x78\xa4\x5a\x41\xfe\xe2\x26\x9a\xcf\xa7\x1b\x87\xa1\xac\x6c\x7b\x1f\xd3\x4f\xaa\x5e\xb7\x55\x6f\x19\x7a\x3c\x1e\xea\x1a\x03\x4d\x06\x08\xdb\x1c\x94\x1a\x84\x0f\x2d\xb3\x4b\x28\xc6\xca\xa7\xe5\xe0\x64\xd6\x0e\x20\x6d\x8a\x73\xb1\x5a\xf5\x95\x83\xb4\xfc\xc7\xde\x57\x5c\xe6\xf8\xa2\x5d\x5c\x4b\xd5\x84\x1b\x0c\xfd\x6d\xdb\x76\x02\x52\x85\x81\x02\x49\x0d\x8e\xfd\x4a\xb1\xc3\xd2\x5c\x8b\x44\xaa\xba\xb9\x10\x95\x0a\x09\x8f\xb0\x4f\x03\xcf\xb5\xf5\xc4\x8b\xbf\xb8\x49\xcb\xd0\x77\xe4\x5c\x86\xe8\xc1\x4d\x5a\x5a\xa5\xaa\x60\x41\x00\x90\x1e\xd9\xb0\x1c\x8d\x88\x60\xc2\xd9\xcf\x19\x46\x22\x64\x33\x0d\x31\x55\x14\x6f\x41\xfc\xf3\x4f\x74\xbd\xd0\xc4\xb0\x74\xc1\x88\xd8\x9a\x47\x02\x75\x91\x51\x93\x52\x82\x10\x12\x58\x29\x34\xb5\x6b\x17\xf6\xd0\xe7\xd9\x01\xfa\x19\x47\xe4\xdc\x1b\x80\xa2\xff\x8f\xc7\xff\xf9\x8f\xff\xbc\x64\x4c\x0d\x87\x63\xf3\xfb\x7f\xe3\x6f\xc8\x08\x0a\xda\x90\x26\xd1\x45\x4a\xe1\x67\x7a\x53\x45\x6a\x5c\x91\xe3\x31\x05\x2d\xba\x22\x75\x90\xb7\x87\x77\xf3\xf6\x28\x5c\x42\xb0\xf5\x7c\xa5\x4a\x4c\x3a\x02\xa2\x5f\x5a\x5a\x89\xb1\xf7\x05\x70\xa9\x7f\x30\x35\xf5\xc3\x1d\x7d\x10\xd9\xef\x86\x51\x2d\x1b\x49\x5e\x1a\xa4\xbd\x53\xdd\x2c\x39\x17\xbf\x46\x9a\x3c\x10\x42\x45\xc4\xf6\x0a\xcc\x27\x0b\x30\xaf\x09\x4a\xae\x17\x98\x4c\xb0\x49\xa6\x84\x46\xb0\x5e\x7b\xad\x5c\x32\xb7\x82\xa5\x34\x63\xe5\xd4\xaf\x54\x4a\x9e\xfa\xdf\x90\x98\xb4\x60\x25\xcd\x58\x6a\x78\x51\x01\x51\x2f\x16\x2c\x33\xd4\x62\xb6\xad\xb4\xba\x63\x87\x9b\xb4\x84\xf8\xb2\x49\x41\x6f\xd2\xf2\x2b\x99\x27\x59\x3b\x0b\x06\xbd\x13\x32\x49\xe9\x5d\xba\x4b\x4a\x97\x42\x82\x4a\x92\x34\xfd\xf9\x81\x83\x69\x4b\x90\xb9\x4f\xf5\x05\x24\x11\x60\x3c\x01\x56\x4c\xf7\xf8\x33\x65\xca\xac\xb4\x4d\xa1\x6e\x26\xaa\x18\x63\x15\x2d\x9c\x1a\xc4\xe6\xf7\x47\x6f\x44\x26\xe8\x9a\x89\x91\x9c\x66\x4f\xd6\x90\x1f\x63\xcb\xf8\x3c\x5b\xd0\xdc\x30\x65\xf9\x5c\xd9\x41\x95\x90\xb6\x1c\xc6\x95\x9a\xd7\x19\xa1\x85\x4d\x63\xae\xa2\x2d\xcd\x69\x45\xb3\x26\x45\x6b\x10\x46\xa5\x50\x61\xd4\xb7\xe1\xd0\x1b\x63\xea\xd8\xad\x55\xb7\xf0\x2b\x99\xd7\x08\x11\x33\x75\xc1\x77\x9a\xcb\xbc\xba\x78\x23\x0e\xf0\xf9\xb7\xa5\xb8\x13\x06\x37\xbf\x04\x4b\xfc\xce\x29\x09\xd6\x08\x61\x22\x84\x60\xfe\xbc\xaa\x08\x9a\xda\x75\xad\x6b\x27\xc6\x38\x56\x14\x57\x38\xa9\x6a\xa6\xe9\x01\x44\xc6\xdf\xf1\x7d\x92\xb2\xc1\x6e\x80\x42\x63\xf3\x58\xb2\xc1\x7e\x50\x5b\xd9\xb6\xe9\x59\xc8\x5b\x5a\x58\xdb\x59\x5c\xcf\x59\x9a\x98\x63\x84\x45\x55\x53\xb4\x76\x5b\x94\xd3\x2d\x5d\xd1\x5b\xd8\xa2\x9c\x09\xba\x85\x2d\xca\x9f\x6c\xa7\xa3\x51\x4e\x6e\x19\x9f\xe7\x0b\xba\x32\x5b\xb4\xf2\x5b\xe4\xf6\xa7\xd0\xd1\x2d\x2d\x08\xcd\x09\x5d\xfb\x2d\x82\xc2\x8c\xd0\x15\xad\x68\xde\x6c\xd4\xda\x32\xd0\x10\xd8\x16\x42\xe9\x02\x18\x6b\x16\xa0\xda\x6e\xbc\x7d\xd9\xb9\x4a\x81\x0b\xa6\xbd\x07\x53\x35\x1c\x72\x43\x14\x06\x2e\xd2\x06\xec\xda\x23\x0a\xec\xff\x9d\x90\xcd\x8d\x84\x47\xc3\x24\x08\x08\x69\x7a\x97\xee\x9a\xdb\x09\x8f\xe6\x5d\xba\x43\x3d\x06\x9c\xec\x67\x12\x81\xed\xa9\xf6\xaf\xf9\x1c\x75\x3b\x27\xdc\xce\xc9\x9a\x71\xda\xb0\xf8\x6e\x22\x28\x6f\xb5\x83\xa7\x29\x2b\x54\x54\x91\xd9\x60\x3e\x18\x55\xa8\x2a\x1e\x0d\xe8\x85\x79\xe0\x32\x1f\x0d\x16\x83\x64\x30\x18\x49\x7f\xc9\x3c\x5c\x54\x73\x89\x7b\xe1\x79\x51\x30\xec\x33\xb5\x45\x5f\x6d\x7b\xe3\x89\xa5\x13\xd2\xba\x3e\xef\xfa\xa1\x63\x2e\x0d\x59\x61\x2d\xb9\xd6\x69\x29\xe4\x2d\xbb\x9c\x50\xdc\xa1\xb0\xa1\x47\x3d\xb8\xe6\x60\x39\xda\x32\x72\x8c\x08\x16\x9e\x31\x33\x0b\x5b\x4f\x03\x67\x24\x2f\x00\x40\xce\x92\x4e\xa8\xb5\x8a\xb0\x36\xb9\xda\x89\x63\x1a\x49\x41\x5f\x14\x3a\x5a\x31\x6b\xe7\x06\x56\xe5\x10\x05\xee\xe4\x88\x40\x44\xd9\x5b\xae\x9f\xa7\x15\x3a\x1a\xd8\xd0\xb2\xa2\xfa\x46\x95\xe2\x4f\x25\x75\x6a\x8a\x32\xd3\xf8\x96\xeb\x77\xdb\x02\x18\xa7\x35\x53\xb1\x15\x95\xbf\x6c\xcc\x0e\x9d\x6f\x38\x04\xcc\xcd\x11\x3c\xbe\x07\xe7\x69\xf7\x66\x4d\xe8\x16\xac\x3b\x81\x55\x74\xa5\x92\xe6\x64\xaa\xec\xec\xdb\x0d\x72\x2a\xe9\x3a\xcc\x1a\xc9\xa7\xeb\x27\x7c\x24\xa6\xeb\xc6\x92\x95\xe3\x97\xec\x21\x5b\x13\x2a\x58\x75\x3c\x7e\x11\xf1\x79\x6a\x37\x7e\x76\xb8\x49\x2b\x6e\x2e\x3e\x4f\xf3\xa4\xac\x13\x58\x8a\x22\xdb\x16\xa9\xe6\xcf\x53\x3c\x28\x30\x7d\x18\xe2\x8a\x75\xde\x03\x67\xef\xde\x9b\xeb\x7d\xcb\x22\xee\x6c\x85\x31\xec\x93\xfd\x14\xdd\xb0\xc3\xda\xaf\x1c\x60\xa7\x8a\x27\x22\x36\xff\x28\x9e\xac\xe7\xa0\xde\x7a\x97\xe6\x62\x5b\x25\x97\xb7\xc7\x63\xa1\x22\x8f\xd9\x08\x04\x91\xb4\xf2\x83\xdb\x78\xa9\xd5\xa6\x53\x82\xaa\x4b\xba\x4b\x8a\x99\x88\x61\x3e\xab\x18\xd3\xb2\xd1\x7d\x52\xcc\xdc\x43\x82\x2f\x9d\x79\x84\x79\x01\x7e\x1a\x96\x86\x40\xe5\x6a\x31\xb3\x09\xb7\xf1\x65\x3d\xdd\x0e\x87\xd1\xc6\x53\xc2\xb9\xf9\xf4\xf9\x6d\x5e\x43\x58\x07\xd5\x3e\xb5\x91\x9e\xaf\x17\x74\x4d\x37\x06\xde\xa3\x25\x01\xac\x52\x9f\x65\x7b\x70\x1a\x2d\x7a\xb5\x16\x36\xbd\x94\xa4\xf6\xa1\x24\x21\x42\x50\xe3\x13\x62\xfa\xe7\x39\x78\xee\xd8\x1b\x92\x3a\xf8\x5e\xd2\x02\xce\x0e\xe6\xdd\xac\x7c\xde\xcd\x82\xc9\x79\xb9\xa0\x4d\xfa\x87\x06\x0a\x14\x1d\x9a\xd3\x1e\x2b\x4e\xe6\xad\x37\xa7\x57\x09\x0f\xc0\x54\xac\xa2\x2f\x22\x4d\x8e\x47\x4c\x46\xab\x89\xcf\xe6\x59\x8b\x55\x84\x12\x11\x65\x7d\xcb\x1b\x0b\xc0\x02\xa7\x41\x8e\x47\x1f\xc9\x52\x39\x7c\xcd\x18\x73\xaf\x87\xc3\x14\x51\x8d\x2b\xa0\x85\x3f\x1b\x9a\xdc\x94\x3c\xfd\xbd\xf6\x3e\x56\xb8\x18\xc7\xa3\x6d\x62\x15\x4d\x34\x6d\x76\x05\x95\x9a\x3d\xd1\x62\xed\x9e\x39\xf7\x49\xaf\xcb\xf4\xef\xbc\xb7\x42\x57\xa6\x16\xb7\xb6\x5c\x98\xad\x6a\x56\x79\xd6\xf8\xd2\x73\x92\x8c\xaf\x2d\xf8\x86\xb5\x50\x33\xe9\x8d\x21\x12\x55\x07\xd0\xe6\x21\x0f\x9e\x16\x08\x86\xe3\x63\x4f\x52\x93\x75\x87\xa6\x96\x7c\x9e\x80\xd7\x56\x00\x49\xa7\xd5\x93\x74\x3a\x1a\x55\xc4\x46\xf0\x93\xa7\x24\xb8\x0e\x8e\x40\x45\x1c\xde\xa1\x15\x09\xfd\x9f\x6f\xd2\xf2\xc3\x5a\x64\xbf\x4b\x5e\x39\xdf\xc1\x83\x21\x3f\xcb\xe3\xb1\x52\x91\x24\x74\x03\xa0\x23\x51\x96\x44\x95\x00\x3a\x4a\xfc\x14\x90\xab\x32\x5e\x72\x99\xe3\x73\xe5\x77\x26\xd1\x71\x67\xab\x08\xad\x10\xbf\xd2\xdb\x52\x6d\x37\x3c\x4f\x78\x6c\x7f\x51\x10\x2f\x24\xe5\xec\x3a\xe1\xb1\xf3\xf1\x69\x32\xae\x3c\x82\x61\x36\xcf\x75\x7d\x16\x02\x76\xe5\xce\x87\x7b\x87\xdd\x97\xf6\xae\x01\x7e\x6f\x53\x00\x00\xe7\x94\xa1\xba\x9f\xa7\xe5\x1b\x58\x5e\x43\xbf\xf1\xc0\x85\x88\xb7\x49\x01\x83\x79\x1c\x29\x50\x18\x52\xa0\x24\x0d\xd9\xcc\x52\x87\xb5\xe9\x96\x4d\xe8\x8a\xc9\x19\x8f\xd3\xcd\xa6\xd8\xc3\x6a\x40\x52\x27\x49\x92\x7c\xba\xba\x64\x0c\xf2\xc9\xb1\xd5\x38\xa7\x2b\x96\x43\xf2\xe0\x28\x67\xa5\xa7\x7b\xe9\x0a\x1f\xbe\x92\xf9\x38\x28\x9d\x60\xcb\x97\x3a\xca\x6d\xe6\x63\x57\x8b\x40\x7f\x13\x42\xb7\x23\x96\xbb\x8d\xbe\x65\x5f\x44\x8a\x1c\x8f\xc5\x6c\x9b\x28\x4c\x43\x87\xc0\xaa\x7d\x60\x6e\xc9\x34\x63\x8d\xef\x75\xdc\xa7\xe6\x9e\xf5\xb4\xdb\x8e\x56\x24\xd9\xd0\x35\xcb\xc6\x9b\x06\x2e\x55\xc3\xa1\xe7\x8b\xd6\xe4\x49\x65\x58\x4e\xb6\x7e\x32\x99\x8d\xab\xa4\xa2\x06\x32\x98\xb9\x6f\xc6\x6c\x7d\xf5\xd8\x60\xe9\xcd\x68\xed\xc6\x7b\x67\xa0\xcc\xc4\x80\xa3\x0d\xc8\x60\x4f\xbe\x78\x47\x9a\x9b\x85\x64\x93\x90\x1c\x92\x16\x05\x55\xae\x1e\x4f\xd7\x4f\x27\xb3\x68\x33\x62\x9a\xae\xc7\x4c\x93\x64\xfd\x64\x82\xdf\xd4\x74\x3d\x62\xda\xb1\xd6\xe8\x06\xb8\x46\x84\xb7\x41\x4c\x9b\x51\x8b\x8c\xb2\xd1\xfa\xea\x71\xe7\xc8\x85\x48\xb5\x57\x81\x8f\xa6\xf2\x21\xac\x07\xf2\xb1\xfa\x5d\x6c\x7e\xd8\x16\x05\x4d\xd9\x77\x11\x90\xab\xcf\x83\xfb\x47\xaf\xaf\x26\xc4\xc3\x7e\x48\x3d\x6c\x2f\x48\x03\xa7\xaa\x99\xe8\x5e\x2c\x4d\x12\x1e\x37\x37\xcf\x30\x0d\xab\x82\x5b\x56\x22\xbc\xdf\x0f\xf0\xc6\x3c\xc6\x7b\x6e\x46\x39\xd7\x36\xa5\x1a\xd3\x4f\x27\x33\x35\xd7\xe3\xeb\x05\xda\xb9\x94\x4c\x3f\x51\x1e\xd2\x99\x57\x23\xfb\xca\x47\x3a\x10\x3d\xf7\x77\x6a\xf3\x0b\xa6\x20\x6d\xa8\xc6\x91\x7d\x2e\x67\xdc\x50\xcb\x63\x8e\x04\x74\x52\x8e\x2b\xe2\x93\x11\x96\x20\x68\xae\x46\xd5\x38\x75\xc7\x22\x33\x6d\xab\x71\x20\xae\x28\x09\xb9\x7a\xfc\xc8\x49\xab\x0f\xd9\x7a\x2b\x7f\x4f\x1a\x6e\x7c\x9c\xc2\xeb\x2b\x69\x01\x8c\x68\xc3\x11\x0b\xd2\x32\xcb\x63\x2b\x73\x2b\x1f\x12\x1f\xb4\x60\xa5\x83\xcf\xde\x69\x5c\x91\x59\x54\x31\x6e\x46\xf6\xa8\x6f\x15\x00\x82\xb7\xbe\x4f\x92\xa8\x62\xea\x91\xa1\x6f\xaf\x09\xb5\xa3\xaf\xfc\x68\x53\x3b\x3e\xb7\x37\x73\xbd\x18\x57\xe6\x28\xfa\xd1\x1a\xb6\x3c\xee\xe0\x35\x1b\x2b\x91\x8a\x53\x02\x9f\x56\x33\xed\x6c\x36\xa6\x25\xcb\x2c\xdf\x92\xc5\xf0\xe5\x47\x6b\xf7\xeb\xea\x31\x2d\x42\xa1\x90\xab\x90\xc5\x30\x30\xab\xda\x2d\x59\x0f\xce\x11\x2d\x38\xe9\x71\x8e\x26\xed\x1e\x71\x95\xb8\xed\x6f\xea\x33\xa5\x19\x42\x77\x5c\x5c\x3d\xb6\xc4\xee\xc8\xfc\xb4\xd7\xb0\xa4\x70\x49\x8b\xba\xcf\xea\x96\x86\x21\x60\x4e\xf1\xea\xfd\x7b\x7f\x1b\x81\x2b\x31\x57\x31\x48\x22\x9a\x5a\x8f\x89\xdf\x74\x84\x76\x86\x3c\x34\x37\x4d\x81\xf6\x4a\x89\x0d\x9a\x1b\x62\xd5\xd4\xcf\x70\x38\x54\xf3\xd4\x1b\xab\x2e\x33\xbd\x23\xd3\x9f\x6c\x77\xa4\xae\x33\x15\x8b\x9c\x81\x3c\x91\x66\xca\xe7\xe9\x60\x7d\xae\x54\x97\xd7\x27\xbe\x54\xb6\xe1\xc9\x91\x4a\xe2\xff\x4d\x5b\x27\x2a\x89\xff\x8f\xc7\xae\x97\x13\xda\xf8\xe0\x24\x07\x09\x79\x77\xaa\xc4\x26\xd7\xc3\xc7\x01\xdd\x78\xa1\x7d\x32\x1f\xec\x06\x74\xb0\x1f\xd0\x81\xd9\x88\x01\x1d\x00\xa1\x3d\xa0\x03\xa4\xc3\x07\x8b\xba\xae\xcd\xe8\xbd\x4c\x9e\x1d\x84\xd9\x9a\x14\x13\xd6\xa0\x35\xba\xf5\x7b\xac\xa9\x0b\x81\x6a\x83\x69\xba\xef\x7a\x0f\x5e\x8a\x16\x14\x66\x98\xb7\xa5\xc8\x93\x83\x7f\xae\x6b\x6a\xe3\x6d\xba\x46\xd6\xd7\x97\xde\xf0\x5b\x21\x9f\xe9\xdf\x78\xa9\xa0\x9e\x8b\x4d\xb3\x6e\xc9\x80\x5a\x0c\x32\xa0\xb3\xcf\x66\x8b\x3f\x25\xe5\xd9\x39\x29\xcf\xfe\xbf\x21\xe5\x09\x45\x6c\x67\x04\x6c\x39\x0a\xd8\xac\xf8\xec\xb0\x4b\x02\xc1\x4d\x0e\x6a\x35\x42\xf7\x49\x15\x16\x96\x50\x68\x89\x92\x24\x1f\x0e\xf3\xb8\x1c\x0e\x47\x79\x5c\xd6\x81\xcc\xad\xeb\x81\x63\xe7\x05\xb6\x90\xda\x1b\x28\xf2\xfa\x94\xc9\x47\x29\xf8\x24\x10\x82\x37\x69\xeb\xa7\x12\x0d\x80\x25\x11\x61\x8e\x64\x3d\x97\x8b\x18\x6d\x4f\xae\x1e\x83\x65\x8e\x67\x15\x7d\xa0\xcf\xa7\x93\xe1\x50\xfc\x45\x89\xcd\xce\xd1\x74\xfb\x4f\x4a\x6c\xfa\x64\x2a\xf1\x8e\xd0\x94\xf5\xc9\x66\xe2\x3d\x88\x42\xbd\x88\xaa\x25\xa1\xe1\xd6\x05\xd3\xaa\x8d\xa2\xc1\xa8\x42\x91\x4f\x3a\x8a\xca\x19\xfc\x2a\x93\xc1\x80\x8c\x06\x64\x50\x7f\x86\xd8\x04\x40\x51\xbf\xec\x04\xc4\x26\xff\x2d\x89\x89\x5b\xa3\xd4\xad\x51\x59\xb7\x65\x28\xb4\xf8\xb4\x18\x24\xeb\x13\x83\x14\x28\x41\x39\x11\x83\x64\x8d\xa4\xa3\x60\x7c\x5a\x80\xa4\xa3\x08\x25\x1d\x7a\x5e\x2c\xa8\x60\x97\xd5\x70\x18\x0a\x3d\x0a\xf3\xa1\x6a\x96\x86\xc8\xe4\x25\xa6\xf2\x8a\xe2\xbf\x93\x24\xed\xc3\x32\x3b\x42\x73\x56\xcd\xca\x8e\xfc\x27\x29\xfb\x2a\xef\x09\xdd\xb2\xc3\x2e\xc9\xe8\x3e\xc9\xa9\xa1\xc1\x12\x64\x70\x33\xcf\xea\xe6\xa4\x9e\xae\x0d\xe9\xec\x05\x08\x0f\xad\x4e\x01\xea\x83\xb0\x7a\x5c\x82\x4c\x84\x4d\x7a\xa4\x0a\x9c\x16\x74\x4b\x25\xa9\xfb\xc5\x44\x19\x95\xb4\x20\xf5\xf9\xaf\x9d\x4a\x1f\xc2\x43\x6e\x2d\xa2\x11\x96\x3d\xdc\xc9\x54\xba\x80\x80\x90\xf9\xaa\xad\x67\x3b\xd4\x54\xd2\x26\x06\xe2\x75\x1d\xc4\xcd\x93\x76\x7a\xce\x2a\x00\x35\xdf\x36\x23\x7d\x24\x9b\xc9\x53\xf7\x7b\xc4\xbe\xc3\xa0\x6b\x8e\x4b\x52\x84\xca\xba\x5e\x5b\x3c\xb8\xbd\xb9\x29\xf8\x80\xae\xff\x0b\xa8\x10\xb2\xf0\x0d\xfe\xeb\xa8\xad\xc9\x6f\x3a\xa0\x03\x1c\x2c\x62\xb6\x75\x0b\xb3\x39\xf4\xb5\xeb\xe0\xa0\x9a\xee\xbb\x25\x35\xb5\xea\xfa\xe4\xa0\x95\x2a\xb4\xd8\x24\x07\x97\x13\xd4\x94\x09\x5d\xf0\x24\x22\xec\xe9\x60\x50\x37\x38\x6b\xdb\xc2\x59\x27\xa1\xd5\x60\x3f\xd1\x3e\xf7\x21\x14\x86\x41\xc0\xa4\x74\x42\xb9\x56\xd4\x28\xb5\xd5\xfd\xe5\xd6\x8e\xf1\xb4\xec\x57\x5b\x56\x87\x1e\xe1\x87\x50\x75\x7c\xc6\xa0\x3b\xc2\xb0\xc8\x2e\xfd\x55\x17\x71\x28\x8a\xbe\x42\x8a\x69\x5a\x31\x3d\xe2\x53\x05\x54\x95\x22\x32\xb6\x48\x67\xae\x16\x6c\x24\xe6\x6a\x81\xe2\x13\xa5\x61\x77\x1b\x1d\xf6\x57\xba\x1d\x4e\xa5\xb4\x35\xc6\xff\x67\x82\x5e\x3f\x2f\x44\x99\x6d\xef\x6c\x2c\x81\xf3\xed\xb2\xb0\x1a\x69\x7d\xec\x2b\xb3\x1f\xa0\x37\x47\xdf\xd9\xa5\xa6\x9c\x8d\x97\xba\x65\xc5\x1e\x6a\x81\xa7\xf2\x89\xb5\x52\xe9\xb7\xe9\x1d\x8d\x24\x84\xef\xb4\x75\x7a\x1c\xf6\x43\xf6\xa2\xc5\x74\x37\x86\xbf\x81\xe4\x0e\x55\x10\xad\xe5\x41\xd9\xf8\xe9\xf4\xa7\x3a\xd0\x9c\xd0\x8a\x50\xde\x60\x66\x4e\xab\x51\xea\x79\x5f\xb7\x90\x89\xa6\xad\xb5\x49\xf8\x58\x9f\xc3\x63\xd4\x3b\x98\xd2\x83\x27\x95\x0d\x1e\x16\xa0\x18\x0f\xf1\x8c\x19\x32\x1c\x0d\x2b\x44\xf9\x3e\xdd\x3d\x6f\xee\x60\x44\x46\xae\x14\x13\x16\x47\x6d\xb5\x6b\xe4\x27\x21\xad\xb3\xab\xb4\x2e\x31\x04\x98\x3b\x3a\x69\x71\x16\x5f\x07\x06\x5f\xd9\x56\xab\x2d\xc4\x64\x87\x94\xcf\x1c\x17\x4e\xc8\xdb\x7f\x41\xfb\xc8\x59\xb8\xd1\x43\x7b\xda\x6b\xea\x57\x24\xaf\x5d\xb3\xee\x09\xa1\x07\x60\x5d\x7e\x49\xb6\xc8\xac\xfd\x9a\xac\x2c\x41\xfb\x4b\x72\x6b\x7f\xfd\x9a\x6c\xba\xa9\xda\x5d\xcc\xd2\x6b\xaa\xd8\x35\x45\xb1\x1e\xc8\x39\xf8\x93\xa5\x6e\x92\x76\x68\x5a\xb0\x72\xc4\x69\x86\x33\xcb\x54\x15\x95\x06\xed\xc2\x53\x25\x24\x44\x44\x68\xde\x15\x06\xbf\xf9\x77\x05\xa1\x2b\x0c\x8a\x2e\x09\x7b\xfa\x8d\x8e\x34\x2d\x69\x41\x66\xd7\x49\x70\x00\xf8\x23\x41\x25\x95\x8f\x04\xe8\x03\x4e\x2b\x8f\x5d\x6d\x21\xdb\xb5\x37\x6c\x15\x4d\x68\x46\x73\x42\xef\xd8\x2a\xfa\x5e\xd3\x35\xdd\x12\xba\x63\xb7\xd1\x8d\xc6\xf2\x1b\xf8\x3d\xb2\xaf\xa6\x92\x45\x9b\xf1\xce\xec\x95\x62\xd1\xdd\xf8\xc6\xfc\xaa\xd8\x38\xda\x8c\xa0\x30\x65\xe3\xe8\x6e\x64\x4a\xfd\x81\xc4\x95\x95\x6e\x65\x95\x5f\xd9\xca\xaf\x6c\x5a\xd7\x51\x4e\xd7\xb4\x30\xc3\x70\x67\xc3\x9c\x88\x2d\xdd\x99\x67\x3c\x23\xa6\x60\x45\x6f\x9a\xe3\xe4\xe7\x74\x47\x77\xf6\xf4\x2c\xd9\x6f\xc1\xa1\x41\x84\x40\x6f\x08\xdd\xb3\x68\x39\xf6\x0d\x97\x8f\x0a\x3a\x21\xe4\x0a\x8f\x43\xdb\x67\x06\xcf\xd3\x07\x05\xea\xa6\x69\x63\x26\x7e\xfb\x68\x19\x18\x8a\x6f\x1e\x2d\xa9\x8a\xb5\xa9\x65\xee\x8e\x13\x26\xd9\x66\xa6\x62\x00\xad\x97\xe3\xfd\xa3\xee\x81\xb5\xf7\xc3\x1f\x5b\xde\x82\xfb\xcd\xde\x86\x1d\x8d\xf7\x8f\x32\x0a\xde\xe9\x1d\x1a\xb2\xa2\x13\x5a\x05\x24\xe6\xb2\x75\x07\x3e\x23\xe4\x58\xfb\x8a\x8b\x5e\x10\xd4\xe4\x04\x97\x4d\xc0\x07\xfb\x8b\xc3\x95\xe2\xc7\xe3\xe5\x27\x04\x8e\xc7\xa3\x15\x08\x29\x8f\x29\xf4\x62\x36\x49\x44\xb3\x86\xed\xef\x86\xf5\x1e\x55\x57\xcb\xff\xba\xca\x11\x01\x5c\xc9\xd2\xc0\x12\xb1\x60\xe9\x69\x10\x0b\x9a\xb1\xa8\x04\x77\xc0\x51\x89\xbe\x7e\xe6\x70\xad\x4d\xa1\x56\x9b\x51\x69\x7d\xf6\x4c\x61\xce\xaa\xe1\xb0\x70\x8b\x80\x72\x89\x2d\xcb\xc1\x19\x30\xd8\x4f\xba\xb2\x65\xc1\x66\xd2\xdb\x4f\x93\xec\x9b\x3e\x92\xfd\xd6\x5c\x92\x1e\x92\x7d\x83\xc4\xe3\x8e\xde\x9c\xa0\x15\xc0\x72\x3b\x36\x99\xee\x9e\xf0\xe9\x68\xb4\x23\x37\x23\xe0\x1f\x5a\x2b\xbd\xa3\x95\xab\xc8\x4d\xc5\x91\x80\xaa\x81\x6a\xb3\xa7\x01\x15\x4c\xcf\x77\x0b\x9a\x02\x45\x3e\x52\xee\xba\xd0\x7d\xb2\xf6\x4f\xbf\xa2\xe8\x0b\x53\xef\xde\x50\x2e\x73\xfb\xd3\x00\xc5\x36\x92\xa2\xc1\x0a\x25\x2b\x1a\xac\x61\xb2\xad\xa7\x77\xe0\xee\xed\x68\xfa\xcd\xc3\x4a\xc1\x1d\x28\x05\x6f\x46\x86\x9b\xec\xd0\xf0\x82\xee\x40\x6a\x7f\x86\x86\xdf\x50\x49\x6f\x49\xdd\xbd\xd5\xdd\x54\x06\xcd\x95\xe1\xd6\x0d\xc8\x99\x70\x59\xc6\xda\x30\xd8\xa2\xc9\xe4\x24\x5a\xec\x93\x3f\xd7\x62\xe1\x32\x38\xf1\xe1\xf0\x12\x19\x18\x4e\xac\x67\xc0\xb9\x9b\x84\xe9\x6e\x47\xcc\x4b\x47\x79\x63\x59\x26\xeb\x33\x77\xe9\x21\xf6\x15\x40\x99\xbf\xe3\x86\x8f\xbf\x74\x6a\xc3\xd9\x52\x3f\x8a\xfc\x87\x34\xb9\xe2\x24\x99\xfc\x35\x26\x5f\x04\x8a\x08\x00\x3e\xa8\xfb\x6a\x12\x8d\x28\xf6\xa7\x88\x78\x70\xd7\x69\xa3\x56\x2d\x94\xb9\x57\x1d\x0b\x0b\x09\xe9\x8d\x06\x03\xcb\xc0\xab\xba\x3e\xa5\x46\xba\xc1\x3a\x9c\xd0\xa3\x71\x44\x80\x01\x85\x1e\x22\x28\xa9\xbf\xd4\x04\xe9\xdb\x09\xe8\x72\x7b\xa9\x41\x47\xf2\x8a\x55\x24\x4f\x69\x41\x45\xc8\xc1\x4a\x25\x42\xfa\x4f\x11\xaa\x19\xf6\x85\x90\xa8\x21\x06\x4b\x3c\x00\x65\x18\x7c\x63\x8a\x7a\x54\x1c\x91\xdd\x9b\xc9\xb4\x19\x9a\xee\x0e\xa6\x60\x0f\x71\x8d\x8a\xd0\x01\xdc\x28\xc3\xeb\x15\x31\x32\x4f\xcf\x0a\x71\x2b\x21\x0b\x4e\x20\xeb\x71\x2f\x61\x1d\x8f\xc7\x09\x2d\xd0\xc2\xfc\x79\xab\xb4\xb1\x2a\x12\x75\x8b\xea\xd3\xd6\xf5\x29\x10\x30\x39\x53\xc1\x8e\x91\x60\xfb\x4e\x9d\x1f\xba\x20\xd3\x16\xc9\xab\x2d\x68\xc1\x98\x5b\x30\xb6\x1f\x5d\x41\x90\x95\xad\x17\xe7\xf6\x8f\x6e\x2a\x9e\x68\x18\x52\x70\xef\x4e\xb6\x15\xdd\x46\x46\x8d\x7e\x39\xa0\x40\x45\xb3\x1c\xdd\x0f\x07\x8a\x6d\x3f\x87\xef\xa2\xe0\x43\x5d\xc7\xdb\xf8\x23\x86\x80\xb9\x26\xd4\xf2\x43\x0f\x10\x29\x27\x3a\xf3\xd3\x19\x9f\xf9\x92\xdd\x0d\x72\x3c\x5e\xd7\xf5\x16\xb9\xf9\x5c\x6d\x6f\xd7\x72\xab\x07\x74\xfb\x5f\xe0\xe7\xd3\x32\x0b\xb8\xf9\xe4\xd0\x22\x0f\x1a\x21\x36\xa2\xca\xe4\xf2\xba\xfe\xcb\xac\x7f\x0b\x61\x0c\xe8\xc0\x61\x93\x81\x3d\xdb\x88\x2d\x06\x74\x10\x60\x92\x81\xcd\x10\xe7\x2a\x3a\xf1\x01\x9e\xa1\x8e\x1c\x61\x51\xd7\x14\xb9\x8d\x64\xf0\xf7\xc9\x97\x83\x86\x81\x98\x74\xb0\xd5\x7f\xfc\x63\x42\x91\xc4\x4c\x06\xd7\x13\x53\xd5\x07\x8b\x4b\x06\xe5\xa0\x36\x2b\x18\x08\x21\xc2\x20\xc5\xd7\x8d\x94\xa1\xe0\xb7\x5c\xe6\x09\xc2\xb5\x2a\x39\xdc\x72\xc9\x4b\x88\x38\x74\xd3\xd5\x81\x23\x92\xf1\x61\x76\xb1\x85\x77\xc7\xe1\xdd\x7d\x9d\xf9\x2a\xe8\x9c\xd5\x49\xac\x74\xc2\x9d\x4e\x48\xc7\xe2\x04\x7d\x79\x7c\x3a\x87\x83\xe6\x3b\x9d\x70\xba\x12\x45\x01\xaf\x12\x19\xdf\xa4\xd9\xef\xb7\xa5\xda\xca\xfc\x85\x2a\x54\x49\x2b\x5d\xaa\xdf\xb9\x7f\x0b\xeb\x8a\x6f\x0a\xa7\xd4\xf5\xe5\xf0\xe4\x9c\xf2\x2f\xfb\xd1\x5c\xe8\xbb\x95\xcc\xcd\xde\x58\x9f\x0e\xc7\x82\x39\x4e\xbb\xd7\x19\xdc\x93\xd7\xae\x96\x0b\xa1\x56\xd7\xf4\x53\xd2\x1d\x0a\x6b\xd7\x40\x0b\x9b\x18\xcb\x8b\x0f\x06\x90\x08\x3e\x46\x8b\x7b\x6d\x2d\xc1\xa7\xde\x78\x99\x93\x59\xc4\x7d\x4c\x49\x42\xf9\x7c\xb2\x18\x31\x41\x12\x3e\x62\x82\xf2\x3a\x10\x1e\xad\xfe\xc7\x14\x1e\x0f\xf0\xf7\x2d\x99\xbb\x8f\x92\x80\x71\x13\x20\x21\xa5\x0f\x6d\x50\x19\xc6\x3f\x75\xd8\xb1\x2f\xf6\x9b\xc1\x98\x07\xab\xe5\xa5\x19\x18\x88\x14\xa7\xcc\x71\x83\x66\x03\xed\x9c\xc5\xa9\x36\xa4\x0e\xc6\x4d\xe8\x58\xdd\xa6\x5e\x73\x51\xd6\xc0\x3c\xa3\x45\x1b\x05\x4b\x96\x0c\x0c\xa9\xd7\xf4\x4e\xc8\x97\x18\x43\x3a\xc9\x4d\x91\x7b\xd8\xd6\x68\x46\xf8\x73\x65\xb0\xd5\x56\xe6\x55\x44\xa6\x39\xb8\x79\xfe\xa0\x1b\x8e\x51\xf1\xa8\xa4\xb6\xdf\x8c\xc4\x85\xa2\x62\x26\x13\xc5\x41\xbc\xdb\x23\xa3\xce\x88\xa9\x44\xe8\x84\xca\xf1\x35\x98\xd1\x6f\x67\xbe\xbf\x74\xd7\xea\x6f\x4d\xe2\xb5\x18\x5d\x53\x61\x48\xfe\xb3\x3d\xae\x09\x56\x23\xa0\xfb\x1d\xab\x44\x8e\x95\x37\x60\xb0\xf6\xe3\xb8\xb4\x55\x5d\x47\x82\x2a\x9a\x1a\x76\x14\x22\xab\x80\xd9\x08\x46\x57\x33\x8f\x60\x28\xc0\x0a\x7a\x92\x55\xd5\xe9\x0d\xb8\xd3\x1b\x08\xba\x04\x51\x28\x18\xf6\x56\x89\x34\xeb\xab\xd8\x61\x67\x56\x16\xb4\xb8\x74\x67\x96\x97\x83\x01\xee\xde\x94\x0a\x28\xdd\x9b\x52\x61\x4a\x6b\xeb\xaa\xd9\x78\x31\x04\x1d\x32\x45\x2f\x27\x4d\x76\xe0\xd8\xf4\x0b\x41\x5a\xee\x84\x3c\x1e\xcd\x73\xba\xc3\xe7\x74\x67\x9e\xf7\xf8\x5e\xb8\xf7\x7b\x7c\x0f\x1f\xea\xa4\xb8\x70\xfe\x23\x54\x11\x5a\xd5\x88\x88\x4b\x36\x01\xc5\x87\xc5\x62\x54\xc6\xcb\x1c\x54\x0d\x9a\xe7\xec\xf2\xb2\x0a\x1e\xa9\x8c\x41\xd4\x5c\x31\xe5\xcd\x0f\x78\x48\x6f\x34\xb8\xcd\xcb\xd8\x81\xfb\x77\xa6\x7d\x6b\xf5\xf1\x8d\x90\xfc\x78\x8c\xb2\x10\x7a\xb1\x09\xa1\x59\x5c\xf1\x5b\xd3\x32\x48\x57\x62\x4b\xba\xac\x7a\x24\x5d\x1a\x1c\x87\x19\xf3\xe4\x32\xf5\xee\x7c\x59\x4d\x75\x0f\x7b\xaf\x20\x67\xeb\xff\x94\xea\xa8\xb1\x9d\x2a\xba\x5a\xa4\xec\xd3\x2c\xe9\xba\x8f\x25\xcd\xd0\xc8\xf6\x84\x25\xb5\x46\xb6\x7e\x51\x36\xa9\xfc\x3a\xdd\x18\x76\xf8\x99\x8e\xb6\x64\xb6\x4d\x7e\x00\x14\x1f\xbf\xfd\xf1\xfd\xeb\x0f\xaf\xff\xf9\xd5\xf2\xf5\x0f\xaf\x5e\xff\xf0\xfa\xc3\xaf\xc0\x1d\x9f\x85\x3f\xc7\x63\x75\x3c\x0e\xa4\x92\x1c\xa3\x53\xa3\x0d\x14\x70\x2d\xa1\x2e\x8a\x8f\xaf\x1b\x35\x56\xc6\xf8\x34\xb3\x5c\x6d\x16\xaa\xb1\xb2\x05\x15\x2d\xbb\xdd\xcc\x8c\xfa\x76\xc6\x93\x43\x4d\xef\xd8\x17\xa8\x72\xda\xb1\x6d\xbc\x63\xfd\xda\x2b\x9a\x11\x7a\xc3\xb6\xf1\x9e\x55\xc7\xe3\xdd\x67\xa9\xb1\x8a\x99\x0a\x8d\xc8\x4a\x2a\x68\x41\x12\x11\xef\x69\x46\xa6\x5b\xb0\x2c\x62\xc8\x7f\xed\xbc\x56\xeb\x86\x1c\x8f\x77\x74\x0b\xb1\x28\x58\x06\xaa\xd6\x78\x37\xde\xc4\xbb\xa7\x2b\xba\x45\xe5\x71\xce\x04\xcd\x5b\x8a\xaf\xf5\xc3\x4c\x72\x06\x4c\xf2\xad\xa9\xd4\x55\x73\x65\xa0\xe6\xa2\x1b\x26\xce\xb0\xc9\x6b\x2a\x69\x46\xce\x28\xa1\x5b\x81\x00\x5a\x9c\xa0\x27\x53\x42\x29\x54\x98\x2a\xba\xc3\x7a\xb8\x50\x7c\x90\xb5\xd9\x00\x1f\x7f\xe1\x1d\xbf\xeb\x21\x8e\x9a\x4f\xac\x8a\x5a\x3f\x30\xe7\x09\x38\x41\xab\x79\x63\xfb\xf4\x19\x8d\x9a\xca\x0d\x99\xef\xa1\xbf\x04\xef\xd8\xab\xc7\xce\x98\xa5\x57\x39\x5c\x71\x47\x7e\xd8\x68\xa0\x6f\x01\x20\x85\xc4\x79\x63\xa7\x62\x91\x3b\xf6\x57\xd7\x2b\xa4\xcd\x0d\x09\x35\xa0\xab\x4f\xd0\xe5\xb6\xda\x39\x5d\x9b\x03\x65\x86\x1a\x77\x37\x12\x28\xf1\x55\xaf\xca\xec\x9c\xc5\xc7\x39\xb3\x8e\x86\xac\xb9\xfd\x6b\x3a\xb1\xcf\xd7\x7b\xfd\x35\x75\xfc\xd2\xf9\xb2\x98\x1e\x5c\x14\xde\x4f\xe8\xe8\x3b\x6d\xce\x5b\x27\xb5\x03\x73\xf2\x40\xa8\xea\x49\x0d\x81\x32\xc4\xb1\xc0\x08\x63\xc2\x8a\x0f\xc7\x22\xd6\x6a\x63\x28\x89\x86\x88\x40\x19\x76\xca\xbc\x59\x9c\x39\x5b\x56\xf5\xd1\x98\x05\xcd\xaa\xab\xeb\xc9\xe4\xd1\xe9\x8b\xe4\xda\x4a\xb5\xcf\xc5\x8c\x9a\xea\xd6\x6a\x56\xe3\xf4\x91\x0b\xae\xa6\x5b\xeb\xdf\xaa\x37\x4e\xff\xbb\x22\x57\xd5\xd2\x2d\x9e\x95\xba\xb6\xdd\x42\x4a\x14\xa7\xae\x59\x16\xef\x5e\xa0\x5b\x41\xce\xb2\x78\x6f\x7f\x6f\x59\x66\x7a\x05\x33\x39\xe0\xeb\xa2\x09\x19\xc7\x7f\x7f\x74\x83\xf2\x9c\x15\xbd\x65\x5b\x0b\x18\x36\xec\x3f\xfe\x31\xb9\x52\x31\x90\x53\x76\x61\x9a\x38\x18\x80\x21\x56\x6c\x32\x5d\x81\x70\x74\x45\x6e\x51\x38\xaa\xee\x36\x5b\xcd\xb1\xeb\x15\x05\xf9\x2a\xd6\xe4\xa6\xe6\x48\x4c\x57\x6d\x73\x88\xd5\xc2\x0a\x98\x6e\xe9\x1d\xbb\x1d\xf5\x77\x41\x2d\x0e\xe9\xf0\x29\x2b\x32\x83\xd9\xbc\x14\xe8\x43\xfd\xaa\x54\x77\x38\x4f\x8f\x31\x4a\x8c\x66\xb1\x5a\x90\x64\x32\xbd\x65\x77\x60\xb1\xd0\x16\x3d\x0f\x87\xd1\xce\x10\x24\x45\x5b\x2a\x0f\xc2\x9d\x3b\xb6\xf5\x26\x00\x37\xec\x60\x48\xe8\x7d\x92\xb7\x04\xac\x93\x96\xf0\x75\x17\x0a\x6d\x45\x23\xb4\xbd\x6b\x82\x0f\x3c\x00\x2e\x57\x54\x92\x7a\x7a\x8a\x54\x56\xf4\x06\xdc\x31\xfa\xb7\xa2\x2d\x0d\x6a\x9d\x99\x1e\x47\x25\x27\xb8\xf1\xfc\x30\xc8\x36\x4e\xa2\xe5\x3b\x39\x26\xae\x9f\x5c\x7c\x4a\xb4\x2a\xc9\x70\x28\x46\xa3\x1a\xf2\x12\xb7\xf7\xb0\x27\xf6\xc7\x03\xb6\xd5\x4e\x57\xfd\xb0\x19\x47\x9c\x9a\xae\x8f\x47\x41\x92\x49\x5d\xdf\x22\xa0\xdf\xa8\x22\x2d\x21\x52\x2f\xbd\xed\x42\xfb\xff\x8e\xbc\x65\xf2\xd7\xe5\x2d\x4e\x56\xd2\x12\xa0\x7c\x96\xd0\xc5\x30\xeb\x2d\x81\x48\x78\xa2\x26\xb5\x99\xd9\xff\x2f\x1d\xf9\xff\xb6\x74\x24\xc1\x34\x83\x8d\x64\x11\x57\x74\x8e\x4f\x00\x9b\x17\xa3\x5e\x41\x49\x5d\x37\x46\xa5\xa5\x3b\x80\xad\x0c\x31\x14\x2e\x86\x79\xa8\x92\x43\x2e\x2a\x08\x0c\x63\xe8\x94\xb6\xdd\xa8\xb5\x37\x05\xc9\x5c\x91\x96\x70\xc6\x81\xd2\x79\x63\x4f\x4c\xd8\xb4\x75\x00\x3d\xa1\xb2\x69\x08\x95\xad\x3a\xd4\x1b\x7b\x07\x05\x1f\xd0\x4d\x78\xfb\xac\x54\x70\xf2\x17\x24\x82\xee\x13\x77\x2d\x5a\xe8\xaf\xe8\x4e\x9c\xb9\x73\x9f\xb1\x58\xc7\xa2\x31\xf0\x01\x9f\xeb\x85\x33\x6d\x1c\x8c\xf8\xa9\x71\xa4\x98\x5b\x2f\x31\xf2\xa0\x51\x48\xd7\xf4\xda\x51\xee\xce\xf6\x1a\x74\x37\x95\xf7\x75\x0a\x47\x00\x59\x5a\x3c\xd3\x4d\x5d\xe4\xb1\x4b\xc6\x74\xa3\x8c\xfe\x2f\xb2\xe0\xa2\xc3\x82\xfb\xf8\x69\x87\x65\xa1\xd4\xc6\x9c\x8a\xe5\x6a\x5b\x14\x6f\xcc\x83\xbb\xef\xa0\x74\xb6\x24\x9e\x43\x61\xa2\x9e\x9e\xe5\xd0\x53\x43\x08\xf6\xf0\xe2\x13\xaa\xfe\xba\x35\x67\x87\x02\xea\xba\xc0\x96\xce\x05\x36\xa0\xa0\x3e\xd3\x00\xf3\x93\x96\x8d\x99\xe5\x5a\xcd\x4e\xbc\xb5\x21\xdf\x1b\x3e\xd4\x86\x0c\x9b\x17\x0b\xc3\xdb\x97\xb3\xd4\x11\x5b\x49\x16\xef\x68\x0e\x25\x7b\x5f\xb2\x47\xd3\x4b\xa4\x1d\xe0\x7e\xfe\x3f\xbc\xbd\x69\x77\xdb\x46\xb6\x28\xfa\xfd\xfd\x0a\x8b\x37\x97\x17\x25\x6c\x42\xa4\x92\xee\xd3\x07\x54\x89\xcb\x71\x86\x76\x62\xa7\x33\x38\xe9\xa4\xf9\xb8\x74\x21\xb2\x28\x56\x1b\x44\xb1\x51\x45\x8b\x0c\x89\xff\xfe\x56\xed\x1a\x01\x52\xb6\x73\xfa\x9c\xf7\x45\x22\x6a\x1e\x77\xed\x79\xe7\x73\xf3\x80\xc5\x8a\x98\xab\x48\x11\x33\x5e\xea\x27\x55\x29\x9b\x66\x6d\xee\x5c\x5d\x2c\xf4\xcd\x5f\xff\x7b\x14\x4e\xe7\xd1\x89\xe8\x1d\x17\xea\x3c\x3f\xe8\x16\xf2\x83\x86\xd5\xb9\x79\xda\x34\xe1\xa2\x3b\x7e\xf2\x49\x7a\x3f\xb4\x0a\xd0\x64\x17\xae\xfa\x52\x1c\x9a\x9d\x99\x99\x9c\x6b\xd8\x57\xf7\x60\x17\xcf\x2d\x8c\x6d\x84\xef\x06\x02\xa9\xdd\x87\x94\xf2\xcd\x34\xff\x80\xa2\x62\x00\xd6\xbd\x44\x83\x62\xfc\x34\xea\xce\x5d\xb8\x6c\xf4\x9d\x03\x6c\xfe\x18\x8d\x49\xe3\xc5\xe5\xde\xc7\x2b\x5b\xd6\x8c\xfd\xce\x92\xc3\xdd\x1d\xfa\x7a\xb9\xbb\x33\xf6\x46\x9f\x17\x75\x88\x83\x91\xcf\x05\x7c\x8e\x8a\xa3\x51\xda\x4a\xc0\x17\x56\xfc\x14\xa5\x6e\x05\xe8\x35\x8a\x52\x96\x02\xbe\x77\x18\x52\x94\xfc\x20\xe0\x7b\x1e\x97\xdb\x08\xf8\x51\x9f\xa8\x28\x69\x2d\xe0\x27\xb3\x15\x51\xe2\x4e\x34\x24\xf8\x9d\xb9\x13\x71\x94\xef\xf3\x58\x70\x65\x2c\x29\x5f\x17\xf5\x03\xaf\x72\x01\xbb\x5c\xc2\x3e\x2f\x5a\x18\x74\xdd\xc2\xae\xcb\xc6\x3a\xe7\x9a\x53\x71\x55\x8f\x55\x86\xcf\xd8\xf7\x85\x5a\xa1\x9f\xf7\xa2\x9e\x27\x46\x02\xcd\x07\x73\xa8\xd2\x39\x81\xf2\x56\x4c\x12\x5d\xba\x8c\xf2\x4b\x9d\x87\x65\x2e\x86\x84\xe4\x21\x43\x40\x95\xbe\x56\xc0\x07\xaf\x15\x7a\x03\x2d\x85\x64\xbe\xf5\x79\xc9\x37\x49\x14\x21\x79\x7f\xc6\xb3\xc8\x2b\x16\xf9\xb8\xbc\x8f\x4c\xc3\x61\x6a\xd0\x3a\xe4\x3a\x3b\x1c\xef\xcb\x6a\xe1\x90\x3f\x97\x8e\x1f\x3a\x7d\x46\x3c\x47\x26\xe1\x03\x66\xf4\xc2\x3c\x3d\x2c\xa1\xba\x64\x57\xd7\x04\xea\x56\x5c\xe3\x84\x0f\xa2\x22\x8a\x90\xcb\xea\xea\xda\x61\x73\xdf\xa9\x44\xc1\x10\xa2\x02\x8c\x90\xc6\x47\xa5\xf3\xa3\xcb\xeb\xc4\x2a\xd9\xe0\x27\x01\x37\xd6\x90\xf1\x65\xb5\x20\x10\xc6\x9d\x7f\xa7\x12\xab\xab\x63\x8d\x31\xa1\xb0\xf9\xba\x5a\xc8\xfd\xb2\x5a\x60\x5e\x14\x22\xfb\xdd\x89\xd3\x9a\xc3\x2e\xe7\xa9\xba\xf4\xba\x7c\x8c\xc0\x3e\xaf\x5c\x8a\xe4\x55\xc2\xe2\x06\x1e\x5b\x27\x6d\x97\x73\x5d\x3a\x46\x52\x44\x38\x71\xb2\x75\xe2\x8a\x86\xb2\x58\xb3\xb2\xad\x27\x56\x18\x45\xca\x96\x4e\x59\x5a\x40\xbc\x50\xf3\xb0\x34\xab\x78\x39\x16\x61\xee\xdb\x86\xee\x05\x02\xe9\x1a\xe4\x40\x10\x58\xd2\x7a\x30\x87\x07\x5a\x0f\x56\xb0\xa1\x22\x9d\x5f\x2d\x61\x4d\xe5\x60\x75\xf5\x00\x3b\x5a\xa6\x0b\xb8\xa7\x65\xba\x85\x3b\x2a\xd2\xc5\xd5\x0e\xf6\x54\x0e\xb6\x57\xf7\x46\xd6\x73\x7a\xdc\x39\x54\x50\xc3\x06\xd6\x04\x56\xb7\xc3\xf0\xb0\xbd\x13\xc9\x03\xac\x8d\xa3\x11\x53\x92\x65\x3b\x60\xd9\x1e\x56\xb0\x06\x99\xbe\x56\xc4\x7a\x5d\x7b\xa7\xcb\xde\x83\x34\x65\xb1\x1b\x0d\x94\xde\x88\xe4\x5d\xb6\x83\x77\x68\x2a\xd0\x69\xf9\x1e\xf6\xe7\x5a\xde\x62\xbb\xb0\x4f\x71\x45\xbf\x7f\x49\x4c\x1c\x06\x37\xcc\x12\xf4\x4c\x4a\xd0\xf3\x32\x41\x28\x17\x9d\x76\x77\x70\x77\xae\xdd\x05\xdc\xb9\x26\x41\x0c\xc2\xc8\x1f\x75\x9d\x25\x88\xd3\x91\x3f\x66\x3b\x78\xd4\x23\x9f\x77\x7a\x58\xc2\xe6\x5c\x0f\x73\x6c\x17\x36\xa4\x69\x5d\xfa\x70\xc8\x5e\xb7\x0e\x59\x78\x8e\x29\x83\x8a\x5a\x7d\x10\xb4\xcb\x8d\xf4\x41\xc6\x2d\x0c\xab\xdf\x4f\xaa\x89\x1d\xa1\x41\xb9\xae\x2f\x5b\x05\xc0\xe4\x7d\x23\x78\x45\x7b\x48\xee\xf4\x30\xd0\x46\xa8\xf0\x74\xf1\x7b\xf6\x8e\x95\x3d\x02\x2c\xd3\x18\xdb\x0b\x5e\xcf\x4b\x26\xfb\xfd\xb3\xc2\xc4\xc3\x2e\xaf\x60\xef\xac\xd7\xdd\xbd\xf0\x57\xa4\x68\x5d\x91\x5a\xcf\xb0\x7c\xea\x8a\xd4\xfa\x8a\xcc\x3b\x57\xa4\x46\x00\xbd\x32\xca\x5b\xe8\x20\xd4\xb5\x4d\xad\x55\x2b\x7e\xa4\x77\x0a\xec\x23\x01\x51\x91\x22\xfe\xa0\xad\x1a\xad\xb6\x52\x54\x45\x8f\xa7\x3b\x18\xa0\x0b\xe1\xd3\x2b\x52\x81\x80\x39\x48\xdd\xa1\xc4\x63\xb7\xa2\xc3\xf1\xea\xa6\x55\x79\x9c\xa6\x2b\xa2\x32\x43\x5a\x26\x2e\x18\xc7\xf9\xb6\x4a\x90\xd8\xda\xc7\x34\xd4\x58\x45\x62\xa8\xfa\x7d\x37\x5b\x0b\xb0\x20\x94\x22\xd6\x71\xd3\xdb\x80\xf5\x7c\xc9\xbb\x5e\xed\x0c\xab\xd7\xb2\x5e\x9d\x64\xa2\x15\x1d\x3c\xa6\xa2\x5a\x39\x61\x0d\x5b\xc9\x7e\x9d\xe3\xc4\x3f\x6a\x39\x11\x9d\x16\x6a\x93\xa2\xe5\xd0\x49\xfd\x7e\x5b\xfc\x68\x3c\xbb\x92\x86\x57\x28\xf0\x3c\xeb\x76\x42\xe3\xda\xb5\xd8\xc8\xc4\xf1\x4e\xd0\x97\xe5\xa1\xb0\x00\x7d\x61\x59\x7c\xb9\x6c\xe8\x57\x9a\xdc\x38\xec\x72\x05\x7b\xf4\xd1\x1e\x23\x1b\x45\x38\xd7\x1d\x44\xa2\x85\x64\xcc\x3b\x14\xe8\xaa\x39\x19\xc5\x1f\xe7\xdc\x40\x47\xcb\x46\xcf\xc0\x3e\xb6\xc9\xea\x96\xde\xa9\xe3\xf1\xaf\x2a\x11\x80\x26\xdb\xfd\x7e\x22\x6f\x69\xd9\xef\xcb\x1b\x6a\xc4\x41\x86\x5c\x40\x82\x23\x12\x45\xe7\x0c\xf6\x39\x8f\xaf\x6e\x15\xa6\x28\x5a\x53\x94\xad\x29\x16\xa7\x53\xfa\x37\x98\x52\xc6\xf3\x6d\x52\xa5\x82\xa0\x65\x74\x22\xd3\x82\x78\x4c\x43\x0f\x33\x8d\xd4\xf4\x2f\x4b\x3d\xe8\x34\x52\xd5\xbf\x2c\x9b\xc6\xe2\xdb\x8e\x9a\xea\xfa\x2d\x39\x59\x02\x23\x17\x3a\x47\x60\x3b\x79\x01\x8a\xc2\x9c\x02\x9b\x71\x16\x10\x9f\xce\x27\xc0\xf4\x24\xfb\xf4\xd3\x7c\xd8\x86\x25\x06\xe0\x2d\x4b\x21\xea\x84\xb5\x2f\xd6\x95\xbe\xf8\x17\xc9\x10\x81\x53\x2b\xe7\x78\x6c\x01\xc2\x9b\xa1\x4e\x88\x96\xed\x66\x48\xd0\x5b\x8d\xca\x64\xf1\x0e\x9d\x8f\xf7\xfb\x9d\x26\x22\x93\x07\x4e\x93\x16\xb8\x0c\xa0\x4f\x2f\xb4\xca\x54\x5d\x54\xb2\x2c\x14\x4b\xfc\x4a\x73\x72\x59\x81\x5f\x65\xfd\xa5\xdf\x33\xcf\x4a\x43\x5b\xfb\x36\x2b\xcd\x81\x20\x9f\x1f\x31\xd3\xe2\xc7\x03\xc7\xdd\x5a\x21\x72\x78\x0f\x5c\xb7\x10\xae\xa3\x19\xd8\x85\x93\x9c\x98\xc1\x25\xa4\xb1\x0a\xbb\x1d\x4b\x24\x0c\x43\x78\xbe\x93\x4e\xd1\xff\xad\x37\x25\xc0\x55\xdb\xaa\xf9\x7c\xed\x93\x6b\x26\x95\xa8\x91\x35\x17\x5e\xf6\x9f\x2c\xfe\x49\x19\x39\x98\x27\xf5\x45\xb1\xa1\xdf\x7a\xf6\xc8\x8b\x62\x83\xab\x03\xac\x93\x80\x00\xdc\xb8\xd9\xf8\xa2\x90\xab\x24\x54\xd1\x9f\xbe\xb8\xfe\xc0\x87\xa9\xb4\x05\x8d\x3a\x21\x6d\x17\x37\x89\xad\x4a\x26\x89\xc4\xef\x7c\xa8\xa3\x3f\xdb\xc3\xf2\x29\xae\x86\xc1\x19\xbe\x6d\xf3\x79\x7c\x71\xfc\x22\x9d\x03\x10\xcd\x1a\x8f\x00\x8b\xbf\x22\x6c\xe8\x7b\xe1\x80\xb6\xc7\xb8\x78\xb6\x03\x9e\xed\xa3\x42\x5f\xb8\x85\x3d\x34\x11\x70\x77\x5c\x1f\xa7\x9e\x43\x87\xc6\xc1\x23\xad\x06\xa3\x86\x72\x97\x8e\x80\xfb\x04\xfb\x10\xc6\x7d\x72\x44\xc1\xa0\x0d\x90\xb8\x29\x34\xe0\x2c\x8e\x47\x71\x5b\xf7\xfb\xf2\xb6\xf6\xde\x2f\x50\xf7\xc7\x52\x03\x79\x09\xc8\xd8\x62\x99\xfe\x07\xbc\x64\x55\x3e\xbf\x29\xfb\xfd\x8b\xd5\xa4\x4a\xe7\x83\x32\x9f\x0f\xca\xe8\x64\xbc\xe8\xd2\x77\x07\xc3\x88\xcb\x85\xe7\xc7\x48\x3d\x40\xdb\x8b\xf3\x4d\x51\x9b\x5e\x4a\xdb\x41\x43\xbf\x10\x89\x41\x51\x61\xd5\x72\xc0\xea\xb5\x7f\xa4\x62\x9b\x0d\x5b\x4c\xbe\x51\xb9\xca\x14\xab\x24\xc6\x13\xec\xad\x45\x25\x94\x55\xcf\x50\xd9\x7c\x7b\xcf\xe7\x2f\x35\x40\xdc\x88\x12\x19\xa7\xaf\xc5\x82\x4d\x14\xcb\xbf\x17\x4d\x62\x3d\x71\x1a\x07\x88\x87\xb5\x78\xc7\x72\x54\x6f\xab\xd9\x3b\x56\x4b\x96\x6f\x1a\x5a\x1d\x8f\x87\xc6\xba\x46\x1c\x8e\x17\x37\x74\x8e\x5e\x11\xb7\x54\x4c\x93\x3a\x4d\x36\x93\xf9\x60\x91\x2f\x08\xf9\xdf\xc5\x0c\x8c\xf2\xc4\xf1\x98\x3c\x68\x94\x55\x37\xf7\x46\x24\xdb\x6c\x07\x5b\x8d\x54\x3f\xd0\x8b\x11\xc9\x57\x89\x82\x25\x6c\x61\x03\xd2\xcd\x40\x53\x37\xdb\x60\x5a\x8f\x7e\x77\x7c\xeb\xf9\xd0\xb4\x7d\xae\x1e\x81\x8b\x8b\xc8\xfb\xe5\xdf\xce\x50\xd6\xcc\xb2\x41\xdd\x72\x4b\x70\x27\x05\xd7\xb9\x8e\xd7\xd9\x2c\x40\x19\x2f\xc0\xdc\x2d\x00\x22\xa7\x60\x3d\x45\x6a\xc2\x89\x0e\x61\xe7\xf5\xd9\xef\x35\x39\x9d\x14\x69\x32\x9f\xd4\x03\x95\x2b\x42\xfe\xb7\x84\x3b\xe3\x35\x76\x79\x41\xe9\x03\x3a\x61\xb4\x67\x7e\x0d\x0f\xee\xb2\xe1\xd7\xb2\xf5\xb5\x21\xd6\x47\xac\x71\x60\x24\xa6\xf7\xc9\x90\xcc\xc0\x2f\xe7\x22\xdb\xc1\x22\xdb\x13\x87\x46\xd2\x1a\x71\x47\x0d\x69\x4d\xe9\x15\x99\xc1\x02\x37\xc2\xfb\x1c\xf3\x41\x0f\x74\x65\x4e\x17\xd9\x1e\x2a\x3a\x3c\xb2\x71\x45\x29\xdd\x4e\x12\x7e\xb3\x9c\x2c\x29\xcf\xf9\xad\x1e\xe8\x03\xe5\x68\x0b\xb6\xbb\x5c\xa7\x8c\x5c\xa5\xe9\x8e\xe4\xc9\x5d\x12\x8d\x12\x43\xa2\x6c\x69\xa5\x57\x00\x96\x14\x2b\x6c\x28\x6f\xee\x62\xda\xe7\xb9\x68\x4b\x88\xe2\x80\xfb\x01\x5c\xe9\xb7\x2c\x7c\x39\x6d\x45\x1b\x01\xc8\xc4\x91\xb6\x6a\x64\xc7\xa3\xca\x90\xc5\xac\xdf\xc7\xf3\xc7\x9d\x3d\x71\xdc\x75\x0d\x7b\x66\x8e\x47\x4e\x26\x7f\x13\xf9\x0b\xd1\xbc\x35\xdc\x41\x14\xe3\xbd\x8d\x39\x83\xd1\x2b\x9f\xf7\x8c\xd3\x94\x1e\x44\x20\x2d\xef\xfd\xaf\xe5\x72\xe9\x92\xbc\x04\x37\x82\x90\xf9\xb5\x73\xca\x31\xb4\xac\x5a\xab\x3d\x11\x75\xf4\xa3\x7e\xe3\x75\x6f\xed\xc7\x35\xef\x75\x12\x7a\x2e\x44\xc9\x1b\x71\x2e\x06\xa7\xa6\x33\xae\xbf\x08\x1c\xb4\x1f\xa3\x4b\xf0\x46\xf4\xfb\x26\x42\xaa\xd5\x89\xf3\xf2\xb7\x53\xe7\x3c\x46\x41\x14\xcd\x40\xd4\x6a\x2c\x8e\xc7\xc4\x7f\x61\xf0\x5f\xd3\x0f\xb0\x4c\xa7\xd8\x1b\xd3\xef\x8b\x98\xee\x25\x60\x5e\xc5\x10\xa5\xdb\xd3\x2a\x82\x44\xde\x77\x9f\xf0\x0c\x74\x70\x83\xec\x42\xca\x82\x3e\x17\x89\xc5\x0b\xac\x61\xe7\x33\xb1\x7c\x26\x08\x76\x27\xa1\xce\xa4\x7b\xc1\x62\xd2\xcb\xf8\x1c\xa9\xdd\xdb\x80\x1c\xc4\x9c\xa7\xfa\xc5\x20\x18\x69\xb5\xc5\xa6\x6b\x93\x5e\xe8\xa8\xd6\x50\x57\xaf\x3e\x9a\xba\x72\x6a\x85\xde\x9c\xfc\x1c\xb9\x85\x47\xb8\x9d\xe2\xc4\x25\xed\x54\x5c\xf9\x76\x8a\x11\xe8\xb4\xd2\xdc\x9a\xb5\x53\x23\x55\xcc\x51\xab\xb2\x89\x04\x6a\x92\x9f\x22\xb0\xce\x2a\x4a\x3d\x8d\x3f\x6b\x8c\x39\xe1\x4f\xdc\x48\xfe\xc4\x8d\x24\xfd\xfe\x05\x77\x77\xb2\xdf\xbf\x60\x9d\x01\x46\x31\x65\xbc\xea\xe2\x84\x99\xc5\xcb\x59\x58\xb2\xf1\xb2\x4a\x7c\x5d\xe0\x60\x23\x94\x77\x67\x3b\x6c\x1a\xc9\xd4\xb3\xcd\xd9\xc9\x8c\x7d\x79\xaa\xa2\xd8\x55\x6e\x65\xa3\x24\xbd\x25\x67\x5a\x1f\x69\xc2\xcb\x35\xde\x0d\x4f\x82\xa9\x58\xc0\x35\xd8\x2d\xe2\xd2\x5d\x64\xba\xb0\xa7\x6f\xdd\xa6\xc4\x66\xf7\x36\x97\x90\x66\xc9\x6b\xa9\xba\x9a\x1b\x7e\xd8\x56\x9c\x69\x06\x30\xf6\x58\x83\x97\xc9\x4f\x31\xcc\x0e\xde\x8d\x59\x53\x16\x1f\xdb\x12\xba\x85\x6a\x81\xe9\x67\xdc\xb4\xc6\x07\xa3\x99\xc6\xbb\x67\x0d\xf7\xdb\xfd\x61\x03\x58\x35\x65\x46\x8c\x69\x9b\x2f\xe8\xeb\x2a\xe1\x70\xb0\x8a\x10\xfb\x9c\xb5\x5c\x7a\x8b\xc6\x84\x14\x29\xda\x8a\x88\xde\x0f\x23\x06\xdf\x78\x2f\xba\xb4\xa9\xfe\x38\xba\xb4\xae\xf2\x87\xaa\x49\x2a\xef\x9b\xd0\x7a\x1b\x1a\xc2\x8a\x7a\x3f\x92\xce\xe5\x50\x2c\xbc\xb0\x50\x67\xd5\xd0\xc2\xf8\xf9\x96\x53\x3e\x83\x2d\x95\xd3\x15\x2a\x54\x2e\xf4\x03\x4c\x0e\xb5\x71\x4f\x64\xdc\x0c\x1a\x37\xa1\x66\x42\x4b\x5a\x26\x1a\xe9\xf0\x26\x80\x89\x18\x2c\xa6\x6c\x46\xae\x92\xed\x94\xcd\xcc\x6f\x02\x95\x47\x89\xc6\xcb\x29\x9b\x99\x45\xb5\x8d\x2e\xbd\x4d\x96\x7e\x0a\x5c\x04\x99\x49\x3d\x1d\xce\xf2\xba\xd1\x47\xfa\x27\xb3\xd1\x1d\x15\x9b\xe7\x26\xe4\x24\x49\x14\x18\xd5\x3b\xe0\x04\x8b\x3f\x19\x44\xc7\x1f\x18\xa9\x01\xb6\x5d\xac\x82\x56\xe6\xda\x8e\x19\x65\x18\xce\x91\xf2\xe3\xd1\x49\xa7\x9d\x66\x27\x3b\x05\xee\x45\x9f\x4a\x8c\x2a\xe6\x21\x38\xc3\xb5\x64\x29\xd7\x10\xdc\xe1\x08\x17\x85\xa5\xf0\x9f\x94\xfe\xda\xb3\x86\x68\x5c\x22\x6c\xc7\xc7\xe3\x74\x46\xfc\x65\x90\x1d\x96\xab\xa7\xb5\xf1\x35\xb5\xe8\x61\x44\x09\x82\xf0\xc0\xbe\xdf\x47\x3b\xe9\x2e\x74\x15\x2d\x00\x4e\x62\xc9\xc3\x97\x67\x5d\xcb\xbb\x1b\x71\x98\xf2\x19\x7a\xc3\x8a\x78\x2e\x7c\x06\x55\x5b\xf3\x15\xcd\x4e\x07\x92\xdc\x08\xe7\x26\x46\x64\x2b\xae\x0c\x2e\xd2\xbc\x8a\x55\x56\x5f\x9d\xa2\x36\x8e\x16\xcd\x7b\xf7\x5b\xa5\x1c\x22\xa3\x11\xb0\x7c\x3a\x83\x2e\x2d\xe9\x51\x1b\x4f\x2b\xe6\xbd\x35\x8f\x90\x22\x83\xf2\x7c\x0a\xf3\x62\xf3\x39\xfb\x9d\x5b\xde\x8b\xcc\x2f\x86\x70\xfe\x3e\xe5\x3d\x3b\xa2\x9e\x93\xbc\xc6\xda\xb0\x60\xcf\xb2\xfe\x69\xef\x69\x3e\x6c\xa2\x79\x7c\x34\xe6\xd4\x46\xda\xa2\x8f\x9e\x6d\x4e\xce\x6b\xbe\x51\xa2\x96\xf4\x70\x67\x7e\xdb\xa8\x47\x60\x54\x6f\xf1\x4b\xd1\xdb\x5e\x58\x13\xd4\xa2\xe8\xf7\x7b\x7a\xe0\xf8\xdb\xc9\x9e\x5f\xfe\x5b\x5c\x58\xab\x42\xde\x62\xbf\xbe\xe5\x9b\x0e\x3f\x36\xc2\x11\xfe\x2b\xec\x51\x7f\xc6\x76\xb9\x80\x3d\x9e\xb2\x27\xb9\xa6\xad\xe3\xb6\x11\x8f\x89\x1a\x08\xb8\x26\xa9\xff\x66\x03\x09\xd7\xe4\xc6\x7f\x57\xe1\x00\xa6\xce\x79\x11\x5c\xeb\x01\xfd\xea\x47\x14\x7c\xc9\xf8\x78\xb6\xba\x4b\xa6\x4b\xfd\xf6\xde\x52\x7b\x2c\xf5\x01\x06\xe7\xd3\xf3\x09\x0a\x3b\xae\x68\x63\xd4\xce\x9d\x85\x59\xa2\xa8\x6a\x87\xb4\x42\x8f\xde\x76\x1e\xc7\xe3\xb0\x6d\x03\x8b\xa1\xfa\xd1\xf4\xf5\x47\x57\xc0\xaf\xd8\xf5\x65\xc2\xd2\x04\x4b\xdc\x77\x6c\x76\x3f\xc8\x8c\x1c\x33\x4b\x42\x73\xdb\xf3\x4d\x36\xd2\x78\xc1\x7b\x18\x6e\xef\x13\xf5\xbc\x8f\x89\xf7\xad\x32\xa1\xfd\x8c\x2c\xcb\x38\xfb\x37\x5b\xd0\xc1\x03\x62\xf8\xe9\x5f\x53\x0b\x75\x54\x04\x75\x02\x84\xfb\xfd\xac\x6c\xd5\x3a\x07\x36\x4e\xc7\xa5\xf3\x49\x5e\xb4\x41\xdd\x87\x3c\x27\x02\xf3\x7e\x5d\xc1\xf8\xfd\xf3\x43\x0a\x1e\xd7\x27\xc9\x82\x16\x57\xd7\x5e\x48\xcb\x35\x46\x21\x48\xcc\x13\xc2\xef\x39\xad\x06\x0b\x58\xd1\x2a\x5d\x90\x3c\x59\x50\x89\x75\xf8\x60\x01\x25\xe5\xe9\x22\xe6\x19\xa1\xf1\xd3\x8a\x46\x66\x07\x82\x10\x1b\xd4\xba\xc6\x78\xd4\x73\x1b\xc0\xba\x74\x01\xa8\x57\xd1\x8a\x7c\x25\x62\x63\xc6\xb6\xa4\xff\xa7\xb7\x5c\x83\x3b\x6f\xdc\x18\x96\x99\x4d\x12\x5d\x3c\x9a\xd8\x2f\x9a\xf8\xee\xe9\x5e\x7b\xd0\xc3\xfe\x7a\x9a\xea\x29\x24\xbb\x55\xd9\x8e\xe4\x26\xdf\x0c\xa0\x07\x3d\x25\x36\x2e\xff\x46\x65\x7b\x02\x5c\x63\x07\x17\x43\xe0\x24\xe7\x61\x74\xbf\x44\x2f\x12\x06\x05\x82\xc8\x1f\x6b\x35\x49\x0a\xca\x41\xd1\xcf\x45\xa2\x68\x22\xa8\x22\x94\xd2\x44\x52\x46\x26\x45\x2e\x28\xa5\xc5\x44\x93\x6c\x1c\x18\x21\xb9\x29\x06\x26\x68\x6a\xe8\xc1\x27\xda\xab\x6d\xf5\x7e\x30\xfa\x2d\xcb\x7b\xac\x5a\x98\xdf\x3c\x8f\x2a\xfd\xfd\x34\x30\x8d\x9a\x0c\xf3\x53\x1f\x34\x28\x2a\xe3\x11\xef\xe1\xaf\x2d\xde\x83\x3e\x8e\x04\x6f\x99\xb1\x06\x60\xc6\x1a\xa0\x42\x46\x04\xda\x03\x30\x0c\x1c\x2e\x9e\x34\xa3\xec\x6e\x99\xb9\x5f\x82\xe2\xb6\x82\xa4\x5f\xb2\xa4\x0a\xba\xa8\xf9\xdf\x45\x82\x01\xc9\x41\xe2\x5f\xbd\xdc\x50\x9b\x54\x13\xbf\x5c\xda\xff\x43\x60\x04\xee\x4d\x8e\x75\xe9\x2f\xdd\x0f\xac\x55\x9a\x3c\x1c\xaf\x34\xff\x74\x1d\xf4\x5c\xcb\xaf\xae\xa1\xba\xba\xd6\xfd\x9f\x97\xd8\x9e\x89\x34\x50\x75\x6e\xdb\x69\x91\xde\x8c\x80\x38\x99\xb0\xd5\x46\x91\xf4\xa5\xa6\xe5\x63\x95\x12\xdc\xe9\xda\xae\x44\x49\xab\xe3\xf1\xe7\x24\x38\xa2\x55\x62\xf3\x4a\x5f\x91\xbf\x8b\xe4\xa2\x3c\x1e\xd1\x8b\x8b\xfe\x67\x27\x64\xb3\x8d\xe2\x87\x12\x9b\x1f\xf1\x06\x75\x0b\xbb\x35\x73\x05\x4c\x71\xb3\x4c\xed\xd6\x4d\x5a\xd4\x41\x28\x14\x57\xea\x74\x13\x6a\xb9\x9e\xa2\x62\x56\xf1\x24\x5a\xef\x96\xea\x4b\xae\x1f\x14\xd3\xdb\x3e\x37\xc7\xe8\x31\xe7\xb0\x42\xa7\x4b\x46\xa0\xd6\x18\xf9\x5a\x28\x99\x8a\xac\x74\xa5\x53\x91\x29\x5d\x63\x20\xb2\x72\x20\xb2\x5a\xd7\x1c\x88\x4c\x0d\x44\x76\xef\x5a\xf0\xab\xe8\x4f\xfe\x30\xac\x5d\xb0\x56\xd1\x2d\x89\xac\x24\xd1\x4a\x76\x2b\x60\x6a\xb7\x46\x4d\x5a\x8b\xd9\xaa\x13\xd2\xe3\x5a\xf7\xb6\x9f\x78\x35\xcf\x54\x3b\xe9\xed\xde\xf4\xd6\xc4\x82\x94\xef\xce\x60\xc3\xd6\x4f\x11\x03\xe9\x7e\x72\x28\x34\xd6\x75\x91\x88\x7e\x5f\x92\x7e\x1f\x5f\x98\x80\x13\x17\x1a\x0b\x3f\x1e\xd9\x2d\x52\x63\x4b\xd5\xef\xb3\x1b\x5a\x58\x07\x42\xfd\x7e\x22\x8f\x47\xae\xf3\x94\xd8\xf4\xfb\x5c\x67\x59\x3f\x42\x61\x1c\xff\xb4\x6f\x96\xc6\xf2\xe7\xca\xab\x78\xb0\xec\x11\x58\xb6\x22\xcd\x4b\x67\x1d\x80\x6a\x8e\x2f\x4f\x91\x6a\x83\x05\x8f\xc0\x3f\x88\xfa\x77\xc7\x71\x87\x4b\xb2\x05\x3e\x33\xda\xd2\x16\xa7\x9e\xa3\x88\xab\xe7\x76\xfd\xd3\x48\xd5\xb9\x89\x3a\xfc\xef\xc0\x7e\x2d\xca\xfa\xc9\xbf\x85\xb2\x86\x67\xa9\x95\xac\x1f\x9b\x56\x02\x3e\xe1\xed\x9a\xf8\x98\x7f\x10\x95\x3d\x87\x2c\x05\x1c\xd6\x5c\x2a\x6e\xc4\xd5\x1a\xb0\xfd\xd5\x12\xac\x20\xf4\xeb\xe4\x50\x50\xe2\x2e\xca\xf1\x18\xae\x80\xfe\x1d\x8e\x76\xf8\xc2\xcc\x09\x67\xf9\x3f\x05\xea\x55\xca\x71\x90\xba\x66\x8f\xc8\xcf\x7a\xec\xf7\xab\x6c\x85\x3f\x57\x88\x99\xc5\x2c\x47\x73\x2a\xbd\xd2\x1f\x7e\x73\xd2\x42\xc4\x58\x07\x79\x43\xa1\x63\x8f\xbd\x63\x95\x58\x2c\x7a\x27\xba\x28\xe7\x5b\x38\x91\xc7\x1a\xd1\x65\x4b\x58\xd9\x21\x04\x9c\x36\x9f\x68\x85\xc8\x3d\x8b\x9c\x87\x32\xa8\x3b\xfa\x14\x76\xee\x8a\x99\xa8\xe7\x1f\x81\x9e\x1b\x0c\xb0\x82\x28\x34\x8e\x78\x5a\xbf\xc0\x22\x80\xa1\x70\x07\x8d\x17\x93\x84\xa5\x15\xb9\xba\xc6\xc6\xc5\x84\xe7\x09\xc7\xef\x26\x60\xb2\x9e\xff\x63\x82\x7f\xa9\x49\x38\x90\x57\xd7\x79\x74\x16\x75\xad\x4f\x22\xd7\xe2\x9f\x9c\xde\x6f\x8b\xa6\x39\x95\xe5\x16\xed\x3b\xec\xca\x02\xce\x05\xf9\x19\xc6\xb7\xdd\xc9\x03\x3e\xf9\xef\xbd\xd7\xfa\xd0\xfe\xf6\x21\x65\xe0\xe7\xf5\xdc\xaa\x6e\xe7\x6f\x8d\x7e\xaf\xfb\x7c\x25\x00\xb7\xce\x7d\xbf\x14\xf0\x79\x51\xbb\xaf\x4f\x5a\xaa\xba\x3f\x0b\x1b\x0e\x32\x16\xd2\xb4\x9c\x6c\xa1\x4d\x90\xe5\x9e\xc6\x85\x20\x4a\x2b\x54\x01\xe7\x62\x9f\xee\x13\x05\x18\x2d\xbc\x07\x07\x63\x4c\xc1\x9a\x98\x89\xf2\x2b\xf6\xde\xf5\xa9\xe3\x0d\xd0\x14\xbd\x3d\xe0\x00\x1b\x42\x1a\xbd\x28\xff\x12\xf4\xc0\x17\x79\xcf\x0e\x82\x8b\xaa\x07\x21\xae\x6a\x51\x3e\x88\x9a\xab\xd5\x3a\xef\xad\x79\x35\x58\x17\x9a\x28\xc5\x2d\x5c\x58\xb3\x17\x8c\x55\x6c\x35\xd8\x0d\x9b\x27\xb7\x37\x8b\xde\x62\x4c\x4c\x6e\x5d\x85\x2c\x9c\x69\x34\xee\x30\x0e\x73\x1c\xd0\x48\x3c\x7a\xe3\x27\x87\x9d\x30\x13\x69\xdf\x5c\x1b\x5c\x9d\x5c\x46\x3a\xf5\x56\xf5\xf4\xc4\x3e\x0a\x69\x1b\x79\x3c\x1a\x43\xab\x31\x5f\x26\xbd\xbd\x3e\xf0\xbf\xb0\x64\x5a\x44\x01\xf2\x7d\x4b\x33\x1f\x6d\x59\x97\x45\x46\xd1\x05\xa5\xb5\x89\xd9\xdc\x62\xa9\xce\xa9\x32\x21\x38\xe4\xb4\xce\xb0\xee\xcb\x2f\x66\xbe\x56\x81\xfe\xac\xe6\x2e\x1c\xb0\xe2\x6b\x16\x12\xa2\x2e\xc2\x10\xac\xf7\x76\x97\x67\xe4\xaa\x96\xc1\xb7\xb0\x5e\x37\xb6\x6d\x87\x26\xb1\xcd\x4a\xe4\xce\xa4\x02\xe1\x04\xb0\xce\x7d\x89\xf1\x22\x1f\x05\x7d\x8c\x7d\x95\x94\xb1\xaf\x92\x79\x43\xe5\x89\xaf\x92\x48\x02\x8d\x2e\x4b\xd0\x83\x88\x89\x18\x08\x05\x7a\x2a\x19\x02\x47\x1f\x24\x15\x9d\x4f\x3a\x05\x6a\xeb\x7a\x44\x00\x27\x03\xa1\x31\x3a\xe8\x38\x13\xa9\x9a\x26\xa9\xa1\x44\x06\xf6\xf6\x86\x7e\x76\x59\x91\x9f\x51\xb0\xc5\x4a\xc9\x0e\xf2\x91\xab\xf9\x2a\xf9\xc4\xc4\x12\x65\xe6\x6e\xd0\x32\x88\x1f\xde\x73\x57\x98\xbf\x2b\xce\x83\x99\xe3\x63\x45\x21\x72\x2f\x86\xf0\xc0\x54\x90\xbe\x75\xc4\x10\xfe\x7a\x36\x20\xe3\x62\x2e\x32\xae\x19\x8f\x6a\xd0\x38\x33\xf3\x37\x86\x1c\xe6\x1a\x50\x97\x4a\xdd\xf7\xf2\x15\xed\xca\xf6\x40\xb8\xdd\xc3\xa8\x2d\xc5\x7a\x53\x32\x79\x3c\xe2\xa1\x90\xb7\x94\x07\x67\x26\xc6\x59\x0f\x03\x96\xf2\x60\x54\x34\x9d\x41\x4d\x13\x3e\xb8\x26\x57\x89\x1c\x5c\xbb\xc0\xa1\xc3\xe0\x48\x24\xe5\x83\x51\x57\x3a\x4f\x0d\x3b\xb9\x98\x96\x69\x3a\xa3\x6a\xba\x99\x59\x61\xb9\x1c\x5c\x9b\x98\x71\xfe\xfc\x80\x8c\x1a\x8b\xd4\xb7\x92\x55\x3a\x22\x97\x35\x49\x47\x29\x83\x75\x20\x77\xda\x45\xae\x4d\x11\xe0\x24\x65\xb0\xa3\xeb\xc1\x1c\x3b\xae\xe8\x7c\x5c\xdd\xac\x31\x96\xad\x48\x29\x7a\xef\xdf\x81\xb4\xbf\xf6\x63\x71\x45\x77\x20\xaf\xe8\xce\xab\x0f\x44\xcd\xae\x5c\xaf\x77\x4f\x8e\xe7\xb0\xcb\xf7\xb0\xcf\xdf\x35\x38\x37\xec\x72\x4b\x97\x74\x30\x82\x8a\xde\x8f\xab\x9b\x3b\xec\x79\x49\xb3\x3f\x5d\x06\xc1\xc1\x7e\x20\xc8\x65\x62\x46\x30\x78\x47\x06\xc9\x7e\x60\x06\x46\x2e\x13\x39\x78\x47\x08\x2c\x6f\xb7\x26\x86\x12\x2c\x70\xa4\xf0\x40\x2b\x32\xb6\xab\xb8\x80\x0d\x7d\xf0\x61\xc5\xdc\xca\xce\x67\x50\x34\x49\x89\x4b\x5f\x01\xb7\x5e\xf3\xc6\x78\x24\x1c\x24\x3d\x73\x2a\xac\xb8\xd9\xf9\xfd\x03\x1b\x24\x55\xef\x1e\x1d\xc2\xc6\x6f\xc9\x5a\x1f\x80\x9d\xd9\x64\xb8\x47\xb1\x46\xb6\x83\x3b\xf4\x7b\x99\xed\x06\xf7\xd6\x1f\x1f\x1b\x0b\xeb\x5d\x44\x90\x83\xa4\x6a\x2a\x66\x68\xc9\xaf\x8b\x90\xab\xbb\xcb\x0a\x6a\x2a\xb3\xbd\x57\x83\x18\x1e\x11\x46\x32\x4a\x69\x49\xea\x9b\xed\x24\xd9\x62\xa4\x59\x41\xf2\xfa\x76\xd9\xef\x27\x4b\x5a\xc3\x8a\x0a\x74\xe9\xbb\xb9\x7c\x48\x65\xb6\x23\x57\x69\xba\x31\x37\xd5\x41\x23\x31\x18\xa1\x2c\xea\x93\x64\x4e\xfa\xfd\x8b\x4f\x92\x15\x09\xef\x9f\x3f\x31\x73\x58\x69\x78\xe1\xc9\x20\xfd\x3d\x66\x26\xec\x94\xfe\xc7\xfb\xfd\xb5\x8d\x4e\x91\x65\x19\x4a\x6e\x76\xf9\x43\x43\xa0\x32\x65\xaa\x33\x65\x2a\x5b\xa6\x11\xe8\x8a\xc4\x14\xb4\x25\x14\xc6\x0b\xb6\x1f\x12\x6d\x11\xf4\x8a\x82\x3e\x21\x7a\x92\x2b\xba\xa0\xa2\x71\x3b\xb9\xf6\xbb\xe7\xf6\xce\xbe\x8a\x79\x37\x1c\xfa\xff\xfd\xb9\x92\xdb\xcd\x06\xbd\x46\x3d\x0b\xcf\xe8\x33\x0f\x09\x9e\xfd\x9f\x4f\x0e\x11\x60\x68\xfe\xcf\xff\x25\x0d\x8b\x65\xd1\x2b\x0d\x3b\x1a\x58\x30\xa9\x6a\xb1\xd7\x80\x05\x9f\xc7\x26\x8a\x3b\xfd\xad\xe8\xb2\x5e\x4e\x1c\x2b\x9d\x28\x93\x68\xdc\xd7\xfa\xee\xb7\xae\xf2\x55\x51\x3f\x30\x15\x31\xb1\x7d\x08\xbd\x0a\xfd\xf5\x5f\x5c\x9c\xe0\xd0\x04\x2e\x46\x17\x98\x6f\x1d\x82\xea\x92\x17\x58\x65\xd2\x13\x35\x7f\xe0\x55\x2f\xaf\x08\x06\x8b\xe6\xcb\xe4\xe7\xa4\x72\x2f\xa8\xb5\x71\xaf\x32\x44\x53\x48\xbf\x5f\x59\x67\x5b\xc8\xd2\xff\xaa\x14\x85\x0a\xcc\xa1\x67\xbf\x26\x82\xd8\x70\x5e\xe6\x62\x0b\x82\x61\xef\x92\xde\x40\x3f\x9c\xd5\x74\x38\xeb\xf7\x7b\xa9\xfb\x6d\xd4\x30\x52\x41\xe0\x22\x11\x9a\x24\x3e\x1e\xc5\xcd\xf0\x78\x14\x1a\x74\xf6\xfb\x82\xe4\x53\x37\x3a\x70\x88\x29\x72\xd3\xf0\x6b\xfe\xb6\x37\xf3\xb1\xf6\x2a\x72\x4b\x87\xfd\x7e\x65\xd5\x1a\xbe\x3e\x1f\xff\x7c\x47\x55\xb6\x33\x54\xda\x9e\xaa\x6c\x6f\x7e\xd6\xce\x35\x85\xf9\x71\x4e\x6a\xd8\x36\x12\xf0\xbc\x0f\x23\x61\x77\xfc\x4c\x3d\x7e\xfb\x28\x1a\x05\xbe\x3b\xd5\x44\x2a\xf2\xd2\x68\xf1\x83\xd5\xd9\x44\xad\xfb\x0b\x9e\xdd\xe3\xfb\xdc\x96\x2a\x77\x49\x8b\x3a\xf0\xb7\x8c\x38\x52\x19\xdb\xc8\xb3\xba\xc5\x82\x5c\x56\x6d\xdd\x62\x4c\xb1\x6a\xe2\x31\x77\xe2\x1f\x51\x40\xe5\xc4\x22\x42\x46\x30\xf0\x94\x59\xe7\xe4\xd4\x13\x98\xb4\x8e\xc0\x50\xd6\xc5\x35\x8a\xd2\x76\x57\xd2\xb2\xdd\x75\x9a\x8e\x52\x83\xbe\x42\x93\xb1\x46\x15\x6d\x82\x9e\xbb\xac\xa7\xb0\xba\x9d\x6e\x3c\x88\x15\x3b\xf3\x3a\x6a\x68\xaa\xcf\xe8\x8a\x06\x2e\x2b\x9f\x14\x9e\xcb\xca\x27\x75\xfe\x73\xc2\xc9\x84\x67\x16\xb3\x76\x4e\x93\x8c\x5d\xaa\xa6\x75\x1f\x6a\xbe\xc8\x9c\x5d\xb7\x7b\xa3\xe7\x36\x78\xdf\x59\x6b\x56\x64\xaf\x69\x68\xf1\xb5\x48\x0e\x3b\xb4\x60\xdd\xa3\xd5\xaa\xdd\x19\xf6\x01\x1f\x1b\x2b\xd2\x58\xcd\x5f\x3a\x1c\x97\x37\x62\x9c\xa6\x25\x91\x06\x8a\xbd\xa7\xdb\x12\x56\xc1\x29\x90\xd4\xd7\x33\x7f\x72\x03\x30\x82\xb3\xdb\x03\x8f\x42\x62\xd0\xb5\x2e\x57\x9a\x4f\x84\xe7\x0e\x47\x0b\xa7\x13\x95\xd8\x98\xe5\xb3\x1b\xd7\x75\x49\x65\xc0\x40\x58\x54\xcc\x47\x8c\x92\x75\x9c\x53\x11\xf8\x15\x63\x9d\xe9\xd3\xd0\x09\xb5\xab\x2f\xd2\x44\x18\x72\x6d\x9f\x57\x13\xfd\x23\x17\x0d\xc1\x14\x3d\xcb\x70\x44\x7f\x08\xbc\x6e\x13\x41\x20\x67\x36\x3e\x24\xb7\x2e\x0a\x2a\xf4\x70\x80\xb4\xb6\x3b\x56\x22\x08\xeb\x6b\xea\x64\xe2\x2d\xcd\x89\x18\xed\x9e\xce\xc0\x3a\x65\xf8\x09\xc1\x7f\xdb\xc1\x8d\x09\xcc\x1a\xd4\xb6\x15\x1d\x8e\xd5\x8d\x8b\xe3\x39\x56\xc1\x70\x5a\xd0\x6a\xaa\x90\x78\x08\xc1\x6c\x99\x09\x58\x3a\xfe\x46\x20\x64\xe4\xd9\xb6\x92\x2b\xbe\x54\x89\x70\xf4\x91\xd7\x5f\xe0\x0d\x72\xfa\xc7\x36\x98\x13\x97\xfa\xa0\xd9\x25\x72\xae\x7d\x1a\x10\xa4\x3b\x94\xe2\xcc\x50\x18\x2d\xf4\x50\x42\x41\x0b\x73\xc6\xea\x86\x22\x10\xc2\xc2\xe8\xdf\xbd\x9e\xaa\x99\x89\x2b\x63\xe4\x21\xec\xf1\xd9\x2b\x91\x38\xad\x5e\xe9\x75\xd5\x0e\x4d\xe3\x4c\xcc\xbe\x11\x54\xd1\x5b\x43\x57\xa1\x5e\x89\xa1\x8f\x2e\x54\x66\x7c\x4a\x84\x07\xcf\xd9\xad\x87\x07\x6f\x1a\x46\xa5\xe9\x1b\x71\xc3\xbd\xa7\xe1\x30\x7c\x49\xb9\x46\x72\x0e\xa8\x02\x94\x17\x50\x16\x52\xe5\xb5\x61\x30\xa0\xb9\xab\x1e\x38\x83\xde\xae\x67\xd4\x65\x90\xd3\x5d\xf4\xfb\x35\x21\x7c\x99\x14\xa4\xf2\xcb\x5c\x1a\xf2\xe3\x19\x12\x69\x26\xaa\x2c\x81\x8b\xda\x86\x91\xb5\x49\x59\x96\x55\xd1\x91\x63\x27\x83\x56\x59\x0c\x9d\x75\x56\xcb\x57\xe1\xa1\x09\x11\x70\xa6\x7c\x86\xee\x93\xfd\x01\x2c\xa8\x72\x5a\x4a\x28\xd9\xa3\x17\x23\x28\xe9\xc5\xa8\xb3\x8b\xf2\xec\x2e\x4a\xbd\x39\x15\x2d\xa6\x76\xff\x66\xba\xf9\x39\x7e\xb3\x6a\xa1\xbf\xf0\xb8\xdd\xea\xf7\x5c\xdc\xd0\x39\x39\x98\x08\xfd\x15\x94\xf8\x7f\x6e\xfd\x3c\x3b\xf7\x8f\x66\x41\x6b\xb3\xa0\xa5\x5d\xd0\x2a\x7a\x11\xb8\x0d\x23\x64\x0c\x14\xf0\x66\x5c\x8c\x82\xeb\x51\x85\xf7\xf9\x62\x08\x9c\x2a\x92\xf3\x73\x17\xea\xb0\xcb\x39\x75\x37\x1b\x7f\x34\x54\xe9\x47\x05\x22\x75\x65\x7d\x4f\x83\x3f\x15\xaf\xbe\xd9\x62\x70\x78\xef\xf0\x53\x65\x27\x8f\xbe\xd5\x14\xce\xdc\x39\x36\xaf\x26\x89\xf4\xc1\xcf\x18\x42\xe5\xaa\x21\x10\xd2\x0a\x97\x66\x80\x8b\xc1\x36\xa3\x3a\x1c\x45\x15\xfb\x56\x1d\x9d\x56\xe8\x34\xa2\xa9\x41\x69\x4d\x26\xdc\x41\x9d\x74\xae\x08\x0f\x57\x24\xd2\xe7\x30\xba\x7c\x55\xe4\x2d\xa2\x72\xf0\xcd\x2f\x76\x25\xdb\xd1\x3e\x90\x00\x40\x7c\xcf\x1d\xa7\x29\xb3\x01\x7e\xf1\xbc\xf1\xe0\x97\x4e\x1f\x9e\xb1\xc3\xe9\x4c\x6c\xe1\x08\x15\x1a\x23\xe7\xea\xe2\xd7\x80\xc6\x3d\x33\x24\xa9\x21\x7c\x2e\xa4\x43\xee\x10\xc3\x97\xd9\x3b\x03\xf1\x42\x59\x17\x2c\x58\xa3\xf6\x12\x87\xd4\xb8\x1a\x61\xf8\x42\x06\x83\x87\x98\xa9\x6b\x95\x68\x55\xac\x52\x9d\x19\x15\xbc\x6c\x67\xd8\xbd\x3e\xd9\x2a\xf9\xb9\xf4\xf7\xdb\x96\xcb\xc8\x0b\x06\xd7\xf3\x8c\xb8\x2c\x82\x32\x7d\x57\x34\xe4\x50\x33\x1f\x7f\x0a\x8d\xb6\x28\xaa\xb4\x24\x82\xfe\x5d\x25\x82\x80\xd4\xff\x25\x21\x91\x12\x9e\x6a\x29\xe1\xc5\xb2\xfe\x42\x9e\x0a\x69\xfb\x7d\x36\xa9\x90\x10\x01\xa6\xa9\x91\x5c\x4d\xf4\x47\xce\x26\xfa\x33\x1f\x86\xda\x75\x1b\x92\x1c\x14\x1e\x09\xfb\xee\x8a\xc6\x3b\xc2\x0d\x9e\xde\xc2\x98\x82\x2a\xbf\x35\xfa\xe0\xa8\xa2\x60\x58\xbd\x12\xb5\xb8\xda\x96\x98\x28\xd8\x29\xa0\x82\x7a\x50\x80\x18\x44\x8c\xfa\x68\x09\xcb\x33\x8e\x44\x58\x0b\xba\xa1\x21\xb0\xe8\xf7\xfd\x26\x89\x6c\x07\xa2\x65\xc4\x32\x8f\x63\x8d\x19\x7f\x17\x1c\x0c\x55\x92\x57\xe0\x67\x20\x60\x8e\x4c\x5c\x69\x1f\x6c\xcb\xde\x7b\x5a\x0a\xed\x61\xa6\xf0\x30\x13\x64\x00\x1a\x85\x7b\x3c\xac\xfe\xc6\x33\xb1\x7c\x56\x45\x11\x6f\x65\xc2\x21\x02\x16\x0e\x54\xe8\xe3\x26\xc4\xc6\x80\xec\x00\x6c\xc8\xc1\xc6\x0c\x3f\x48\xb1\xad\xe7\x2c\x57\x61\x0a\xf6\x34\x84\xb6\x50\x37\xd3\xb6\xd7\x9c\xa8\x31\xd6\xf4\xb5\x91\xd2\x47\xa3\x63\x7a\x74\x75\x08\xd5\x83\xa3\x93\x1e\x8e\xe3\x4f\x1c\x9d\x31\xbc\xd1\x84\xec\x63\x85\xda\x78\x35\xe9\x4e\xb2\x24\x4f\x0d\xd5\x69\x8f\xa2\x5a\x5d\x21\x93\x0a\x6a\x4f\x28\x39\x92\x9c\x34\x38\xfc\x56\x11\xa4\xa1\x1c\x0d\x4f\xf4\xcb\xee\x79\x1f\x4d\x62\xf8\x5a\x61\x10\xae\x57\xe6\x7a\x2d\x5d\x40\x5c\xab\xfd\x19\xcd\xf4\x80\xba\xea\xf9\x09\x9b\xbf\xa6\xb2\xa1\x87\xa6\xa1\x2c\xc8\x9c\x62\xa9\x4f\x0d\x78\x57\x0a\x90\x32\x11\x1a\xc5\xef\x8a\x8a\x2c\x48\x5c\xd0\x8b\x0b\x9e\x75\x08\x34\x32\x5e\x4c\x5a\xb0\x23\x2f\x8d\x6e\xe5\x4a\xcf\xc3\x54\xdc\xd2\x8b\x8b\xaa\x53\xb1\xb4\x36\x31\x0b\x6f\x10\x73\x31\x6c\x08\x2c\xe9\xa2\xdf\xdf\x8e\x97\xc7\xa3\x6d\x66\x0e\xe2\x14\x38\xa1\x4c\x6a\x39\xf1\xb2\xad\xbc\x57\x89\xea\x77\x56\x8b\x5e\xc7\xaa\xce\x5f\x9b\xd5\x09\x5e\x71\x8a\xcb\x3b\x14\xd7\x20\xf2\x06\xb3\xd5\x54\x9f\x3e\xbc\xbf\x26\xdc\x83\xf3\xf3\x98\xec\x09\xe7\x9c\x91\x48\x8b\xf9\x4c\xd4\x01\xa6\xc9\x24\xef\xb2\x1b\x91\x6f\x87\xdd\x58\x0a\x5b\x13\x06\xae\xd3\x1f\x22\x76\xbf\xa0\x48\x3c\xba\xe6\xc5\x33\x5e\x19\xe2\x47\x2c\x9f\x7d\x2d\x26\x22\xe7\x7a\x23\x2b\xd2\x24\x8c\x80\x81\x11\xc2\xc2\x01\x09\x45\xc4\xed\xf7\x1a\xac\x50\xd2\x1a\x57\x15\xe6\x18\xcb\xbc\x2d\x04\x3c\x14\xf7\x7a\xab\x56\x74\x0e\xf7\xac\x14\x8f\xf9\x82\xce\x1b\x5a\x22\x4c\xd4\xc8\x4f\x5b\xe3\xb6\xdf\x4f\xfe\xa1\x8c\x6c\xf1\x1c\x8a\xd2\x05\x59\xa6\x6d\x61\x5b\x96\x50\xd4\xac\xc8\x0b\x3b\x5c\x6b\x6f\xc7\x8d\x9e\xef\xc4\x3e\x2a\xb9\xf1\x72\x15\x4e\xb3\x01\xcc\x65\xbf\x2f\x2f\x28\x15\xfa\xbd\x31\x87\xa7\x30\xce\x38\x11\x66\x9e\x74\x6c\x00\xa4\x5b\x98\x3a\xc0\xce\xb2\x69\x6b\xe4\xfa\x6e\x7c\xab\x56\xc4\xff\xfe\x96\xe5\x07\x5b\x6e\x7c\x65\xd1\x5d\x8f\x95\x5b\x69\xb3\x1e\xbc\xbb\x7d\x04\x7e\x50\x89\xb2\x12\xa8\x85\x34\x12\x28\xbd\x81\xac\xee\x41\xb1\x54\xac\xb6\xa7\xcd\x8a\x94\xba\xa7\x3f\xe9\x08\x8a\x62\xa5\x65\x10\x2e\xfc\xad\x0b\x9e\x82\x2c\x6f\x3a\x1c\x17\x18\xdf\xa3\x20\xf2\xf4\xb4\x17\x04\x39\xa1\xce\x95\x58\x69\xd0\xd0\xba\xdf\xaf\x83\x33\xe0\x3a\x3e\xa8\xaf\xf4\x3e\x95\xf4\x60\xf1\x9f\xfc\xcc\x0d\x29\x9c\x27\xbc\xc2\xdc\xc9\x6f\x45\x52\x63\x84\x1f\x30\x57\x55\x99\xd5\x90\xb1\x07\xbf\x13\x79\x94\x5b\x39\xe7\x71\x0d\xd7\xbb\x46\x9c\xf3\x13\xb3\x60\xb4\x04\xe1\xe8\x93\x93\xa9\x96\x54\x4c\x8b\x19\x94\xfd\x3e\x62\x7c\x25\xde\x12\xf4\xd1\xa9\x7f\xd0\x4a\xa2\xc1\x3c\xcf\xf4\x1e\x17\x0f\x85\x62\x84\x38\xa1\xde\x17\x5e\x79\x3c\xac\x7b\x2f\x64\x19\x23\x96\x45\x5d\x3c\xbe\xe1\x6b\x8c\x8d\xfb\x01\x32\x18\x69\x1b\x8f\xae\x78\x1a\x86\xd1\xe0\xc5\x78\xcc\x4c\x90\xdf\x08\x2e\x09\x8d\xd8\xda\x99\x8e\x35\xfe\xcd\x11\xb9\x38\xeb\xb4\x58\xa2\x09\xb8\x86\x95\x18\x62\x9b\x83\x24\xa4\xf1\xd3\xb1\x47\x25\x9e\x96\x86\x55\xa7\xb9\xbd\x8b\x78\x66\x6d\xa1\xdf\x1f\xa0\xf6\x19\xad\xde\x37\xb1\xaa\x33\xb1\x68\xd8\xb1\xcf\xe5\xee\xf8\xcf\xed\x0a\xcb\xd6\x4c\x15\xbe\xb1\xca\x6e\x76\x65\x37\xbb\x77\xd2\x40\x7b\xef\xa2\xbe\xab\x76\xdf\x91\x20\xd8\x1f\x90\xfc\x62\x08\xae\x66\x7e\xa6\xe9\xc6\x51\xb4\x5b\x89\x51\xff\x08\xbd\xd5\x34\xca\xe1\x5e\xec\xfe\x6a\x14\x5b\x39\x65\x70\x2f\x76\xd6\xb1\x24\x65\xfa\x8d\xf2\xc2\xae\xad\x64\xdf\x7b\x65\x80\x28\x02\x10\x6a\xaa\xb2\x20\x2f\x40\xcd\x53\xa6\x51\xf1\xd0\x14\x44\x7d\x00\x57\x6c\x6d\x3f\x22\xf5\x64\x4e\x42\x80\x0b\xf9\xb1\x3a\x3e\x77\xc5\x62\x11\x59\x8c\x19\xef\x9f\x7f\xe5\xea\x73\xb1\x63\xc8\x36\x32\xa5\x50\x81\x89\x2d\x5e\x2a\xb6\x36\xe0\x03\x93\x5d\xe8\x9a\xd7\x62\xc1\x7c\x13\xb8\xc6\xde\x63\x73\x4b\x91\x28\xb0\xf0\x4d\x49\xb5\xa3\x66\x6f\xa2\xbe\x75\x17\x1d\x5f\x25\xa2\xdc\xae\xab\x9f\xf8\xef\xac\x9d\xee\x35\xa0\xdb\xc9\xeb\xc2\x2e\x54\x37\xf5\xef\x27\x2a\x49\xaa\x63\x7e\x67\x1e\x8f\x76\x27\x6c\xd9\x6e\xa8\x3e\x69\x7a\x75\xda\xdb\xa9\xf6\xd3\xdd\x1a\x1d\x4b\x74\xf4\xff\x2d\x63\xb3\x5d\xf7\xb4\x39\x4d\x2c\xeb\xf9\x77\xdd\x71\x9f\x6a\xfb\x8f\xab\x30\x53\x05\x55\xb4\x18\x0c\xaa\x30\x08\x0e\x55\xa6\x0f\x35\x5f\x1b\xd2\x5c\x22\xff\xf7\x7e\xcb\xcb\x85\x63\x49\x83\xbe\x62\x2a\x21\x4d\xa7\x5c\x4b\x4d\x7c\xac\x3a\x3c\xcc\x49\x62\xd5\x1d\xa8\xf2\x03\xd1\x94\x86\x5e\xc5\xa1\x7e\x60\x71\x2c\xb6\x0c\xba\x2c\xb2\xab\xa7\xc2\x50\x41\xe1\xc6\xe8\xe2\x76\x43\x5c\x29\xd2\xb4\x86\x78\xea\x25\xdc\x47\x30\xb3\x81\xce\xac\x0d\x37\xa7\x3f\x20\x3b\x39\xf6\x64\x0b\x53\x7b\x44\x67\xa0\x08\xba\xba\x47\xa1\x92\x62\x35\xde\x4c\x6e\x3f\x92\x84\xd3\x5b\x97\x93\x78\xf8\x85\xaf\x29\x21\x44\x93\xf5\x52\xd4\xca\xd6\xd1\x3f\x93\x04\x89\x45\x5d\x0b\x3f\x79\x80\x3c\xa1\x56\x18\xaa\xc5\xb1\xfb\x7d\xee\x7e\x1a\xa3\xeb\xe8\x32\xf0\x06\x77\xa2\x3d\x5d\xef\x58\x8a\xc1\x5c\xed\x0c\x77\xdb\x90\x72\xd6\xff\x6a\xac\x92\x12\xed\x8a\x5d\xef\x21\x89\xa2\xb2\x99\xe5\x02\x41\xbf\x62\x49\x95\x2d\x45\x85\xfa\xcd\x02\x9d\xf4\x23\xa7\xce\x79\x65\x7e\xc3\x55\xc9\xcc\x2e\x25\x31\x78\xaa\x63\x90\x54\x36\x74\xab\x09\x2a\x19\x0c\xe7\x38\x36\xaa\x5b\x54\x35\xaf\x1e\xe0\xf4\xd8\xcc\xe3\x03\xa3\xc7\x79\xb7\xe4\xea\x47\xf1\x28\x93\x02\x24\x06\x55\x4d\x47\x43\x92\x27\xab\xd6\x49\x99\xdb\x82\x2f\x44\xd9\x2e\x08\x6e\xc2\x91\x44\x96\xf9\x0e\x8e\xc7\xd0\x99\x2e\x6a\xd7\xc4\x97\x5d\x99\xb2\xa6\x13\x5b\xf8\xaf\xf6\x04\xfa\x71\x9d\xb7\x3d\x3b\xe8\xcd\x90\xe0\x5a\xcf\x43\xac\x11\xef\x51\x79\x53\x2c\x16\xbc\x7a\xc8\xeb\xa6\x69\xa8\xb0\x01\x55\xba\x60\x77\xae\x53\x03\x80\x9b\x0e\x67\x68\xb2\x60\x1c\x66\x2d\xa8\x1a\xcb\x4c\xb1\x9d\x42\x7b\x75\x6a\x6d\x03\x4c\xd2\xe7\x85\x64\xba\x26\xed\xad\xf9\x62\x51\xb2\x1e\x56\xd9\xd2\xc1\x08\x96\x74\xe0\xed\x37\x45\x7c\xc4\x22\x65\x24\x15\x29\x23\x3d\x7b\xa0\x3c\x65\x57\xd7\xa9\xcc\xd6\xac\x90\xdb\x9a\xbd\x61\x3b\x95\x28\xec\x87\x58\xbd\x26\x74\x6f\x23\x8e\xc7\xf9\x74\x1e\xe2\x3b\xa4\x0f\xe9\xf5\x65\x7d\x5b\x90\x7e\x3f\x59\xa4\x74\x05\x51\x6e\x22\x6e\x87\x93\x61\x3e\x22\x33\x3a\x84\xa5\xce\xdc\xa6\x29\x81\x72\x2a\x66\xd4\xd8\x56\x0c\xd1\xb6\x62\x09\xb5\x78\xcc\xb7\xd6\x76\xe4\xc1\xd9\x8e\x54\x0d\xb4\xbb\xa2\x0f\x69\xdd\x10\x02\x8b\xc6\x1f\x85\x0f\xed\x8d\x3d\xa9\xff\xce\xe6\xc4\xaf\xd2\x54\x6f\x4e\x31\x50\x76\x73\x6a\xd8\xa2\x8b\x84\xa1\x53\x6c\x80\x75\xf0\x9a\xfe\x51\xeb\x5e\x7c\xc4\xba\xa3\x16\xc0\x32\x65\xb8\xd0\x2b\xb3\xce\xdb\xb4\x86\xb9\x65\x8a\x98\x65\xdb\xba\x65\x5b\x36\x04\x1e\x4c\x89\x75\x9a\x7a\xe5\x80\xa1\x0f\x7e\xac\x91\x88\x2d\x14\x44\x6f\x09\x4b\xeb\x78\x3f\x1e\x70\x3f\x36\x9a\x7a\xca\xd7\x76\x3f\x8a\x68\x3f\x36\xa9\x3e\x9a\xb8\x07\x29\xdd\xbe\x6f\x04\x8b\xa6\x58\xfc\x73\x2b\x95\x5b\xcd\xee\x7b\x82\x91\x17\x3d\x6c\x6c\x43\xb2\x71\xa4\x65\x78\x1e\x1e\xb5\x77\x2a\x66\x41\x17\xe8\xd7\xa1\x82\xee\x36\x4b\xbd\xcd\x08\x38\x8d\x43\xb3\x16\x54\x32\x0c\xe8\x82\x0e\x35\x3d\x9e\x54\xf6\x1d\x4b\xa5\x7b\xc7\x06\x91\x15\x96\x9c\x16\xb3\x98\x43\x55\x3e\x13\xcb\x67\x9c\x14\x48\x98\xd4\xe2\xb1\xdf\x4f\x0a\xf3\xeb\x63\x1b\x23\x50\xa2\xad\x00\xc5\xe7\x30\x65\xa9\x84\xd2\x3c\xa4\x35\xd4\x29\x2d\xcd\x31\x48\x65\x83\xea\x29\xdd\x91\xba\x2a\xee\x0d\x1d\xa8\xf8\xc4\x4e\x8b\x99\x7b\x52\x4f\xc7\x5c\xea\x92\x17\x94\x16\x76\xcc\x73\x51\xfe\xf1\x66\xed\xe8\x69\x6d\x07\x9d\x52\x3f\x61\x1c\xbc\x29\x96\xca\xa6\x69\xaf\xb9\x53\xdd\x55\x62\x83\x1c\xea\xd8\x08\xde\xa1\x4b\xc7\xa3\x33\x76\x7a\xaa\x84\x8b\x0b\xd3\x3d\x5b\xa7\x47\x2b\x52\x2f\x99\xab\xdd\xf8\x1f\x2a\x61\x80\x2c\xfa\x3b\xd3\x84\x26\xe6\x19\x69\x9a\xbb\x33\x2d\xb6\xde\xe0\xb0\x0a\x96\x41\x65\x76\x32\xaf\xf0\x79\x2e\x50\x47\xd1\x9c\xc2\xda\x9d\xc2\xb2\xa1\x0c\xe6\x74\x87\x6b\x28\x6a\x58\xd1\x5d\x95\xb0\xac\x56\xa5\x3d\x1d\xee\x15\x23\xb0\xd0\x4f\x73\x69\x9f\xe6\x83\xe1\x62\x6c\xc1\x9d\xe2\x65\x43\x4b\x78\xa0\x0b\xf3\x60\x6f\xe8\xc3\xd5\x35\x82\xa2\xf5\x58\x59\x4a\x48\x95\x1a\xad\x28\xa2\x37\x63\x15\x7e\x27\xe6\xfd\x70\xf9\x27\x0f\x08\x14\x91\xb5\x61\xf6\x27\x28\xcc\x6b\xbe\xb0\xaf\xb9\xd5\xc6\xf4\xc8\xc0\x2e\xa2\x55\xee\x63\xc4\xe0\x0e\x11\x83\x12\x1e\x08\xec\x69\x17\x05\x80\x77\xce\x13\xfb\x99\xab\x3d\x5e\xd3\xfd\xe4\xb0\xcb\x45\x52\xbb\x8b\xb3\xf4\x17\xa7\x9a\x0e\x67\x04\xf6\xb9\x39\x9d\xcb\xf4\x9d\x61\x24\x0c\x9b\xfc\xb0\xcb\x7d\xe9\xbd\xad\xac\xcb\xbc\xc3\xda\x2e\x2c\xcc\x74\xe8\x4f\xad\xab\x09\xf7\x95\xa5\x1a\x19\x2e\xc9\x17\xbc\x66\xc8\x2e\x73\x58\xd3\x23\xbd\x4b\x97\x63\xf5\x04\x2c\x67\xf0\x5a\xc3\xf2\xa2\x65\xc0\xc9\x70\xd5\x90\x6d\x77\x3c\x6e\xf5\x22\x46\xca\xfd\x71\x96\xed\xe2\x2d\x2d\x5a\xd0\x9f\xc5\xd0\x1f\x7e\x6a\x6d\x20\x0b\xbf\xd1\x11\x5a\xd8\xe6\x32\xfc\x26\x04\xbe\xa7\xbb\xf4\xe1\xea\x3a\x7d\x8b\xc7\xe3\x0b\xba\xce\x76\xf0\x82\xae\xb3\xfd\x78\xa5\xe9\x01\x1b\xc5\xd7\x1d\xba\xfd\xe4\xb5\x7e\x63\xbe\x48\xbf\x4f\x97\xb7\x76\xb5\xfb\xfd\x04\x2b\xa4\xf4\x11\xd6\x78\x2c\xd2\x14\xb0\x21\xfa\xc4\xe6\x98\x52\x33\x42\x72\x6c\xed\x45\xfa\x78\xeb\xd6\xbe\xdf\x4f\x4c\xd5\x2f\x52\xee\xca\x59\xb0\xb6\x0c\xad\x63\x7f\xf4\xc9\xdd\x73\xf5\x1c\xe0\x19\x5f\x74\x45\x29\x7c\x99\x84\xd0\x5e\xbb\x1b\x3a\x8c\x03\x7c\xdd\xdf\x0c\xdd\xfb\x52\x58\x6e\xa2\xc7\x8c\xbf\xb5\x1c\x19\x83\x95\x8e\x90\x01\x1c\xef\x9b\xce\xf7\x5f\x30\x27\xf6\xa2\x38\xa7\x70\xf6\x37\x18\x83\x7b\x97\xdb\xf1\xeb\xd6\x4e\xc2\x70\x44\x6d\x27\x6e\xee\x03\xac\xfd\x3d\x69\x5d\xc7\x0a\x8a\x13\xb7\x6c\xd1\xb7\x19\x54\xd7\xf5\x9c\xeb\x13\xa6\xe6\x99\x69\x31\x26\x02\xd2\x74\xb0\x5a\x3c\x3b\xa3\x50\xfa\xd3\x0f\x3f\xbe\xb9\xbe\xba\x8e\x0d\x1a\xac\x03\x0f\xd3\x95\xb7\x5c\xe2\x99\xfb\xd9\xb2\x97\xa8\x1a\x90\x74\x95\xed\xbe\x2f\xb7\x1a\x43\xdb\x5d\x5d\x93\xf1\xb7\x2a\x29\x8c\x0e\x58\xba\x21\x4d\xa4\x68\x29\xa8\x55\xdd\xd2\x68\x49\xf2\x30\xb8\x27\x26\x56\x93\x6e\x41\x1f\xb1\xaf\x44\xfd\x4a\xd5\xba\x19\x02\x35\x7d\xc9\xbc\x37\x3a\x63\x78\x41\xc6\x45\x4b\x0e\x68\x35\xa8\x51\x4d\x47\x26\x35\xc9\xa4\x58\x33\x14\xa8\x0f\x31\x40\x02\x99\x70\x96\x14\x70\x30\x6e\xc5\x05\x3c\xe6\x3b\x58\xe5\xde\x18\xaf\x6e\x48\x5e\x18\x51\xa2\x04\x01\x3b\xb8\x27\xf6\x02\x27\x04\x86\x46\xdc\x5c\x04\x6f\x42\x45\xcc\x48\x5e\x65\xbb\xe4\x0b\x02\x2f\x80\x11\xf8\x82\xca\xe4\x27\xf8\x22\xdd\xa5\x1b\x77\x43\xda\xac\x78\x7d\x5c\x99\x1e\x09\xc7\x7b\x0b\x0a\x58\x7a\x77\x75\x0d\x0b\x38\x68\x60\xfb\x96\xa9\x55\x2d\xb6\x0f\xab\x9c\x5b\xa5\x12\xf0\xd7\x3b\xe7\xe1\xaa\x37\x9d\x7e\xf7\x93\x75\xb6\x4b\xe9\xf7\xe9\x32\x37\x77\x57\xe3\x6a\x77\x4f\xc1\xb8\x26\x7a\x33\x9e\xa4\xb1\x51\x0b\x13\x7d\xde\x83\xd4\x8f\x13\xb7\x8f\x53\x41\x7f\xd7\x1f\xf6\x69\x32\xe2\x45\x7e\x1e\x93\xab\x9f\x7e\xef\x4a\xcb\x18\x9a\xa3\x8f\x18\xf3\xa8\xc3\x8a\x4a\x7c\xe1\xf4\x7a\x18\xbb\xbe\xd4\x18\x05\x6c\x61\x69\xd1\x0b\x78\xf0\xf6\x16\x4f\xe0\x74\x0f\x01\xdb\xcd\xb2\x2c\x46\xba\x34\x26\x6c\x20\xcc\x02\x96\x54\x24\x2c\xc3\xf7\x1a\x02\x30\x7b\x20\x6d\x5d\xe0\x16\xf2\x93\xd5\x6c\xb1\x9d\x33\x44\xe5\x19\xa1\xb7\xbe\x1b\x05\x2c\x60\x45\x43\x32\xde\xd2\x45\x1a\x5a\xc7\x1e\x23\x78\x36\xf0\x51\x72\xec\x12\x0e\x9e\x40\x77\x9d\xda\xd1\x86\x8a\x64\x0e\x4b\x58\xa6\x0f\x64\x1c\x81\x7b\x5a\x47\x0f\x44\x95\xcc\x0d\x4a\x76\xf6\x7d\x2f\x3b\x9e\x06\x0c\x3a\x52\xb6\x3c\x09\xf8\x34\xfd\xf4\x4b\x47\xc8\x33\x96\x94\xee\xac\x6e\x60\x0b\x92\x34\x67\x07\x7b\xd6\xb9\x80\x3d\x3e\x4c\x1f\x1f\x65\x8f\x0f\xa7\x18\x17\xd0\x1f\x1f\xcf\x3c\xb5\x27\x68\xc2\x70\xc7\x4c\xbb\x29\xb7\x0b\x9b\x0f\x31\x66\xf7\x2b\xff\x1a\x3f\x57\xa7\x3e\x88\x9c\xf2\x1f\x18\x54\xf0\x96\x72\x6b\x36\xaa\x6e\x28\x77\xaf\x1b\xd3\xc9\x68\x31\xca\x6e\xa8\xd3\x2f\xc3\xe0\xf4\x12\xcb\xc7\x04\x06\x54\x74\x38\xae\x82\x8e\x52\x9a\x56\x84\x2f\x13\x41\xe5\xb4\x9a\x81\xba\xa5\x22\x74\x20\x9c\x11\xf0\xa3\xf1\x36\xc3\x74\xae\xeb\x47\x58\xa3\x60\x7b\x4a\x9c\x6c\x32\xc6\x2e\xa6\x95\xd7\x0c\x42\x81\xe4\xaa\xa8\x16\x25\xfb\xf2\x1d\x8b\x4c\xee\x9e\xf0\xd3\x75\x71\xe2\x9d\xb5\xb7\x16\x5b\xc9\xd6\xe2\x9d\xd7\x02\x61\x99\xa8\xfe\x2a\xde\xb1\x1a\xfd\xd0\x56\xaf\x58\xf1\x8e\x79\x5d\x67\xeb\x1f\xd7\x46\x3a\xea\xf7\x93\xde\x5c\xff\xc0\xba\xe8\x4c\x69\x2b\xd9\xd6\xa0\xeb\x51\x1d\xaf\x19\x93\x18\x4d\x38\xf0\x62\xda\x88\xf3\x74\xba\x6b\xd9\x0e\x54\xb6\x37\xb2\xd6\xf6\x30\x8d\x79\x91\xa7\x90\x59\x8b\x2b\x0d\x35\xda\x7f\x82\xd5\x66\x4a\x04\x2d\x88\x57\xeb\x96\xfd\xbe\x57\x2c\x7c\x69\x55\x10\x65\x2b\x41\x17\xe0\x3e\xc7\x44\x69\x1a\x17\xfd\xfe\x45\xdd\xef\xff\x90\x70\xb7\x24\x30\x55\x50\x00\x9b\x19\x47\xe4\x6d\xae\x38\x54\x71\x71\x5c\x4c\x5d\xbc\x32\xc5\xf1\x6d\x7b\x56\xb9\x6c\x5c\xc9\x28\x7b\x6c\x3d\x30\x34\x28\x35\x7c\xb0\x52\x43\xb3\xff\x3d\xb8\xb3\x61\x54\xf2\xa5\x55\x7f\x39\x55\x12\x31\x45\xd1\x69\xdf\x52\x26\xc8\xe9\x30\x10\xd4\xfb\x9f\x77\x92\xb9\x86\x8c\xbf\x66\x99\x33\x19\x62\x28\x37\xe5\x04\xbe\x66\x59\xb1\x58\x7c\x2e\x76\x68\xcd\xda\x80\x54\x62\xa3\x4f\xd6\xd7\x2c\xab\x71\x0f\x4c\x96\xeb\x89\x04\x7b\x3e\x93\xe0\x64\x39\xe7\x85\x9a\xae\xd4\xd9\xae\x2b\x2f\x20\xe0\x8d\x91\x90\xfe\x7c\x1a\x21\xc9\x35\xc0\x3a\x6c\x6a\x3d\xec\x36\x47\xc1\xb6\x61\xef\x86\x3e\xf2\x7a\x0a\x1a\x78\x1c\x8f\xae\x99\x2c\xbe\x3f\x2c\x63\xfa\x7f\x4b\x26\xe4\x83\x58\x0d\xc1\xbd\x3f\xb9\xf1\xac\x51\xb4\x5d\x44\x3a\xde\x7c\x1e\x39\x10\xbd\x18\x81\xe1\xe2\xe7\x23\xf6\x69\x37\x42\x58\x38\xfc\xf1\x01\xc4\x08\x4f\xb8\x43\x63\x71\x2a\x6e\xad\xc8\x24\x11\xfa\xc5\xd7\x3f\xf5\x73\x82\x6f\x3f\xbd\x18\x92\x3c\x11\x18\xa8\xa9\x9d\x3e\x22\x0d\xd8\x33\x68\x74\x65\xed\xf9\x35\x1f\x8e\x09\x62\xc8\xc8\x28\xa2\x98\x83\xcb\x06\xd6\x7b\x82\xee\xb3\xa1\xa7\x34\x47\x43\xf8\x50\x40\x38\x2f\xb8\x06\xcf\x54\x6b\xe1\x97\x39\x8f\x31\xc8\x18\x81\xf1\x9a\x51\x4d\xe3\xf7\xdb\x03\xb2\x10\xff\xda\xcb\x28\xdb\xc2\x49\x13\x55\x4e\x45\x3c\x34\x75\x3e\x7e\xdc\x64\x68\x0d\x71\x35\xe6\xf8\x3b\x4b\x8a\x96\x9b\xe3\x76\x70\xb9\xa9\x0d\xd7\x39\x33\x8f\x72\x14\x6b\xae\x38\xd1\xc5\xf0\x34\x5c\x2e\xa3\x28\x72\x56\x88\x0e\x96\x18\xc8\x8b\xae\xaf\x68\x87\x8c\xfb\x1c\xc4\xcc\xdb\x64\x41\x2b\xcf\x52\x0a\x8e\x2e\xf0\x79\xc1\xcf\x73\x88\x6d\x97\xd4\x96\x9e\xaa\xdd\xe3\x72\xf5\x59\x2b\x28\x5e\xd1\xb2\x4c\x8f\x37\xe6\x78\x2c\xce\x62\xf7\x45\xc0\xee\xa3\xad\xd3\xa5\xfd\x67\xd7\x30\x3a\x3e\xe8\xb9\x5d\xd1\xa6\x31\x12\x48\x8c\x7d\x87\xa1\x93\x3e\x74\x1e\x43\x5c\xb9\xe8\x4a\xba\x6b\x88\xdb\xd5\xeb\x35\x68\x69\xe4\x1c\x7c\xe5\x2d\x07\x5f\x8a\xde\x5e\x58\xd5\x35\xf9\x77\xae\x56\x49\x4f\x54\x3d\xe2\x6f\x43\xb7\xe8\xb4\xd7\x3e\xe9\x3d\xe8\x19\x29\x50\x0f\x7a\x52\xd4\x0a\xad\x6c\x30\xfe\xb6\x44\x7b\x26\x1f\xdc\xee\xa3\x65\xaf\xff\x65\x49\xe9\x9d\xbd\x8e\xff\x03\xc2\xcc\x0f\xfa\x6d\xf8\xef\x91\x5b\x3e\xed\x1d\x52\x3f\xfc\xdc\x09\x0c\xb9\x95\x04\x5e\x54\x67\xa5\x5a\xdc\x0e\xd7\xa1\x81\x0e\x8d\xf3\x88\x1b\x1d\x92\x71\x28\x64\x85\x8f\x10\x15\xb7\xc5\x98\x57\xdf\xfa\x2d\xa9\x0c\x9f\x65\x62\xfe\x5b\xdc\x2e\x1f\x8d\x79\x58\xf6\xdf\x59\x52\x05\x14\xd5\x5b\xba\x5e\x06\xf1\x59\x1b\x49\x75\x15\x6d\xbf\x63\xde\x15\x7f\xf9\x11\xc9\xdc\x0d\x57\x76\x19\xa3\x67\x31\x68\xb7\x13\xe3\x0e\xdb\xb4\xcd\x22\x35\xdc\xcb\xe7\xf5\x43\x00\xd7\xa8\x74\xcb\x00\xd9\xfb\xdc\xe9\xde\x56\xd6\x9b\x55\x30\x6f\xb0\xa1\x20\xa0\xa6\x85\xa1\x57\x82\xb5\x11\x2c\x82\x50\xe3\x0c\xa1\x85\x91\xb4\x30\xce\xba\x24\xb0\xa2\x2c\x55\x50\x52\x39\xe0\x24\xb7\xec\x46\x4a\x69\xe1\xc7\xaf\x4b\xf3\x54\xc1\x0a\xeb\x54\x1a\xbf\x5a\x50\x13\x5d\x98\xe4\xc9\x9c\xca\x81\xcb\x43\xb7\x4f\x0b\x6a\xb2\xa0\xa4\xd5\x80\x11\x30\xc1\xd7\x7e\xcd\xe7\x06\x94\xfc\x96\xaf\x82\x04\xae\x0c\xa0\x6b\xd1\x9c\x63\x0c\x83\x65\xfe\xa2\x93\xd3\x18\x6f\x7e\x82\x6c\x15\x31\xb9\x2b\xad\x9c\xee\xaf\xd6\xa3\x44\xaa\xc2\x6e\x6b\xc2\xce\x8d\xac\x70\x23\xab\xcf\x8e\x6c\xde\x50\xcb\x72\xc6\x5d\x92\x64\xcc\xd0\xa2\xc2\x50\x56\x43\x18\x82\x70\x6c\x5f\x47\x8a\x9d\x6d\x26\x02\xc9\x55\xc2\xcd\x96\x11\x88\xa9\xbe\xdc\x53\x7d\x2e\x2a\x84\xae\x38\x2d\xa0\x9e\x35\xc4\xa0\x9f\x6b\x8b\x7e\xe2\x88\x23\xec\x73\xd3\xc1\x3e\x2f\xce\xab\x5b\x6a\x1c\x74\x73\x16\x07\x65\x4f\xe3\xa0\xa8\x00\x13\xe3\xa0\xa8\xf8\x8e\x23\xf8\xbc\x14\xf3\xb7\x94\xa3\x37\xa6\x08\x31\x0d\x58\x47\x28\x35\xee\x60\xab\x2c\x42\x53\x43\xa9\x0f\xa1\xaa\xed\xf6\xde\x8f\xae\xc6\xee\x24\x5a\x78\x61\xf4\x5a\xe9\x93\x92\x1f\x2c\x46\xd8\xbb\x17\xe5\xa2\xd7\xb4\xf0\xc6\x08\xbb\xea\x20\x9b\xf6\x59\x73\xe8\xe4\x35\xfb\xd4\x77\x69\x5c\x87\xb8\x57\xb3\x37\xb7\x2e\x2e\x9f\x7c\xfe\xda\xfe\x2d\x2f\x46\x5e\x99\x69\x27\xe9\xa1\x78\xc7\xea\xc2\xf8\x4e\x31\x22\xb8\x96\xa3\xdd\x0b\x63\x6c\x8f\x32\x55\x3a\xf4\x16\xf4\x9a\x2a\x66\x74\x18\x3b\x07\x66\x37\x7c\x9c\xa6\x31\x78\x9f\xb2\x59\x66\x0f\x10\x82\xf6\x7e\x9f\x67\xab\x42\x5a\xab\xc3\x70\x13\x35\xa8\x6f\x07\x79\x21\xe3\x2a\x45\xc3\x54\x91\xa2\x51\x6a\x9a\x4a\x6f\xb1\xb3\xcb\xab\x2b\xe4\xd4\x5d\xc9\xa6\x81\x8a\x15\x35\x93\xca\xd3\xb7\x4f\x8c\x1f\x35\xb8\x51\x75\x7e\x07\x9a\x7e\xdc\x43\x4d\xbf\xc3\x10\xcc\xd9\xf7\x7f\xfb\xe9\xe5\x9b\x97\xbf\x7c\x79\xf7\xf2\xbb\xaf\x5e\x7e\xf7\xf2\xcd\x6f\x26\x24\x15\x1d\x42\x88\x43\x31\xe6\xa8\x78\x18\x1f\x95\x29\x6f\x4d\xae\x42\x37\x41\x67\x26\xf7\x8b\x4a\x18\x54\xdd\x38\x35\x84\x8c\xd5\x4d\xdd\xef\x27\x35\x55\x20\x68\x45\x9a\x86\x2f\x13\x11\xaa\x89\x33\x6b\x22\x71\x49\x34\x72\xbb\x0f\x8b\x81\xb1\x10\x9b\xd8\xa6\xfa\x5e\xb6\xdc\xf6\xb0\x7e\x3f\xf9\x2d\x61\x64\xf2\xbc\xae\x8b\x7d\x86\x9e\x61\x34\x39\x8d\x92\xda\xac\xd8\x6c\xca\x3d\x96\xcf\xad\xed\x17\x23\x2d\x4f\x7e\x77\x32\xb2\x88\xed\x19\xde\x4f\xf0\x46\xaf\x8e\x47\x15\xeb\x8f\xfe\x84\xf9\xe8\x6e\xdd\x19\xdf\xf4\xfe\xdf\xaa\x47\x6e\x07\xa3\x89\xca\xe4\xa6\xe4\xca\x24\xc4\x7e\xff\xf6\x2d\xeb\x09\x07\x72\x78\x1b\x77\xac\xac\xd6\xa9\x68\xd0\x4f\xd8\x89\xaa\x6b\x15\x87\x8c\xb6\xf4\x47\x5e\xd8\xc0\xbc\xb5\xf5\xfe\xd1\x8e\x04\x1c\xdc\xd6\x39\xcd\x55\x57\xcb\x78\x67\xcd\xe3\x10\xc0\x82\x40\x5d\x3c\xe6\x1d\x42\x67\x5a\xcd\x4c\x4c\x57\x31\x83\x76\x70\xcf\xbc\x76\xe3\xcf\x3b\xe1\xd5\x7d\xb8\xe6\x5c\x74\xa7\xe8\xe7\x1e\x47\x3e\x94\x5d\x95\xf6\x3b\x6b\x23\xa3\x76\x70\xb8\x17\x8b\x7d\x5e\xc1\x52\x08\xc5\xea\x5c\x58\x7c\xda\xf8\x45\xf1\x54\x5c\x11\x89\xe5\xac\xf2\xf6\x57\x2c\x61\x99\xae\xfc\x15\xbe\x65\x73\x93\x80\xb5\x4d\xca\xca\xa4\x98\x86\x4d\xd2\x82\x3a\x7e\x17\x6c\x43\x7c\xdf\xa5\x57\x08\x05\x44\x8f\x58\x40\x8f\xf4\xcd\xdb\x50\x87\xfb\xd8\x18\x1a\x55\x97\x5b\xaa\x52\x96\x19\xa8\x6c\xdb\x49\x0d\xa3\x4f\x86\x4f\xa4\xea\xdd\xc5\x86\x21\xb2\x88\x76\x1a\x34\x98\x7a\x9f\x8b\xc5\xde\x15\x56\xa6\x70\x94\x04\x8b\x7e\x3f\xd9\xa4\x74\x71\x39\x8f\x71\xb3\x64\x31\x18\x91\x4b\x3b\xe7\x9f\x36\xc5\x9c\x57\x0f\xa9\xfd\x34\xa1\xa0\x3e\x37\x1c\x40\xd8\x91\xc3\x26\xa5\xcb\xcb\xc4\x2b\x58\x21\xc9\x24\x27\x9e\xcf\x8b\xa2\x6e\xdf\x34\xc9\x5b\x5f\x69\xb2\x1b\x2c\xc9\x65\xd9\xea\x7c\x67\x3a\xd7\x3b\x60\xfb\x6e\xb6\x66\x98\x6e\xcd\xcd\x18\xde\x88\x4d\xba\xbd\x5c\xb5\xea\x6e\x4d\x5d\x53\xcc\xd6\x36\x8b\x7d\xef\x3d\x69\xdc\xb5\x4c\x29\xd6\x81\xf3\xbd\x06\xde\xd6\x2c\xb1\x62\xc5\xf4\xde\x07\xf9\x7c\xc6\x9d\x6e\xbd\x55\xde\x9a\x3b\x9e\xef\x37\x89\x7d\x22\xe1\xce\x67\x96\x71\x66\xb4\x1f\x73\x51\xcd\x0b\x95\x44\xfb\x41\x74\xad\x7b\xda\x5d\xc5\x22\xbd\xce\x87\xf0\x4d\x52\x01\x92\xf0\xa1\x19\x5d\xfc\x1b\x1b\x47\x45\xba\x0f\x6c\x0d\xee\xd0\x10\xf1\x1e\xe9\x04\x1c\xc5\x2a\x1e\x85\x59\x19\x33\xc6\x60\x31\xb0\x4e\xe9\x83\x15\xa1\x5a\xfd\x94\xb5\xd3\x4f\xd9\xc4\x81\x42\xbb\x3a\x44\xe8\x3d\xd9\x7a\xb1\xc5\xd8\x43\x4e\x0b\xc6\xab\x14\xe7\xce\x2f\xac\xf5\x08\xdb\x38\x7b\xf3\x39\x75\x48\x81\xc3\xd2\xed\x27\xfa\x99\x98\x53\x71\x43\x93\x3a\x2d\xc9\xd5\xf5\xc4\x60\xc6\xb9\x75\xf2\x9a\x8b\x1b\x2a\xaf\xae\x27\x73\xab\xe0\x95\x8b\x5b\x5a\x0c\xe4\xd5\x75\xbf\x9f\xcc\xa9\x2d\xd4\x95\x1f\x3d\x31\xe2\x0a\x0a\xca\xb3\x79\x51\x33\xa5\xb1\x8e\xd4\xfe\xfe\xde\xdc\x52\x37\x30\x87\x98\xab\x7e\x5f\xa4\x32\x2d\x6e\x99\x59\xab\xe3\xd1\xf6\x66\xf3\x06\x72\x50\xdc\x0c\x8f\x47\x4b\xd7\x25\x73\xeb\x5a\xce\x0c\xcc\x4e\x8f\xc0\x3c\x0a\x8a\x79\x62\xe5\xc3\xb2\xbd\x15\x58\x9f\xb3\x45\xd9\xe7\x3c\x28\x18\x51\xe6\x0f\xe5\x4d\xa5\x97\x49\xe3\x46\x39\xbf\x75\x9a\x7b\x03\x4c\xb4\x04\x8f\x47\xc1\x0c\xc2\xe8\x7d\x41\x18\xac\x98\x65\x3b\xdb\x6b\xb4\xc5\xb0\xb7\x28\x73\x74\x02\xde\x9e\x9c\x00\xbf\x78\xb9\x80\x78\xf1\x72\x09\x73\x51\x57\xad\xf0\x79\xe0\xfa\xab\x5d\xdb\xa5\x3e\x35\x73\x2a\x52\x09\x2b\x5a\xa4\xd2\xea\x8a\xb5\xe7\x5e\x32\xe3\x39\xf9\xd1\x0a\x4c\xbd\xf6\x78\x58\x7e\x36\xe1\x03\x1a\xf0\x4c\x9d\xd2\xef\x27\x3a\xed\xea\x9a\x00\x5a\xcb\xd7\xc1\xba\xab\x2b\x5b\xd4\x1d\xe8\xa7\xc2\x2e\xad\x88\x7a\xb0\xa4\x23\x9b\x54\x29\xe5\x79\x35\xa0\x11\x05\xc9\x26\x22\xe5\xb9\xb8\xba\x86\xaa\xc1\x00\xb5\x73\x72\x7a\x96\xcb\x89\x3f\x3f\xf5\x64\x91\xd2\x79\x1e\x46\xad\xb1\x9c\xc5\x80\xce\x49\x1e\x97\x19\xd0\x55\xb7\x4c\x4a\x57\xe8\x08\xe1\x3b\x95\x2c\x60\x08\x95\x39\x7f\x03\xe6\x75\x12\x74\xce\x16\x73\xec\xde\x7b\xe1\x5a\x1c\xa8\xed\xe4\xb4\xb5\x85\x92\x27\x63\x67\x13\x95\xed\x52\xe5\xbd\x04\xc6\xeb\x1d\xe5\x0c\x2a\xc3\x44\xc8\x75\x5a\x85\x5c\x8a\x28\xd2\x59\x84\x24\x69\xfc\x6b\x3a\x03\xc4\x9b\xe2\x38\x67\x9d\x67\x9c\xf5\xfb\x9e\x0d\x1c\xfd\x74\x68\xdf\x99\xa4\xcc\x47\x1c\xf7\x57\x62\xa2\x7c\xf8\xf2\x84\x6b\x9c\xca\xf0\xa1\x5e\x7c\x34\x1f\x4a\xe8\xd7\x43\xed\x5d\x48\xce\xbb\x62\xae\xf8\x3b\x16\x94\xff\x1d\x9f\xea\x2e\x62\x54\xdd\x21\xcb\xfc\xfb\x73\x8c\xa0\x3b\x19\x58\x3e\xae\x05\x8c\xbc\xff\xbc\xb2\xee\x83\x3a\x81\x6b\xec\xd4\x8c\xb2\xb3\xeb\xf5\x93\xe2\x7c\xe9\x4f\x34\x82\xc7\x76\x6d\x46\xd3\x13\x6c\x33\xbd\x76\xdf\x9f\xc6\xcf\xc1\xc7\xab\xcd\x2b\xf3\x2f\x56\x87\x85\xd6\x49\xf0\xaf\x58\x9b\xcd\x85\x2f\x4d\x2b\xc9\xdc\xfe\x56\xd2\xfe\x34\x69\xd7\x2e\xf0\xc7\xad\x0a\x10\x0c\xfd\x7a\x9a\xf4\x5b\x9b\xed\xa7\x11\x5a\xf3\xce\x9e\xa6\x07\xc6\xfc\x99\x4c\x8d\x1c\xb4\x2a\x36\xbc\xe2\x8a\x17\xa5\x75\xa3\xdf\x5e\xfc\x8f\xd9\xec\xce\xf6\x35\x77\x35\x93\xa2\x7c\xc7\x42\xf1\x73\xdc\x99\x93\x36\x51\x22\xe9\x78\x81\x6c\xdc\x45\x8b\x21\xf6\xde\x2d\x19\x06\xb7\x37\x1a\xb4\x0f\xe1\x83\x10\x0c\x60\x62\x3d\x38\x6a\xba\xd4\xd5\xf0\x47\x4f\xd3\x73\xe1\x1c\x82\x44\xde\xc6\x3b\x9e\xf8\x7e\x44\x30\xf5\xb4\x83\xd4\x8d\xa1\x19\xfa\xe9\x3a\xb4\x1d\x74\x4a\x74\xa2\x10\x8f\xa7\xad\xae\xe8\x05\x1e\x6e\xc9\xd0\xf1\xac\x5f\xbf\x84\x05\x22\x20\x6e\x44\x53\xe3\x38\xfd\xd6\xb5\x82\xb6\xb3\x5d\xfb\x35\xaf\x19\x7a\xee\x20\x70\xb0\x85\x73\x0e\x71\xb5\xbc\x02\x4d\xe3\xe5\x3d\x9b\xd8\x6b\x08\x31\xb2\x4a\x7c\x19\xf5\xe8\x8d\x9e\xc8\x29\x4b\xf7\xe0\x01\x15\x3e\xe0\xb8\xd4\xe6\x9a\x61\x0d\x4b\x73\x72\x98\xaa\x19\xd1\x0b\x6b\x6e\x65\x3b\xb9\xa0\x95\xb9\x72\xa7\x55\xac\xd7\x90\xe0\x2f\xa3\xa6\xf7\x32\xa9\x35\xc4\x15\x84\x40\xf8\x92\xad\xaf\x42\x7f\xe9\x61\x7f\xee\x6f\x7c\x8b\x4e\xfe\x5e\x26\x2c\x80\xd8\x18\x93\xb5\x34\xb2\x9e\x9a\xee\x1f\xfd\xde\x86\xea\x1f\x98\x7a\x18\xe6\x37\x89\x82\x96\x1f\x8f\x83\xe9\x23\x9f\xce\x50\x6c\x23\xf5\x0f\x9c\x73\x3e\x9d\x35\x20\xe9\x17\x32\xa9\x40\x91\xf1\xbd\x1e\x99\x43\x89\x65\xe2\xc6\x86\xc4\x2c\x8e\x38\xe1\xa0\x88\xc6\x87\x75\x41\x83\x2d\xdb\x5b\x1c\x65\x9b\x5c\x8b\x3e\xeb\x56\xf0\xe7\x69\x23\xc2\xb1\x01\x34\x86\x2d\xf4\x5c\x9f\x3b\xd0\xf7\xbe\xf5\x0a\x54\xd7\xb9\xe5\xfa\x0a\xe1\xe4\x1f\x3a\x2b\xa6\xca\xe9\x61\x59\x9e\x49\xf7\xa7\xe5\x4c\xa5\x7f\xeb\xb8\xdc\x99\x7b\x82\x37\xe2\xac\xb2\x85\x7d\x31\xd1\xc3\xd7\x5d\xb0\xf4\xc1\x8d\x77\xce\x97\xac\xdd\x2e\x3a\x3e\x74\x1e\x1b\x6a\x3a\x44\x13\x6a\x17\x59\xeb\xa6\x1c\xa7\x69\x4d\xac\xfe\xfc\x5e\xaf\xad\x85\x33\x7c\x5a\xcf\x48\xa4\x0d\xe3\x2d\x95\xe6\x74\xee\x2d\x95\x12\x7d\x29\x85\x26\xa6\x5d\x12\x26\x40\x65\x4d\x8e\xb8\x62\xeb\x9f\x8c\xb1\x92\xae\x66\x8d\x95\x8c\x3b\xdb\x90\x9b\x18\xa4\x57\x57\xf9\x26\x99\x43\xc2\xfd\x61\xad\xf4\x61\x54\x61\xbb\x35\x76\xed\x5c\xa2\x44\x8f\x8c\x39\x48\xa8\xef\xe1\xbc\xc7\x54\xdd\xb7\x26\x2e\x53\xb4\xca\xf8\x27\x27\x2a\xd2\xa0\x9d\x55\xfc\x8c\x09\xf7\x1d\x3f\x5f\xd2\x25\x46\xcf\x56\x01\x2c\x46\x03\xe6\x30\xff\x28\x29\x54\xfc\x68\xf0\x93\x47\x83\xbb\x1d\x77\xa6\xd8\x7a\x43\xd1\xc1\x95\xe5\x4e\x78\x48\xbe\x93\xd3\xca\x0b\x39\x66\xee\x8a\x09\xe0\x1d\x04\x8a\x8c\x35\x79\xd6\x3a\x68\x95\x26\x5c\x0d\xaa\x82\x23\x30\x80\x16\x6d\xac\x79\x8c\xb0\x60\x66\x04\xce\x5c\x89\x90\x17\xa5\x06\x04\x06\xb3\xc2\xa5\xb6\xf9\x16\x95\xc1\x4c\x7b\x5d\x75\x8e\x37\xbd\xe0\x0e\xbb\x93\x68\xe4\x06\x35\x6d\x3f\x2c\x87\x06\x9d\x82\x43\x49\x5f\xcb\x84\xfb\xd7\x18\x6a\x02\x73\xfa\xd6\xb8\xd9\x28\xc1\x65\x90\x31\x77\x98\x52\x69\x7f\x00\x77\x88\x52\x69\x7f\x80\xa4\x07\x8b\xa1\xe6\x23\x88\x9d\xcc\x19\x2a\xc9\x92\x06\x8e\xa4\x71\xe4\x80\x21\xd1\x7e\xd5\x98\xba\xf9\xf9\x5b\xae\xb2\x7d\x63\xf4\x70\x86\x68\x06\x6d\x5b\xed\xf7\x93\xa8\x8b\x61\xa3\x47\xd5\xc6\x49\x0b\xe0\x27\x78\xa7\xd4\x08\xc3\x39\xec\xc5\x9a\x6f\x27\x28\xf8\x52\x1a\x83\xd0\x68\x78\x5d\x15\x65\xfc\xdb\x1d\x05\xcb\x75\xf4\x4b\x15\xde\x61\xa3\xb2\x82\x1e\xb0\x17\x75\xf1\xf8\x42\xcf\xe1\xbc\x61\x13\x9e\x4f\x24\x48\x7d\x7c\x74\x2c\x34\x66\x91\xb3\x9a\x11\x88\x6c\x3f\xc2\x7b\xe4\xd3\xae\x75\xda\x75\x3b\xed\x53\x9d\xf6\xa9\x71\xeb\xde\x69\x33\x76\xf9\x68\x09\x66\x47\xdd\x5a\x97\xee\x70\x68\x51\xc2\x12\x02\xcd\x5c\x20\xd7\x64\xa7\x09\x62\x4d\x0b\x2b\xc7\x41\x99\xbb\x8d\x5b\x69\x32\x3f\x04\x26\xc6\x78\xbb\xa7\x84\x9a\x98\x24\x1b\x5a\xa6\xab\xab\x6b\xf0\x94\x64\x35\x49\x8c\xd1\xd5\x62\x50\xc0\x03\xdd\xa4\x05\xac\xe9\x66\x50\x60\x78\x9f\x3a\x9d\xeb\x9c\x14\x73\x06\x98\x93\x16\xe8\xcd\x84\xe4\xc9\x96\x46\x8d\xd4\xa9\x4c\x8b\x88\xf2\xd3\x29\xf3\x81\x1c\x14\x79\x84\x6b\x83\xa3\x93\xc5\x24\x79\xa0\x25\x6c\xe8\xc3\xa0\x80\x05\xdd\x0e\x0a\x58\xd2\x6d\xaa\x3b\x7d\xd0\x03\xd4\x39\x29\xe6\xa4\x98\x33\x28\x08\xac\xe9\x83\x26\x6e\x47\xf9\x02\x76\xd7\xf9\x16\x76\x9f\xe6\x4b\xd8\x8f\xf2\x07\xd8\x5f\xe7\x1b\xd8\x7f\x9a\xaf\x9b\x48\xf9\xf6\xa9\x90\x7b\x4e\xeb\x56\xc4\x8e\xb1\x8d\x3b\x08\xbe\x4c\xa4\xab\x31\xa7\xbb\x2a\xe1\xa8\x5e\x5b\x65\x3b\x47\x4a\x1b\x83\x1f\x95\xed\xe8\x4f\xfa\x3a\x5a\x10\x63\xef\x1e\x81\xd8\x76\x60\x1e\x29\x90\xc6\xe5\x5c\xa9\x33\x86\x22\x46\x34\x1a\x71\x90\x35\x2c\x89\x99\xab\xc0\x5a\x5a\xa5\x98\xe5\x02\x88\x23\x0f\xaf\x70\x3c\x3c\xe3\x12\x52\xa2\x4b\x48\x53\x09\x59\x95\x62\x5a\xce\x60\x9e\xed\xf4\x14\xf4\xa3\xb6\x4f\x8b\x96\x00\xd6\xa4\xd1\x38\x31\xad\xa1\x4c\x47\xde\x3f\xd4\x3e\x75\x1d\xc7\x1c\xde\x41\xed\xac\x7c\x70\x38\x56\x7e\xd8\xf5\x66\x8d\x7b\x50\xd0\x16\x4d\x35\xe5\x33\x74\xb0\xd1\x7d\x8f\x74\x7a\xe4\x1a\xa0\x0c\x0a\x55\xf3\x86\x0a\xc3\x5b\x17\x11\xfb\x7d\xa1\x77\x44\xba\x40\x4f\x02\x83\x2d\x67\xbb\x04\x23\x65\x97\x37\x31\xf7\x77\x92\xc4\x5f\x83\x92\x5c\x5d\xe7\x43\x78\xa0\x2c\xdb\xa7\x4b\xf3\x10\x9d\x55\xe9\x67\x5e\xa5\x3f\x32\x8b\xd5\xb5\x63\xc5\x9f\xfa\xac\xd6\x4f\x7d\x5e\xa7\x7f\xd4\x00\xa7\x55\xac\x91\xbf\x85\x39\x49\xe7\x57\xd7\x20\xe9\x43\x5a\x5e\x5d\x8f\xdb\x61\xcc\x44\xb6\xde\x96\x8a\x7f\xcb\xf6\x9f\x7b\xf5\xa9\x96\xcb\xa3\xb3\x05\xbe\x75\x90\x4f\x76\xc3\xd0\x17\x67\x22\x6b\xf8\x9c\x73\xc1\xcf\x4c\x2b\xc6\xd4\x20\x8e\xa2\x56\xb4\xe3\xb6\x8d\x3e\xd0\x4f\x6c\x52\x11\x2b\x67\xa1\xb7\x96\xd3\x08\xfe\xa7\xfa\x5b\xc7\xe3\xd0\xbf\xaa\x27\x0b\xd8\x5d\xd4\xca\x5a\x4d\x6c\x61\x44\x60\x3e\xc0\xf0\x4f\x2f\x83\xe6\x9a\xb3\x79\x68\x5b\x39\xc8\x33\x56\x0e\x27\xee\xd1\x3e\xb0\xf0\x5c\x43\x21\xeb\x6f\xf7\x01\x1e\x35\xb4\xce\xcb\xe0\xe8\x97\xc4\x51\x48\x9c\xf9\xc3\x07\x76\xa1\x3d\x02\xd7\x01\xd7\x1d\xa4\x23\xdd\xc5\xe0\x5a\x77\x32\xb8\x3e\xd7\x0d\xba\x0c\xf8\xc0\x98\x4d\x81\x1f\x31\xa0\x0f\x3c\x80\x3e\xe1\x7e\x74\xdd\xd4\xf7\x0f\xd4\xb7\xc3\x41\x8f\x4d\x8f\xac\x1c\x5c\x13\xd2\xc4\x15\x4f\x58\x25\x53\x3e\x43\x20\xee\x88\xa5\x13\x18\x6e\xe4\x75\xa2\xa1\x95\xf9\x69\x41\x63\x2e\x41\x7f\x99\x07\xb5\x80\x96\x7c\x24\xaf\xe1\x29\x48\xc2\x0d\x24\xe1\x01\x92\x58\xd6\x72\x0c\x23\x60\xeb\x85\x42\xcb\xf3\xcf\x02\x3c\x04\x66\x31\x27\x87\x08\xe2\x72\x58\x1a\x70\x9b\x6e\x0d\x70\x5d\x78\x28\xbb\x48\x65\x03\x1b\xba\x8c\x1e\x8a\xc2\x74\xbf\x86\x1d\xdc\xc3\x1d\xec\xe1\x1d\x3c\x1a\x4d\x81\xe8\x65\x29\x9e\x7c\x41\x58\x47\x92\xe3\x1f\xaa\x8d\x79\x9d\x5a\xc1\x0d\xc5\xc2\x2c\x0f\x7c\x93\x54\x11\x4a\x0c\x0f\x1a\x76\xd6\xfd\xbe\x7d\xcb\x2f\x28\xdd\x4c\x22\x1c\xa2\x98\xcc\xaf\xae\xd3\x51\x3e\x47\x99\xd3\x1d\x1d\xc2\xbb\xf0\x96\xde\xdd\xbc\x1b\xa7\xe9\x1d\x39\xe8\x31\xaf\xa9\x98\xde\xcd\x50\x5e\xd9\xdd\xe4\xbb\x59\x6b\x34\x3b\xf8\x26\x59\x3b\xfa\xfc\x81\xc0\x3d\x5d\x5b\x22\xbc\xee\xf7\xef\x83\xff\xad\x2a\x6b\xbf\x31\x0c\x14\xdc\xc1\x52\x4f\x6e\x11\xe4\x73\xad\xad\x2b\x09\x81\x3d\x1d\xc2\x23\x75\xed\x8c\xf7\x37\x8f\xe3\x34\xdd\x93\x87\xe4\x7e\xba\x9f\xe9\xaa\x71\x8d\xb1\x1e\x8a\xa1\xf1\x1f\x48\xb3\xa5\xc3\x4e\x3e\x2e\x97\xa7\x04\xf4\x68\x55\xb6\x1f\x50\x89\xa7\x36\x10\xe9\x67\x71\x0f\x2b\x55\x3b\x45\x3e\x5a\xa8\x47\xf9\x71\xa8\x87\x69\xcc\xe3\x1e\xf6\x69\xee\x48\x3e\xe1\x09\x73\xc6\xa4\x55\xff\x83\x38\x49\x2c\xc3\x6e\x1f\x23\x93\x73\x1e\x09\xa9\xe9\x70\x5c\x23\x12\x52\xb7\x91\x90\x7a\x06\xe5\x1f\x43\x42\x78\x5b\x56\x6b\x10\xbd\x00\xb6\x4e\xa4\x77\x4e\x27\xda\x02\x04\xe9\x30\xec\x5d\x5e\xc0\x3e\xaf\x23\x14\xba\x74\x28\xb4\x85\x06\x55\x16\x63\xe1\xe3\x78\xb6\xd5\x09\x94\x63\xad\x67\xae\x6a\x3d\x73\x2c\x36\x34\x6c\x45\x41\x64\x1d\xd7\xa6\x6b\xf1\x4e\xd3\x0f\x45\xba\xd2\xa4\x9e\xc3\x91\x65\xbf\x6f\xf8\xf0\x27\xf4\x4b\x20\x39\x8a\xb4\x1c\x60\x25\x96\xfd\x6b\x5b\x2c\xea\x42\xf1\xf9\x8b\x6d\x6d\x9b\x2b\xa1\x06\xfc\x9b\xae\x08\x44\x57\x58\xfa\xcb\x4d\xd1\xad\xdc\xc7\x74\x03\x1a\x9d\x5f\x3d\xdd\x51\x3a\x07\x3b\x98\x74\x4e\x20\x92\x7e\x7d\xdc\x34\x6c\xbd\x73\xad\x9b\xb6\x5d\xff\x9d\x69\x38\x02\xe4\xa3\x66\x61\x16\xe2\x7c\x1f\x60\x57\x9f\xb5\x5c\x32\x32\xf7\x40\xb7\x36\xf0\x76\xd8\xef\xb3\x60\xb0\x78\x67\x68\x56\x4f\xc6\xbe\x41\x97\x77\xe7\x79\x5d\x8e\xc1\xce\x62\x11\x8d\x86\x0d\x9a\xbc\xdd\x81\x34\x3f\xf6\x88\x86\x1e\x8f\x32\xc0\x90\x9d\x9c\xaa\x2e\x2f\x84\x41\xe0\x9e\xb1\x13\x9e\x48\xec\x4d\x7a\x1c\xd9\xfd\x38\x16\x04\x9a\xea\x9f\x61\x41\x54\x60\x0b\x39\x46\x04\x28\xcf\x81\x50\x86\x03\x41\xc6\x42\x13\xf9\x94\xd2\x79\xb6\xeb\xf7\xa5\xff\xd8\xa3\x01\x75\x97\x2b\xc1\x4e\xb8\x12\x96\xed\x40\x0b\xcb\x7e\x70\x7c\x07\x5a\x38\x06\x04\x73\xe2\x99\x0a\xc3\x0a\x5a\xc1\x4c\x85\xf1\x05\xdf\xc7\x38\x60\x30\x27\xa4\xf9\x60\xd4\xe0\x98\x45\xc5\xda\x2c\x2a\x1b\x18\x83\x39\x0e\x47\x7b\x21\x59\xf6\xc4\x76\xf3\xe0\xc5\xf2\xf0\x5e\xde\x4a\x03\x92\x7a\xc7\xd2\x2c\xdb\x37\x63\xeb\xba\xac\xb8\x97\x49\x45\x6e\xb2\xe1\x70\x34\x19\xe6\x61\xcf\x5a\xd2\x57\xa8\x9d\xe5\xa8\x7d\x42\x8e\x47\x76\xaa\xc3\x83\x89\xed\xcf\xae\x4e\x8f\x4e\xb3\x8c\x60\xa7\xa1\x17\xa4\x39\x75\x1c\xd1\x5d\x65\x0f\xa5\xb8\x2f\xca\xe7\xe5\x66\x55\x50\xbd\x79\x1d\xe0\x2b\x01\x23\xbe\x13\x34\xfc\xb7\x6a\xb7\xc1\x20\x16\xa4\x81\xe6\x0a\x5f\xa3\x40\xa0\xeb\x5a\x88\x95\x78\x74\xaf\x95\x62\x9f\x52\x9b\x76\x77\xb6\xe5\xc8\xe7\x24\xf2\x5c\x9e\xe3\x5d\x70\x41\xc7\xba\xe1\x9a\xcc\x4d\xd1\x34\x46\x23\x4f\xca\x9e\x67\x68\xba\xdb\x25\xd0\x71\xd2\x26\x49\x92\x43\xdb\x1c\xc3\xaa\xd4\xb1\x86\x44\x8c\x5e\x1e\x89\x96\x62\x05\x3b\xe5\x2e\x65\x37\x50\x4c\xef\x45\x51\x55\x42\x3d\x5b\xf2\x6a\xf1\xac\x78\x66\xbb\x78\x56\xa8\x67\xd8\xfc\xb3\x5e\x1a\xc2\xf8\x75\xfa\x77\xfa\x6e\x95\x51\xa2\x63\x33\x3f\x22\xf4\x33\x4e\x2f\x94\x32\x91\x98\x91\x45\xea\x00\xc8\x8b\x55\x51\x3d\xb0\x45\x22\x80\x91\x71\x22\x8f\x47\xf4\x35\xe4\xe7\x4b\x4f\x79\xac\x94\x01\x77\x77\xec\x62\x48\x48\xdb\x86\xf2\x7d\xdc\xe0\x98\xe5\x8b\xae\xc2\x90\xed\x4b\x2f\x46\x86\xf3\x6b\xec\x14\xc5\x16\x31\x4d\xe7\xf6\x3f\x29\x5a\x8b\xe8\x76\xe9\xb9\xc2\xfe\xbe\x12\xf5\x6b\xb1\x40\x95\xe2\x6c\x2d\x16\xcc\xa8\xbc\x57\xc1\x1b\x58\x11\xbc\x81\x11\x6f\x2e\x7d\x66\xfa\x45\x08\x8f\xf8\x4c\x52\x76\x3c\xea\xd5\x2a\x40\x90\xe3\xb1\x06\xd9\x5a\x91\x02\x12\x2f\xed\x3c\x1e\x03\x2b\xd2\xae\x5b\x7b\xad\xd0\x75\xc6\x0e\xbd\x6a\xec\x9b\x78\xdd\x80\x11\x14\x5c\x9e\x8c\x24\x52\xde\xb1\xac\x57\xee\x18\xaf\x95\xd7\x00\x77\x7c\x42\xa9\x5f\x03\xd1\x7d\x0d\x6c\x00\x4a\xef\x9e\x17\x5d\x3d\xe2\x24\xf4\xff\x6c\x77\x3c\x56\xf8\x63\x4f\x9a\xe6\x45\x30\x7e\x60\xb5\x6e\x0e\xc5\x92\x7f\x73\xaa\xeb\x56\x5e\x19\x94\xd7\x5f\x48\x88\xca\xe7\x3b\x69\xa4\x6c\x2f\x2b\xee\xf5\xd9\x39\x82\x0c\x5b\x15\x45\xbd\x2f\x64\x72\xb8\x73\x7a\xa1\xde\x92\xb2\x09\x8e\x44\xdb\x2a\xe4\x91\xe2\x46\x50\xd8\x88\x24\xe6\x9c\x34\x50\x33\xc9\xd4\x1f\xa9\x60\x5c\xc6\x76\x1f\x03\x57\x1a\x38\xf5\xb2\x5b\xd6\x18\xdf\xf7\x2a\xab\x84\xe2\xcb\xfd\xf7\xe5\xf6\x81\x57\xd2\xf9\x01\x7d\x63\x8a\xa1\x27\x4b\xa3\xab\x65\xd4\x4f\xb0\xe9\x6c\xae\x10\x93\xed\xd4\x34\xd2\xd7\x76\xc5\x33\x06\x96\xe8\x5a\xc7\x0e\x23\x0e\xdc\x67\x78\xd8\xe3\x30\xb7\x33\x66\x97\x76\x28\xd9\xdc\x9c\x23\x7a\x31\x24\x4d\xac\x57\xef\x43\x32\x0e\xc1\x9d\x58\x63\xc6\x18\x54\xe5\xad\xf6\x7a\x0f\x4e\xc2\x68\xd6\x0f\xf7\x45\x32\x44\x03\x8a\x61\xf6\x17\xd2\x83\xc0\x70\xcc\x7b\xff\x6b\xb9\x5c\xda\x94\xaf\xce\xa9\xe8\xc7\x7c\xcb\xfc\x1a\x4e\x58\x86\xf9\x9f\x21\x30\x45\xad\x62\x13\x78\xa2\xd4\xb5\x1f\x53\xf8\xd7\xe0\x88\xf4\xfc\xd0\x44\xd4\xbe\xad\x1b\x51\x22\xae\x76\x8b\x66\xc8\xaf\xa1\x43\x1c\xe5\x7f\x86\x40\xd8\x9c\xda\x18\x04\xfa\xc8\x75\xe1\x4c\x0d\xfe\xdc\x56\x61\xbb\x8e\x38\xf5\x7f\x6a\xab\xb3\xfd\x39\xe2\x3c\x58\x65\xdd\xa0\x34\x6c\x3c\x0e\x79\x76\xc4\x13\xf9\xa7\x4c\x1a\x37\xbd\x36\x93\xe3\x62\xd8\x0e\x73\x1a\xef\x1e\xe9\x06\x5f\xf5\xc8\x67\x7e\x58\x6c\x6b\xf3\xeb\xb3\xe1\x10\x58\x21\xf5\x8c\x7a\xac\x90\xec\x6f\x5b\xf5\xc3\xb6\xa8\x55\xaf\x09\xc5\x65\x7e\xa8\x50\xe9\x5f\xe6\x07\xa3\xdc\x60\x3e\x7b\xce\x19\x34\x67\x32\x0f\x11\x69\x11\x01\xea\x41\xcf\x60\x3e\x3d\xe8\x19\xe0\xe6\x7e\xfc\xd6\x9b\x35\xe0\x64\x47\x07\xd7\xb7\x8d\x92\x09\x7e\x60\xd7\xc3\x61\xd3\x40\x10\x73\x1f\x22\x55\x88\xfc\x3b\x73\x8c\x7c\x64\x55\x83\xcc\xdc\x0e\xa3\xeb\x3e\x1d\xce\x10\xf7\x0b\xc2\x65\xe7\xb4\xb1\xa2\x7c\xe2\x24\xc8\xf9\xd0\x79\xbd\xb0\xb4\x84\xf7\xef\xdc\xb3\x6f\xee\x89\xc7\x2c\xfd\xf8\x78\x3d\x1a\xaf\x6c\x86\x6d\x1f\x8f\xbd\x9e\xb1\xfc\xc7\xcf\x50\x0a\x3f\xd1\xde\xc1\x90\x11\x5e\x97\xfd\xa6\xf2\xbe\x0b\xa6\x51\xf2\xcc\x1a\x2a\xf4\x7a\x16\x72\xb8\x69\x07\xa4\xcf\x7f\xa0\x2a\x42\xfe\x9d\x51\xc2\x77\x2b\xf2\x5f\x9d\x90\x9d\x48\xda\xcb\x35\xfe\x91\xb5\x75\xf3\x8f\xc7\x6e\x8a\x31\x72\xb1\x66\xcc\xed\x65\xd0\x0f\x3a\x4b\xa9\x6e\x88\x44\xda\x46\x9d\xfa\xb6\xdb\x4f\x12\x4e\x34\x15\x91\x52\x8d\x0a\x36\x10\x64\x05\x6d\x10\x7e\x1e\xc3\x6a\x99\x85\x93\xb3\x26\xcc\x2a\xac\xad\x47\xaa\xe2\x8b\xd3\x0e\x37\xdd\x05\x8a\xa7\xa1\xa4\xe3\x8b\xc5\x5a\x74\x7e\xe0\x5a\xfb\x0c\x34\x4f\xee\x72\xb3\x5b\xb9\xd6\x40\xb9\x6d\x04\xdc\xd8\x75\xf0\x2c\xb4\x0e\x6e\xeb\xf6\xcf\xc3\x4f\x5b\x3e\x48\x31\xfe\x07\x17\x2f\x12\x7f\xb0\xb3\xe2\x0f\xe6\xc5\x1f\x8d\x3d\xc3\xee\xa0\x7a\xa2\xc4\x1f\x61\x83\xf6\xe7\xdf\x39\x7b\x0c\x5b\xc6\x25\x37\x27\x16\x5c\xfe\x4d\xe8\x2d\x45\xe5\x9f\x81\x38\x25\xbc\x51\x26\xe1\xfd\x56\xce\xce\x4e\xf9\x02\x95\xb5\x7b\x4e\xb1\xc3\x7d\xbb\x57\x14\xbf\xdb\x36\x61\x31\x84\x6a\xd9\x8e\x8d\x3a\xb6\x63\x31\xf4\xbd\x5b\xda\x4a\xed\x0c\x19\xe7\xf4\x7c\x72\x4f\x2f\xe0\x62\x81\x2f\x77\x51\xfe\x0d\x77\xfd\xa7\xb9\xd8\x20\xd0\xc5\x88\x2b\x05\x92\x46\x1a\xae\x3e\xef\xea\xcc\x75\x83\x5a\x7f\xe1\x23\x52\xe6\xff\x12\xf0\x15\xfa\x08\xcf\x17\x12\x8c\x4b\x91\xfc\x41\x82\x01\x35\x6b\x09\x16\x89\xc9\xff\x26\xe3\x78\xd6\x6f\xce\x06\xb5\x32\x36\x4b\x0c\x69\x1d\x8c\x22\xe4\xe0\x5a\xe2\xc3\x3f\x9f\x18\x41\xb1\x89\x37\x9d\x1a\x8c\x72\xe3\x5f\x8c\x11\x13\xc3\x8d\x13\x5b\xcf\xbb\x70\x41\x2c\xad\x2c\xa4\x39\xa8\xd8\xd9\x84\xe7\x2e\x4c\xe3\x8f\x41\x75\xf7\xd7\x27\x54\x77\x95\xf3\xdf\x8d\x16\x99\x08\x7e\xda\x4a\xb5\x28\x04\xc2\x80\xe8\x74\xd8\xa0\x29\x93\x47\xd5\x3e\x49\x54\x08\x80\xb4\xf5\x71\x95\xb8\x57\x26\x70\xce\x35\xc6\xd1\xac\x09\xbd\xd5\x65\x31\xa0\x3a\x4e\xea\x3b\x65\xe2\xe2\x5a\xee\x25\x01\x24\x0d\x12\x46\xb9\xfc\x4a\xa3\xb0\x2c\x61\xa4\xdf\xe7\x53\x36\xc3\x3a\x2c\x7f\x63\x18\x31\xdf\x22\xdb\x26\x04\x8e\x1a\x8c\x48\xb3\x60\x8a\xd5\x6b\x14\x99\xa9\xe2\x15\x5f\x73\x75\xa2\x06\x7a\x88\x82\x3a\xb3\x38\xa8\x33\x6f\x8c\x1d\x58\x2b\xa8\x73\xc9\x14\x46\x84\xae\x30\x22\xb4\xb0\x45\x5e\xf3\xea\x75\xb1\xd3\xc4\xdf\xb8\xa7\xf8\xfc\xad\x34\x7e\x65\x02\xe8\xd1\xb5\x35\xa4\x3f\x1e\x93\x8a\x0e\x09\x70\x0c\xc5\xa9\xce\x84\x6a\xc4\x80\xd0\x2a\x5b\xf3\x8a\x56\x80\x0e\x82\xa9\x30\xde\xa9\xdf\xe8\x86\xcf\x29\xb1\xae\x79\x85\xa6\x92\xeb\x62\xd7\xd2\x4f\x15\x06\x5c\x86\xd0\x0e\xed\x0e\xc7\x92\x0e\x8d\x96\x3f\x47\xe7\x34\xae\xff\x89\xcc\xa5\x8f\x9f\xcc\xd3\x11\x3a\xad\x8c\x76\xdd\x0b\x13\x7c\x95\xa4\x42\xb7\xbb\x60\x8a\x46\x07\x07\xc7\xa6\xb3\xb3\x3f\xe5\xc3\x38\x90\x1e\x1b\xab\x1b\xca\x31\xf8\x9a\x55\xf4\xb2\x71\xd9\x55\x13\x14\x5f\x1b\x37\x58\x1f\x0f\xb1\xc3\xb5\x3a\x73\xa4\x9e\x29\x8c\x4b\xaa\x6e\x1c\x0f\x68\xc2\xa6\x6a\x96\xab\x26\x58\xdb\x76\xd4\x61\xf1\xd8\x47\xc6\xb8\xe4\xc4\x89\x34\xea\xc7\xde\x59\xb2\x19\xa3\x2d\x4a\x7a\xd1\x4d\x41\x26\x4b\x3b\x7a\x63\x67\xb4\x4e\xe7\xc4\x62\x85\x17\xde\xca\x51\x53\x87\x94\x65\xf6\x2e\x11\x02\xfe\x3e\x7c\x57\x7c\x97\xb7\xc2\x42\x1a\xb8\x54\x26\x89\x1a\xb0\x78\xa5\xc9\x15\x8b\xf7\xa8\x35\x98\x37\xe8\x23\xa6\xb3\x72\x78\x4a\xfd\x9a\xdd\x0c\x8f\x47\x75\xcb\xc2\x11\xc0\x8b\xe8\x96\xb8\x3d\x2b\xbd\x9e\x36\x2c\xa5\xee\x05\x13\xbf\x12\xb5\x89\x42\x79\x7e\xca\xcf\xa2\xfb\xdc\x1a\x76\x6a\xe2\x78\x9a\x49\x45\x8d\x5c\x9e\xce\x26\x8a\x74\xd9\x7a\xde\x6d\xa0\xc6\xe6\x47\x99\xf1\x05\xed\xcd\x0b\xc5\x1e\x44\xbd\xef\xc1\x8f\x32\x73\xb4\x1e\x3d\xe0\x74\x73\xaf\x0e\x9a\xff\x28\x23\x53\xd5\xee\x31\xf3\x1e\x37\x5e\x7d\x3c\xb8\xc4\x39\xb5\x00\x25\xab\x16\x1d\xeb\x85\x27\x00\x2a\xb3\x16\xa3\x1f\x07\x66\x3d\x2a\xa8\xf4\xb9\x74\x87\xe9\x49\x93\x59\x63\x86\x4c\xfa\xfd\x0b\x0f\x3d\x53\x65\x5f\x8f\x54\x59\x4e\x95\x3e\x21\xd8\x95\x79\x35\x4f\x41\x24\xca\x64\x9e\xab\x7f\xb0\x5a\x98\x18\x58\xf3\xb7\x0e\x40\x3a\x4e\x56\x0c\x46\xab\x18\x8c\x8a\xf7\x81\x51\x74\x9a\x8d\x86\x4d\x9e\x17\xa5\xe8\xad\xa4\xd5\x44\xe6\x0a\x4a\xfd\x51\x50\x31\x29\x72\x34\x83\x66\xc7\x23\x0f\x43\xfb\x42\x25\x92\x00\xd3\xff\x0b\x32\x56\x37\x9a\x64\xb8\x19\x4e\xca\x64\x48\x72\x85\x04\x84\xfe\x53\x27\x43\xd2\x48\x6a\xbc\x0e\x97\x49\xa1\xe1\x19\x3b\x1e\xeb\x44\x46\x90\x56\x5a\x48\x5b\x18\xc5\xf3\xf9\x5b\x7c\x2a\xde\x17\x08\xc0\xdc\x1f\x63\xfc\x7d\x58\x17\x3b\x04\xce\x58\x0b\xe3\x99\xb1\x8d\xb1\xe7\x8a\x2c\xcc\xc4\xc4\x86\xe5\x98\x33\x5e\x26\xd8\xdf\x95\x20\x83\x28\x78\x33\x8e\xe5\x4a\x90\x74\x94\x27\xc6\x07\xa0\xf5\x77\xe7\xc7\x03\x15\xad\x8e\xc7\xd1\x08\x63\xb6\xc4\x61\x3e\x2a\x54\x78\xe5\xcd\x69\x15\x77\x60\x9e\xb2\x47\xff\xc0\xdb\xd2\x76\xca\xe8\xe6\x6c\x23\xba\x44\xdd\x8c\xa3\x98\xe4\xd7\x41\xb1\x53\x3c\x1d\x03\xf6\xa0\x57\x49\x1f\x15\x5e\xe5\x02\x8f\x81\x84\x4d\xcd\xe6\x1c\x43\x3a\x16\x30\x17\xdb\x4a\x19\xaf\x16\x38\xba\xbc\xc4\x53\xc5\x1f\xb8\x92\xf9\x1c\x56\x1e\x40\xe7\xab\x86\x2a\x58\xe0\xca\xc0\x96\x96\x83\x11\x1e\xc6\x7c\x89\x8d\x3e\x34\x18\xb1\xfc\xe2\x93\x44\x10\x58\xeb\xff\x92\xc0\x4e\xff\xaf\x09\xdc\xd3\xe4\x61\xb0\x24\x57\x73\x9c\x95\x55\x2a\x80\xd7\xf4\x85\x4a\x4c\xc6\xf6\x6a\x41\x2e\x17\xfa\xec\xbd\xbe\x19\xb1\xc1\xe8\xb3\x7e\xff\x62\xd3\xef\x5f\xac\x2d\x9a\x33\xb5\xcf\xd6\xb2\x01\xfb\xeb\xa1\x99\x8d\x1f\xa3\x8d\x7e\xb8\x7a\xdd\xda\xe4\xe5\xd5\x6b\x02\x8f\x18\x1b\x1f\xfb\x79\xbc\x7c\x6d\x7b\x21\xf0\x49\x52\xe8\x4b\x6d\x43\xf5\x6f\xc4\x63\x32\xc2\x40\xc8\xaf\xa3\xf6\x5e\x5f\xde\x91\xab\x3b\x02\x7b\xda\x69\xf4\xf2\x35\xbc\xeb\xf4\x7b\xf9\x1a\x36\xfd\xfe\xba\xdf\xaf\xfa\xfd\x1f\x55\x92\xc8\x81\x20\x57\x15\xbc\xbe\x1a\xb1\x4f\xc9\x24\x79\x0c\xe7\xc7\x64\xbd\x86\x52\x77\x66\x3e\x1e\x61\x4f\x05\xbc\xa3\x92\xe4\xbb\x49\xb2\xa7\x9b\x89\xc8\xf7\xf0\x8e\xae\x27\x32\x7f\x07\x8f\xb4\x1e\x8c\x74\xd9\x77\x83\x3d\xb9\x7a\x24\x79\xf2\x68\x7f\xbf\x86\x47\xfa\x46\x25\x8f\x10\x01\xfd\x47\xe2\x3a\x6d\x25\xe6\x61\xb8\x8f\x84\x40\x7b\xde\x7a\x31\x26\xbf\xab\xe4\x35\xc9\x0b\x3f\x5d\x53\x73\x8f\x6b\xe0\xa6\x6b\xd2\xde\x61\x1a\xee\xe3\x5b\xeb\x42\x62\x83\xfc\xe7\x18\xbb\x10\x0d\x81\xfd\x0d\x15\xfd\xfe\xdb\x34\x85\x37\x2d\x44\x33\xd9\xa7\x6f\x2f\x5f\x13\xd3\xb4\x80\xfb\xcb\x64\x35\x49\x7a\xbd\x54\x10\xef\xaa\x87\x10\xac\x48\xc6\x6f\x51\x4d\xe2\x2d\x69\xb7\xfe\x44\x63\x01\xa3\x59\x4f\xde\xa8\x84\x4f\x03\x82\x6a\x1f\x52\x90\xa1\x3b\x19\x77\x37\x39\x53\x18\x5d\xf9\xc4\xdd\xca\x86\x74\x52\xde\x35\x68\xb0\xe9\xe1\x92\xbd\x65\x26\xda\xb7\x8d\xfb\x5d\xec\xa2\xdb\xc6\x33\xff\x1b\x81\x57\xce\x33\x07\xc3\xec\x3d\xe4\x19\xfe\x8f\xae\xa0\xca\xee\xfc\x47\x42\xe2\xfb\xd8\xc5\x9f\x1a\x8d\x15\xd6\xfa\x7d\x39\x1e\xbd\x50\x21\xe0\xc9\xcc\xe3\xc7\xaf\x54\x22\x40\x41\x0f\x67\xd1\x23\xc0\x7c\x58\xf2\x44\xb4\x82\x98\x98\xd7\xd6\x60\xbc\x18\x25\xd2\x60\x9a\xa8\xc4\xe5\xf3\x78\x15\xf2\x8a\x1d\x01\xf1\x24\x0e\x08\xc6\xd9\x8b\x87\xe8\xb6\x76\x65\x6a\xa2\x02\xca\x19\x24\xb1\x8d\x66\xf7\xfb\xec\x44\x49\x3f\xa9\x06\x9c\x5c\x85\x48\x4a\x7e\x2b\x61\x44\xae\xae\xc7\x7c\x40\x15\x54\x29\x55\x4d\x1b\x6b\xe6\x7a\xbd\x3c\x56\x50\x75\xd0\xef\x6a\xc0\xcf\x22\xc6\xf6\x8c\xfd\xce\x13\x15\x79\x01\x0b\x91\x6b\xc4\xbc\x28\x19\x69\x2c\x25\xf8\x65\x40\x6d\x5e\xc9\xc3\x47\x92\x49\x86\x3e\xf2\x84\x51\x44\xf5\x98\x67\xf4\x57\x4d\x6d\xb2\x7c\x68\x5f\xd3\x5f\x31\x2c\x7c\x3e\x02\x95\x3d\x85\x67\x9c\x7b\xb0\x4e\x7d\xeb\x77\x3c\xed\xc6\x48\xa5\x7d\x4c\x8d\x79\xed\x67\x43\x12\xc7\x72\xb0\xa2\x70\xdd\xf6\x57\xa2\x52\xae\xd3\x21\x19\x9f\x69\xc1\xb0\x61\xaf\x62\x2f\xb0\xe7\x51\xfa\x88\xbe\xf5\x38\x7a\x17\x53\x8e\xd0\xf4\x2e\xee\x47\xae\xba\xf8\xdd\x79\x34\xba\x25\x9e\x8d\x30\x66\xd7\xd5\x19\xa4\xb9\xdb\x70\x64\xb7\xfc\x32\x36\x21\x1e\xe9\x71\x5f\xc5\xb0\x36\x7a\x4a\xbe\xc7\x18\x83\xa4\xf9\xd2\xe0\xd3\x8e\xe1\xfc\xe5\xfb\xb0\xe9\x5f\xb8\x67\x58\xd6\x32\xab\xb6\x6b\x56\xf3\xb9\xc7\xa2\x7f\xff\xff\x05\x8b\x7e\x0a\x5f\x76\x18\xc7\xab\x18\xe3\xc7\xfc\xb6\x29\x19\xb0\x19\xb2\x65\xd0\x82\xc3\x1d\x31\x7d\x82\xfb\x7d\x7e\x3b\x9c\x70\xe4\x0b\x8d\x4d\x5f\xbf\xb3\x5a\xd0\x8b\xe1\xc7\x72\x17\x3e\xea\xda\x78\x28\x31\x04\x66\x02\x57\xc7\xb7\x28\xca\xe5\x3e\xd7\xb3\x17\x02\x76\x6e\x0c\x44\xed\xf8\xc8\xfb\x2e\xde\x47\x62\xfe\x7f\x94\x39\x82\xa8\xa1\x06\x9e\xc2\x02\x4f\xaf\xe0\x4e\x6f\x2b\xca\x26\x55\xae\xa0\xd0\x1f\x82\xf2\x89\xc8\x15\xd4\x34\x76\x6b\xfd\xd4\x79\x4c\x99\xc6\x30\x4d\xa0\xcf\xea\x86\x0e\x27\x89\x4c\x46\x04\x8a\x64\x34\x24\x24\x4f\x64\x52\x27\x15\x20\x42\x5f\x24\x75\x22\x60\x84\x66\x66\xba\x64\xbf\x2f\x31\x05\x33\x05\x26\x14\x58\xda\x60\xff\xb8\x56\xfd\x3e\x8e\x19\x79\x67\x77\x72\xfb\xf0\xc0\xa4\x62\x8b\xd7\xbc\xea\xf7\x75\xa7\x16\x33\x87\x21\x31\x8d\xd9\x9e\xfe\x18\x93\x26\x20\xd2\xe7\x31\xe2\xf6\x94\xf1\x81\x26\x3e\x72\x1f\xc2\x28\x4c\x6b\x5d\x5b\x8e\x46\x63\x9e\xbb\xf3\x2f\x3b\xce\xa7\x57\x12\x31\x00\x5c\x9a\xa2\xd3\xa1\x31\x4f\x8c\xd2\xe4\x55\x1b\x09\x25\x50\xd2\xe2\x66\x38\x39\x69\xbc\xb8\x47\x73\xc6\x7c\x34\x5e\x88\x43\x9b\x99\xa3\xa9\xbb\x7f\x8a\x3a\x7f\x29\x13\x49\x1a\x02\x69\x5a\xc3\x68\x68\x3d\x32\xd4\x74\x04\x69\x5a\xe8\x66\x6f\xe9\x70\x32\xca\x4b\x02\x32\xc6\xe8\xea\xcb\xf6\x08\x2e\x4b\x72\x55\x36\x8f\x2b\x5e\xb2\xa4\xb8\xe1\xc7\x63\x41\x31\xba\x7c\x7d\xe3\xc9\x8d\xb9\x59\x83\x62\x07\x32\xb2\x9f\x8e\x87\x34\xef\x0c\x49\x68\x34\x89\x57\x1a\x9b\xd9\x4a\x56\xbf\xb6\x08\x92\xfb\x2c\x76\x0d\x7c\x08\x65\xe1\x4f\xa0\x2c\xfc\xdf\x47\x59\xde\xfb\xd2\x1b\xf0\x87\x6f\x50\x6f\xd8\xcb\x3f\xf4\xf0\xbf\x17\xf9\x59\xf3\xea\x2c\x23\x2c\x06\xb7\xfa\xfc\x74\x79\x81\xfa\x76\xe2\x50\x07\x98\xfb\xb1\x3c\x30\x3b\x76\x23\x37\xc0\x7f\xc7\x23\xf2\xc0\xf4\x8a\x78\xf6\x97\x8b\xa6\xa1\xc8\xd3\x8c\x30\x85\xbb\xb1\xe6\xd5\x64\x98\x1b\x58\xf1\x61\xc6\xd8\x7b\x59\x56\x48\xe9\x9e\x7d\x5f\x5b\x58\x83\x3d\x95\x6d\x6e\x16\xef\xf2\xad\xc2\x03\xfc\x95\xec\xba\x16\xd4\xd8\x26\x0a\x41\xad\xa8\x5a\x03\xa1\x4e\xf0\x20\x65\x3c\x5e\xe9\x37\x76\x51\x8b\xcd\xf7\x1d\xdf\xf5\xdf\x26\x46\xab\x58\xa3\x9e\x4b\x2f\x13\xdf\xa9\xf0\x41\x52\x87\xd8\xb8\x78\xe3\x51\xb4\xfc\x5f\x64\x6c\x88\xe4\xf0\x0d\x4a\x35\x21\xad\xd0\x10\xed\x60\xe2\x8e\xb3\x01\xbf\xba\xc6\xd8\xe3\x2c\xe5\x57\xd7\x4d\xae\x6e\x74\x91\xdb\xb8\x80\xc9\x6e\x72\x97\xe2\x8a\x47\x4b\xf0\x79\x8c\x83\x0c\xcd\x06\x8f\xfe\x62\x0f\xb0\x55\x5d\xcd\xd5\xcd\xe8\x2f\xc3\x8e\xbb\xa2\xd0\xc4\xdf\xbd\x54\xe5\x3f\x6d\x03\xd7\xff\x61\x1a\xe0\xd9\x7e\x40\x59\xb6\xba\xba\xce\x13\x75\x7b\xfd\x1f\xc3\xe3\x51\xdd\xfc\xe7\xd0\xa8\x20\x99\xac\xc8\x5d\xca\x5f\x4f\x7d\xf0\x28\xc3\xcd\x47\x57\x82\x44\x64\x45\x3d\x4f\x54\xb6\x33\x5e\xf5\x40\x65\x7b\xfb\x8b\xc1\x10\xee\x94\x0d\xd7\xe0\x88\x05\x7d\x2e\x05\x0f\x4a\x4e\xf8\x86\x8f\x85\x53\x5e\xe6\xd9\x0e\x78\xb6\x0f\xfc\x6f\x49\x47\x63\x79\x53\x8d\x65\x9a\x92\xb3\xf5\x25\x30\x02\xc2\x29\xe7\xba\xfa\xd1\x5a\x7e\x17\xaf\xe5\x73\x7d\xec\x27\x2a\x1f\x36\xbf\x5b\xa4\x4d\x3c\x14\x35\x57\xab\x35\x9f\xf7\xe0\xf7\x8f\xc7\xdc\xa2\x7a\x16\x4c\x46\x4a\x32\xc1\x11\xf1\x3f\x5b\xb4\xc3\x7b\x11\x3a\xbb\x80\x6d\x77\x24\x67\xd2\x16\x75\xf1\xc8\xab\x87\xe7\x35\x2b\xda\x68\x1d\xca\x4c\x0d\x8f\x3f\xf8\x8d\x09\x89\xce\xb5\xcb\x87\x82\x89\x9e\x09\x1d\x7a\x36\x3e\xa8\xd5\x62\x79\x23\x36\x54\xdf\x58\x07\x44\xc9\xd5\x35\xf8\xd3\x40\x5b\x2c\x3b\xeb\xd8\x87\x84\x33\x12\xe7\x7b\xc2\x62\x10\xb7\x4d\x4c\xf9\x78\xd2\x9e\x13\x73\xb6\x82\x8f\x5f\x72\x75\xfd\x6f\x22\x9d\xa3\x18\xe9\x44\xae\xb0\x95\x27\xb6\xc9\x36\x9f\x83\x14\xdc\xf0\x0f\x52\x70\xa7\xe4\x55\x67\x8f\xaf\xf4\xe2\x46\xc2\x79\x54\x51\x35\x3e\xb1\xb1\x9d\x53\x0f\xf0\x7a\x1b\x5f\xb5\x99\xf6\xdd\xf2\x4e\xf1\x1b\xed\x30\x5a\x07\x87\xb5\xe4\x6c\x46\x59\x55\xa1\xb4\xd5\x4b\x67\x7f\x48\x58\xe4\xfb\xd8\x57\xf5\x3e\x09\x34\x85\xc0\x67\x41\x83\xf0\x59\x75\x3c\x0e\xd1\x96\xb7\xca\x7b\xbd\x86\x90\x73\xa1\x54\x23\xbc\x6f\x1c\xc1\x79\xd6\xea\xc1\xc5\x1f\x89\x3d\xea\x79\xab\xca\x32\x1f\x42\x9d\xdb\xdd\x07\x95\x0f\xe1\x3e\x3f\x7b\x42\x1a\xe0\xd4\x06\xa5\x35\x91\x48\x9c\x86\xf4\x74\x06\xb5\xfe\x53\x9e\x95\x36\x22\x48\x32\xd1\x46\xca\x71\x95\xa6\xc1\xce\x46\x9d\x5d\x8e\xa7\x5c\xde\x54\x84\x8c\xeb\x69\x35\xa3\xa5\x1b\x93\xf3\xa2\xd9\x06\x6c\x55\xfb\xd8\xa7\xba\x4e\x70\x2a\x16\x42\xd1\x8d\x51\x6f\xd0\xd8\xcc\x6c\x9d\xcd\xcc\xdc\xba\x53\x5e\xd1\x6d\x6c\x74\x24\xe8\x6f\x89\xc6\x98\xe2\x2d\xd7\xcd\x4e\x0e\x8f\xf9\xcf\x2a\x99\xc3\x1c\xdb\x81\x05\x81\x55\xbe\xb0\x13\xbf\x5c\x35\xf9\xe1\x31\x9f\xb7\xbc\x15\x2e\x5c\x10\xb4\x55\xbe\x6a\xa0\xd0\xf3\x11\xde\xb8\x0d\x67\x83\x52\xf5\xe7\xd5\x03\x86\x4e\x80\x07\xfa\x52\x25\x4b\x02\x1b\xfa\x8b\x4c\x1e\x40\x66\x3b\x10\xd9\x23\x0c\x61\xf4\x97\x21\x81\xb5\x4b\xdd\x83\xc8\x56\xf0\x9f\x43\xb8\xfe\x8f\x21\x19\x6f\x0c\xba\x77\xc3\xb2\x12\xd5\x13\x4b\x6a\x53\x80\x67\x25\xd5\xad\x69\x0c\xf0\x96\x65\x35\x66\xd7\x14\xbf\x81\x67\xb5\xce\x5c\xfb\xda\x26\xba\x8a\xa2\x6b\x5f\x5b\x99\x02\xa6\xf6\x3d\x66\xdf\xd3\xb5\xad\x7d\x4f\x97\x04\x9d\x30\xa3\x87\xed\xb1\x46\xed\x98\xfa\x91\x2d\xb6\x73\x73\x91\x5b\xfb\x82\x6f\x2c\xa8\x73\x90\xd6\x6f\x56\x20\x69\x96\x1a\x56\x6e\x09\x86\x48\xd2\x98\x13\x37\xd2\xa7\xaf\x6a\xb1\x36\x10\xd1\x63\x83\x5b\x83\xf7\x78\x1c\x19\x41\x51\x6e\x30\xc8\xa7\x8e\x22\xa3\xe6\x9e\x2d\xd1\x16\xf9\xec\x73\x59\xc1\x43\xca\xcc\x59\x02\xa1\xf7\xe4\x74\xaf\x34\x6d\xa1\x37\x74\xfc\x77\x99\x08\x40\x0f\x23\xee\xa4\x7f\x2e\x13\x61\x23\x0a\x8f\xe7\xc1\x6e\xbf\x9c\xf0\x6c\x97\xb7\xdc\xd0\xf1\x6c\x37\x90\xd9\xe3\xd5\x75\x6e\x7f\xd9\x36\x56\x74\x9e\xea\xaf\xd3\x05\xd3\x47\xe8\xb0\xd3\xc5\x61\x9f\xf3\x6c\x1f\xb9\xd1\x2e\x8d\x8f\xf4\x39\x86\x40\xd5\x59\xc6\x01\xdc\xca\xf9\x4c\xe7\xd9\x3e\x95\xd9\xaa\x69\x9a\x44\x91\x1c\x0d\x83\x63\x1f\xbf\x4e\x7b\xb1\xe9\xee\xe3\x99\xd8\xdd\x7a\x6e\x82\xb2\xac\x34\x34\x9d\xe4\x55\xc2\xb3\x40\x6e\x19\xde\x64\x3d\xb0\xd6\x74\x30\x24\x71\xb9\x5a\x13\x8b\x03\x96\x59\x8e\xd1\x5c\xc8\x84\x67\x68\x1e\x33\x88\x6a\xdf\x0f\x12\xef\x38\xaf\x8a\x9f\x3a\xdf\x9a\xa9\x78\x4f\xc6\x82\x7e\xa7\x57\x1c\xa4\xfe\x2f\x75\xeb\xdf\x69\x0a\x12\x6a\xfd\xbf\x26\x50\x9d\x79\x18\x8b\x5d\xa2\xae\xae\xc1\xbf\x92\xf1\x13\x3c\x48\x44\x2a\xf1\x49\x6d\xa7\x16\x69\xad\x53\x35\x15\xdd\x5d\x3c\x7d\x02\x0a\xa8\x31\x06\x79\x9c\x7e\x3e\xbc\xaf\xa4\xc2\x79\x0b\x1c\x88\xd6\x35\x29\xa8\x4a\xdb\x29\x35\xe5\x9d\x94\x92\xba\xf8\x4b\x03\x11\x2d\xcc\xa0\x6a\xb7\x35\x16\xe7\xf0\x8b\xa4\xc0\xa9\xa5\x26\xca\x93\xc6\x09\xcf\x21\x19\xc6\xc9\x67\xea\xa2\x3d\x45\xab\xdf\xb4\xef\x41\xc0\x19\xff\xae\x12\x75\x99\xdc\xa9\xab\xae\x1e\x85\x63\x62\xa7\x5f\xaa\xd6\x13\x6d\xa0\x0c\x36\x73\x3c\x0e\x8d\x4d\xc9\x7b\x6e\x7a\xf7\xed\xee\x68\x07\x7d\x57\x7c\x37\x0e\xda\xdc\x31\x66\x60\xd8\x1c\x03\x43\x0a\x8e\xbd\x6a\x68\x27\xaa\xf9\xc4\x16\x53\xe4\x92\xe7\xa8\x0c\xa1\x8b\x5f\xf2\x98\xbc\x3b\x1d\x9d\x55\xfb\x3c\x3f\x10\x4f\xfe\x29\x3d\x86\x27\x87\xf4\x9e\x31\x99\x62\xdc\xc8\x59\x52\x1c\x4b\x1b\x4e\x9d\xb7\x05\xe9\x6e\xd1\xe0\xb5\xf2\xbe\x45\x73\x7f\x75\x2a\x72\xc9\x52\xee\x29\x92\x7d\xee\xaf\xa8\xcd\x71\x14\x4a\xa1\x9b\xc9\xab\xe6\xa4\xfb\xb0\x37\x91\x1e\x81\xe7\x5c\x77\x06\xea\xd9\xcc\x4f\x6f\x31\x23\x41\x31\xc3\x57\x7c\x7f\xc3\x61\x08\xc7\xe3\xd0\xf7\xa1\x5b\x70\x9e\xcd\xfd\xa8\x5f\x19\x1d\xcd\xd0\xae\xa1\xd3\x10\x66\x32\x03\x33\x2d\xc4\xac\x1c\xc4\xb4\x36\x20\xa7\x40\x58\x39\xef\x5c\x1f\xa8\xde\xb5\x9c\x3d\x51\x83\xe8\x2a\xbc\xc2\x43\xcd\x17\xf9\x61\xce\xeb\xf9\xb6\x2c\xea\x9c\x37\x4d\x3b\xd4\x03\x8b\xf5\xff\xe6\x6a\x37\xae\x9c\xe5\x58\xd5\x32\x74\x45\x82\xf4\x43\xaf\x67\x24\x15\x22\xc0\xe1\x1c\x3e\xa7\xdb\x8d\x8d\x34\xab\x38\x18\xac\xfd\x4a\x8c\x3d\x90\x8b\x23\x89\x93\xfe\xba\xe6\x27\xd3\x3d\x13\xbd\x02\x0e\x78\xba\x5e\xa1\x3f\xb9\xca\xcc\x5e\x34\x94\x83\x3c\x8f\x5d\xb6\xfc\xc7\xf0\x73\x68\x6f\xbf\x7f\xce\x49\xaf\xa6\xc6\xa3\x20\xdb\x51\x3d\x7d\xb0\xa9\xf2\x94\xb4\xa0\x6c\x30\x1a\x8b\x5b\x3a\x1c\x8b\xc1\x20\xc0\x9c\xea\x49\x4c\x55\x20\x2a\x60\xbd\x8f\x63\xd0\x63\x6b\xf7\xfc\xd4\xd3\xbc\xb2\xe7\x64\xe1\xce\xc9\xb6\xa1\x67\x1e\x7a\x31\x33\xe7\x63\x51\x8b\x8d\x39\x1d\xcb\x86\x32\x34\x6a\xfb\x24\x59\x92\x0f\xb1\x74\xa2\x78\xaf\x74\x09\x3c\x78\x88\x98\x0f\x6c\x00\xca\xd5\xc0\x44\x74\x5c\x0c\xe6\xce\x6b\x2c\x6c\x07\x2b\xcf\xe1\x21\x0d\x63\x09\x87\x0e\x1e\x2c\x66\x68\x1f\x2c\x5b\x56\xe4\x20\x5d\xcc\x0f\x66\x63\x7e\xc4\x73\x3f\x1b\xd4\xa3\x21\x1a\x0f\x01\xa9\x9f\xa0\x88\x61\x65\xd0\xb9\x38\x40\x31\x52\x55\x5e\x8e\x72\xa8\x3f\x88\x13\x32\xab\x4c\xe6\x83\xf7\x89\x27\xf7\x8e\x0f\x46\xa7\x61\x78\x5b\xcf\x35\x1e\x58\x4d\xf0\xb9\x2b\xe9\x26\x5a\x44\xb1\xab\x6a\xbd\x31\x17\xb2\xdf\xbf\xa8\x8e\xc7\x8b\xe2\x78\xbc\xa8\x8f\x47\x7e\x33\x3c\x1e\x13\xe1\xae\xa7\x68\x3b\x69\xb1\xcc\x18\x63\xb1\x5e\x83\x68\xb9\x69\x89\x55\xd4\x1d\xdb\x26\x72\xd1\x72\x46\x83\x5d\x9c\x5e\x7e\x0e\x12\x2a\x5d\x3b\xbe\xbe\x22\xf8\x3e\x11\xb1\x3d\x27\x46\x0c\xa9\x41\x12\xb4\x64\x0c\x31\x8c\x0e\xc6\x1f\x86\x9d\x43\xf1\x84\x82\xe9\xb8\xd0\xf7\xa5\x08\xf7\x45\xbc\xe7\xbe\x14\xc4\xc7\x05\x97\xd1\x1a\xce\x1b\x4d\x1a\xf5\xfb\xd2\xd0\x30\x7e\x6d\xe6\x1d\xbb\x7f\xa9\xbf\xa3\xb5\x12\xad\xb5\x62\xdd\xb5\x12\xa7\x6b\xf5\xe1\x13\xc4\xdf\x43\x55\x38\xba\xb7\xfd\xbe\x15\xc6\x9c\xfd\xac\xab\x81\x33\x5c\xbf\xc8\x50\xbe\xcc\x76\x50\x66\x7b\x02\xc1\xc8\x9d\x34\xac\x0b\x4e\x3f\xc7\x49\x24\xe4\x80\x5f\x6e\x03\x3e\x0c\x5e\x2b\xca\x03\x67\xf8\x24\x36\xd5\x38\x1c\xf3\x36\xce\x30\x24\xce\x5f\xe0\xd8\x6f\x3e\xcb\x5c\x18\x1e\xf6\xd4\x94\xd0\xf2\x80\x69\x54\x3c\xf6\x84\xe1\x03\xcd\x3c\xe5\xf7\xe2\xf4\xd6\x0b\xa8\xdd\xad\x37\xc2\x9d\x0b\x2f\xfb\x68\x0f\xbd\x7c\xcf\x41\xab\x89\x0d\x14\xe1\x58\x00\x7c\x99\xc8\x0f\xee\xbd\x1d\xcc\xb4\x76\x3a\xa9\x50\x62\xd8\x42\x5c\xf3\xcf\x2d\x94\x25\x87\x82\xb2\x16\xa5\x2f\xac\xe9\x91\xb7\xb1\x0f\xd0\xb7\xcc\x5a\x60\x7c\x1c\xc1\xed\xf2\x14\x6e\xb3\x00\xa9\x07\xc5\xd5\xb5\x03\xd6\x03\x39\x98\xdb\x90\xc2\x16\x6c\x17\x1e\x68\x9b\x8c\x36\xdc\x66\x60\x47\x04\x43\x18\x48\x98\xbb\x2b\x57\x1a\xf8\xdc\x58\x4f\x95\xe1\x98\xc5\x91\x94\x9b\xe6\x9f\x86\xef\x5b\x17\x0b\x5e\x94\xaf\xac\xc8\xfe\x9f\x31\xe3\x37\x0a\x3b\x69\x2c\x26\x58\x3b\x02\xa5\x8f\x36\xd0\x83\xe8\x89\x8f\xab\x85\xbb\x3f\x8a\xad\x77\xa6\xb3\x53\x9b\x9d\x61\xd3\x45\x8c\x2e\x46\x0d\x04\x02\x22\x1f\x82\x65\x44\x9f\x6c\x95\xee\xe9\x03\x7a\x05\x10\xa3\x03\x9d\x37\xd7\xf2\x8e\x3b\xdb\x94\x5f\x43\x34\x11\x13\x35\x49\xef\x41\x3e\x1a\x06\xdb\x39\x8c\x0f\xe8\xcd\x18\xff\xd4\x34\xd1\x02\x1a\x83\x1a\x7a\xe8\x85\xb5\x31\x1b\xd3\xcb\x7b\x91\x45\x54\x0f\x7a\x2d\xde\xa1\x2d\x31\xb7\x79\xe6\xda\xb4\x53\x6d\x2f\xde\xe6\x86\xc6\x18\x56\x6c\xe7\xa2\x17\xb4\xe7\x43\x2b\x7d\x22\xe9\x61\xcd\xcb\x92\x4b\x36\x17\x95\x5e\x69\xb1\x5e\x8b\x4a\x4f\xcf\x4c\x0c\xd5\xb8\x64\x3e\x62\x9f\x36\xf0\x54\x19\xf6\xa9\x2d\xf5\xe7\x61\x03\x6b\x5e\x6d\x15\x3b\x29\xf4\x67\xf6\x59\x54\x68\x25\xb6\xf5\x49\x91\x4f\xff\xcc\xfe\x64\xcb\x5c\x7f\xd6\xc0\xa2\xd8\x9f\x14\xf9\xcb\x9f\x3f\xf3\x65\x3e\x1d\x36\xf0\xc8\xd8\xdb\x50\x68\x64\xbb\x1a\x7e\xf6\x17\x5f\xea\xb3\x06\xd6\xa2\x52\xab\x93\xa6\xae\xff\x7c\xfd\x17\xf6\x67\x37\xbd\xeb\x06\xfe\xb5\x2d\x6a\xc5\xea\x93\xe6\xfe\xe3\x2f\x7f\xf9\xcc\x17\xfc\xac\x81\x3d\x2b\xce\x8c\x7d\xf4\xa7\xcf\xd8\x7f\x34\x0d\xfc\xe6\x2d\x82\xde\xb2\xbd\x4c\x3e\x91\x91\x2d\xcf\xcf\xed\x90\x47\x6a\xc0\x82\x20\xe6\x57\x19\x59\xbf\xb0\x27\xac\x5f\xb2\xbb\x62\x51\x6c\x34\xfc\x3d\xa0\xe6\x49\x9d\x57\x60\xac\x55\x05\x70\x29\xfe\xce\xd8\x5b\xbd\x6a\xd2\x20\x96\xba\xc4\xdf\x36\xca\xb0\x6d\x0a\xaf\x40\xdc\x73\x7d\x06\x65\xef\x0a\xbd\x01\x54\xf8\x46\xff\x6a\x74\x47\x0b\x7a\x62\x40\x54\x4d\xb8\xb5\x2d\x28\xa0\x42\x55\x41\xf3\x11\x0c\x0d\x0a\xa3\x0f\x9e\x08\x6c\xaf\xa7\x77\xa7\x77\x41\xa9\x38\x1e\x2f\x9e\xab\x44\x92\x7e\xff\x42\xa3\x73\x72\xc2\x0d\x17\xe0\x6f\x4b\xf4\x04\x90\xc7\x9f\x3d\x3b\x91\x1e\x48\x42\x20\x2d\x22\x69\xdb\xbf\xba\xd2\xb6\x67\x82\xfe\x26\x63\x0e\xb4\x11\x8a\xfd\x26\xbd\x89\x94\x22\x63\x79\x23\x06\xa3\x71\x9a\xca\xf0\x6e\x7e\x22\xa7\xbf\xc9\xa9\x9c\xcd\x50\xbb\x04\x77\x76\x62\xff\xe7\x56\xbf\xf9\xf5\xf3\x5f\xef\x7e\x7a\xfe\xd5\x97\x77\x2f\xbf\x7b\xf3\xe5\xd7\x5f\xfe\x68\x14\xc8\xcc\xae\xf7\xfb\x41\x22\x91\xf0\x01\x23\x57\x89\xb8\xb4\x82\x52\x72\xe3\x6d\xb1\x9e\x61\x1f\x4d\xf8\x10\x83\xd1\x2c\xcc\xe6\x5b\xcf\x63\x43\x51\xe1\xc1\x86\x95\x8c\x34\xff\x0e\xa5\xc8\x2b\x58\x71\xf4\xc4\xa6\x71\x72\x46\xc6\x6a\xca\xa7\xd5\xec\x96\xb2\x89\xfe\x9f\xf3\xa9\x98\xcd\xe8\xc5\xd0\xba\x8d\x55\x53\x86\x5f\xbe\x93\xaf\x4f\x18\x79\x53\x3d\xe9\x43\x83\xc8\x6d\xc7\x41\x96\x5e\xc1\x42\x63\x74\xe8\x4f\xaa\x20\x35\x65\xd3\x62\x06\x62\x5a\xcf\x68\x01\x55\x4b\xff\xa1\xb6\x82\xbd\x8b\x51\x50\x4b\x1d\x1a\x67\x08\x7c\xf2\x3e\xc4\xda\x1f\x61\x49\x53\xe1\xf7\x9d\x4d\x87\x4e\x87\xb5\xd2\x08\x27\x9b\xb2\xae\xbe\xaa\xf3\x4e\x6d\x1d\x53\xcb\x71\x7d\x43\x8b\x71\xad\x5b\x29\x16\x8b\xa4\x86\x11\x54\x84\x94\x94\xa3\xeb\x2b\xb4\xde\x49\xd8\xb4\x9c\x65\x38\x50\x7a\x11\xd4\xf5\x98\xc1\x7b\x05\xaa\x40\x59\x3d\xc6\x7f\xfc\x01\x8b\x36\xf4\x9b\x4f\xd1\x4b\x08\x7a\x45\x37\x2f\xc8\x74\x06\x45\x59\xa2\x53\x74\x53\x6c\x5b\x71\x45\x7b\x8b\x62\xdf\xb3\x09\x38\x90\x9f\x75\x6a\x4b\x34\x69\xf4\x3e\xa5\xde\x14\x93\x50\xe9\xb7\xaa\xe4\xbf\xb3\x05\xbd\x18\xf9\x80\xaa\xf6\x36\xc7\x71\x0e\xba\x21\xb7\x14\x5f\x33\x34\x32\xd2\x3f\xe8\xa1\x21\x50\x59\x4e\x86\x5d\x74\xf4\x19\x21\x44\x76\x67\x9c\x42\x64\x36\x19\x03\x51\x30\x32\x96\x2a\xf1\x21\x25\xbf\xc2\x07\x53\x6a\x5a\xdf\xfc\x4a\x08\x39\x19\x8b\x03\x43\x16\x16\xd4\x16\x1a\xf1\xcc\x3a\x67\x0c\x30\x89\x67\xe1\xa3\x01\xa3\x22\x62\xa6\x40\x4e\x67\xcd\xb2\xf0\x71\xc6\x66\x25\x52\x58\x41\x78\xf3\xab\xb4\x8e\x39\x48\xe3\x6c\xb4\xf7\x62\xab\x12\xbb\x71\x59\x3b\xf1\x23\x37\xf1\x63\xe4\xa3\x6d\x7d\xac\xe8\x6c\x57\x68\xe5\xb0\x66\x99\x3e\x03\xc7\x23\x1e\x02\x6f\xab\xe2\x8c\x14\x22\x95\xb8\x22\x56\x89\xab\xcf\xa9\xc4\xf9\x1b\x8d\x8a\x26\x85\x57\x69\x41\x5e\x22\x1a\x01\x7a\x9e\xb6\x30\x1a\x65\x84\x40\x1d\x15\x2b\x76\xba\x58\xc4\xa9\xb7\xb6\x2a\x84\x34\x45\xbf\x5f\x1f\x8f\x49\x99\x98\xd8\xd5\x06\x8f\xb2\x1d\x13\x38\xa7\xad\xd4\x33\xcb\x65\x12\x0d\x3a\x22\xc5\xb6\x9e\xb3\xe3\xb1\x4c\x3a\x72\x62\xd4\x2d\xfb\x35\x11\x41\x16\x2c\xc8\x44\xe4\x69\x00\xfa\x5f\x14\x4a\xef\xf8\x63\x42\xf4\xe5\x97\xf4\x57\xf3\x54\x98\xc2\x92\x4c\xa4\x2e\xcc\xaa\x45\xb7\x68\x3a\xb2\xba\x73\xd1\xd4\xe5\x00\x8d\x07\xd7\xc5\x2e\xcc\x54\xa4\x23\x4d\xe2\x9e\xcc\xad\x1d\xb1\xd6\xe5\xbe\xe1\x6b\x26\x55\xb1\xde\x38\x4d\x44\xf6\x64\x0c\x46\xe0\x2e\xeb\xbb\x2f\xbf\x7e\xde\x0e\xcf\x18\x2c\xfc\x9d\x67\x45\xef\x25\x41\x4d\x55\x00\x6e\xa4\x2d\x65\xff\x43\x66\x34\x6b\x66\x4f\xda\xfc\xad\x04\x41\xa3\x5d\xa9\xec\x7e\x4c\xd4\xd9\x79\xe5\xb8\xd3\x46\xf0\x9d\xc4\xb6\xa6\x61\x8b\x45\x18\xb8\x59\xe4\xa0\xd9\x76\x3c\x0a\x3d\x11\xb3\xca\x41\xc3\x4d\x27\x8b\x68\x62\x41\x65\x13\x15\xd2\x0a\x5a\x30\x14\x98\x58\x31\x5d\x88\x97\x8e\x80\x92\xdb\xbb\x92\x54\x59\xb1\x55\xe2\xa7\xb7\x7c\x33\xf9\x97\x4c\xb8\xae\xab\x61\x26\x38\xb5\x36\xa3\xf8\xe6\x37\xf3\x45\x61\x7c\x5b\x24\x92\x90\xbc\xfb\xf0\x80\x30\xac\x0c\x8f\x17\x78\xd6\x85\xbc\x8d\xd1\x04\x4e\xc6\x32\xb0\x31\x38\xc5\x97\x1b\x65\x0d\x72\xca\x67\xfe\xd9\x0f\x37\x3c\x5b\xf0\xe5\x32\x11\x18\x6f\xf5\x96\xb2\xc1\xc8\xfb\x95\x88\xde\x7b\x3e\x69\x75\x91\x0f\x67\xfa\xe1\x29\x5c\xc0\xbf\xb3\x53\x33\xca\xa4\xe1\xa5\xa8\xcc\xf3\x15\xdc\x86\xf5\x34\xfe\x69\xfc\x38\xe1\xc2\xb5\xe4\xfd\x6e\xae\xac\x8d\x03\xa5\x23\xe0\x11\xa6\xe4\x62\xa0\x9a\xf9\xfd\x26\xa7\x6c\xe6\xe6\x18\xa1\x2e\x4c\x8f\xd6\xf4\x41\x1c\x3d\xa4\x10\x66\x1b\xea\x4c\x1a\x82\xff\x9c\x43\x28\x40\xb4\xa3\x68\xcd\x84\x34\x71\xd5\xae\x58\xc7\x47\x3f\xb5\x91\x5b\xd9\x89\x55\x44\x74\x91\x4e\x34\xeb\x2c\x09\x3f\x1d\xa2\xdc\x16\xd5\xd2\x9d\xd5\xee\x68\xc0\xf3\xe4\x89\xe2\xa3\x19\x19\x70\x72\x75\x8d\x97\xe8\x6c\x89\xd6\x3d\x95\xed\x96\xab\x3c\xa9\x06\x1f\xaa\x77\x3d\x23\xe4\xea\x9a\x78\x2d\x0a\x97\x71\xf3\xe9\x24\xfb\x53\x9e\x5d\xff\x69\x2c\xe8\x77\x2a\x11\x80\xf6\x59\x52\xff\x96\xe6\x37\x8b\x10\x03\xa3\x0b\x27\x50\x17\x4e\xc2\xb2\xd0\x78\x49\x3e\xba\xd2\x80\x2d\x95\xa4\x69\xa2\xbb\x7c\x26\x34\x91\x7b\x94\x3a\xe6\x28\x88\xfb\x3a\x78\x22\xa9\x30\xf0\xa4\xa0\xd2\x5e\xc4\x7f\xc9\x44\xfa\x13\xaa\xf7\xe6\xdc\xa5\xe3\xa8\xee\xfb\x6d\x22\x83\x9d\xcf\x88\x40\x69\x71\x7f\x63\x2d\x29\xa3\xa7\x1f\xe6\xf4\xb9\x4a\x4a\x72\x3c\x5e\xe8\x07\xbc\x84\x95\xd3\x36\x41\xa7\xfa\x94\xeb\x4b\x37\xef\xf7\x93\x25\x4d\x99\x7f\x20\x96\x11\x55\x50\x12\x02\x9d\xcc\xf9\x04\x1f\xd8\x1c\x57\x0d\x2f\x66\x05\x1c\x0a\x72\x3b\x62\x7f\xba\xac\x4f\x3c\xc1\xf1\xb4\xf7\xac\xa8\x16\xcf\x7a\x69\xa5\x7f\xd5\xec\x99\x12\xe2\xd9\xb2\xa8\x9f\x15\x9b\xa2\x56\xcf\x1e\xb9\x5a\x3d\x73\xd3\x79\x26\x96\xcf\x7a\x69\x9d\xf6\x9e\xf5\xd2\xc2\xed\xe3\x03\x45\xef\x2e\xe8\x14\xb3\xf5\xfc\xe9\x63\x6a\x3d\x7e\xb4\x9e\x11\x7d\x2f\x17\x74\x89\x4e\x8e\x17\x37\xd5\x78\xa1\x27\xa0\xb1\xd6\x05\xd4\x7a\xb3\xb7\x69\x4a\xbe\x95\xc9\x0a\x16\x10\xd4\x32\x17\x17\x94\x56\xfd\xbe\x05\xce\x9a\x8e\xf2\xc0\x79\x74\x41\xe9\xf6\x78\xf4\x55\x20\xa6\x39\x57\xc4\x45\x4e\xb1\x91\x4b\x07\x8c\x58\x6d\x25\x45\x6f\x53\x65\x84\x63\xef\x37\x88\x8f\x0f\x4d\x2c\xbc\xd5\x67\x24\xc4\xdf\xb4\x4e\xb3\x0c\xaa\x38\x61\x16\x53\x34\x5e\x04\xe3\x2c\x92\xb7\xf2\xda\x18\x26\xe2\x9e\xba\x5d\xd2\xdc\x29\x34\xb1\xd1\xc9\x5f\x3d\x4d\x42\x38\x51\xba\x3b\xbb\xfa\xc8\x22\xb2\xd5\xc1\x5c\x6b\x2a\x0c\xf8\x42\xd1\x79\x80\x44\x30\xa7\x75\xbf\x5f\x68\x32\x61\x45\x4b\xfd\xab\x9c\xc1\x82\xf2\x29\x9b\xc1\x56\x27\xac\xfa\xfd\x45\xbf\xbf\x30\x90\x17\x96\xba\xb2\x03\xfb\x7e\x16\xd5\xf1\x98\x6c\x27\xab\x7c\x4e\x08\x3c\x50\x6b\xc7\xeb\xb5\xbc\xdc\x12\x3d\x4c\x7e\x48\x1e\x60\xba\xd4\x73\x98\x69\x4a\x77\xf9\x84\x7e\x9a\x8f\x5e\xfd\xbe\x98\xd5\x15\x06\xab\x06\xeb\xea\xd9\x6e\xd4\x99\x25\xab\x2c\x25\xc5\xc0\x98\x1e\x9d\xc0\xa7\xf3\xba\xd4\x2d\x4b\x24\x2f\x0b\x6f\x4b\xad\x3f\xa8\x98\x6d\x5c\xb0\x5a\xc8\xf5\x24\x60\x8d\x84\xdf\xe7\xac\x9d\x2c\x6a\x98\x56\xe4\x92\x67\x06\xdc\x7d\xa4\xc2\xf5\xd3\x3d\xbb\x3a\x57\xae\xc5\x01\xe2\x94\x61\x20\x6b\x5e\xa5\xd5\x65\x7b\xb6\x1e\xe0\xfd\x64\xe3\xc9\x3d\xe9\xeb\xd4\xe0\x60\xba\xd7\xb9\xda\x9d\x0d\x5d\x0b\x82\x7e\xa9\x12\xd6\x31\x42\x9b\x70\xdd\xdf\x8f\xce\x8d\x0f\xe2\x04\xee\xcb\x2b\xd6\xcc\x05\x3e\xb6\x56\x89\x45\x6a\x44\xd7\x38\x29\x7d\xda\x34\x0d\xb9\x11\x4e\x5a\xfd\x98\x57\x97\x32\xad\x2f\x0b\x58\xe5\xd5\x65\x91\xd6\x97\xb2\x69\x4e\x81\xf9\xfb\xe7\x87\xc8\xe6\x09\x79\x28\x68\x35\x35\xa8\xdb\xec\x78\xac\xb2\x88\x97\x88\x1c\x86\xf3\x37\x7a\xa8\x11\x04\x06\x53\x35\xd3\xcf\x5c\x40\x11\x8c\x0f\x4d\x96\xb5\x97\x5d\x76\x2c\x4a\x4e\x96\xd0\x3a\x81\xbd\x2a\xb2\x47\xef\x01\xf6\xaa\xc8\x56\x64\x30\xf2\xd1\xb4\x6e\x87\x93\x3a\x1f\x35\x67\x80\x73\x5b\xc5\x37\x84\x91\x77\x01\xf2\xd0\x1b\x13\x3a\xd5\xe4\xcb\xa4\x6a\xc7\x6d\x7f\xd6\x91\xa5\xbc\x2e\xd4\x7c\xc5\xab\x87\x5f\xb8\xe4\xf7\x25\x7b\xcd\x54\xa1\xc1\x3f\xf2\x8e\x22\x02\x37\x60\xd7\xc1\x11\x58\xd4\x19\xd5\xe8\x75\xc7\x35\xd4\xf3\xb2\x34\x21\xbe\x7f\x31\xb1\x13\x14\x89\x20\x85\x38\x85\x14\x95\x8b\x6c\x2c\xa6\xec\xc3\x6d\xc5\xd8\x78\x34\x0e\x15\xe8\xf0\xa4\x0a\xaf\xc6\x93\xab\x17\x85\x42\xb4\xde\xdf\xbc\x33\x52\x0d\xda\x8c\xab\xb6\xd6\xbc\x59\x7b\xfd\xbc\x13\x96\x10\xdc\x5e\x74\x83\xdb\x9b\x90\xf0\x09\x32\x4c\xc5\x94\xcf\xce\x0c\xde\xf4\x4d\x5b\x6b\x3e\x61\x79\x3c\x1d\x46\x9a\xf0\x11\xb4\x4d\x56\x2c\x51\xe6\xf5\xfc\x59\x92\x58\x51\xfe\x07\x19\x45\xa8\x35\xaa\xb1\x50\x18\x25\xff\xaa\x75\x38\x35\x06\x1a\xd9\x5a\xe9\x3b\xac\xa6\xd5\x0c\x0a\xaa\xa6\x62\x66\xf4\xfd\x1d\x99\x51\x19\xbe\xc6\x58\x52\x9e\x95\x02\xdd\xc7\xae\xf0\xe8\x4d\x25\xb2\x26\xa7\xc5\xac\x71\xee\x2c\xc4\xa0\xf2\x87\x79\x22\xd3\xa4\x18\x48\x72\x99\xb0\x41\x45\xae\xea\x5c\x36\xff\x30\x92\x1b\x7d\x4d\x7b\xf0\x8f\x58\x62\x63\x70\x86\xdc\x60\x2c\xe0\x38\x3e\xf9\xa1\x01\x5d\x38\x77\x2c\x9c\x8b\x11\xe8\x5b\xac\xff\x1b\x26\xce\xc5\x28\xe6\xdf\x5c\x8c\xc0\xe2\x82\x79\x2f\xba\xe4\xde\x41\xa2\x85\x07\xf9\xa1\x69\xbc\x4c\x06\x51\xa2\xbc\xa7\xc9\xb9\xde\x89\x1d\xc0\x28\xd8\x01\x7c\x13\x78\x6f\xff\xf8\x80\x1d\xc0\x1d\xba\xfb\x0a\x1a\xfc\xeb\x62\x87\xf2\xcb\x98\x2b\xe6\x68\x8c\x73\x67\x13\x9d\x4d\xb8\xd3\xfb\x95\xa8\xdf\xe8\xe6\x12\x32\x56\xae\x65\x95\x21\x01\xfe\x4a\x88\xb7\xdb\x8d\xc9\x65\x96\x24\xb3\x3d\xb9\xa2\xc1\x08\x3b\x70\xb3\x5c\xd7\x8c\x34\x27\xcd\x78\xd5\xa3\xb6\x52\xbd\x55\x6a\xbb\x50\xed\x9b\x31\x3d\xe0\xee\x30\xd8\x08\x99\x0f\x1b\x30\x9f\x1c\x3f\x47\xcd\xcc\x2b\x39\x4c\x99\xb9\x5f\xe6\x3c\xea\x6b\x63\x48\x28\x8f\x3c\x08\x64\xe6\x0a\x62\xce\x1f\x14\xb7\x9a\x54\xbb\xe1\xfd\xbe\xe5\xe7\x16\x41\x13\xdd\x24\x70\x02\x55\xf3\xd4\x42\x9d\x01\x96\xe1\xca\x17\x65\xe9\xe0\xe4\x53\xf7\xdc\xaa\xdd\x76\x61\x30\x54\xf4\x3c\xa3\xc2\x3f\xcf\x3e\x08\xa1\x1e\xb8\xa5\xc0\xe2\xfb\xcc\x1d\xbc\xab\x08\xc9\x5d\xd9\x09\xcf\x2b\x68\x0f\x90\x32\x60\x67\x90\x98\xf3\x9e\xa9\x1d\x32\x81\xde\x95\x63\xf0\xcd\xc3\x79\xb8\x1d\xf6\xfb\x17\x9f\xa0\xe5\xc3\x55\x94\x9c\xf3\xf7\x23\x3e\xfc\x2c\xe2\x53\x59\xc4\x47\x90\xcb\x2a\x46\x7c\xce\x60\x70\xb6\x9d\x1f\xac\x01\x84\x39\x93\xa0\x9c\x59\xb8\x1b\xc7\xff\x30\xda\xf4\x03\x86\x80\x34\x7d\x57\x97\xed\xae\x01\x1d\xe5\x7e\x13\x00\x93\x64\x35\x67\xb2\x07\xdf\x44\xe0\x29\x02\x55\xe8\x1f\x59\x15\x1f\xf0\xa8\xf7\xc2\x3a\x66\xfa\x69\x5e\x94\x2c\xff\x51\x82\x11\x53\x9b\xcf\x2f\x25\xbc\x0a\xb6\x47\x26\xed\x77\x09\x3f\x46\xf2\x6c\x93\xf8\x4f\x09\xfa\x98\x99\x8f\x7f\xd8\x0f\x1c\x9e\x49\xfa\x46\x06\xb1\xc6\x37\x55\x56\xb3\x07\x2e\x15\xab\x93\x7b\x01\xaa\x80\xdf\x04\x3c\x97\x04\xbe\xa9\xb2\x15\x2b\x37\xac\x96\xf4\x90\x65\xd9\x8b\xaa\xd1\x49\x8e\x56\x90\x54\x08\xfd\xed\x9d\xd8\xd3\x3b\xde\xfa\x96\xf4\x1d\x26\x18\x39\xba\xa8\x69\xa1\xbf\xc2\x53\x2d\xe9\xf3\xd6\x27\x06\xc0\x94\xba\x8c\xf5\x28\xf9\xc2\xe7\xd1\x57\xd8\x92\xf5\xe1\x4d\xbf\xc4\x2f\xeb\x58\x5a\xd2\xdf\x70\x1c\x2f\x83\xeb\x42\xfa\x37\xa6\x53\x4a\xe4\x88\x4b\xfa\x35\x7e\x6d\xca\x42\x69\xf2\x46\xd2\x2d\x56\xc7\x85\xa0\xbf\xe2\x6f\xe4\x47\xd2\x5f\x78\x27\x4a\xef\x37\x15\x44\x0b\x02\x5b\x8e\x6b\xf2\x02\xa3\x71\x7f\x53\x41\x6f\x5b\x2d\x0c\x4f\x3b\x38\x29\x7b\xe4\xd5\x42\x3c\xf6\xfb\x89\xf9\xe1\x0b\xeb\x9a\x0d\x21\xe3\xff\xe7\xff\x0b\x00\x00\xff\xff\x3b\x38\xee\x9a\xb4\xc8\x02\x00") + +func assetsChartMinJsBytes() ([]byte, error) { + return bindataRead( + _assetsChartMinJs, + "assets/chart.min.js", + ) +} + +func assetsChartMinJs() (*asset, error) { + bytes, err := assetsChartMinJsBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/chart.min.js", size: 182452, mode: os.FileMode(420), modTime: time.Unix(1621756582, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "assets/chart.min.js": assetsChartMinJs, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "assets": &bintree{nil, map[string]*bintree{ + "chart.min.js": &bintree{assetsChartMinJs, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/template/chartjs/assets/chart.min.js b/template/chartjs/assets/chart.min.js new file mode 100644 index 0000000000000000000000000000000000000000..6f839dd6d06a459f230c779b244e383cd3ee2a95 --- /dev/null +++ b/template/chartjs/assets/chart.min.js @@ -0,0 +1,13 @@ +/*! + * Chart.js v3.2.1 + * https://www.chartjs.org + * (c) 2021 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";const t="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function e(e,i,n){const o=n||(t=>Array.prototype.slice.call(t));let s=!1,a=[];return function(...n){a=o(n),s||(s=!0,t.call(window,(()=>{s=!1,e.apply(i,a)})))}}function i(t,e){let i;return function(){return e?(clearTimeout(i),i=setTimeout(t,e)):t(),e}}const n=t=>"start"===t?"left":"end"===t?"right":"center",o=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,s=(t,e,i)=>"right"===t?i:"center"===t?(e+i)/2:e;var a=new class{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,n){const o=e.listeners[n],s=e.duration;o.forEach((n=>n({chart:t,initial:e.initial,numSteps:s,currentStep:Math.min(i-e.start,s)})))}_refresh(){const e=this;e._request||(e._running=!0,e._request=t.call(window,(()=>{e._update(),e._request=null,e._running&&e._refresh()})))}_update(t=Date.now()){const e=this;let i=0;e._charts.forEach(((n,o)=>{if(!n.running||!n.items.length)return;const s=n.items;let a,r=s.length-1,l=!1;for(;r>=0;--r)a=s[r],a._active?(a._total>n.duration&&(n.duration=a._total),a.tick(t),l=!0):(s[r]=s[s.length-1],s.pop());l&&(o.draw(),e._notify(o,n,t,"progress")),s.length||(n.running=!1,e._notify(o,n,t,"complete"),n.initial=!1),i+=s.length})),e._lastDate=t,0===i&&(e._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let n=i.length-1;for(;n>=0;--n)i[n].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}; +/*! + * @kurkle/color v0.1.9 + * https://github.com/kurkle/color#readme + * (c) 2020 Jukka Kurkela + * Released under the MIT License + */const r={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},l="0123456789ABCDEF",c=t=>l[15&t],h=t=>l[(240&t)>>4]+l[15&t],d=t=>(240&t)>>4==(15&t);function u(t){var e=function(t){return d(t.r)&&d(t.g)&&d(t.b)&&d(t.a)}(t)?c:h;return t?"#"+e(t.r)+e(t.g)+e(t.b)+(t.a<255?e(t.a):""):t}function f(t){return t+.5|0}const g=(t,e,i)=>Math.max(Math.min(t,i),e);function p(t){return g(f(2.55*t),0,255)}function m(t){return g(f(255*t),0,255)}function x(t){return g(f(t/2.55)/100,0,1)}function b(t){return g(f(100*t),0,100)}const _=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const y=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function v(t,e,i){const n=e*Math.min(i,1-i),o=(e,o=(e+t/30)%12)=>i-n*Math.max(Math.min(o-3,9-o,1),-1);return[o(0),o(8),o(4)]}function w(t,e,i){const n=(n,o=(n+t/60)%6)=>i-i*e*Math.max(Math.min(o,4-o,1),0);return[n(5),n(3),n(1)]}function M(t,e,i){const n=v(t,1,.5);let o;for(e+i>1&&(o=1/(e+i),e*=o,i*=o),o=0;o<3;o++)n[o]*=1-e-i,n[o]+=e;return n}function k(t){const e=t.r/255,i=t.g/255,n=t.b/255,o=Math.max(e,i,n),s=Math.min(e,i,n),a=(o+s)/2;let r,l,c;return o!==s&&(c=o-s,l=a>.5?c/(2-o-s):c/(o+s),r=o===e?(i-n)/c+(i>16&255,s>>8&255,255&s]}return t}(),T.transparent=[0,0,0,0]);const e=T[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}function L(t,e,i){if(t){let n=k(t);n[e]=Math.max(0,Math.min(n[e]+n[e]*i,0===e?360:1)),n=P(n),t.r=n[0],t.g=n[1],t.b=n[2]}}function E(t,e){return t?Object.assign(e||{},t):t}function I(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=m(t[3]))):(e=E(t,{r:0,g:0,b:0,a:1})).a=m(e.a),e}function z(t){return"r"===t.charAt(0)?function(t){const e=_.exec(t);let i,n,o,s=255;if(e){if(e[7]!==i){const t=+e[7];s=255&(e[8]?p(t):255*t)}return i=+e[1],n=+e[3],o=+e[5],i=255&(e[2]?p(i):i),n=255&(e[4]?p(n):n),o=255&(e[6]?p(o):o),{r:i,g:n,b:o,a:s}}}(t):C(t)}class F{constructor(t){if(t instanceof F)return t;const e=typeof t;let i;var n,o,s;"object"===e?i=I(t):"string"===e&&(s=(n=t).length,"#"===n[0]&&(4===s||5===s?o={r:255&17*r[n[1]],g:255&17*r[n[2]],b:255&17*r[n[3]],a:5===s?17*r[n[4]]:255}:7!==s&&9!==s||(o={r:r[n[1]]<<4|r[n[2]],g:r[n[3]]<<4|r[n[4]],b:r[n[5]]<<4|r[n[6]],a:9===s?r[n[7]]<<4|r[n[8]]:255})),i=o||R(t)||z(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=E(this._rgb);return t&&(t.a=x(t.a)),t}set rgb(t){this._rgb=I(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${x(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):this._rgb;var t}hexString(){return this._valid?u(this._rgb):this._rgb}hslString(){return this._valid?function(t){if(!t)return;const e=k(t),i=e[0],n=b(e[1]),o=b(e[2]);return t.a<255?`hsla(${i}, ${n}%, ${o}%, ${x(t.a)})`:`hsl(${i}, ${n}%, ${o}%)`}(this._rgb):this._rgb}mix(t,e){const i=this;if(t){const n=i.rgb,o=t.rgb;let s;const a=e===s?.5:e,r=2*a-1,l=n.a-o.a,c=((r*l==-1?r:(r+l)/(1+r*l))+1)/2;s=1-c,n.r=255&c*n.r+s*o.r+.5,n.g=255&c*n.g+s*o.g+.5,n.b=255&c*n.b+s*o.b+.5,n.a=a*n.a+(1-a)*o.a,i.rgb=n}return i}clone(){return new F(this.rgb)}alpha(t){return this._rgb.a=m(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=f(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return L(this._rgb,2,t),this}darken(t){return L(this._rgb,2,-t),this}saturate(t){return L(this._rgb,1,t),this}desaturate(t){return L(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=k(t);i[0]=D(i[0]+e),i=P(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function V(t){return new F(t)}const B=t=>t instanceof CanvasGradient||t instanceof CanvasPattern;function W(t){return B(t)?t:V(t)}function H(t){return B(t)?t:V(t).saturate(.5).darken(.1).hexString()}function N(){}const j=function(){let t=0;return function(){return t++}}();function $(t){return null==t}function Y(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.substr(0,7)&&"Array]"===e.substr(-6)}function U(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}const X=t=>("number"==typeof t||t instanceof Number)&&isFinite(+t);function q(t,e){return X(t)?t:e}function K(t,e){return void 0===t?e:t}const G=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:t/e,Z=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function Q(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function J(t,e,i,n){let o,s,a;if(Y(t))if(s=t.length,n)for(o=s-1;o>=0;o--)e.call(i,t[o],o);else for(o=0;oi;)t=t[e.substr(i,n-i)],i=n+1,n=rt(e,i);return t}function ct(t){return t.charAt(0).toUpperCase()+t.slice(1)}const ht=t=>void 0!==t,dt=t=>"function"==typeof t,ut=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0},ft=Object.create(null),gt=Object.create(null);function pt(t,e){if(!e)return t;const i=e.split(".");for(let e=0,n=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>H(e.backgroundColor),this.hoverBorderColor=(t,e)=>H(e.borderColor),this.hoverColor=(t,e)=>H(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.describe(t)}set(t,e){return mt(this,t,e)}get(t){return pt(this,t)}describe(t,e){return mt(gt,t,e)}override(t,e){return mt(ft,t,e)}route(t,e,i,n){const o=pt(this,t),s=pt(this,i),a="_"+e;Object.defineProperties(o,{[a]:{value:o[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[a],e=s[n];return U(t)?Object.assign({},e,t):K(t,e)},set(t){this[a]=t}}})}}({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}});const bt=Math.PI,_t=2*bt,yt=_t+bt,vt=Number.POSITIVE_INFINITY,wt=bt/180,Mt=bt/2,kt=bt/4,St=2*bt/3,Pt=Math.log10,Dt=Math.sign;function Ct(t){const e=Math.pow(10,Math.floor(Pt(t))),i=t/e;return(i<=1?1:i<=2?2:i<=5?5:10)*e}function Ot(t){const e=[],i=Math.sqrt(t);let n;for(n=1;nt-e)).pop(),e}function At(t){return!isNaN(parseFloat(t))&&isFinite(t)}function Tt(t,e,i){return Math.abs(t-e)=t}function Lt(t,e,i){let n,o,s;for(n=0,o=t.length;nr&&ln&&(n=s),n}function Ut(t,e,i,n){let o=(n=n||{}).data=n.data||{},s=n.garbageCollect=n.garbageCollect||[];n.font!==e&&(o=n.data={},s=n.garbageCollect=[],n.font=e),t.save(),t.font=e;let a=0;const r=i.length;let l,c,h,d,u;for(l=0;li.length){for(l=0;l0&&t.stroke()}}function Gt(t,e,i){return i=i||.5,t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==s.strokeColor;let l,c;for(t.save(),s.translation&&t.translate(s.translation[0],s.translation[1]),$(s.rotation)||t.rotate(s.rotation),t.font=o.string,s.color&&(t.fillStyle=s.color),s.textAlign&&(t.textAlign=s.textAlign),s.textBaseline&&(t.textBaseline=s.textBaseline),l=0;lt[i]1;)n=s+o>>1,i(n)?s=n:o=n;return{lo:s,hi:o}}const oe=(t,e,i)=>ne(t,i,(n=>t[n][e]ne(t,i,(n=>t[n][e]>=i));function ae(t,e,i){let n=0,o=t.length;for(;nn&&t[o-1]>i;)o--;return n>0||o{const i="_onData"+ct(e),n=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const o=n.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),o}})})))}function ce(t,e){const i=t._chartjs;if(!i)return;const n=i.listeners,o=n.indexOf(e);-1!==o&&n.splice(o,1),n.length>0||(re.forEach((e=>{delete t[e]})),delete t._chartjs)}function he(t){const e=new Set;let i,n;for(i=0,n=t.length;i{o.push(t)})),o}function de(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function ue(t,e,i){let n;return"string"==typeof t?(n=parseInt(t,10),-1!==t.indexOf("%")&&(n=n/100*e.parentNode[i])):n=t,n}const fe=t=>window.getComputedStyle(t,null);function ge(t,e){return fe(t).getPropertyValue(e)}const pe=["top","right","bottom","left"];function me(t,e,i){const n={};i=i?"-"+i:"";for(let o=0;o<4;o++){const s=pe[o];n[s]=parseFloat(t[e+"-"+s+i])||0}return n.width=n.left+n.right,n.height=n.top+n.bottom,n}function xe(t,e){const{canvas:i,currentDevicePixelRatio:n}=e,o=fe(i),s="border-box"===o.boxSizing,a=me(o,"padding"),r=me(o,"border","width"),{x:l,y:c,box:h}=function(t,e){const i=t.native||t,n=i.touches,o=n&&n.length?n[0]:i,{offsetX:s,offsetY:a}=o;let r,l,c=!1;if(((t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot))(s,a,i.target))r=s,l=a;else{const t=e.getBoundingClientRect();r=o.clientX-t.left,l=o.clientY-t.top,c=!0}return{x:r,y:l,box:c}}(t,i),d=a.left+(h&&r.left),u=a.top+(h&&r.top);let{width:f,height:g}=e;return s&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/n),y:Math.round((c-u)/g*i.height/n)}}const be=t=>Math.round(10*t)/10;function _e(t,e,i,n){const o=fe(t),s=me(o,"margin"),a=ue(o.maxWidth,t,"clientWidth")||vt,r=ue(o.maxHeight,t,"clientHeight")||vt,l=function(t,e,i){let n,o;if(void 0===e||void 0===i){const s=de(t);if(s){const t=s.getBoundingClientRect(),a=fe(s),r=me(a,"border","width"),l=me(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,n=ue(a.maxWidth,s,"clientWidth"),o=ue(a.maxHeight,s,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:n||vt,maxHeight:o||vt}}(t,e,i);let{width:c,height:h}=l;if("content-box"===o.boxSizing){const t=me(o,"border","width"),e=me(o,"padding");c-=e.width+t.width,h-=e.height+t.height}return c=Math.max(0,c-s.width),h=Math.max(0,n?Math.floor(c/n):h-s.height),c=be(Math.min(c,a,l.maxWidth)),h=be(Math.min(h,r,l.maxHeight)),c&&!h&&(h=be(c/2)),{width:c,height:h}}function ye(t,e,i){const n=t.currentDevicePixelRatio=e||1,{canvas:o,width:s,height:a}=t;o.height=a*n,o.width=s*n,t.ctx.setTransform(n,0,0,n,0,0),o.style&&(i||!o.style.height&&!o.style.width)&&(o.style.height=a+"px",o.style.width=s+"px")}const ve=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};window.addEventListener("test",null,e),window.removeEventListener("test",null,e)}catch(t){}return t}();function we(t,e){const i=ge(t,e),n=i&&i.match(/^(\d+)(\.\d+)?px$/);return n?+n[1]:void 0}function Me(t,e){return"native"in t?{x:t.x,y:t.y}:xe(t,e)}function ke(t,e,i,n){const{controller:o,data:s,_sorted:a}=t,r=o._cachedMeta.iScale;if(r&&e===r.axis&&a&&s.length){const t=r._reversePixels?se:oe;if(!n)return t(s,e,i);if(o._sharedOptions){const n=s[0],o="function"==typeof n.getRange&&n.getRange(e);if(o){const n=t(s,e,i-o),a=t(s,e,i+o);return{lo:n.lo,hi:a.hi}}}}return{lo:0,hi:s.length-1}}function Se(t,e,i,n,o){const s=t.getSortedVisibleDatasetMetas(),a=i[e];for(let t=0,i=s.length;t{t[r](o[a],n)&&s.push({element:t,datasetIndex:e,index:i}),t.inRange(o.x,o.y,n)&&(l=!0)})),i.intersect&&!l?[]:s}var Oe={modes:{index(t,e,i,n){const o=Me(e,t),s=i.axis||"x",a=i.intersect?Pe(t,o,s,n):De(t,o,s,!1,n),r=[];return a.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=a[0].index,i=t.data[e];i&&!i.skip&&r.push({element:i,datasetIndex:t.index,index:e})})),r):[]},dataset(t,e,i,n){const o=Me(e,t),s=i.axis||"xy";let a=i.intersect?Pe(t,o,s,n):De(t,o,s,!1,n);if(a.length>0){const e=a[0].datasetIndex,i=t.getDatasetMeta(e).data;a=[];for(let t=0;tPe(t,Me(e,t),i.axis||"xy",n),nearest:(t,e,i,n)=>De(t,Me(e,t),i.axis||"xy",i.intersect,n),x:(t,e,i,n)=>(i.axis="x",Ce(t,e,i,n)),y:(t,e,i,n)=>(i.axis="y",Ce(t,e,i,n))}};const Ae=new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/),Te=new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/);function Re(t,e){const i=(""+t).match(Ae);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}function Le(t,e){const i={},n=U(e),o=n?Object.keys(e):e,s=U(t)?n?i=>K(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of o)i[t]=+s(t)||0;return i}function Ee(t){return Le(t,{top:"y",right:"x",bottom:"y",left:"x"})}function Ie(t){return Le(t,["topLeft","topRight","bottomLeft","bottomRight"])}function ze(t){const e=Ee(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Fe(t,e){t=t||{},e=e||xt.font;let i=K(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let n=K(t.style,e.style);n&&!(""+n).match(Te)&&(console.warn('Invalid font style specified: "'+n+'"'),n="");const o={family:K(t.family,e.family),lineHeight:Re(K(t.lineHeight,e.lineHeight),i),size:i,style:n,weight:K(t.weight,e.weight),string:""};return o.string=$t(o),o}function Ve(t,e,i,n){let o,s,a,r=!0;for(o=0,s=t.length;ot.pos===e))}function Ne(t,e){return t.filter((t=>-1===We.indexOf(t.pos)&&t.box.axis===e))}function je(t,e){return t.sort(((t,i)=>{const n=e?i:t,o=e?t:i;return n.weight===o.weight?n.index-o.index:n.weight-o.weight}))}function $e(t,e,i,n){return Math.max(t[i],e[i])+Math.max(t[n],e[n])}function Ye(t,e){t.top=Math.max(t.top,e.top),t.left=Math.max(t.left,e.left),t.bottom=Math.max(t.bottom,e.bottom),t.right=Math.max(t.right,e.right)}function Ue(t,e,i){const n=i.box,o=t.maxPadding;U(i.pos)||(i.size&&(t[i.pos]-=i.size),i.size=i.horizontal?n.height:n.width,t[i.pos]+=i.size),n.getPadding&&Ye(o,n.getPadding());const s=Math.max(0,e.outerWidth-$e(o,t,"left","right")),a=Math.max(0,e.outerHeight-$e(o,t,"top","bottom")),r=s!==t.w,l=a!==t.h;return t.w=s,t.h=a,i.horizontal?{same:r,other:l}:{same:l,other:r}}function Xe(t,e){const i=e.maxPadding;function n(t){const n={left:0,top:0,right:0,bottom:0};return t.forEach((t=>{n[t]=Math.max(e[t],i[t])})),n}return n(t?["left","right"]:["top","bottom"])}function qe(t,e,i){const n=[];let o,s,a,r,l,c;for(o=0,s=t.length,l=0;ot.box.fullSize)),!0),n=je(He(e,"left"),!0),o=je(He(e,"right")),s=je(He(e,"top"),!0),a=je(He(e,"bottom")),r=Ne(e,"x"),l=Ne(e,"y");return{fullSize:i,leftAndTop:n.concat(s),rightAndBottom:o.concat(l).concat(a).concat(r),chartArea:He(e,"chartArea"),vertical:n.concat(o).concat(l),horizontal:s.concat(a).concat(r)}}(t.boxes),l=r.vertical,c=r.horizontal;J(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const h=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:o,availableWidth:s,availableHeight:a,vBoxMaxWidth:s/2/h,hBoxMaxHeight:a/2}),u=Object.assign({},o);Ye(u,ze(n));const f=Object.assign({maxPadding:u,w:s,h:a,x:o.left,y:o.top},o);!function(t,e){let i,n,o;for(i=0,n=t.length;i{const i=e.box;Object.assign(i,t.chartArea),i.update(f.w,f.h)}))}};class Ze{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,n){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,n?Math.floor(e/n):i)}}isAttached(t){return!0}}class Qe extends Ze{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}}const Je={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},ti=t=>null===t||""===t;const ei=!!ve&&{passive:!0};function ii(t,e,i){t.canvas.removeEventListener(e,i,ei)}function ni(t,e,i){const n=t.canvas,o=n&&de(n)||n,s=new MutationObserver((t=>{const e=de(o);t.forEach((t=>{for(let n=0;n{t.forEach((t=>{for(let e=0;e{i.currentDevicePixelRatio!==t&&e()})))}function li(t,i,n){const o=t.canvas,s=o&&de(o);if(!s)return;const a=e(((t,e)=>{const i=s.clientWidth;n(t,e),i{const e=t[0],i=e.contentRect.width,n=e.contentRect.height;0===i&&0===n||a(i,n)}));return r.observe(s),function(t,e){si.size||window.addEventListener("resize",ri),si.set(t,e)}(t,a),r}function ci(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){si.delete(t),si.size||window.removeEventListener("resize",ri)}(t)}function hi(t,i,n){const o=t.canvas,s=e((e=>{null!==t.ctx&&n(function(t,e){const i=Je[t.type]||t.type,{x:n,y:o}=xe(t,e);return{type:i,chart:e,native:t,x:void 0!==n?n:null,y:void 0!==o?o:null}}(e,t))}),t,(t=>{const e=t[0];return[e,e.offsetX,e.offsetY]}));return function(t,e,i){t.addEventListener(e,i,ei)}(o,i,s),s}class di extends Ze{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,n=t.getAttribute("height"),o=t.getAttribute("width");if(t.$chartjs={initial:{height:n,width:o,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",ti(o)){const e=we(t,"width");void 0!==e&&(t.width=e)}if(ti(n))if(""===t.style.height)t.height=t.width/(e||2);else{const e=we(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e.$chartjs)return!1;const i=e.$chartjs.initial;["height","width"].forEach((t=>{const n=i[t];$(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e.$chartjs,!0}addEventListener(t,e,i){this.removeEventListener(t,e);const n=t.$proxies||(t.$proxies={}),o={attach:ni,detach:oi,resize:li}[e]||hi;n[e]=o(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),n=i[e];if(!n)return;({attach:ci,detach:ci,resize:ci}[e]||ii)(t,e,n),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,n){return _e(t,e,i,n)}isAttached(t){const e=de(t);return!(!e||!de(e))}}var ui=Object.freeze({__proto__:null,BasePlatform:Ze,BasicPlatform:Qe,DomPlatform:di});const fi=t=>0===t||1===t,gi=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*_t/i),pi=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*_t/i)+1,mi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*Mt),easeOutSine:t=>Math.sin(t*Mt),easeInOutSine:t=>-.5*(Math.cos(bt*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>fi(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>fi(t)?t:gi(t,.075,.3),easeOutElastic:t=>fi(t)?t:pi(t,.075,.3),easeInOutElastic(t){const e=.1125;return fi(t)?t:t<.5?.5*gi(2*t,e,.45):.5+.5*pi(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-mi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*mi.easeInBounce(2*t):.5*mi.easeOutBounce(2*t-1)+.5},xi="transparent",bi={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const n=W(t||xi),o=n.valid&&W(e||xi);return o&&o.valid?o.mix(n,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class _i{constructor(t,e,i,n){const o=e[i];n=Ve([t.to,n,o,t.from]);const s=Ve([t.from,o,n]);this._active=!0,this._fn=t.fn||bi[t.type||typeof s],this._easing=mi[t.easing]||mi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=s,this._to=n,this._promises=void 0}active(){return this._active}update(t,e,i){const n=this;if(n._active){n._notify(!1);const o=n._target[n._prop],s=i-n._start,a=n._duration-s;n._start=i,n._duration=Math.floor(Math.max(a,t.duration)),n._total+=s,n._loop=!!t.loop,n._to=Ve([t.to,e,o,t.from]),n._from=Ve([t.from,o,e])}}cancel(){const t=this;t._active&&(t.tick(Date.now()),t._active=!1,t._notify(!1))}tick(t){const e=this,i=t-e._start,n=e._duration,o=e._prop,s=e._from,a=e._loop,r=e._to;let l;if(e._active=s!==r&&(a||i1?2-l:l,l=e._easing(Math.min(1,Math.max(0,l))),e._target[o]=e._fn(s,r,l))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),xt.set("animations",{colors:{type:"color",properties:["color","borderColor","backgroundColor"]},numbers:{type:"number",properties:["x","y","borderWidth","radius","tension"]}}),xt.describe("animations",{_fallback:"animation"}),xt.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}});class vi{constructor(t,e){this._chart=t,this._properties=new Map,this.configure(e)}configure(t){if(!U(t))return;const e=this._properties;Object.getOwnPropertyNames(t).forEach((i=>{const n=t[i];if(!U(n))return;const o={};for(const t of yi)o[t]=n[t];(Y(n.properties)&&n.properties||[i]).forEach((t=>{t!==i&&e.has(t)||e.set(t,o)}))}))}_animateOptions(t,e){const i=e.options,n=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!n)return[];const o=this._createAnimations(n,i);return i.$shared&&function(t,e){const i=[],n=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),o}_createAnimations(t,e){const i=this._properties,n=[],o=t.$animations||(t.$animations={}),s=Object.keys(e),a=Date.now();let r;for(r=s.length-1;r>=0;--r){const l=s[r];if("$"===l.charAt(0))continue;if("options"===l){n.push(...this._animateOptions(t,e));continue}const c=e[l];let h=o[l];const d=i.get(l);if(h){if(d&&h.active()){h.update(d,c,a);continue}h.cancel()}d&&d.duration?(o[l]=h=new _i(d,t,l,c),n.push(h)):t[l]=c}return n}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(a.add(this._chart,i),!0):void 0}}function wi(t,e){const i=t&&t.options||{},n=i.reverse,o=void 0===i.min?e:0,s=void 0===i.max?e:0;return{start:n?s:o,end:n?o:s}}function Mi(t,e){const i=[],n=t._getSortedDatasetMetas(e);let o,s;for(o=0,s=n.length;o0||!i&&e<0)return n.index}return null}function Ci(t,e){const{chart:i,_cachedMeta:n}=t,o=i._stacks||(i._stacks={}),{iScale:s,vScale:a,index:r}=n,l=s.axis,c=a.axis,h=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(s,a,n),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Ai(t,e){e=e||t._parsed;for(const i of e){const e=i._stacks;if(!e||void 0===e[t.vScale.id]||void 0===e[t.vScale.id][t.index])return;delete e[t.vScale.id][t.index]}}const Ti=t=>"reset"===t||"none"===t,Ri=(t,e)=>e?t:Object.assign({},t);class Li{constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.$context=void 0,this.initialize()}initialize(){const t=this,e=t._cachedMeta;t.configure(),t.linkScales(),e._stacked=Si(e.vScale,e),t.addElements()}updateIndex(t){this.index=t}linkScales(){const t=this,e=t.chart,i=t._cachedMeta,n=t.getDataset(),o=(t,e,i,n)=>"x"===t?e:"r"===t?n:i,s=i.xAxisID=K(n.xAxisID,Oi(e,"x")),a=i.yAxisID=K(n.yAxisID,Oi(e,"y")),r=i.rAxisID=K(n.rAxisID,Oi(e,"r")),l=i.indexAxis,c=i.iAxisID=o(l,s,a,r),h=i.vAxisID=o(l,a,s,r);i.xScale=t.getScaleForId(s),i.yScale=t.getScaleForId(a),i.rScale=t.getScaleForId(r),i.iScale=t.getScaleForId(c),i.vScale=t.getScaleForId(h)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&ce(this._data,this),t._stacked&&Ai(t)}_dataCheck(){const t=this,e=t.getDataset(),i=e.data||(e.data=[]);U(i)?t._data=function(t){const e=Object.keys(t),i=new Array(e.length);let n,o,s;for(n=0,o=e.length;n0&&n._parsed[t-1];if(!1===i._parsing)n._parsed=o,n._sorted=!0,h=o;else{h=Y(o[t])?i.parseArrayData(n,o,t,e):U(o[t])?i.parseObjectData(n,o,t,e):i.parsePrimitiveData(n,o,t,e);const s=()=>null===c[r]||u&&c[r]p||d=0;--u)if(!m()){i.updateRangeFromParsed(c,t,g,l);break}return c}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let n,o,s;for(n=0,o=e.length;n=0&&tn.getContext(i,o)),d);return g.$shared&&(g.$shared=l,s[a]=Object.freeze(Ri(g,l))),g}_resolveAnimations(t,e,i){const n=this,o=n.chart,s=n._cachedDataOpts,a="animation-"+e,r=s[a];if(r)return r;let l;if(!1!==o.options.animation){const o=n.chart.config,s=o.datasetAnimationScopeKeys(n._type,e),a=o.getOptionScopes(n.getDataset(),s);l=o.createResolver(a,n.getContext(t,i,e))}const c=new vi(o,l&&l.animations);return l&&l._cacheable&&(s[a]=Object.freeze(c)),c}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Ti(t)||this.chart._animationsDisabled}updateElement(t,e,i,n){Ti(n)?Object.assign(t,i):this._resolveAnimations(e,n).update(t,i)}updateSharedOptions(t,e,i){t&&!Ti(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,n){t.active=n;const o=this.getStyle(e,n);this._resolveAnimations(e,i,n).update(t,{options:!n&&this.getSharedOptions(o)||o})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this,i=e._cachedMeta.data.length,n=e._data.length;n>i?e._insertElements(i,n-i,t):n{for(t.length+=e,r=t.length-1;r>=a;r--)t[r]=t[r-e]};for(l(s),r=t;r{o[t]=n[t]&&n[t].active()?n[t]._to:i[t]})),o}}Ei.defaults={},Ei.defaultRoutes=void 0;const Ii=new Map;function zi(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let n=Ii.get(i);return n||(n=new Intl.NumberFormat(t,e),Ii.set(i,n)),n}(e,i).format(t)}const Fi={values:t=>Y(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const n=this.chart.options.locale;let o,s=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(o="scientific"),s=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=Pt(Math.abs(s)),r=Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:o,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),zi(t,n,l)},logarithmic(t,e,i){if(0===t)return"0";const n=t/Math.pow(10,Math.floor(Pt(t)));return 1===n||2===n||5===n?Fi.numeric.call(this,t,e,i):""}};var Vi={formatters:Fi};function Bi(t,e){const i=t.options.ticks,n=i.maxTicksLimit||function(t){const e=t.options.offset,i=t._tickSize(),n=t._length/i+(e?0:1),o=t._maxLength/i;return Math.floor(Math.min(n,o))}(t),o=i.major.enabled?function(t){const e=[];let i,n;for(i=0,n=t.length;in)return function(t,e,i,n){let o,s=0,a=i[0];for(n=Math.ceil(n),o=0;oo)return e}return Math.max(o,1)}(o,e,n);if(s>0){let t,i;const n=s>1?Math.round((r-a)/(s-1)):null;for(Wi(e,l,c,$(n)?0:a-n,a),t=0,i=s-1;te.lineWidth,tickColor:(t,e)=>e.color,offset:!1,borderDash:[],borderDashOffset:0,borderWidth:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:Vi.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),xt.route("scale.ticks","color","","color"),xt.route("scale.grid","color","","borderColor"),xt.route("scale.grid","borderColor","","borderColor"),xt.route("scale.title","color","","color"),xt.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t}),xt.describe("scales",{_fallback:"scale"});const Hi=(t,e,i)=>"top"===e||"left"===e?t[e]+i:t[e]-i;function Ni(t,e){const i=[],n=t.length/e,o=t.length;let s=0;for(;sa+r)))return c}function $i(t){return t.drawTicks?t.tickLength:0}function Yi(t,e){if(!t.display)return 0;const i=Fe(t.font,e),n=ze(t.padding);return(Y(t.text)?t.text.length:1)*i.lineHeight+n.height}function Ui(t,e,i){let o=n(t);return(i&&"right"!==e||!i&&"right"===e)&&(o=(t=>"left"===t?"right":"right"===t?"left":t)(o)),o}class Xi extends Ei{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){const e=this;e.options=t.setContext(e.getContext()),e.axis=t.axis,e._userMin=e.parse(t.min),e._userMax=e.parse(t.max),e._suggestedMin=e.parse(t.suggestedMin),e._suggestedMax=e.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:n}=this;return t=q(t,Number.POSITIVE_INFINITY),e=q(e,Number.NEGATIVE_INFINITY),i=q(i,Number.POSITIVE_INFINITY),n=q(n,Number.NEGATIVE_INFINITY),{min:q(t,i),max:q(e,n),minDefined:X(t),maxDefined:X(e)}}getMinMax(t){const e=this;let i,{min:n,max:o,minDefined:s,maxDefined:a}=e.getUserBounds();if(s&&a)return{min:n,max:o};const r=e.getMatchingVisibleMetas();for(let l=0,c=r.length;l=s||n<=1||!t.isHorizontal())return void(t.labelRotation=o);const h=t._getLabelSizes(),d=h.widest.width,u=h.highest.height,f=Nt(t.chart.width-d,0,t.maxWidth);a=e.offset?t.maxWidth/n:f/(n-1),d+6>a&&(a=f/(n-(e.offset?.5:1)),r=t.maxHeight-$i(e.grid)-i.padding-Yi(e.title,t.chart.options.font),l=Math.sqrt(d*d+u*u),c=It(Math.min(Math.asin(Math.min((h.highest.height+6)/a,1)),Math.asin(Math.min(r/l,1))-Math.asin(u/l))),c=Math.max(o,Math.min(s,c))),t.labelRotation=c}afterCalculateLabelRotation(){Q(this.options.afterCalculateLabelRotation,[this])}beforeFit(){Q(this.options.beforeFit,[this])}fit(){const t=this,e={width:0,height:0},{chart:i,options:{ticks:n,title:o,grid:s}}=t,a=t._isVisible(),r=t.isHorizontal();if(a){const a=Yi(o,i.options.font);if(r?(e.width=t.maxWidth,e.height=$i(s)+a):(e.height=t.maxHeight,e.width=$i(s)+a),n.display&&t.ticks.length){const{first:i,last:o,widest:s,highest:a}=t._getLabelSizes(),l=2*n.padding,c=Et(t.labelRotation),h=Math.cos(c),d=Math.sin(c);if(r){const i=n.mirror?0:d*s.width+h*a.height;e.height=Math.min(t.maxHeight,e.height+i+l)}else{const i=n.mirror?0:h*s.width+d*a.height;e.width=Math.min(t.maxWidth,e.width+i+l)}t._calculatePadding(i,o,d,h)}}t._handleMargins(),r?(t.width=t._length=i.width-t._margins.left-t._margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=i.height-t._margins.top-t._margins.bottom)}_calculatePadding(t,e,i,n){const o=this,{ticks:{align:s,padding:a},position:r}=o.options,l=0!==o.labelRotation,c="top"!==r&&"x"===o.axis;if(o.isHorizontal()){const r=o.getPixelForTick(0)-o.left,h=o.right-o.getPixelForTick(o.ticks.length-1);let d=0,u=0;l?c?(d=n*t.width,u=i*e.height):(d=i*t.height,u=n*e.width):"start"===s?u=e.width:"end"===s?d=t.width:(d=t.width/2,u=e.width/2),o.paddingLeft=Math.max((d-r+a)*o.width/(o.width-r),0),o.paddingRight=Math.max((u-h+a)*o.width/(o.width-h),0)}else{let i=e.height/2,n=t.height/2;"start"===s?(i=0,n=t.height):"end"===s&&(i=e.height,n=0),o.paddingTop=i+a,o.paddingBottom=n+a}}_handleMargins(){const t=this;t._margins&&(t._margins.left=Math.max(t.paddingLeft,t._margins.left),t._margins.top=Math.max(t.paddingTop,t._margins.top),t._margins.right=Math.max(t.paddingRight,t._margins.right),t._margins.bottom=Math.max(t.paddingBottom,t._margins.bottom))}afterFit(){Q(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){const e=this;e.beforeTickToLabelConversion(),e.generateTickLabels(t),e.afterTickToLabelConversion()}_getLabelSizes(){const t=this;let e=t._labelSizes;if(!e){const i=t.options.ticks.sampleSize;let n=t.ticks;i{const i=t.gc,n=i.length/2;let o;if(n>e){for(o=0;o({width:o[t]||0,height:s[t]||0});return{first:v(0),last:v(e-1),widest:v(_),highest:v(y),widths:o,heights:s}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){const e=this;e._reversePixels&&(t=1-t);const i=e._startPixel+t*e._length;return jt(e._alignToPixels?Xt(e.chart,i,0):i)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this,i=e.ticks||[];if(t>=0&&tr*o?r/n:l/o:l*o0}_computeGridLineItems(t){const e=this,i=e.axis,n=e.chart,o=e.options,{grid:s,position:a}=o,r=s.offset,l=e.isHorizontal(),c=e.ticks.length+(r?1:0),h=$i(s),d=[],u=s.setContext(e.getContext()),f=u.drawBorder?u.borderWidth:0,g=f/2,p=function(t){return Xt(n,t,f)};let m,x,b,_,y,v,w,M,k,S,P,D;if("top"===a)m=p(e.bottom),v=e.bottom-h,M=m-g,S=p(t.top)+g,D=t.bottom;else if("bottom"===a)m=p(e.top),S=t.top,D=p(t.bottom)-g,v=m+g,M=e.top+h;else if("left"===a)m=p(e.right),y=e.right-h,w=m-g,k=p(t.left)+g,P=t.right;else if("right"===a)m=p(e.left),k=t.left,P=p(t.right)-g,y=m+g,w=e.left+h;else if("x"===i){if("center"===a)m=p((t.top+t.bottom)/2+.5);else if(U(a)){const t=Object.keys(a)[0],i=a[t];m=p(e.chart.scales[t].getPixelForValue(i))}S=t.top,D=t.bottom,v=m+g,M=v+h}else if("y"===i){if("center"===a)m=p((t.left+t.right)/2);else if(U(a)){const t=Object.keys(a)[0],i=a[t];m=p(e.chart.scales[t].getPixelForValue(i))}y=m-g,w=y-h,k=t.left,P=t.right}for(x=0;xe.value===t));if(n>=0){return i.setContext(e.getContext(n)).lineWidth}return 0}drawGrid(t){const e=this,i=e.options.grid,n=e.ctx,o=e._gridLineItems||(e._gridLineItems=e._computeGridLineItems(t));let s,a;const r=(t,e,i)=>{i.width&&i.color&&(n.save(),n.lineWidth=i.width,n.strokeStyle=i.color,n.setLineDash(i.borderDash||[]),n.lineDashOffset=i.borderDashOffset,n.beginPath(),n.moveTo(t.x,t.y),n.lineTo(e.x,e.y),n.stroke(),n.restore())};if(i.display)for(s=0,a=o.length;st[0])){ht(n)||(n=an("_fallback",t));const s={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:i,_fallback:n,_getTarget:o,override:o=>qi([o,...t],e,i,n)};return new Proxy(s,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,n)=>Ji(i,n,(()=>function(t,e,i,n){let o;for(const s of e)if(o=an(Zi(s,t),i),ht(o))return Qi(t,o)?on(i,n,t,o):o}(n,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>rn(t).includes(e),ownKeys:t=>rn(t),set:(t,e,i)=>((t._storage||(t._storage=o()))[e]=i,delete t[e],delete t._keys,!0)})}function Ki(t,e,i,n){const o={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Gi(t,n),setContext:e=>Ki(t,e,i,n),override:o=>Ki(t.override(o),e,i,n)};return new Proxy(o,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>Ji(t,e,(()=>function(t,e,i){const{_proxy:n,_context:o,_subProxy:s,_descriptors:a}=t;let r=n[e];dt(r)&&a.isScriptable(e)&&(r=function(t,e,i,n){const{_proxy:o,_context:s,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+[...r].join("->")+"->"+t);r.add(t),e=e(s,a||n),r.delete(t),U(e)&&(e=on(o._scopes,o,t,e));return e}(e,r,t,i));Y(r)&&r.length&&(r=function(t,e,i,n){const{_proxy:o,_context:s,_subProxy:a,_descriptors:r}=i;if(ht(s.index)&&n(t))e=e[s.index%e.length];else if(U(e[0])){const i=e,n=o._scopes.filter((t=>t!==i));e=[];for(const l of i){const i=on(n,o,t,l);e.push(Ki(i,s,a&&a[t],r))}}return e}(e,r,t,a.isIndexable));Qi(e,r)&&(r=Ki(r,o,s&&s[e],a));return r}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,n)=>(t[i]=n,delete e[i],!0)})}function Gi(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:n=e.indexable,_allKeys:o=e.allKeys}=t;return{allKeys:o,scriptable:i,indexable:n,isScriptable:dt(i)?i:()=>i,isIndexable:dt(n)?n:()=>n}}const Zi=(t,e)=>t?t+ct(e):e,Qi=(t,e)=>U(e)&&"adapters"!==t;function Ji(t,e,i){let n=t[e];return ht(n)||(n=i(),ht(n)&&(t[e]=n)),n}function tn(t,e,i){return dt(t)?t(e,i):t}const en=(t,e)=>!0===t?e:"string"==typeof t?lt(e,t):void 0;function nn(t,e,i,n){for(const o of e){const e=en(i,o);if(e){t.add(e);const o=tn(e._fallback,i,e);if(ht(o)&&o!==i&&o!==n)return o}else if(!1===e&&ht(n)&&i!==n)return null}return!1}function on(t,e,i,n){const o=e._rootScopes,s=tn(e._fallback,i,n),a=[...t,...o],r=new Set;r.add(n);let l=sn(r,a,i,s||i);return null!==l&&((!ht(s)||s===i||(l=sn(r,a,s,l),null!==l))&&qi([...r],[""],o,s,(()=>{const t=e._getTarget();return i in t||(t[i]={}),t[i]})))}function sn(t,e,i,n){for(;i;)i=nn(t,e,i,n);return i}function an(t,e){for(const i of e){if(!i)continue;const e=i[t];if(ht(e))return e}}function rn(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return[...e]}(t._scopes)),e}const ln=Number.EPSILON||1e-14,cn=(t,e)=>e!t.skip))),"monotone"===e.cubicInterpolationMode)dn(t);else{let i=n?t[t.length-1]:t[0];for(o=0,s=t.length;o0?e.y:t.y}}function mn(t,e,i,n){const o={x:t.cp2x,y:t.cp2y},s={x:e.cp1x,y:e.cp1y},a=gn(t,o,i),r=gn(o,s,i),l=gn(s,e,i),c=gn(a,r,i),h=gn(r,l,i);return gn(c,h,i)}function xn(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function bn(t,e){let i,n;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,n=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=n)}function _n(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function yn(t){return"angle"===t?{between:Ht,compare:Bt,normalize:Wt}:{between:(t,e,i)=>t>=Math.min(e,i)&&t<=Math.max(i,e),compare:(t,e)=>t-e,normalize:t=>t}}function vn({start:t,end:e,count:i,loop:n,style:o}){return{start:t%i,end:e%i,loop:n&&(e-t+1)%i==0,style:o}}function wn(t,e,i){if(!i)return[t];const{property:n,start:o,end:s}=i,a=e.length,{compare:r,between:l,normalize:c}=yn(n),{start:h,end:d,loop:u,style:f}=function(t,e,i){const{property:n,start:o,end:s}=i,{between:a,normalize:r}=yn(n),l=e.length;let c,h,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,c=0,h=l;cb||l(o,x,p)&&0!==r(o,x),v=()=>!b||0===r(s,p)||l(s,x,p);for(let t=h,i=h;t<=d;++t)m=e[t%a],m.skip||(p=c(m[n]),b=l(p,o,s),null===_&&y()&&(_=0===r(p,o)?t:i),null!==_&&v()&&(g.push(vn({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,x=p);return null!==_&&g.push(vn({start:_,end:d,loop:u,count:a,style:f})),g}function Mn(t,e){const i=[],n=t.segments;for(let o=0;oo&&t[s%e].skip;)s--;return s%=e,{start:o,end:s}}(i,o,s,n);if(!0===n)return Sn([{start:a,end:r,loop:s}],i,e);return Sn(function(t,e,i,n){const o=t.length,s=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%o];i.skip||i.stop?l.skip||(n=!1,s.push({start:e%o,end:(a-1)%o,loop:n}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&s.push({start:e%o,end:r%o,loop:n}),s}(i,a,r{const n=i.split("."),o=n.pop(),s=[t].concat(n).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");xt.route(s,o,l,r)}))}(e,t.defaultRoutes);t.descriptors&&xt.describe(e,t.descriptors)}(t,a,n),e.override&&xt.override(t.id,t.overrides)),a}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,n=this.scope;i in e&&delete e[i],n&&i in xt[n]&&(delete xt[n][i],this.override&&delete ft[i])}}var An=new class{constructor(){this.controllers=new On(Li,"datasets",!0),this.elements=new On(Ei,"elements"),this.plugins=new On(Object,"plugins"),this.scales=new On(Xi,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){const n=this;[...e].forEach((e=>{const o=i||n._getRegistryForType(e);i||o.isForType(e)||o===n.plugins&&e.id?n._exec(t,o,e):J(e,(e=>{const o=i||n._getRegistryForType(e);n._exec(t,o,e)}))}))}_exec(t,e,i){const n=ct(t);Q(i["before"+n],[],i),e[t](i),Q(i["after"+n],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(n(e,i),t,"stop"),this._notify(n(i,e),t,"start")}}function Rn(t,e){return e||!1!==t?!0===t?{}:t:null}function Ln(t,e,i,n){const o=t.pluginScopeKeys(e),s=t.getOptionScopes(i,o);return t.createResolver(s,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function En(t,e){const i=xt.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function In(t,e){return"x"===t||"y"===t?t:e.axis||("top"===(i=e.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.charAt(0).toLowerCase();var i}function zn(t){const e=t.options||(t.options={});e.plugins=K(e.plugins,{}),e.scales=function(t,e){const i=ft[t.type]||{scales:{}},n=e.scales||{},o=En(t.type,e),s=Object.create(null),a=Object.create(null);return Object.keys(n).forEach((t=>{const e=n[t],r=In(t,e),l=function(t,e){return t===e?"_index_":"_value_"}(r,o),c=i.scales||{};s[r]=s[r]||t,a[t]=st(Object.create(null),[{axis:r},e,c[r],c[l]])})),t.data.datasets.forEach((i=>{const o=i.type||t.type,r=i.indexAxis||En(o,e),l=(ft[o]||{}).scales||{};Object.keys(l).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,r),o=i[e+"AxisID"]||s[e]||e;a[o]=a[o]||Object.create(null),st(a[o],[{axis:e},n[o],l[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];st(e,[xt.scales[e.type],xt.scale])})),a}(t,e)}function Fn(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const Vn=new Map,Bn=new Set;function Wn(t,e){let i=Vn.get(t);return i||(i=e(),Vn.set(t,i),Bn.add(i)),i}const Hn=(t,e,i)=>{const n=lt(e,i);void 0!==n&&t.add(n)};class Nn{constructor(t){this._config=function(t){return(t=t||{}).data=Fn(t.data),zn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=Fn(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),zn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return Wn(t,(()=>[["datasets."+t,""]]))}datasetAnimationScopeKeys(t,e){return Wn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,"transitions."+e],["datasets."+t,""]]))}datasetElementScopeKeys(t,e){return Wn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,"datasets."+t,"elements."+e,""]]))}pluginScopeKeys(t){const e=t.id;return Wn(`${this.type}-plugin-${e}`,(()=>[["plugins."+e,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let n=i.get(t);return n&&!e||(n=new Map,i.set(t,n)),n}getOptionScopes(t,e,i){const{options:n,type:o}=this,s=this._cachedScopes(t,i),a=s.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>Hn(r,t,e)))),e.forEach((t=>Hn(r,n,t))),e.forEach((t=>Hn(r,ft[o]||{},t))),e.forEach((t=>Hn(r,xt,t))),e.forEach((t=>Hn(r,gt,t)))}));const l=[...r];return Bn.has(e)&&s.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,ft[e]||{},xt.datasets[e]||{},{type:e},xt,gt]}resolveNamedOptions(t,e,i,n=[""]){const o={$shared:!0},{resolver:s,subPrefixes:a}=jn(this._resolverCache,t,n);let r=s;if(function(t,e){const{isScriptable:i,isIndexable:n}=Gi(t);for(const o of e)if(i(o)&&dt(t[o])||n(o)&&Y(t[o]))return!0;return!1}(s,e)){o.$shared=!1;r=Ki(s,i=dt(i)?i():i,this.createResolver(t,i,a))}for(const t of e)o[t]=r[t];return o}createResolver(t,e,i=[""],n){const{resolver:o}=jn(this._resolverCache,t,i);return U(e)?Ki(o,e,void 0,n):o}}function jn(t,e,i){let n=t.get(e);n||(n=new Map,t.set(e,n));const o=i.join();let s=n.get(o);if(!s){s={resolver:qi(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},n.set(o,s)}return s}const $n=["top","bottom","left","right","chartArea"];function Yn(t,e){return"top"===t||"bottom"===t||-1===$n.indexOf(t)&&"x"===e}function Un(t,e){return function(i,n){return i[t]===n[t]?i[e]-n[e]:i[t]-n[t]}}function Xn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),Q(i&&i.onComplete,[t],e)}function qn(t){const e=t.chart,i=e.options.animation;Q(i&&i.onProgress,[t],e)}function Kn(){return"undefined"!=typeof window&&"undefined"!=typeof document}function Gn(t){return Kn()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Zn={},Qn=t=>{const e=Gn(t);return Object.values(Zn).filter((t=>t.canvas===e)).pop()};class Jn{constructor(t,e){const n=this;this.config=e=new Nn(e);const o=Gn(t),s=Qn(o);if(s)throw new Error("Canvas is already in use. Chart with ID '"+s.id+"' must be destroyed before the canvas can be reused.");const r=e.createResolver(e.chartOptionScopes(),n.getContext());this.platform=n._initializePlatform(o,e);const l=n.platform.acquireContext(o,r.aspectRatio),c=l&&l.canvas,h=c&&c.height,d=c&&c.width;this.id=j(),this.ctx=l,this.canvas=c,this.width=d,this.height=h,this._options=r,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._sortedMetasets=[],this.scales={},this.scale=void 0,this._plugins=new Tn,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=i((()=>this.update("resize")),r.resizeDelay||0),Zn[n.id]=n,l&&c?(a.listen(n,"complete",Xn),a.listen(n,"progress",qn),n._initialize(),n.attached&&n.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return $(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}_initialize(){const t=this;return t.notifyPlugins("beforeInit"),t.options.responsive?t.resize():ye(t,t.options.devicePixelRatio),t.bindEvents(),t.notifyPlugins("afterInit"),t}_initializePlatform(t,e){return e.platform?new e.platform:!Kn()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?new Qe:new di}clear(){return qt(this.canvas,this.ctx),this}stop(){return a.stop(this),this}resize(t,e){a.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this,n=i.options,o=i.canvas,s=n.maintainAspectRatio&&i.aspectRatio,a=i.platform.getMaximumSize(o,t,e,s),r=i.currentDevicePixelRatio,l=n.devicePixelRatio||i.platform.getDevicePixelRatio();i.width===a.width&&i.height===a.height&&r===l||(i.width=a.width,i.height=a.height,i._aspectRatio=i.aspectRatio,ye(i,l,!0),i.notifyPlugins("resize",{size:a}),Q(n.onResize,[i,a],i),i.attached&&i._doResize()&&i.render())}ensureScalesHaveIDs(){J(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this,e=t.options,i=e.scales,n=t.scales,o=Object.keys(n).reduce(((t,e)=>(t[e]=!1,t)),{});let s=[];i&&(s=s.concat(Object.keys(i).map((t=>{const e=i[t],n=In(t,e),o="r"===n,s="x"===n;return{options:e,dposition:o?"chartArea":s?"bottom":"left",dtype:o?"radialLinear":s?"category":"linear"}})))),J(s,(i=>{const s=i.options,a=s.id,r=In(a,s),l=K(s.type,i.dtype);void 0!==s.position&&Yn(s.position,r)===Yn(i.dposition)||(s.position=i.dposition),o[a]=!0;let c=null;if(a in n&&n[a].type===l)c=n[a];else{c=new(An.getScale(l))({id:a,type:l,ctx:t.ctx,chart:t}),n[c.id]=c}c.init(s,e)})),J(o,((t,e)=>{t||delete n[e]})),J(n,(e=>{Ge.configure(t,e,e.options),Ge.addBox(t,e)}))}_updateMetasetIndex(t,e){const i=this._metasets,n=t.index;n!==e&&(i[n]=i[e],i[e]=t,t.index=e)}_updateMetasets(){const t=this,e=t._metasets,i=t.data.datasets.length,n=e.length;if(n>i){for(let e=i;ei.length&&delete t._stacks,e.forEach(((e,n)=>{0===i.filter((t=>t===e._dataset)).length&&t._destroyDatasetMeta(n)}))}buildOrUpdateControllers(){const t=this,e=[],i=t.data.datasets;let n,o;for(t._removeUnreferencedMetasets(),n=0,o=i.length;n{t.getDatasetMeta(i).controller.reset()}),t)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this,i=e.config;i.update(),e._options=i.createResolver(i.chartOptionScopes(),e.getContext()),J(e.scales,(t=>{Ge.removeBox(e,t)}));const n=e._animationsDisabled=!e.options.animation;e.ensureScalesHaveIDs(),e.buildOrUpdateScales();const o=new Set(Object.keys(e._listeners)),s=new Set(e.options.events);if(ut(o,s)||(e.unbindEvents(),e.bindEvents()),e._plugins.invalidate(),!1===e.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const a=e.buildOrUpdateControllers();e.notifyPlugins("beforeElementsUpdate");let r=0;for(let t=0,i=e.data.datasets.length;t{t.reset()})),e._updateDatasets(t),e.notifyPlugins("afterUpdate",{mode:t}),e._layers.sort(Un("z","_idx")),e._lastEvent&&e._eventHandler(e._lastEvent,!0),e.render()}_updateLayout(t){const e=this;if(!1===e.notifyPlugins("beforeLayout",{cancelable:!0}))return;Ge.update(e,e.width,e.height,t);const i=e.chartArea,n=i.width<=0||i.height<=0;e._layers=[],J(e.boxes,(t=>{n&&"chartArea"===t.position||(t.configure&&t.configure(),e._layers.push(...t._layers()))}),e),e._layers.forEach(((t,e)=>{t._idx=e})),e.notifyPlugins("afterLayout")}_updateDatasets(t){const e=this,i="function"==typeof t;if(!1!==e.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let n=0,o=e.data.datasets.length;n=0;--i)t._drawDataset(e[i]);t.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this,i=e.ctx,n=t._clip,o=e.chartArea,s={meta:t,index:t.index,cancelable:!0};!1!==e.notifyPlugins("beforeDatasetDraw",s)&&(Zt(i,{left:!1===n.left?0:o.left-n.left,right:!1===n.right?e.width:o.right+n.right,top:!1===n.top?0:o.top-n.top,bottom:!1===n.bottom?e.height:o.bottom+n.bottom}),t.controller.draw(),Qt(i),s.cancelable=!1,e.notifyPlugins("afterDatasetDraw",s))}getElementsAtEventForMode(t,e,i,n){const o=Oe.modes[e];return"function"==typeof o?o(this,t,i,n):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let n=i.filter((t=>t&&t._dataset===e)).pop();return n||(n=i[t]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1}),n}getContext(){return this.$context||(this.$context={chart:this,type:"chart"})}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateDatasetVisibility(t,e){const i=this,n=e?"show":"hide",o=i.getDatasetMeta(t),s=o.controller._resolveAnimations(void 0,n);i.setDatasetVisibility(t,e),s.update(o,{visible:e}),i.update((e=>e.datasetIndex===t?n:void 0))}hide(t){this._updateDatasetVisibility(t,!1)}show(t){this._updateDatasetVisibility(t,!0)}_destroyDatasetMeta(t){const e=this,i=e._metasets&&e._metasets[t];i&&i.controller&&(i.controller._destroy(),delete e._metasets[t])}destroy(){const t=this,{canvas:e,ctx:i}=t;let n,o;for(t.stop(),a.remove(t),n=0,o=t.data.datasets.length;n{i.addEventListener(t,n,o),e[n]=o},o=(n,o)=>{e[n]&&(i.removeEventListener(t,n,o),delete e[n])};let s=function(e,i,n){e.offsetX=i,e.offsetY=n,t._eventHandler(e)};if(J(t.options.events,(t=>n(t,s))),t.options.responsive){let e;s=(e,i)=>{t.canvas&&t.resize(e,i)};const a=()=>{o("attach",a),t.attached=!0,t.resize(),n("resize",s),n("detach",e)};e=()=>{t.attached=!1,o("resize",s),n("attach",a)},i.isAttached(t.canvas)?a():e()}else t.attached=!0}unbindEvents(){const t=this,e=t._listeners;e&&(t._listeners={},J(e,((e,i)=>{t.platform.removeEventListener(t,i,e)})))}updateHoverStyle(t,e,i){const n=i?"set":"remove";let o,s,a,r;for("dataset"===e&&(o=this.getDatasetMeta(t[0].datasetIndex),o.controller["_"+n+"DatasetHoverStyle"]()),a=0,r=t.length;a{const n=e.getDatasetMeta(t);if(!n)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:n.data[i],index:i}}));!tt(n,i)&&(e._active=n,e._updateHoverStyles(n,i))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}_updateHoverStyles(t,e,i){const n=this,o=n.options.hover,s=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),a=s(e,t),r=i?t:s(t,e);a.length&&n.updateHoverStyle(a,o.mode,!1),r.length&&o.mode&&n.updateHoverStyle(r,o.mode,!0)}_eventHandler(t,e){const i=this,n={event:t,replay:e,cancelable:!0},o=e=>(e.options.events||this.options.events).includes(t.type);if(!1===i.notifyPlugins("beforeEvent",n,o))return;const s=i._handleEvent(t,e);return n.cancelable=!1,i.notifyPlugins("afterEvent",n,o),(s||n.changed)&&i.render(),i}_handleEvent(t,e){const i=this,{_active:n=[],options:o}=i,s=o.hover,a=e;let r=[],l=!1,c=null;return"mouseout"!==t.type&&(r=i.getElementsAtEventForMode(t,s.mode,s,a),c="click"===t.type?i._lastEvent:t),i._lastEvent=null,Gt(t,i.chartArea,i._minPadding)&&(Q(o.onHover,[t,r,i],i),"mouseup"!==t.type&&"click"!==t.type&&"contextmenu"!==t.type||Q(o.onClick,[t,r,i],i)),l=!tt(r,n),(l||e)&&(i._active=r,i._updateHoverStyles(r,n,e)),i._lastEvent=c,l}}const to=()=>J(Jn.instances,(t=>t._plugins.invalidate())),eo=!0;function io(){throw new Error("This method is not implemented: either no adapter can be found or an incomplete integration was provided.")}Object.defineProperties(Jn,{defaults:{enumerable:eo,value:xt},instances:{enumerable:eo,value:Zn},overrides:{enumerable:eo,value:ft},registry:{enumerable:eo,value:An},version:{enumerable:eo,value:"3.2.1"},getChart:{enumerable:eo,value:Qn},register:{enumerable:eo,value:(...t)=>{An.add(...t),to()}},unregister:{enumerable:eo,value:(...t)=>{An.remove(...t),to()}}});class no{constructor(t){this.options=t||{}}formats(){return io()}parse(t,e){return io()}format(t,e){return io()}add(t,e,i){return io()}diff(t,e,i){return io()}startOf(t,e,i){return io()}endOf(t,e){return io()}}no.override=function(t){Object.assign(no.prototype,t)};var oo={_date:no};function so(t){const e=function(t){if(!t._cache.$bar){const e=t.getMatchingVisibleMetas("bar");let i=[];for(let n=0,o=e.length;nt-e)))}return t._cache.$bar}(t);let i,n,o,s,a=t._length;const r=()=>{32767!==o&&-32768!==o&&(ht(s)&&(a=Math.min(a,Math.abs(o-s)||a)),s=o)};for(i=0,n=e.length;iMath.abs(r)&&(l=r,c=a),e[i.axis]=c,e._custom={barStart:l,barEnd:c,start:o,end:s,min:a,max:r}}(t,e,i,n):e[i.axis]=i.parse(t,n),e}function ro(t,e,i,n){const o=t.iScale,s=t.vScale,a=o.getLabels(),r=o===s,l=[];let c,h,d,u;for(c=i,h=i+n;c0?(p+=t,h-=t):h<0&&(p-=t,h+=t)}return{size:h,base:p,head:c,center:c+h/2}}_calculateBarIndexPixels(t,e){const i=this,n=e.scale,o=i.options,s=o.skipNull,a=K(o.maxBarThickness,1/0);let r,l;if(e.grouped){const n=s?i._getStackCount(t):e.stackCount,c="flex"===o.barThickness?function(t,e,i,n){const o=e.pixels,s=o[t];let a=t>0?o[t-1]:null,r=t=0;--n)i=Math.max(i,t[n].size()/2,e[n]._custom);return i>0&&i}getLabelAndValue(t){const e=this._cachedMeta,{xScale:i,yScale:n}=e,o=this.getParsed(t),s=i.getLabelForValue(o.x),a=n.getLabelForValue(o.y),r=o._custom;return{label:e.label,value:"("+s+", "+a+(r?", "+r:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,n){const o=this,s="reset"===n,{xScale:a,yScale:r}=o._cachedMeta,l=o.resolveDataElementOptions(e,n),c=o.getSharedOptions(l),h=o.includeOptions(n,c);for(let l=e;l""}}}};class uo extends Li{constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,n=this._cachedMeta;let o,s;for(o=t,s=t+e;oHt(t,r,l)?1:Math.max(e,e*i,n,n*i),g=(t,e,n)=>Ht(t,r,l)?-1:Math.min(e,e*i,n,n*i),p=f(0,c,d),m=f(Mt,h,u),x=g(bt,c,d),b=g(bt+Mt,h,u);n=(p-x)/2,o=(m-b)/2,s=-(p+x)/2,a=-(m+b)/2}return{ratioX:n,ratioY:o,offsetX:s,offsetY:a}}(d,h,l),m=(n.width-a)/u,x=(n.height-a)/f,b=Math.max(Math.min(m,x)/2,0),_=Z(e.options.radius,b),y=(_-Math.max(_*l,0))/e._getVisibleDatasetWeightTotal();e.offsetX=g*_,e.offsetY=p*_,o.total=e.calculateTotal(),e.outerRadius=_-y*e._getRingWeightOffset(e.index),e.innerRadius=Math.max(e.outerRadius-y*c,0),e.updateElements(s,0,s.length,t)}_circumference(t,e){const i=this,n=i.options,o=i._cachedMeta,s=i._getCircumference();return e&&n.animation.animateRotate||!this.chart.getDataVisibility(t)||null===o._parsed[t]?0:i.calculateCircumference(o._parsed[t]*s/_t)}updateElements(t,e,i,n){const o=this,s="reset"===n,a=o.chart,r=a.chartArea,l=a.options.animation,c=(r.left+r.right)/2,h=(r.top+r.bottom)/2,d=s&&l.animateScale,u=d?0:o.innerRadius,f=d?0:o.outerRadius,g=o.resolveDataElementOptions(e,n),p=o.getSharedOptions(g),m=o.includeOptions(n,p);let x,b=o._getRotation();for(x=0;x0&&!isNaN(t)?_t*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,n=i.data.labels||[],o=zi(e._parsed[t],i.options.locale);return{label:n[t]||"",value:o}}getMaxBorderWidth(t){const e=this;let i=0;const n=e.chart;let o,s,a,r,l;if(!t)for(o=0,s=n.data.datasets.length;o{const n=t.getDatasetMeta(0).controller.getStyle(i);return{text:e,fillStyle:n.backgroundColor,strokeStyle:n.borderColor,lineWidth:n.borderWidth,hidden:!t.getDataVisibility(i),index:i}})):[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}},tooltip:{callbacks:{title:()=>"",label(t){let e=t.label;const i=": "+t.formattedValue;return Y(e)?(e=e.slice(),e[0]+=i):e+=i,e}}}}};class fo extends Li{initialize(){this.enableOptionSharing=!0,super.initialize()}update(t){const e=this,i=e._cachedMeta,{dataset:n,data:o=[],_dataset:s}=i,a=e.chart._animationsDisabled;let{start:r,count:l}=function(t,e,i){const n=e.length;let o=0,s=n;if(t._sorted){const{iScale:a,_parsed:r}=t,l=a.axis,{min:c,max:h,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(o=Nt(Math.min(oe(r,a.axis,c).lo,i?n:oe(e,l,a.getPixelForValue(c)).lo),0,n-1)),s=u?Nt(Math.max(oe(r,a.axis,h).hi+1,i?0:oe(e,l,a.getPixelForValue(h)).hi+1),o,n)-o:n-o}return{start:o,count:s}}(i,o,a);e._drawStart=r,e._drawCount=l,function(t){const{xScale:e,yScale:i,_scaleRanges:n}=t,o={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!n)return t._scaleRanges=o,!0;const s=n.xmin!==e.min||n.xmax!==e.max||n.ymin!==i.min||n.ymax!==i.max;return Object.assign(n,o),s}(i)&&(r=0,l=o.length),n._decimated=!!s._decimated,n.points=o;const c=e.resolveDatasetElementOptions(t);e.options.showLine||(c.borderWidth=0),c.segment=e.options.segment,e.updateElement(n,void 0,{animated:!a,options:c},t),e.updateElements(o,r,l,t)}updateElements(t,e,i,n){const o=this,s="reset"===n,{xScale:a,yScale:r,_stacked:l}=o._cachedMeta,c=o.resolveDataElementOptions(e,n),h=o.getSharedOptions(c),d=o.includeOptions(n,h),u=o.options.spanGaps,f=At(u)?u:Number.POSITIVE_INFINITY,g=o.chart._animationsDisabled||s||"none"===n;let p=e>0&&o.getParsed(e-1);for(let c=e;c0&&i.x-p.x>f,u.parsed=i,d&&(u.options=h||o.resolveDataElementOptions(c,n)),g||o.updateElement(e,c,u,n),p=i}o.updateSharedOptions(h,n,c)}getMaxOverflow(){const t=this,e=t._cachedMeta,i=e.dataset,n=i.options&&i.options.borderWidth||0,o=e.data||[];if(!o.length)return n;const s=o[0].size(t.resolveDataElementOptions(0)),a=o[o.length-1].size(t.resolveDataElementOptions(o.length-1));return Math.max(n,s,a)/2}draw(){this._cachedMeta.dataset.updateControlPoints(this.chart.chartArea),super.draw()}}fo.id="line",fo.defaults={datasetElementType:"line",dataElementType:"point",showLine:!0,spanGaps:!1},fo.overrides={scales:{_index_:{type:"category"},_value_:{type:"linear"}}};class go extends Li{constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}_updateRadius(){const t=this,e=t.chart,i=e.chartArea,n=e.options,o=Math.min(i.right-i.left,i.bottom-i.top),s=Math.max(o/2,0),a=(s-Math.max(n.cutoutPercentage?s/100*n.cutoutPercentage:1,0))/e.getVisibleDatasetCount();t.outerRadius=s-a*t.index,t.innerRadius=t.outerRadius-a}updateElements(t,e,i,n){const o=this,s="reset"===n,a=o.chart,r=o.getDataset(),l=a.options.animation,c=o._cachedMeta.rScale,h=c.xCenter,d=c.yCenter,u=c.getIndexAngle(0)-.5*bt;let f,g=u;const p=360/o.countVisibleElements();for(f=0;f{!isNaN(t.data[n])&&this.chart.getDataVisibility(n)&&i++})),i}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?Et(this.resolveDataElementOptions(t,e).angle||i):0}}go.id="polarArea",go.defaults={dataElementType:"arc",animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:"number",properties:["x","y","startAngle","endAngle","innerRadius","outerRadius"]}},indexAxis:"r",startAngle:0},go.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(((e,i)=>{const n=t.getDatasetMeta(0).controller.getStyle(i);return{text:e,fillStyle:n.backgroundColor,strokeStyle:n.borderColor,lineWidth:n.borderWidth,hidden:!t.getDataVisibility(i),index:i}})):[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}},tooltip:{callbacks:{title:()=>"",label:t=>t.chart.data.labels[t.dataIndex]+": "+t.formattedValue}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};class po extends uo{}po.id="pie",po.defaults={cutout:0,rotation:0,circumference:360,radius:"100%"};class mo extends Li{getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}update(t){const e=this,i=e._cachedMeta,n=i.dataset,o=i.data||[],s=i.iScale.getLabels();if(n.points=o,"resize"!==t){const i=e.resolveDatasetElementOptions(t);e.options.showLine||(i.borderWidth=0);const a={_loop:!0,_fullLoop:s.length===o.length,options:i};e.updateElement(n,void 0,a,t)}e.updateElements(o,0,o.length,t)}updateElements(t,e,i,n){const o=this,s=o.getDataset(),a=o._cachedMeta.rScale,r="reset"===n;for(let l=e;l"",label:t=>"("+t.label+", "+t.formattedValue+")"}}},scales:{x:{type:"linear"},y:{type:"linear"}}};var bo=Object.freeze({__proto__:null,BarController:co,BubbleController:ho,DoughnutController:uo,LineController:fo,PolarAreaController:go,PieController:po,RadarController:mo,ScatterController:xo});function _o(t,e){const{startAngle:i,endAngle:n,pixelMargin:o,x:s,y:a,outerRadius:r,innerRadius:l}=e;let c=o/r;t.beginPath(),t.arc(s,a,r,i-c,n+c),l>o?(c=o/l,t.arc(s,a,l,n+c,i-c,!0)):t.arc(s,a,o,n+Mt,i-Mt),t.closePath(),t.clip()}function yo(t,e,i,n){const o=Le(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const s=(i-e)/2,a=Math.min(s,n*e/2),r=t=>{const e=(i-Math.min(s,t))*n/2;return Nt(t,0,Math.min(s,e))};return{outerStart:r(o.outerStart),outerEnd:r(o.outerEnd),innerStart:Nt(o.innerStart,0,a),innerEnd:Nt(o.innerEnd,0,a)}}function vo(t,e,i,n){return{x:i+t*Math.cos(e),y:n+t*Math.sin(e)}}function wo(t,e){const{x:i,y:n,startAngle:o,endAngle:s,pixelMargin:a}=e,r=Math.max(e.outerRadius-a,0),l=e.innerRadius+a,{outerStart:c,outerEnd:h,innerStart:d,innerEnd:u}=yo(e,l,r,s-o),f=r-c,g=r-h,p=o+c/f,m=s-h/g,x=l+d,b=l+u,_=o+d/x,y=s-u/b;if(t.beginPath(),t.arc(i,n,r,p,m),h>0){const e=vo(g,m,i,n);t.arc(e.x,e.y,h,m,s+Mt)}const v=vo(b,s,i,n);if(t.lineTo(v.x,v.y),u>0){const e=vo(b,y,i,n);t.arc(e.x,e.y,u,s+Mt,y+Math.PI)}if(t.arc(i,n,l,s-u/l,o+d/l,!0),d>0){const e=vo(x,_,i,n);t.arc(e.x,e.y,d,_+Math.PI,o-Mt)}const w=vo(f,o,i,n);if(t.lineTo(w.x,w.y),c>0){const e=vo(f,p,i,n);t.arc(e.x,e.y,c,o-Mt,p)}t.closePath()}function Mo(t,e){const{options:i}=e,n="inner"===i.borderAlign;i.borderWidth&&(n?(t.lineWidth=2*i.borderWidth,t.lineJoin="round"):(t.lineWidth=i.borderWidth,t.lineJoin="bevel"),e.fullCircles&&function(t,e,i){const{x:n,y:o,startAngle:s,endAngle:a,pixelMargin:r}=e,l=Math.max(e.outerRadius-r,0),c=e.innerRadius+r;let h;for(i&&(e.endAngle=e.startAngle+_t,_o(t,e),e.endAngle=a,e.endAngle===e.startAngle&&(e.endAngle+=_t,e.fullCircles--)),t.beginPath(),t.arc(n,o,c,s+_t,s,!0),h=0;h=_t||Ht(o,a,r))&&(s>=l&&s<=c)}getCenterPoint(t){const{x:e,y:i,startAngle:n,endAngle:o,innerRadius:s,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),r=(n+o)/2,l=(s+a)/2;return{x:e+Math.cos(r)*l,y:i+Math.sin(r)*l}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const e=this,i=e.options,n=i.offset||0;if(e.pixelMargin="inner"===i.borderAlign?.33:0,e.fullCircles=Math.floor(e.circumference/_t),!(0===e.circumference||e.innerRadius<0||e.outerRadius<0)){if(t.save(),n&&e.circumference<_t){const i=(e.startAngle+e.endAngle)/2;t.translate(Math.cos(i)*n,Math.sin(i)*n)}t.fillStyle=i.backgroundColor,t.strokeStyle=i.borderColor,function(t,e){if(e.fullCircles){e.endAngle=e.startAngle+_t,wo(t,e);for(let i=0;ir&&s>r;return{count:n,start:l,loop:e.loop,ilen:c(a+(c?r-t:t))%s,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=o[b(0)],t.moveTo(d.x,d.y)),h=0;h<=r;++h){if(d=o[b(h)],d.skip)continue;const e=d.x,i=d.y,n=0|e;n===u?(ig&&(g=i),m=(x*m+e)/++x):(_(),t.lineTo(e,i),u=n,x=0,f=g=i),p=i}_()}function Ao(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?Oo:Co}ko.id="arc",ko.defaults={borderAlign:"center",borderColor:"#fff",borderRadius:0,borderWidth:2,offset:0,angle:void 0},ko.defaultRoutes={backgroundColor:"backgroundColor"};const To="function"==typeof Path2D;function Ro(t,e,i,n){To&&1===e.segments.length?function(t,e,i,n){let o=e._path;o||(o=e._path=new Path2D,e.path(o,i,n)&&o.closePath()),So(t,e.options),t.stroke(o)}(t,e,i,n):function(t,e,i,n){const{segments:o,options:s}=e,a=Ao(e);for(const r of o)So(t,s,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+n-1})&&t.closePath(),t.stroke()}(t,e,i,n)}class Lo extends Ei{constructor(t){super(),this.animated=!0,this.options=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,t&&Object.assign(this,t)}updateControlPoints(t){const e=this,i=e.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!e._pointsUpdated){const n=i.spanGaps?e._loop:e._fullLoop;fn(e._points,i,t,n),e._pointsUpdated=!0}}set points(t){const e=this;e._points=t,delete e._segments,delete e._path,e._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=kn(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this,n=i.options,o=t[e],s=i.points,a=Mn(i,{property:e,start:o,end:o});if(!a.length)return;const r=[],l=function(t){return t.stepped?pn:t.tension||"monotone"===t.cubicInterpolationMode?mn:gn}(n);let c,h;for(c=0,h=a.length;c"borderDash"!==t&&"fill"!==t};class Io extends Ei{constructor(t){super(),this.options=void 0,this.parsed=void 0,this.skip=void 0,this.stop=void 0,t&&Object.assign(this,t)}inRange(t,e,i){const n=this.options,{x:o,y:s}=this.getProps(["x","y"],i);return Math.pow(t-o,2)+Math.pow(e-s,2)t.x):Vo(e,"bottom","top",t.base=a.left&&e<=a.right)&&(s||i>=a.top&&i<=a.bottom)}function jo(t,e){t.rect(e.x,e.y,e.w,e.h)}Io.id="point",Io.defaults={borderWidth:1,hitRadius:1,hoverBorderWidth:1,hoverRadius:4,pointStyle:"circle",radius:3,rotation:0},Io.defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};class $o extends Ei{constructor(t){super(),this.options=void 0,this.horizontal=void 0,this.base=void 0,this.width=void 0,this.height=void 0,t&&Object.assign(this,t)}draw(t){const e=this.options,{inner:i,outer:n}=Ho(this),o=(s=n.radius).topLeft||s.topRight||s.bottomLeft||s.bottomRight?ie:jo;var s;t.save(),n.w===i.w&&n.h===i.h||(t.beginPath(),o(t,n),t.clip(),o(t,i),t.fillStyle=e.borderColor,t.fill("evenodd")),t.beginPath(),o(t,i),t.fillStyle=e.backgroundColor,t.fill(),t.restore()}inRange(t,e,i){return No(this,t,e,i)}inXRange(t,e){return No(this,t,null,e)}inYRange(t,e){return No(this,null,t,e)}getCenterPoint(t){const{x:e,y:i,base:n,horizontal:o}=this.getProps(["x","y","base","horizontal"],t);return{x:o?(e+n)/2:e,y:o?i:(i+n)/2}}getRange(t){return"x"===t?this.width/2:this.height/2}}$o.id="bar",$o.defaults={borderSkipped:"start",borderWidth:0,borderRadius:0,enableBorderRadius:!0,pointStyle:void 0},$o.defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};var Yo=Object.freeze({__proto__:null,ArcElement:ko,LineElement:Lo,PointElement:Io,BarElement:$o});function Uo(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{value:e})}}function Xo(t){t.data.datasets.forEach((t=>{Uo(t)}))}var qo={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Xo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:s,indexAxis:a}=e,r=t.getDatasetMeta(o),l=s||e.data;if("y"===Ve([a,t.options.indexAxis]))return;if("line"!==r.type)return;const c=t.scales[r.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let h,{start:d,count:u}=function(t,e){const i=e.length;let n,o=0;const{iScale:s}=t,{min:a,max:r,minDefined:l,maxDefined:c}=s.getUserBounds();return l&&(o=Nt(oe(e,s.axis,a).lo,0,i-1)),n=c?Nt(oe(e,s.axis,r).hi+1,o,i)-o:i-o,{start:o,count:n}}(r,l);if(u<=4*n)Uo(e);else{switch($(s)&&(e._data=l,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":h=function(t,e,i,n,o){const s=o.samples||n;if(s>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(s-2);let l=0;const c=e+i-1;let h,d,u,f,g,p=e;for(a[l++]=t[p],h=0;hu&&(u=f,d=t[n],g=n);a[l++]=d,p=g}return a[l++]=t[c],a}(l,d,u,n,i);break;case"min-max":h=function(t,e,i,n){let o,s,a,r,l,c,h,d,u,f,g=0,p=0;const m=[],x=e+i-1,b=t[e].x,_=t[x].x-b;for(o=e;of&&(f=r,h=o),g=(p*g+s.x)/++p;else{const i=o-1;if(!$(c)&&!$(h)){const e=Math.min(c,h),n=Math.max(c,h);e!==d&&e!==i&&m.push({...t[e],x:g}),n!==d&&n!==i&&m.push({...t[n],x:g})}o>0&&i!==d&&m.push(t[i]),m.push(s),l=e,p=0,u=f=r,c=h=d=o}}return m}(l,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=h}}))},destroy(t){Xo(t)}};function Ko(t,e,i){const n=function(t){const e=t.options,i=e.fill;let n=K(i&&i.target,i);return void 0===n&&(n=!!e.backgroundColor),!1!==n&&null!==n&&(!0===n?"origin":n)}(t);if(U(n))return!isNaN(n.value)&&n;let o=parseFloat(n);return X(o)&&Math.floor(o)===o?("-"!==n[0]&&"+"!==n[0]||(o=e+o),!(o===e||o<0||o>=i)&&o):["origin","start","end","stack"].indexOf(n)>=0&&n}class Go{constructor(t){this.x=t.x,this.y=t.y,this.radius=t.radius}pathSegment(t,e,i){const{x:n,y:o,radius:s}=this;return e=e||{start:0,end:_t},t.arc(n,o,s,e.end,e.start,!0),!i.bounds}interpolate(t){const{x:e,y:i,radius:n}=this,o=t.angle;return{x:e+Math.cos(o)*n,y:i+Math.sin(o)*n,angle:o}}}function Zo(t){return(t.scale||{}).getPointPositionForValue?function(t){const{scale:e,fill:i}=t,n=e.options,o=e.getLabels().length,s=[],a=n.reverse?e.max:e.min,r=n.reverse?e.min:e.max;let l,c,h;if(h="start"===i?a:"end"===i?r:U(i)?i.value:e.getBaseValue(),n.grid.circular)return c=e.getPointPositionForValue(0,a),new Go({x:c.x,y:c.y,radius:e.getDistanceFromCenterForValue(h)});for(l=0;l"line"===t.type&&!t.hidden;function ts(t,e,i){const n=[];for(let o=0;o=n&&o<=c){r=o===n,l=o===c;break}}return{first:r,last:l,point:n}}function is(t,e){let i=[],n=!1;return Y(t)?(n=!0,i=t):i=function(t,e){const{x:i=null,y:n=null}=t||{},o=e.points,s=[];return e.segments.forEach((t=>{const e=o[t.start],a=o[t.end];null!==n?(s.push({x:e.x,y:n}),s.push({x:a.x,y:n})):null!==i&&(s.push({x:i,y:e.y}),s.push({x:i,y:a.y}))})),s}(t,e),i.length?new Lo({points:i,options:{tension:0},_loop:n,_fullLoop:n}):null}function ns(t,e,i){let n=t[e].fill;const o=[e];let s;if(!i)return n;for(;!1!==n&&-1===o.indexOf(n);){if(!X(n))return n;if(s=t[n],!s)return!1;if(s.visible)return n;o.push(n),n=s.fill}return!1}function os(t,e,i){t.beginPath(),e.path(t),t.lineTo(e.last().x,i),t.lineTo(e.first().x,i),t.closePath(),t.clip()}function ss(t,e,i,n){if(n)return;let o=e[t],s=i[t];return"angle"===t&&(o=Wt(o),s=Wt(s)),{property:t,start:o,end:s}}function as(t,e,i,n){return t&&e?n(t[i],e[i]):t?t[i]:e?e[i]:0}function rs(t,e,i){const{top:n,bottom:o}=e.chart.chartArea,{property:s,start:a,end:r}=i||{};"x"===s&&(t.beginPath(),t.rect(a,n,r-a,o-n),t.clip())}function ls(t,e,i,n){const o=e.interpolate(i,n);o&&t.lineTo(o.x,o.y)}function cs(t,e){const{line:i,target:n,property:o,color:s,scale:a}=e,r=function(t,e,i){const n=t.segments,o=t.points,s=e.points,a=[];for(const t of n){const n=ss(i,o[t.start],o[t.end],t.loop);if(!e.segments){a.push({source:t,target:n,start:o[t.start],end:o[t.end]});continue}const r=Mn(e,n);for(const e of r){const r=ss(i,s[e.start],s[e.end],e.loop),l=wn(t,o,r);for(const t of l)a.push({source:t,target:e,start:{[i]:as(n,r,"start",Math.max)},end:{[i]:as(n,r,"end",Math.min)}})}}return a}(i,n,o);for(const{source:e,target:l,start:c,end:h}of r){const{style:{backgroundColor:r=s}={}}=e;t.save(),t.fillStyle=r,rs(t,a,ss(o,c,h)),t.beginPath();const d=!!i.pathSegment(t,e);d?t.closePath():ls(t,n,h,o);const u=!!n.pathSegment(t,l,{move:d,reverse:!0}),f=d&&u;f||ls(t,n,c,o),t.closePath(),t.fill(f?"evenodd":"nonzero"),t.restore()}}function hs(t,e,i){const n=function(t){const{chart:e,fill:i,line:n}=t;if(X(i))return function(t,e){const i=t.getDatasetMeta(e);return i&&t.isDatasetVisible(e)?i.dataset:null}(e,i);if("stack"===i)return Qo(t);const o=Zo(t);return o instanceof Go?o:is(o,n)}(e),{line:o,scale:s,axis:a}=e,r=o.options,l=r.fill,c=r.backgroundColor,{above:h=c,below:d=c}=l||{};n&&o.points.length&&(Zt(t,i),function(t,e){const{line:i,target:n,above:o,below:s,area:a,scale:r}=e,l=i._loop?"angle":e.axis;t.save(),"x"===l&&s!==o&&(os(t,n,a.top),cs(t,{line:i,target:n,color:o,scale:r,property:l}),t.restore(),t.save(),os(t,n,a.bottom)),cs(t,{line:i,target:n,color:s,scale:r,property:l}),t.restore()}(t,{line:o,target:n,above:h,below:d,area:i,scale:s,axis:a}),Qt(t))}var ds={id:"filler",afterDatasetsUpdate(t,e,i){const n=(t.data.datasets||[]).length,o=[];let s,a,r,l;for(a=0;a=0;--e){const i=o[e].$filler;i&&(i.line.updateControlPoints(s),n&&hs(t.ctx,i,s))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const n=t.getSortedVisibleDatasetMetas();for(let e=n.length-1;e>=0;--e){const i=n[e].$filler;i&&hs(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const n=e.meta.$filler;n&&!1!==n.fill&&"beforeDatasetDraw"===i.drawTime&&hs(t.ctx,n,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const us=(t,e)=>{let{boxHeight:i=e,boxWidth:n=e}=t;return t.usePointStyle&&(i=Math.min(i,e),n=Math.min(n,e)),{boxWidth:n,boxHeight:i,itemHeight:Math.max(e,i)}};class fs extends Ei{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){const n=this;n.maxWidth=t,n.maxHeight=e,n._margins=i,n.setDimensions(),n.buildLabels(),n.fit()}setDimensions(){const t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height)}buildLabels(){const t=this,e=t.options.labels||{};let i=Q(e.generateLabels,[t.chart],t)||[];e.filter&&(i=i.filter((i=>e.filter(i,t.chart.data)))),e.sort&&(i=i.sort(((i,n)=>e.sort(i,n,t.chart.data)))),t.options.reverse&&i.reverse(),t.legendItems=i}fit(){const t=this,{options:e,ctx:i}=t;if(!e.display)return void(t.width=t.height=0);const n=e.labels,o=Fe(n.font),s=o.size,a=t._computeTitleHeight(),{boxWidth:r,itemHeight:l}=us(n,s);let c,h;i.font=o.string,t.isHorizontal()?(c=t.maxWidth,h=t._fitRows(a,s,r,l)+10):(h=t.maxHeight,c=t._fitCols(a,s,r,l)+10),t.width=Math.min(c,e.maxWidth||t.maxWidth),t.height=Math.min(h,e.maxHeight||t.maxHeight)}_fitRows(t,e,i,n){const o=this,{ctx:s,maxWidth:a,options:{labels:{padding:r}}}=o,l=o.legendHitBoxes=[],c=o.lineWidths=[0],h=n+r;let d=t;s.textAlign="left",s.textBaseline="middle";let u=-1,f=-h;return o.legendItems.forEach(((t,o)=>{const g=i+e/2+s.measureText(t.text).width;(0===o||c[c.length-1]+g+2*r>a)&&(d+=h,c[c.length-(o>0?0:1)]=0,f+=h,u++),l[o]={left:0,top:f,row:u,width:g,height:n},c[c.length-1]+=g+r})),d}_fitCols(t,e,i,n){const o=this,{ctx:s,maxHeight:a,options:{labels:{padding:r}}}=o,l=o.legendHitBoxes=[],c=o.columnSizes=[],h=a-t;let d=r,u=0,f=0,g=0,p=0,m=0;return o.legendItems.forEach(((t,o)=>{const a=i+e/2+s.measureText(t.text).width;o>0&&f+e+2*r>h&&(d+=u+r,c.push({width:u,height:f}),g+=u+r,m++,p=0,u=f=0),u=Math.max(u,a),f+=e+r,l[o]={left:g,top:p,col:m,width:a,height:n},p+=n+r})),d+=u,c.push({width:u,height:f}),d}adjustHitBoxes(){const t=this;if(!t.options.display)return;const e=t._computeTitleHeight(),{legendHitBoxes:i,options:{align:n,labels:{padding:s}}}=t;if(this.isHorizontal()){let a=0,r=o(n,t.left+s,t.right-t.lineWidths[a]);for(const l of i)a!==l.row&&(a=l.row,r=o(n,t.left+s,t.right-t.lineWidths[a])),l.top+=t.top+e+s,l.left=r,r+=l.width+s}else{let a=0,r=o(n,t.top+e+s,t.bottom-t.columnSizes[a].height);for(const l of i)l.col!==a&&(a=l.col,r=o(n,t.top+e+s,t.bottom-t.columnSizes[a].height)),l.top=r,l.left+=t.left+s,r+=l.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){const t=this;if(t.options.display){const e=t.ctx;Zt(e,t),t._draw(),Qt(e)}}_draw(){const t=this,{options:e,columnSizes:i,lineWidths:n,ctx:a}=t,{align:r,labels:l}=e,c=xt.color,h=xn(e.rtl,t.left,t.width),d=Fe(l.font),{color:u,padding:f}=l,g=d.size,p=g/2;let m;t.drawTitle(),a.textAlign=h.textAlign("left"),a.textBaseline="middle",a.lineWidth=.5,a.font=d.string;const{boxWidth:x,boxHeight:b,itemHeight:_}=us(l,g),y=t.isHorizontal(),v=this._computeTitleHeight();m=y?{x:o(r,t.left+f,t.right-n[0]),y:t.top+f+v,line:0}:{x:t.left+f,y:o(r,t.top+v+f,t.bottom-i[0].height),line:0},bn(t.ctx,e.textDirection);const w=_+f;t.legendItems.forEach(((e,M)=>{a.strokeStyle=e.fontColor||u,a.fillStyle=e.fontColor||u;const k=a.measureText(e.text).width,S=h.textAlign(e.textAlign||(e.textAlign=l.textAlign)),P=x+g/2+k;let D=m.x,C=m.y;h.setWidth(t.width),y?M>0&&D+P+f>t.right&&(C=m.y+=w,m.line++,D=m.x=o(r,t.left+f,t.right-n[m.line])):M>0&&C+w>t.bottom&&(D=m.x=D+i[m.line].width+f,m.line++,C=m.y=o(r,t.top+v+f,t.bottom-i[m.line].height));!function(t,e,i){if(isNaN(x)||x<=0||isNaN(b)||b<0)return;a.save();const n=K(i.lineWidth,1);if(a.fillStyle=K(i.fillStyle,c),a.lineCap=K(i.lineCap,"butt"),a.lineDashOffset=K(i.lineDashOffset,0),a.lineJoin=K(i.lineJoin,"miter"),a.lineWidth=n,a.strokeStyle=K(i.strokeStyle,c),a.setLineDash(K(i.lineDash,[])),l.usePointStyle){const o={radius:x*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},s=h.xPlus(t,x/2);Kt(a,o,s,e+p)}else{const o=e+Math.max((g-b)/2,0),s=h.leftForLtr(t,x),r=Ie(i.borderRadius);a.beginPath(),Object.values(r).some((t=>0!==t))?ie(a,{x:s,y:o,w:x,h:b,radius:r}):a.rect(s,o,x,b),a.fill(),0!==n&&a.stroke()}a.restore()}(h.x(D),C,e),D=s(S,D+x+p,t.right),function(t,e,i){ee(a,i.text,t,e+_/2,d,{strikethrough:i.hidden,textAlign:i.textAlign})}(h.x(D),C,e),y?m.x+=P+f:m.y+=w})),_n(t.ctx,e.textDirection)}drawTitle(){const t=this,e=t.options,i=e.title,s=Fe(i.font),a=ze(i.padding);if(!i.display)return;const r=xn(e.rtl,t.left,t.width),l=t.ctx,c=i.position,h=s.size/2,d=a.top+h;let u,f=t.left,g=t.width;if(this.isHorizontal())g=Math.max(...t.lineWidths),u=t.top+d,f=o(e.align,f,t.right-g);else{const i=t.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);u=d+o(e.align,t.top,t.bottom-i-e.labels.padding-t._computeTitleHeight())}const p=o(c,f,f+g);l.textAlign=r.textAlign(n(c)),l.textBaseline="middle",l.strokeStyle=i.color,l.fillStyle=i.color,l.font=s.string,ee(l,i.text,p,u,s)}_computeTitleHeight(){const t=this.options.title,e=Fe(t.font),i=ze(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){const i=this;let n,o,s;if(t>=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom)for(s=i.legendHitBoxes,n=0;n=o.left&&t<=o.left+o.width&&e>=o.top&&e<=o.top+o.height)return i.legendItems[n];return null}handleEvent(t){const e=this,i=e.options;if(!function(t,e){if("mousemove"===t&&(e.onHover||e.onLeave))return!0;if(e.onClick&&("click"===t||"mouseup"===t))return!0;return!1}(t.type,i))return;const n=e._getLegendItemAt(t.x,t.y);if("mousemove"===t.type){const a=e._hoveredItem,r=(s=n,null!==(o=a)&&null!==s&&o.datasetIndex===s.datasetIndex&&o.index===s.index);a&&!r&&Q(i.onLeave,[t,a,e],e),e._hoveredItem=n,n&&!r&&Q(i.onHover,[t,n,e],e)}else n&&Q(i.onClick,[t,n,e],e);var o,s}}var gs={id:"legend",_element:fs,start(t,e,i){const n=t.legend=new fs({ctx:t.ctx,options:i,chart:t});Ge.configure(t,n,i),Ge.addBox(t,n)},stop(t){Ge.removeBox(t,t.legend),delete t.legend},beforeUpdate(t,e,i){const n=t.legend;Ge.configure(t,n,i),n.options=i},afterUpdate(t){const e=t.legend;e.buildLabels(),e.adjustHitBoxes()},afterEvent(t,e){e.replay||t.legend.handleEvent(e.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(t,e,i){const n=e.datasetIndex,o=i.chart;o.isDatasetVisible(n)?(o.hide(n),e.hidden=!0):(o.show(n),e.hidden=!1)},onHover:null,onLeave:null,labels:{color:t=>t.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:n,textAlign:o,color:s}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const a=t.controller.getStyle(i?0:void 0),r=ze(a.borderWidth);return{text:e[t.index].label,fillStyle:a.backgroundColor,fontColor:s,hidden:!t.visible,lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:(r.width+r.height)/4,strokeStyle:a.borderColor,pointStyle:n||a.pointStyle,rotation:a.rotation,textAlign:o||a.textAlign,borderRadius:0,datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class ps extends Ei{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this,n=i.options;if(i.left=0,i.top=0,!n.display)return void(i.width=i.height=i.right=i.bottom=0);i.width=i.right=t,i.height=i.bottom=e;const o=Y(n.text)?n.text.length:1;i._padding=ze(n.padding);const s=o*Fe(n.font).lineHeight+i._padding.height;i.isHorizontal()?i.height=s:i.width=s}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:n,right:s,options:a}=this,r=a.align;let l,c,h,d=0;return this.isHorizontal()?(c=o(r,i,s),h=e+t,l=s-i):("left"===a.position?(c=i+t,h=o(r,n,e),d=-.5*bt):(c=s-t,h=o(r,e,n),d=.5*bt),l=n-e),{titleX:c,titleY:h,maxWidth:l,rotation:d}}draw(){const t=this,e=t.ctx,i=t.options;if(!i.display)return;const o=Fe(i.font),s=o.lineHeight/2+t._padding.top,{titleX:a,titleY:r,maxWidth:l,rotation:c}=t._drawArgs(s);ee(e,i.text,0,0,o,{color:i.color,maxWidth:l,rotation:c,textAlign:n(i.align),textBaseline:"middle",translation:[a,r]})}}var ms={id:"title",_element:ps,start(t,e,i){!function(t,e){const i=new ps({ctx:t.ctx,options:e,chart:t});Ge.configure(t,i,e),Ge.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;Ge.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const n=t.titleBlock;Ge.configure(t,n,i),n.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const xs={average(t){if(!t.length)return!1;let e,i,n=0,o=0,s=0;for(e=0,i=t.length;e-1?t.split("\n"):t}function ys(t,e){const{element:i,datasetIndex:n,index:o}=e,s=t.getDatasetMeta(n).controller,{label:a,value:r}=s.getLabelAndValue(o);return{chart:t,label:a,parsed:s.getParsed(o),raw:t.data.datasets[n].data[o],formattedValue:r,dataset:s.getDataset(),dataIndex:o,datasetIndex:n,element:i}}function vs(t,e){const i=t._chart.ctx,{body:n,footer:o,title:s}=t,{boxWidth:a,boxHeight:r}=e,l=Fe(e.bodyFont),c=Fe(e.titleFont),h=Fe(e.footerFont),d=s.length,u=o.length,f=n.length,g=ze(e.padding);let p=g.height,m=0,x=n.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(x+=t.beforeBody.length+t.afterBody.length,d&&(p+=d*c.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),x){p+=f*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(x-f)*l.lineHeight+(x-1)*e.bodySpacing}u&&(p+=e.footerMarginTop+u*h.lineHeight+(u-1)*e.footerSpacing);let b=0;const _=function(t){m=Math.max(m,i.measureText(t).width+b)};return i.save(),i.font=c.string,J(t.title,_),i.font=l.string,J(t.beforeBody.concat(t.afterBody),_),b=e.displayColors?a+2:0,J(n,(t=>{J(t.before,_),J(t.lines,_),J(t.after,_)})),b=0,i.font=h.string,J(t.footer,_),i.restore(),m+=g.width,{width:m,height:p}}function ws(t,e,i,n){const{x:o,width:s}=i,{width:a,chartArea:{left:r,right:l}}=t;let c="center";return"center"===n?c=o<=(r+l)/2?"left":"right":o<=s/2?c="left":o>=a-s/2&&(c="right"),function(t,e,i,n){const{x:o,width:s}=n,a=i.caretSize+i.caretPadding;return"left"===t&&o+s+a>e.width||"right"===t&&o-s-a<0||void 0}(c,t,e,i)&&(c="center"),c}function Ms(t,e,i){const n=e.yAlign||function(t,e){const{y:i,height:n}=e;return it.height-n/2?"bottom":"center"}(t,i);return{xAlign:e.xAlign||ws(t,e,i,n),yAlign:n}}function ks(t,e,i,n){const{caretSize:o,caretPadding:s,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,c=o+s,h=a+s;let d=function(t,e){let{x:i,width:n}=t;return"right"===e?i-=n:"center"===e&&(i-=n/2),i}(e,r);const u=function(t,e,i){let{y:n,height:o}=t;return"top"===e?n+=i:n-="bottom"===e?o+i:o/2,n}(e,l,c);return"center"===l?"left"===r?d+=c:"right"===r&&(d-=c):"left"===r?d-=h:"right"===r&&(d+=h),{x:Nt(d,0,n.width-e.width),y:Nt(u,0,n.height-e.height)}}function Ss(t,e,i){const n=ze(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-n.right:t.x+n.left}function Ps(t){return bs([],_s(t))}function Ds(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}class Cs extends Ei{constructor(t){super(),this.opacity=0,this._active=[],this._chart=t._chart,this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const t=this,e=t._cachedAnimations;if(e)return e;const i=t._chart,n=t.options.setContext(t.getContext()),o=n.enabled&&i.options.animation&&n.animations,s=new vi(t._chart,o);return o._cacheable&&(t._cachedAnimations=Object.freeze(s)),s}getContext(){const t=this;return t.$context||(t.$context=(e=t._chart.getContext(),i=t,n=t._tooltipItems,Object.assign(Object.create(e),{tooltip:i,tooltipItems:n,type:"tooltip"})));var e,i,n}getTitle(t,e){const i=this,{callbacks:n}=e,o=n.beforeTitle.apply(i,[t]),s=n.title.apply(i,[t]),a=n.afterTitle.apply(i,[t]);let r=[];return r=bs(r,_s(o)),r=bs(r,_s(s)),r=bs(r,_s(a)),r}getBeforeBody(t,e){return Ps(e.callbacks.beforeBody.apply(this,[t]))}getBody(t,e){const i=this,{callbacks:n}=e,o=[];return J(t,(t=>{const e={before:[],lines:[],after:[]},s=Ds(n,t);bs(e.before,_s(s.beforeLabel.call(i,t))),bs(e.lines,s.label.call(i,t)),bs(e.after,_s(s.afterLabel.call(i,t))),o.push(e)})),o}getAfterBody(t,e){return Ps(e.callbacks.afterBody.apply(this,[t]))}getFooter(t,e){const i=this,{callbacks:n}=e,o=n.beforeFooter.apply(i,[t]),s=n.footer.apply(i,[t]),a=n.afterFooter.apply(i,[t]);let r=[];return r=bs(r,_s(o)),r=bs(r,_s(s)),r=bs(r,_s(a)),r}_createItems(t){const e=this,i=e._active,n=e._chart.data,o=[],s=[],a=[];let r,l,c=[];for(r=0,l=i.length;rt.filter(e,i,o,n)))),t.itemSort&&(c=c.sort(((e,i)=>t.itemSort(e,i,n)))),J(c,(i=>{const n=Ds(t.callbacks,i);o.push(n.labelColor.call(e,i)),s.push(n.labelPointStyle.call(e,i)),a.push(n.labelTextColor.call(e,i))})),e.labelColors=o,e.labelPointStyles=s,e.labelTextColors=a,e.dataPoints=c,c}update(t,e){const i=this,n=i.options.setContext(i.getContext()),o=i._active;let s,a=[];if(o.length){const t=xs[n.position].call(i,o,i._eventPosition);a=i._createItems(n),i.title=i.getTitle(a,n),i.beforeBody=i.getBeforeBody(a,n),i.body=i.getBody(a,n),i.afterBody=i.getAfterBody(a,n),i.footer=i.getFooter(a,n);const e=i._size=vs(i,n),r=Object.assign({},t,e),l=Ms(i._chart,n,r),c=ks(n,r,l,i._chart);i.xAlign=l.xAlign,i.yAlign=l.yAlign,s={opacity:1,x:c.x,y:c.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==i.opacity&&(s={opacity:0});i._tooltipItems=a,i.$context=void 0,s&&i._resolveAnimations().update(i,s),t&&n.external&&n.external.call(i,{chart:i._chart,tooltip:i,replay:e})}drawCaret(t,e,i,n){const o=this.getCaretPosition(t,i,n);e.lineTo(o.x1,o.y1),e.lineTo(o.x2,o.y2),e.lineTo(o.x3,o.y3)}getCaretPosition(t,e,i){const{xAlign:n,yAlign:o}=this,{cornerRadius:s,caretSize:a}=i,{x:r,y:l}=t,{width:c,height:h}=e;let d,u,f,g,p,m;return"center"===o?(p=l+h/2,"left"===n?(d=r,u=d-a,g=p+a,m=p-a):(d=r+c,u=d+a,g=p-a,m=p+a),f=d):(u="left"===n?r+s+a:"right"===n?r+c-s-a:this.caretX,"top"===o?(g=l,p=g-a,d=u-a,f=u+a):(g=l+h,p=g+a,d=u+a,f=u-a),m=g),{x1:d,x2:u,x3:f,y1:g,y2:p,y3:m}}drawTitle(t,e,i){const n=this,o=n.title,s=o.length;let a,r,l;if(s){const c=xn(i.rtl,n.x,n.width);for(t.x=Ss(n,i.titleAlign,i),e.textAlign=c.textAlign(i.titleAlign),e.textBaseline="middle",a=Fe(i.titleFont),r=i.titleSpacing,e.fillStyle=i.titleColor,e.font=a.string,l=0;l0!==t))?(t.beginPath(),t.fillStyle=o.multiKeyBackground,ie(t,{x:e,y:g,w:c,h:l,radius:s}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),ie(t,{x:i,y:g+1,w:c-2,h:l-2,radius:s}),t.fill()):(t.fillStyle=o.multiKeyBackground,t.fillRect(e,g,c,l),t.strokeRect(e,g,c,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,c-2,l-2))}t.fillStyle=s.labelTextColors[i]}drawBody(t,e,i){const n=this,{body:o}=n,{bodySpacing:s,bodyAlign:a,displayColors:r,boxHeight:l,boxWidth:c}=i,h=Fe(i.bodyFont);let d=h.lineHeight,u=0;const f=xn(i.rtl,n.x,n.width),g=function(i){e.fillText(i,f.x(t.x+u),t.y+d/2),t.y+=d+s},p=f.textAlign(a);let m,x,b,_,y,v,w;for(e.textAlign=a,e.textBaseline="middle",e.font=h.string,t.x=Ss(n,p,i),e.fillStyle=i.bodyColor,J(n.beforeBody,g),u=r&&"right"!==p?"center"===a?c/2+1:c+2:0,_=0,v=o.length;_0&&e.stroke()}_updateAnimationTarget(t){const e=this,i=e._chart,n=e.$animations,o=n&&n.x,s=n&&n.y;if(o||s){const n=xs[t.position].call(e,e._active,e._eventPosition);if(!n)return;const a=e._size=vs(e,t),r=Object.assign({},n,e._size),l=Ms(i,t,r),c=ks(t,r,l,i);o._to===c.x&&s._to===c.y||(e.xAlign=l.xAlign,e.yAlign=l.yAlign,e.width=a.width,e.height=a.height,e.caretX=n.x,e.caretY=n.y,e._resolveAnimations().update(e,c))}}draw(t){const e=this,i=e.options.setContext(e.getContext());let n=e.opacity;if(!n)return;e._updateAnimationTarget(i);const o={width:e.width,height:e.height},s={x:e.x,y:e.y};n=Math.abs(n)<.001?0:n;const a=ze(i.padding),r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;i.enabled&&r&&(t.save(),t.globalAlpha=n,e.drawBackground(s,t,o,i),bn(t,i.textDirection),s.y+=a.top,e.drawTitle(s,t,i),e.drawBody(s,t,i),e.drawFooter(s,t,i),_n(t,i.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this,n=i._active,o=t.map((({datasetIndex:t,index:e})=>{const n=i._chart.getDatasetMeta(t);if(!n)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:n.data[e],index:e}})),s=!tt(n,o),a=i._positionChanged(o,e);(s||a)&&(i._active=o,i._eventPosition=e,i.update(!0))}handleEvent(t,e){const i=this,n=i.options,o=i._active||[];let s=!1,a=[];"mouseout"!==t.type&&(a=i._chart.getElementsAtEventForMode(t,n.mode,n,e),n.reverse&&a.reverse());const r=i._positionChanged(a,t);return s=e||!tt(a,o)||r,s&&(i._active=a,(n.enabled||n.external)&&(i._eventPosition={x:t.x,y:t.y},i.update(!0,e))),s}_positionChanged(t,e){const{caretX:i,caretY:n,options:o}=this,s=xs[o.position].call(this,t,e);return!1!==s&&(i!==s.x||n!==s.y)}}Cs.positioners=xs;var Os={id:"tooltip",_element:Cs,positioners:xs,afterInit(t,e,i){i&&(t.tooltip=new Cs({_chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip,i={tooltip:e};!1!==t.notifyPlugins("beforeTooltipDraw",i)&&(e&&e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i))},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:{beforeTitle:N,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,n=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(n>0&&e.dataIndex"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]},As=Object.freeze({__proto__:null,Decimation:qo,Filler:ds,Legend:gs,Title:ms,Tooltip:Os});function Ts(t,e,i){const n=t.indexOf(e);if(-1===n)return((t,e,i)=>"string"==typeof e?t.push(e)-1:isNaN(e)?null:i)(t,e,i);return n!==t.lastIndexOf(e)?i:n}class Rs extends Xi{constructor(t){super(t),this._startValue=void 0,this._valueRange=0}parse(t,e){if($(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:Nt(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:Ts(i,t,K(e,t)),i.length-1)}determineDataLimits(){const t=this,{minDefined:e,maxDefined:i}=t.getUserBounds();let{min:n,max:o}=t.getMinMax(!0);"ticks"===t.options.bounds&&(e||(n=0),i||(o=t.getLabels().length-1)),t.min=n,t.max=o}buildTicks(){const t=this,e=t.min,i=t.max,n=t.options.offset,o=[];let s=t.getLabels();s=0===e&&i===s.length-1?s:s.slice(e,i+1),t._valueRange=Math.max(s.length-(n?0:1),1),t._startValue=t.min-(n?.5:0);for(let t=e;t<=i;t++)o.push({value:t});return o}getLabelForValue(t){const e=this.getLabels();return t>=0&&te.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){const e=this;return Math.round(e._startValue+e.getDecimalForPixel(t)*e._valueRange)}getBasePixel(){return this.bottom}}Rs.id="category",Rs.defaults={ticks:{callback:Rs.prototype.getLabelForValue}};class Ls extends Xi{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(t,e){return $(t)||("number"==typeof t||t instanceof Number)&&!isFinite(+t)?null:+t}handleTickRangeOptions(){const t=this,{beginAtZero:e,stacked:i}=t.options,{minDefined:n,maxDefined:o}=t.getUserBounds();let{min:s,max:a}=t;const r=t=>s=n?s:t,l=t=>a=o?a:t;if(e||i){const t=Dt(s),e=Dt(a);t<0&&e<0?l(0):t>0&&e>0&&r(0)}s===a&&(l(a+1),e||r(s-1)),t.min=s,t.max=a}getTickLimit(){const t=this,e=t.options.ticks;let i,{maxTicksLimit:n,stepSize:o}=e;return o?i=Math.ceil(t.max/o)-Math.floor(t.min/o)+1:(i=t.computeTickLimit(),n=n||11),n&&(i=Math.min(n,i)),i}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this,e=t.options,i=e.ticks;let n=t.getTickLimit();n=Math.max(2,n);const o=function(t,e){const i=[],{step:n,min:o,max:s,precision:a,count:r,maxTicks:l,maxDigits:c,horizontal:h}=t,d=n||1,u=l-1,{min:f,max:g}=e,p=!$(o),m=!$(s),x=!$(r),b=(g-f)/c;let _,y,v,w,M=Ct((g-f)/u/d)*d;if(M<1e-14&&!p&&!m)return[{value:f},{value:g}];w=Math.ceil(g/M)-Math.floor(f/M),w>u&&(M=Ct(w*M/u/d)*d),$(a)||(_=Math.pow(10,a),M=Math.ceil(M*_)/_),y=Math.floor(f/M)*M,v=Math.ceil(g/M)*M,p&&m&&n&&Rt((s-o)/n,M/1e3)?(w=Math.min((s-o)/M,l),M=(s-o)/w,y=o,v=s):x?(y=p?o:y,v=m?s:v,w=r-1,M=(v-y)/w):(w=(v-y)/M,w=Tt(w,Math.round(w),M/1e3)?Math.round(w):Math.ceil(w)),_=Math.pow(10,$(a)?zt(M):a),y=Math.round(y*_)/_,v=Math.round(v*_)/_;let k=0;for(p&&(i.push({value:o}),y<=o&&k++,Tt(Math.round((y+k*M)*_)/_,o,b*(h?(""+o).length:1))&&k++);k0?i:null;this._zero=!0}determineDataLimits(){const t=this,{min:e,max:i}=t.getMinMax(!0);t.min=X(e)?Math.max(0,e):null,t.max=X(i)?Math.max(0,i):null,t.options.beginAtZero&&(t._zero=!0),t.handleTickRangeOptions()}handleTickRangeOptions(){const t=this,{minDefined:e,maxDefined:i}=t.getUserBounds();let n=t.min,o=t.max;const s=t=>n=e?n:t,a=t=>o=i?o:t,r=(t,e)=>Math.pow(10,Math.floor(Pt(t))+e);n===o&&(n<=0?(s(1),a(10)):(s(r(n,-1)),a(r(o,1)))),n<=0&&s(r(o,-1)),o<=0&&a(r(n,1)),t._zero&&t.min!==t._suggestedMin&&n===r(t.min,0)&&s(r(n,-1)),t.min=n,t.max=o}buildTicks(){const t=this,e=t.options,i=function(t,e){const i=Math.floor(Pt(e.max)),n=Math.ceil(e.max/Math.pow(10,i)),o=[];let s=q(t.min,Math.pow(10,Math.floor(Pt(e.min)))),a=Math.floor(Pt(s)),r=Math.floor(s/Math.pow(10,a)),l=a<0?Math.pow(10,Math.abs(a)):1;do{o.push({value:s,major:Is(s)}),++r,10===r&&(r=1,++a,l=a>=0?1:l),s=Math.round(r*Math.pow(10,a)*l)/l}while(ao?{start:e-i,end:e}:{start:e,end:e+i}}function Bs(t){return 0===t||180===t?"center":t<180?"left":"right"}function Ws(t,e,i){90===t||270===t?i.y-=e.h/2:(t>270||t<90)&&(i.y-=e.h)}function Hs(t,e,i,n){const{ctx:o}=t;if(i)o.arc(t.xCenter,t.yCenter,e,0,_t);else{let i=t.getPointPosition(0,e);o.moveTo(i.x,i.y);for(let s=1;s{const n=Q(e.options.pointLabels.callback,[t,i],e);return n||0===n?n:""}))}fit(){const t=this,e=t.options;e.display&&e.pointLabels.display?function(t){const e={l:0,r:t.width,t:0,b:t.height-t.paddingTop},i={};let n,o,s;const a=[],r=[],l=t.getLabels().length;for(n=0;ne.r&&(e.r=p.end,i.r=f),m.starte.b&&(e.b=m.end,i.b=f)}var c,h,d;t._setReductions(t.drawingArea,e,i),t._pointLabelItems=[];const u=t.options,f=Fs(u),g=t.getDistanceFromCenterForValue(u.ticks.reverse?t.min:t.max);for(n=0;n=0;o--){const e=n.setContext(t.getContext(o)),s=Fe(e.font),{x:a,y:r,textAlign:l,left:c,top:h,right:d,bottom:u}=t._pointLabelItems[o],{backdropColor:f}=e;if(!$(f)){const t=ze(e.backdropPadding);i.fillStyle=f,i.fillRect(c-t.left,h-t.top,d-c+t.width,u-h+t.height)}ee(i,t._pointLabels[o],a,r+s.lineHeight/2,s,{color:e.color,textAlign:l,textBaseline:"middle"})}}(t,s),o.display&&t.ticks.forEach(((e,i)=>{if(0!==i){r=t.getDistanceFromCenterForValue(e.value);const n=o.setContext(t.getContext(i-1));!function(t,e,i,n){const o=t.ctx,s=e.circular,{color:a,lineWidth:r}=e;!s&&!n||!a||!r||i<0||(o.save(),o.strokeStyle=a,o.lineWidth=r,o.setLineDash(e.borderDash),o.lineDashOffset=e.borderDashOffset,o.beginPath(),Hs(t,i,s,n),o.closePath(),o.stroke(),o.restore())}(t,n,r,s)}})),n.display){for(e.save(),a=t.getLabels().length-1;a>=0;a--){const o=n.setContext(t.getContext(a)),{color:s,lineWidth:c}=o;c&&s&&(e.lineWidth=c,e.strokeStyle=s,e.setLineDash(o.borderDash),e.lineDashOffset=o.borderDashOffset,r=t.getDistanceFromCenterForValue(i.ticks.reverse?t.min:t.max),l=t.getPointPosition(a,r),e.beginPath(),e.moveTo(t.xCenter,t.yCenter),e.lineTo(l.x,l.y),e.stroke())}e.restore()}}drawBorder(){}drawLabels(){const t=this,e=t.ctx,i=t.options,n=i.ticks;if(!n.display)return;const o=t.getIndexAngle(0);let s,a;e.save(),e.translate(t.xCenter,t.yCenter),e.rotate(o),e.textAlign="center",e.textBaseline="middle",t.ticks.forEach(((o,r)=>{if(0===r&&!i.reverse)return;const l=n.setContext(t.getContext(r)),c=Fe(l.font);if(s=t.getDistanceFromCenterForValue(t.ticks[r].value),l.showLabelBackdrop){a=e.measureText(o.label).width,e.fillStyle=l.backdropColor;const t=ze(l.backdropPadding);e.fillRect(-a/2-t.left,-s-c.size/2-t.top,a+t.width,c.size+t.height)}ee(e,o.label,0,-s,c,{color:l.color})})),e.restore()}drawTitle(){}}js.id="radialLinear",js.defaults={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:Vi.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback:t=>t,padding:5}},js.defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"},js.descriptors={angleLines:{_fallback:"grid"}};const $s={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Ys=Object.keys($s);function Us(t,e){return t-e}function Xs(t,e){if($(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:s}=t._parseOpts;let a=e;return"function"==typeof n&&(a=n(a)),X(a)||(a="string"==typeof n?i.parse(a,n):i.parse(a)),null===a?null:(o&&(a="week"!==o||!At(s)&&!0!==s?i.startOf(a,o):i.startOf(a,"isoWeek",s)),+a)}function qs(t,e,i,n){const o=Ys.length;for(let s=Ys.indexOf(t);s=e?i[n]:i[o]]=!0}}else t[e]=!0}function Gs(t,e,i){const n=[],o={},s=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,n,o,i):n}class Zs extends Xi{constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e){const i=t.time||(t.time={}),n=this._adapter=new oo._date(t.adapters.date);st(i.displayFormats,n.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Xs(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this,e=t.options,i=t._adapter,n=e.time.unit||"day";let{min:o,max:s,minDefined:a,maxDefined:r}=t.getUserBounds();function l(t){a||isNaN(t.min)||(o=Math.min(o,t.min)),r||isNaN(t.max)||(s=Math.max(s,t.max))}a&&r||(l(t._getLabelBounds()),"ticks"===e.bounds&&"labels"===e.ticks.source||l(t.getMinMax(!1))),o=X(o)&&!isNaN(o)?o:+i.startOf(Date.now(),n),s=X(s)&&!isNaN(s)?s:+i.endOf(Date.now(),n)+1,t.min=Math.min(o,s-1),t.max=Math.max(o+1,s)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this,e=t.options,i=e.time,n=e.ticks,o="labels"===n.source?t.getLabelTimestamps():t._generate();"ticks"===e.bounds&&o.length&&(t.min=t._userMin||o[0],t.max=t._userMax||o[o.length-1]);const s=t.min,a=ae(o,s,t.max);return t._unit=i.unit||(n.autoSkip?qs(i.minUnit,t.min,t.max,t._getLabelCapacity(s)):function(t,e,i,n,o){for(let s=Ys.length-1;s>=Ys.indexOf(i);s--){const i=Ys[s];if($s[i].common&&t._adapter.diff(o,n,i)>=e-1)return i}return Ys[i?Ys.indexOf(i):0]}(t,a.length,i.minUnit,t.min,t.max)),t._majorUnit=n.major.enabled&&"year"!==t._unit?function(t){for(let e=Ys.indexOf(t)+1,i=Ys.length;e1e5*r)throw new Error(i+" and "+n+" are too far apart with stepSize of "+r+" "+a);const g="data"===o.ticks.source&&t.getDataTimestamps();for(d=f,u=0;dt-e)).map((t=>+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}_tickFormatFunction(t,e,i,n){const o=this,s=o.options,a=s.time.displayFormats,r=o._unit,l=o._majorUnit,c=r&&a[r],h=l&&a[l],d=i[e],u=l&&h&&d&&d.major,f=o._adapter.format(t,n||(u?h:c)),g=s.ticks.callback;return g?Q(g,[f,e,i],o):f}generateTickLabels(t){let e,i,n;for(e=0,i=t.length;e0?r:1}getDataTimestamps(){const t=this;let e,i,n=t._cache.data||[];if(n.length)return n;const o=t.getMatchingVisibleMetas();if(t._normalized&&o.length)return t._cache.data=o[0].controller.getAllParsedValues(t);for(e=0,i=o.length;ee&&a0&&!$(e)?e/i._maxIndex:i.getDecimalForValue(t);return i.getPixelForDecimal((n.start+o)*n.factor)}getDecimalForValue(t){return Qs(this._table,t)/this._maxIndex}getValueForPixel(t){const e=this,i=e._offsets,n=e.getDecimalForPixel(t)/i.factor-i.end;return Qs(e._table,n*this._maxIndex,!0)}}Js.id="timeseries",Js.defaults=Zs.defaults;var ta=Object.freeze({__proto__:null,CategoryScale:Rs,LinearScale:Es,LogarithmicScale:zs,RadialLinearScale:js,TimeScale:Zs,TimeSeriesScale:Js});return Jn.register(bo,ta,Yo,As),Jn.helpers={...Cn},Jn._adapters=oo,Jn.Animation=_i,Jn.Animations=vi,Jn.animator=a,Jn.controllers=An.controllers.items,Jn.DatasetController=Li,Jn.Element=Ei,Jn.elements=Yo,Jn.Interaction=Oe,Jn.layouts=Ge,Jn.platforms=ui,Jn.Scale=Xi,Jn.Ticks=Vi,Object.assign(Jn,bo,ta,Yo,As,ui),Jn.Chart=Jn,"undefined"!=typeof window&&(window.Chart=Jn),Jn})); diff --git a/template/chartjs/assets_list.go b/template/chartjs/assets_list.go new file mode 100644 index 0000000000000000000000000000000000000000..f800482bced4a6154a905d627ba06ff0afdbabe9 --- /dev/null +++ b/template/chartjs/assets_list.go @@ -0,0 +1,5 @@ +package chartjs + +var AssetsList = []string{ + "/chart.min.js", +} diff --git a/template/chartjs/bar.go b/template/chartjs/bar.go new file mode 100644 index 0000000000000000000000000000000000000000..dcd8b4af0bb6ed6d3d45517b8afb9fdfb0ae6553 --- /dev/null +++ b/template/chartjs/bar.go @@ -0,0 +1,264 @@ +package chartjs + +import ( + "bytes" + "encoding/json" + "fmt" + "html/template" + + template2 "github.com/GoAdminGroup/go-admin/template" +) + +type BarChart struct { + *Chart + + JsContent BarJsContent +} + +type BarJsContent struct { + JsContent + + Data BarAttributes `json:"data"` +} + +type BarAttributes struct { + Attributes + + DataSets BarDataSets `json:"datasets"` +} + +type BarDataSets []*BarDataSet + +func (l BarDataSets) Add(ds *BarDataSet) BarDataSets { + return append(l, ds) +} + +type BarDataSet struct { + Label string `json:"label"` + Data []float64 `json:"data"` + Type string `json:"type,omitempty"` + BackgroundColor Color `json:"backgroundColor,omitempty"` + BorderCapStyle string `json:"borderCapStyle,omitempty"` + BorderColor Color `json:"borderColor,omitempty"` + + BorderSkipped string `json:"borderSkipped,omitempty"` + BorderWidth float64 `json:"borderWidth,omitempty"` + + HoverBackgroundColor Color `json:"hoverBackgroundColor,omitempty"` + HoverBorderColor Color `json:"hoverBorderColor,omitempty"` + HoverBorderWidth float64 `json:"hoverBorderWidth,omitempty"` + + Order float64 `json:"order,omitempty"` + XAxisID string `json:"xAxisID,omitempty"` + YAxisID string `json:"yAxisID,omitempty"` +} + +func (l *BarDataSet) SetLabel(label string) *BarDataSet { + l.Label = label + return l +} + +func (l *BarDataSet) SetData(data []float64) *BarDataSet { + l.Data = data + return l +} + +func (l *BarDataSet) SetType(t string) *BarDataSet { + l.Type = t + return l +} + +func (l *BarDataSet) SetBackgroundColor(backgroundColor Color) *BarDataSet { + l.BackgroundColor = backgroundColor + return l +} + +func (l *BarDataSet) SetBorderCapStyle(borderCapStyle string) *BarDataSet { + l.BorderCapStyle = borderCapStyle + return l +} + +func (l *BarDataSet) SetBorderColor(borderColor Color) *BarDataSet { + l.BorderColor = borderColor + return l +} + +func (l *BarDataSet) SetBorderWidth(borderWidth float64) *BarDataSet { + l.BorderWidth = borderWidth + return l +} + +func (l *BarDataSet) SetBorderSkipped(skip string) *BarDataSet { + l.BorderSkipped = skip + return l +} + +func (l *BarDataSet) SetHoverBackgroundColor(hoverBackgroundColor Color) *BarDataSet { + l.HoverBackgroundColor = hoverBackgroundColor + return l +} + +func (l *BarDataSet) SetHoverBorderColor(hoverBorderColor Color) *BarDataSet { + l.HoverBorderColor = hoverBorderColor + return l +} + +func (l *BarDataSet) SetHoverBorderWidth(hoverBorderWidth float64) *BarDataSet { + l.HoverBorderWidth = hoverBorderWidth + return l +} + +func (l *BarDataSet) SetOrder(order float64) *BarDataSet { + l.Order = order + return l +} + +func (l *BarDataSet) SetXAxisID(xAxisID string) *BarDataSet { + l.XAxisID = xAxisID + return l +} + +func (l *BarDataSet) SetYAxisID(yAxisID string) *BarDataSet { + l.YAxisID = yAxisID + return l +} + +func Bar() *BarChart { + return &BarChart{ + Chart: &Chart{ + BaseComponent: &template2.BaseComponent{ + Name: "chartjs", + HTMLData: List["chartjs"], + }, + dataSetIndex: -1, + }, + JsContent: BarJsContent{ + JsContent: JsContent{ + Type: "bar", + }, + Data: BarAttributes{ + Attributes: Attributes{ + Labels: make([]string, 0), + }, + DataSets: make(BarDataSets, 0), + }, + }, + } +} + +func (l *BarChart) SetID(s string) *BarChart { + l.ID = s + return l +} + +func (l *BarChart) SetTitle(s template.HTML) *BarChart { + l.Title = s + return l +} + +func (l *BarChart) SetHeight(s int) *BarChart { + l.Height = s + return l +} + +func (l *BarChart) SetLabels(s []string) *BarChart { + l.JsContent.Data.Labels = s + return l +} + +func (l *BarChart) AddDataSet(s string) *BarChart { + l.dataSetIndex++ + l.JsContent.Data.DataSets = l.JsContent.Data.DataSets.Add(&BarDataSet{ + Type: "bar", + Label: s, + }) + return l +} + +func (l *BarChart) DSLabel(s string) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetLabel(s) + return l +} + +func (l *BarChart) DSData(data []float64) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetData(data) + return l +} + +func (l *BarChart) DSType(t string) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetType(t) + return l +} + +func (l *BarChart) DSBackgroundColor(backgroundColor Color) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBackgroundColor(backgroundColor) + return l +} + +func (l *BarChart) DSBorderCapStyle(borderCapStyle string) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderCapStyle(borderCapStyle) + return l +} + +func (l *BarChart) DSBorderSkipped(skip string) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderSkipped(skip) + return l +} + +func (l *BarChart) DSBorderColor(borderColor Color) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderColor(borderColor) + return l +} + +func (l *BarChart) DSBorderWidth(borderWidth float64) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderWidth(borderWidth) + return l +} + +func (l *BarChart) DSHoverBackgroundColor(hoverBackgroundColor Color) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBackgroundColor(hoverBackgroundColor) + return l +} + +func (l *BarChart) DSHoverBorderColor(hoverBorderColor Color) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderColor(hoverBorderColor) + return l +} + +func (l *BarChart) DSHoverBorderWidth(hoverBorderWidth float64) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderWidth(hoverBorderWidth) + return l +} + +func (l *BarChart) DSOrder(order float64) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetOrder(order) + return l +} + +func (l *BarChart) DSXAxisID(xAxisID string) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetXAxisID(xAxisID) + return l +} + +func (l *BarChart) DSYAxisID(yAxisID string) *BarChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetYAxisID(yAxisID) + return l +} + +func (l *BarChart) GetContent() template.HTML { + buffer := new(bytes.Buffer) + tmpl, defineName := l.GetTemplate() + + if l.JsContentOptions != nil { + l.JsContent.Options = l.JsContentOptions + } + + jsonByte, _ := json.Marshal(l.JsContent) + l.Js = template.JS(string(jsonByte)) + + err := tmpl.ExecuteTemplate(buffer, defineName, l) + if err != nil { + fmt.Println("ComposeHtml Error:", err) + } + return template.HTML(buffer.String()) +} diff --git a/template/chartjs/chart.go b/template/chartjs/chart.go new file mode 100644 index 0000000000000000000000000000000000000000..637c773a10df5f37e304c8ec437f70726e46b83c --- /dev/null +++ b/template/chartjs/chart.go @@ -0,0 +1,838 @@ +package chartjs + +import ( + "html/template" + + template2 "github.com/GoAdminGroup/go-admin/template" +) + +type Chart struct { + *template2.BaseComponent + + ID string + Title template.HTML + Js template.JS + Height int + + JsContentOptions *Options + + dataSetIndex int +} + +func (c *Chart) SetID(id string) *Chart { + c.ID = id + return c +} + +func (c *Chart) SetTitle(title template.HTML) *Chart { + c.Title = title + return c +} + +func (c *Chart) SetHeight(height int) *Chart { + c.Height = height + return c +} + +func (c *Chart) SetOptionAnimationDuration(duration int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Animation == nil { + c.JsContentOptions.Animation = new(OptionAnimation) + } + c.JsContentOptions.Animation.Duration = duration +} + +func (c *Chart) SetOptionAnimationEasing(easing string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Animation == nil { + c.JsContentOptions.Animation = new(OptionAnimation) + } + c.JsContentOptions.Animation.Easing = easing +} + +func (c *Chart) SetOptionLayoutPaddingLeft(left int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Layout == nil { + c.JsContentOptions.Layout = new(OptionLayout) + } + c.JsContentOptions.Layout.Padding.Left = left +} + +func (c *Chart) SetOptionLayoutPaddingRight(right int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Layout == nil { + c.JsContentOptions.Layout = new(OptionLayout) + } + c.JsContentOptions.Layout.Padding.Right = right +} + +func (c *Chart) SetOptionLayoutPaddingTop(top int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Layout == nil { + c.JsContentOptions.Layout = new(OptionLayout) + } + c.JsContentOptions.Layout.Padding.Top = top +} + +func (c *Chart) SetOptionLayoutPaddingBottom(bottom int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Layout == nil { + c.JsContentOptions.Layout = new(OptionLayout) + } + c.JsContentOptions.Layout.Padding.Bottom = bottom +} + +func (c *Chart) SetOptionLegendDisplay(display bool) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Legend == nil { + c.JsContentOptions.Legend = new(OptionLegend) + } + c.JsContentOptions.Legend.Display = display +} + +func (c *Chart) SetOptionLegendPosition(position string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Legend == nil { + c.JsContentOptions.Legend = new(OptionLegend) + } + c.JsContentOptions.Legend.Position = position +} + +func (c *Chart) SetOptionLegendAlign(align string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Legend == nil { + c.JsContentOptions.Legend = new(OptionLegend) + } + c.JsContentOptions.Legend.Align = align +} + +func (c *Chart) SetOptionLegendFullWidt(fullWidth bool) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Legend == nil { + c.JsContentOptions.Legend = new(OptionLegend) + } + c.JsContentOptions.Legend.FullWidth = fullWidth +} + +func (c *Chart) SetOptionLegendRevers(reverse bool) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Legend == nil { + c.JsContentOptions.Legend = new(OptionLegend) + } + c.JsContentOptions.Legend.Reverse = reverse +} + +func (c *Chart) SetOptionLegendRt(rtl bool) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Legend == nil { + c.JsContentOptions.Legend = new(OptionLegend) + } + c.JsContentOptions.Legend.Rtl = rtl +} + +func (c *Chart) SetOptionLegendTextDirection(textDirection string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Legend == nil { + c.JsContentOptions.Legend = new(OptionLegend) + } + c.JsContentOptions.Legend.TextDirection = textDirection +} + +func (c *Chart) SetOptionLegendLabels(labels *OptionLegendLabel) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Legend == nil { + c.JsContentOptions.Legend = new(OptionLegend) + } + c.JsContentOptions.Legend.Labels = labels +} + +func (c *Chart) SetOptionTitleDisplay(display bool) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Title == nil { + c.JsContentOptions.Title = new(OptionTitle) + } + c.JsContentOptions.Title.Display = display +} + +func (c *Chart) SetOptionTitleFontSize(fontSize int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Title == nil { + c.JsContentOptions.Title = new(OptionTitle) + } + c.JsContentOptions.Title.FontSize = fontSize +} + +func (c *Chart) SetOptionTitlePosition(position string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Title == nil { + c.JsContentOptions.Title = new(OptionTitle) + } + c.JsContentOptions.Title.Position = position +} + +func (c *Chart) SetOptionTitleFontFamily(fontFamily string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Title == nil { + c.JsContentOptions.Title = new(OptionTitle) + } + c.JsContentOptions.Title.FontFamily = fontFamily +} + +func (c *Chart) SetOptionTitleFontColor(fontColor Color) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Title == nil { + c.JsContentOptions.Title = new(OptionTitle) + } + c.JsContentOptions.Title.FontColor = fontColor +} + +func (c *Chart) SetOptionTitleFontStyle(fontStyle string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Title == nil { + c.JsContentOptions.Title = new(OptionTitle) + } + c.JsContentOptions.Title.FontStyle = fontStyle +} + +func (c *Chart) SetOptionTitlePadding(padding int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Title == nil { + c.JsContentOptions.Title = new(OptionTitle) + } + c.JsContentOptions.Title.Padding = padding +} + +func (c *Chart) SetOptionTitleLineHeight(lineHeight int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Title == nil { + c.JsContentOptions.Title = new(OptionTitle) + } + c.JsContentOptions.Title.LineHeight = lineHeight +} + +func (c *Chart) SetOptionTitleText(text string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Title == nil { + c.JsContentOptions.Title = new(OptionTitle) + } + c.JsContentOptions.Title.Text = text +} + +func (c *Chart) SetOptionTooltipsEnabled(enabled bool) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.Enabled = enabled +} + +func (c *Chart) SetOptionTooltipsMode(mode string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.Mode = mode +} + +func (c *Chart) SetOptionTooltipsIntersect(intersect bool) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.Intersect = intersect +} + +func (c *Chart) SetOptionTooltipsPosition(position string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.Position = position +} + +func (c *Chart) SetOptionTooltipsBackgroundColor(backgroundColor Color) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.BackgroundColor = backgroundColor +} + +func (c *Chart) SetOptionTooltipsTitleFontFamily(titleFontFamily string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.TitleFontFamily = titleFontFamily +} + +func (c *Chart) SetOptionTooltipsTitleFontSize(titleFontSize int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.TitleFontSize = titleFontSize +} + +func (c *Chart) SetOptionTooltipsTitleFontStyle(titleFontStyle string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.TitleFontStyle = titleFontStyle +} + +func (c *Chart) SetOptionTooltipsTitleFontColor(titleFontColor Color) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.TitleFontColor = titleFontColor +} + +func (c *Chart) SetOptionTooltipsTitleAlign(titleAlign string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.TitleAlign = titleAlign +} + +func (c *Chart) SetOptionTooltipsTitleSpacing(titleSpacing int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.TitleSpacing = titleSpacing +} + +func (c *Chart) SetOptionTooltipsTitleMarginBottom(titleMarginBottom int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.TitleMarginBottom = titleMarginBottom +} + +func (c *Chart) SetOptionTooltipsBodyFontFamily(bodyFontFamily string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.BodyFontFamily = bodyFontFamily +} + +func (c *Chart) SetOptionTooltipsBodyFontSize(bodyFontSize int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.BodyFontSize = bodyFontSize +} + +func (c *Chart) SetOptionTooltipsBodyFontStyle(bodyFontStyle string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.BodyFontStyle = bodyFontStyle +} + +func (c *Chart) SetOptionTooltipsBodyFontColor(bodyFontColor Color) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.BodyFontColor = bodyFontColor +} + +func (c *Chart) SetOptionTooltipsBodyAlign(bodyAlign string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.BodyAlign = bodyAlign +} + +func (c *Chart) SetOptionTooltipsBodySpacing(bodySpacing int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.BodySpacing = bodySpacing +} + +func (c *Chart) SetOptionTooltipsFooterFontFamily(footerFontFamily string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.FooterFontFamily = footerFontFamily +} + +func (c *Chart) SetOptionTooltipsFooterFontSize(footerFontSize int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.FooterFontSize = footerFontSize +} + +func (c *Chart) SetOptionTooltipsFooterFontStyle(footerFontStyle string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.FooterFontStyle = footerFontStyle +} + +func (c *Chart) SetOptionTooltipsFooterFontColor(footerFontColor Color) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.FooterFontColor = footerFontColor +} + +func (c *Chart) SetOptionTooltipsFooterAlign(footerAlign string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.FooterAlign = footerAlign +} + +func (c *Chart) SetOptionTooltipsFooterSpacing(footerSpacing int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.FooterSpacing = footerSpacing +} + +func (c *Chart) SetOptionTooltipsFooterMarginTop(footerMarginTop int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.FooterMarginTop = footerMarginTop +} + +func (c *Chart) SetOptionTooltipsXPadding(xPadding int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.XPadding = xPadding +} + +func (c *Chart) SetOptionTooltipsYPadding(yPadding int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.YPadding = yPadding +} + +func (c *Chart) SetOptionTooltipsCaretPadding(caretPadding int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.CaretPadding = caretPadding +} + +func (c *Chart) SetOptionTooltipsCaretSize(caretSize int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.CaretSize = caretSize +} + +func (c *Chart) SetOptionTooltipsCornerRadius(cornerRadius int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.CornerRadius = cornerRadius +} + +func (c *Chart) SetOptionTooltipsMultiKeyBackground(multiKeyBackground Color) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.MultiKeyBackground = multiKeyBackground +} + +func (c *Chart) SetOptionTooltipsDisplayColors(displayColors bool) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.DisplayColors = displayColors +} + +func (c *Chart) SetOptionTooltipsBorderColor(borderColor Color) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.BorderColor = borderColor +} + +func (c *Chart) SetOptionTooltipsBorderWidth(borderWidth int) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.BorderWidth = borderWidth +} + +func (c *Chart) SetOptionTooltipsRtl(rtl bool) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.Rtl = rtl +} + +func (c *Chart) SetOptionTooltipsTextDirection(textDirection string) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Tooltips == nil { + c.JsContentOptions.Tooltips = new(OptionTooltips) + } + c.JsContentOptions.Tooltips.TextDirection = textDirection +} + +func (c *Chart) SetOptionElementPoint(point *OptionElementPoint) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Elements == nil { + c.JsContentOptions.Elements = new(OptionElement) + } + c.JsContentOptions.Elements.Point = point +} + +func (c *Chart) SetOptionElementLine(line *OptionElementLine) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Elements == nil { + c.JsContentOptions.Elements = new(OptionElement) + } + c.JsContentOptions.Elements.Line = line +} + +func (c *Chart) SetOptionElementArc(arc *OptionElementArc) { + if c.JsContentOptions == nil { + c.JsContentOptions = new(Options) + } + if c.JsContentOptions.Elements == nil { + c.JsContentOptions.Elements = new(OptionElement) + } + c.JsContentOptions.Elements.Arc = arc +} + +func (c *Chart) SetOptionElementRectangle(rectangle *OptionElementRectangle) { + if c.JsContentOptions.Elements == nil { + c.JsContentOptions.Elements = new(OptionElement) + } + c.JsContentOptions.Elements.Rectangle = rectangle +} + +type JsContent struct { + Type string `json:"type,omitempty"` + Options *Options `json:"options,omitempty"` +} + +type OptionAnimation struct { + Duration int `json:"duration,omitempty"` + Easing string `json:"easing,omitempty"` +} + +type OptionLayout struct { + Padding struct { + Left int `json:"left,omitempty"` + Right int `json:"right,omitempty"` + Top int `json:"top,omitempty"` + Bottom int `json:"bottom,omitempty"` + } `json:"padding,omitempty"` +} + +type OptionLegend struct { + Display bool `json:"display,omitempty"` + Position string `json:"position,omitempty"` + Align string `json:"align,omitempty"` + FullWidth bool `json:"full_width,omitempty"` + Reverse bool `json:"reverse,omitempty"` + Rtl bool `json:"rtl,omitempty"` + TextDirection string `json:"text_direction,omitempty"` + Labels *OptionLegendLabel `json:"labels,omitempty"` +} + +type OptionLegendLabel struct { + BoxWidth int `json:"box_width,omitempty"` + FontSize int `json:"fontSize,omitempty"` + FontStyle string `json:"fontStyle,omitempty"` + FontColor Color `json:"fontColor,omitempty"` + FontFamily string `json:"fontFamily,omitempty"` + Padding int `json:"padding,omitempty"` + UsePointStyle bool `json:"usePointStyle,omitempty"` +} + +type OptionTitle struct { + Display bool `json:"display,omitempty"` + Position string `json:"position,omitempty"` + FontSize int `json:"fontSize,omitempty"` + FontFamily string `json:"fontFamily,omitempty"` + FontColor Color `json:"fontColor,omitempty"` + FontStyle string `json:"fontStyle,omitempty"` + Padding int `json:"padding,omitempty"` + LineHeight int `json:"lineHeight,omitempty"` + Text string `json:"text,omitempty"` +} + +type OptionTooltips struct { + Enabled bool `json:"enabled,omitempty"` + Mode string `json:"mode,omitempty"` + Intersect bool `json:"intersect,omitempty"` + Position string `json:"position,omitempty"` + BackgroundColor Color `json:"backgroundColor,omitempty"` + TitleFontFamily string `json:"titleFontFamily,omitempty"` + TitleFontSize int `json:"titleFontSize,omitempty"` + TitleFontStyle string `json:"titleFontStyle,omitempty"` + TitleFontColor Color `json:"titleFontColor,omitempty"` + TitleAlign string `json:"titleAlign,omitempty"` + TitleSpacing int `json:"titleSpacing,omitempty"` + TitleMarginBottom int `json:"titleMarginBottom,omitempty"` + BodyFontFamily string `json:"bodyFontFamily,omitempty"` + BodyFontSize int `json:"bodyFontSize,omitempty"` + BodyFontStyle string `json:"bodyFontStyle,omitempty"` + BodyFontColor Color `json:"bodyFontColor,omitempty"` + BodyAlign string `json:"bodyAlign,omitempty"` + BodySpacing int `json:"bodySpacing,omitempty"` + FooterFontFamily string `json:"footerFontFamily,omitempty"` + FooterFontSize int `json:"footerFontSize,omitempty"` + FooterFontStyle string `json:"footerFontStyle,omitempty"` + FooterFontColor Color `json:"footerFontColor,omitempty"` + FooterAlign string `json:"footerAlign,omitempty"` + FooterSpacing int `json:"footerSpacing,omitempty"` + FooterMarginTop int `json:"footerMarginTop,omitempty"` + XPadding int `json:"xPadding,omitempty"` + YPadding int `json:"yPadding,omitempty"` + CaretPadding int `json:"caretPadding,omitempty"` + CaretSize int `json:"caretSize,omitempty"` + CornerRadius int `json:"cornerRadius,omitempty"` + MultiKeyBackground Color `json:"multiKeyBackground,omitempty"` + DisplayColors bool `json:"displayColors,omitempty"` + BorderColor Color `json:"borderColor,omitempty"` + BorderWidth int `json:"borderWidth,omitempty"` + Rtl bool `json:"rtl,omitempty"` + TextDirection string `json:"textDirection,omitempty"` +} + +type OptionElement struct { + Point *OptionElementPoint `json:"point,omitempty"` + Line *OptionElementLine `json:"line,omitempty"` + Rectangle *OptionElementRectangle `json:"rectangle,omitempty"` + Arc *OptionElementArc `json:"arc,omitempty"` +} + +type OptionElementPoint struct { + Radius int `json:"radius,omitempty"` + PointStyle string `json:"pointStyle,omitempty"` + Rotation int `json:"rotation,omitempty"` + BackgroundColor Color `json:"backgroundColor,omitempty"` + BorderWidth int `json:"borderWidth,omitempty"` + BorderColor Color `json:"borderColor,omitempty"` + HitRadius int `json:"hitRadius,omitempty"` + HoverRadius int `json:"hoverRadius,omitempty"` + HoverBorderWidth int `json:"hoverBorderWidth,omitempty"` +} + +type OptionElementLine struct { + Tension int `json:"tension,omitempty"` + BackgroundColor Color `json:"background_color,omitempty"` + BorderWidth int `json:"border_width,omitempty"` + BorderColor Color `json:"border_color,omitempty"` + BorderCapStyle string `json:"border_cap_style,omitempty"` + BorderDash int `json:"border_dash,omitempty"` + BorderDashOffset int `json:"border_dash_offset,omitempty"` + BorderJoinStyle string `json:"border_join_style,omitempty"` + CapBezierPoints bool `json:"cap_bezier_points,omitempty"` + CubicInterpolationMode string `json:"cubic_interpolation_mode,omitempty"` + Fill bool `json:"fill,omitempty"` + Stepped bool `json:"stepped,omitempty"` +} + +type OptionElementRectangle struct { + BackgroundColor Color `json:"backgroundColor,omitempty"` + BorderWidth int `json:"borderWidth,omitempty"` + BorderColor Color `json:"borderColor,omitempty"` + BorderSkipped string `json:"borderSkipped,omitempty"` +} + +type OptionElementArc struct { + Angle int `json:"angle,omitempty"` + BackgroundColor Color `json:"backgroundColor,omitempty"` + BorderAlign string `json:"borderAlign,omitempty"` + BorderColor Color `json:"borderColor,omitempty"` + BorderWidth int `json:"borderWidth,omitempty"` +} + +type Options struct { + Animation *OptionAnimation `json:"animation,omitempty"` + Layout *OptionLayout `json:"layout,omitempty"` + Legend *OptionLegend `json:"legend,omitempty"` + Title *OptionTitle `json:"title,omitempty"` + Tooltips *OptionTooltips `json:"tooltips,omitempty"` + Elements *OptionElement `json:"elements,omitempty"` +} + +type Attributes struct { + Labels []string `json:"labels,omitempty"` +} + +type DataSets []DataSet + +type DataSet struct { + Label string `json:"label,omitempty"` + Data []float64 `json:"data,omitempty"` + Type string `json:"type,omitempty"` +} + +type Color string + +func NewChart() *Chart { + return &Chart{ + BaseComponent: &template2.BaseComponent{ + Name: "chartjs", + HTMLData: List["chartjs"], + }, + } +} + +func (c *Chart) GetAssetList() []string { return AssetsList } +func (c *Chart) GetAsset(name string) ([]byte, error) { return Asset(name[1:]) } +func (c *Chart) GetContent() template.HTML { return c.GetContentWithData(c) } diff --git a/template/chartjs/chartjs.tmpl b/template/chartjs/chartjs.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..39c7f153d78242f4fa10ae472f196ff7421143fa --- /dev/null +++ b/template/chartjs/chartjs.tmpl @@ -0,0 +1,13 @@ +{{define "chartjs"}} + {{if ne .Title ""}} +

    + {{langHtml .Title}} +

    + {{end}} +
    + +
    + +{{end}} \ No newline at end of file diff --git a/template/chartjs/line.go b/template/chartjs/line.go new file mode 100644 index 0000000000000000000000000000000000000000..a49a214f0348f10a6d4703096df6e40bdd8339d6 --- /dev/null +++ b/template/chartjs/line.go @@ -0,0 +1,514 @@ +package chartjs + +import ( + "bytes" + "encoding/json" + "fmt" + "html/template" + + template2 "github.com/GoAdminGroup/go-admin/template" +) + +type LineChart struct { + *Chart + + JsContent LineJsContent +} + +type LineJsContent struct { + JsContent + + Data LineAttributes `json:"data"` +} + +type LineAttributes struct { + Attributes + + DataSets LineDataSets `json:"datasets"` +} + +type LineDataSets []*LineDataSet + +func (l LineDataSets) Add(ds *LineDataSet) LineDataSets { + return append(l, ds) +} + +type LineDataSet struct { + Label string `json:"label"` + Data []float64 `json:"data"` + Type string `json:"type,omitempty"` + BackgroundColor Color `json:"backgroundColor,omitempty"` + BorderCapStyle string `json:"borderCapStyle,omitempty"` + BorderColor Color `json:"borderColor,omitempty"` + BorderDash []int `json:"borderDash,omitempty"` + BorderDashOffset float64 `json:"borderDashOffset,omitempty"` + BorderJoinStyle string `json:"borderJoinStyle,omitempty"` + BorderWidth float64 `json:"borderWidth,omitempty"` + CubicInterpolationMode string `json:"cubicInterpolationMode,omitempty"` + Fill bool `json:"fill"` + HoverBackgroundColor Color `json:"hoverBackgroundColor,omitempty"` + HoverBorderCapStyle string `json:"hoverBorderCapStyle,omitempty"` + HoverBorderColor Color `json:"hoverBorderColor,omitempty"` + HoverBorderDash float64 `json:"hoverBorderDash,omitempty"` + HoverBorderDashOffset float64 `json:"hoverBorderDashOffset,omitempty"` + HoverBorderJoinStyle string `json:"hoverBorderJoinStyle,omitempty"` + HoverBorderWidth float64 `json:"hoverBorderWidth,omitempty"` + LineTension float64 `json:"lineTension,omitempty"` + Order float64 `json:"order,omitempty"` + PointBackgroundColor Color `json:"pointBackgroundColor,omitempty"` + PointBorderColor Color `json:"pointBorderColor,omitempty"` + PointBorderWidth float64 `json:"pointBorderWidth,omitempty"` + PointHitRadius float64 `json:"pointHitRadius,omitempty"` + PointHoverBackgroundColor Color `json:"pointHoverBackgroundColor,omitempty"` + PointHoverBorderColor Color `json:"pointHoverBorderColor,omitempty"` + PointHoverBorderWidth float64 `json:"pointHoverBorderWidth,omitempty"` + PointHoverRadius float64 `json:"pointHoverRadius,omitempty"` + PointRadius float64 `json:"pointRadius,omitempty"` + PointRotation float64 `json:"pointRotation,omitempty"` + PointStyle string `json:"pointStyle,omitempty"` + ShowLine bool `json:"showLine,omitempty"` + SpanGaps bool `json:"spanGaps,omitempty"` + SteppedLine bool `json:"steppedLine,omitempty"` + XAxisID string `json:"xAxisID,omitempty"` + YAxisID string `json:"yAxisID,omitempty"` +} + +func (l *LineDataSet) SetLabel(label string) *LineDataSet { + l.Label = label + return l +} + +func (l *LineDataSet) SetData(data []float64) *LineDataSet { + l.Data = data + return l +} + +func (l *LineDataSet) SetType(t string) *LineDataSet { + l.Type = t + return l +} + +func (l *LineDataSet) SetBackgroundColor(backgroundColor Color) *LineDataSet { + l.BackgroundColor = backgroundColor + return l +} + +func (l *LineDataSet) SetBorderCapStyle(borderCapStyle string) *LineDataSet { + l.BorderCapStyle = borderCapStyle + return l +} + +func (l *LineDataSet) SetBorderColor(borderColor Color) *LineDataSet { + l.BorderColor = borderColor + return l +} + +func (l *LineDataSet) SetBorderDash(borderDash []int) *LineDataSet { + l.BorderDash = borderDash + return l +} + +func (l *LineDataSet) SetBorderDashOffset(borderDashOffset float64) *LineDataSet { + l.BorderDashOffset = borderDashOffset + return l +} + +func (l *LineDataSet) SetBorderJoinStyle(borderJoinStyle string) *LineDataSet { + l.BorderJoinStyle = borderJoinStyle + return l +} + +func (l *LineDataSet) SetBorderWidth(borderWidth float64) *LineDataSet { + l.BorderWidth = borderWidth + return l +} + +func (l *LineDataSet) SetCubicInterpolationMode(cubicInterpolationMode string) *LineDataSet { + l.CubicInterpolationMode = cubicInterpolationMode + return l +} + +func (l *LineDataSet) SetFill(fill bool) *LineDataSet { + l.Fill = fill + return l +} + +func (l *LineDataSet) SetHoverBackgroundColor(hoverBackgroundColor Color) *LineDataSet { + l.HoverBackgroundColor = hoverBackgroundColor + return l +} + +func (l *LineDataSet) SetHoverBorderCapStyle(hoverBorderCapStyle string) *LineDataSet { + l.HoverBorderCapStyle = hoverBorderCapStyle + return l +} + +func (l *LineDataSet) SetHoverBorderColor(hoverBorderColor Color) *LineDataSet { + l.HoverBorderColor = hoverBorderColor + return l +} + +func (l *LineDataSet) SetHoverBorderDash(hoverBorderDash float64) *LineDataSet { + l.HoverBorderDash = hoverBorderDash + return l +} + +func (l *LineDataSet) SetHoverBorderDashOffset(hoverBorderDashOffset float64) *LineDataSet { + l.HoverBorderDashOffset = hoverBorderDashOffset + return l +} + +func (l *LineDataSet) SetHoverBorderJoinStyle(hoverBorderJoinStyle string) *LineDataSet { + l.HoverBorderJoinStyle = hoverBorderJoinStyle + return l +} + +func (l *LineDataSet) SetHoverBorderWidth(hoverBorderWidth float64) *LineDataSet { + l.HoverBorderWidth = hoverBorderWidth + return l +} + +func (l *LineDataSet) SetLineTension(lineTension float64) *LineDataSet { + l.LineTension = lineTension + return l +} + +func (l *LineDataSet) SetOrder(order float64) *LineDataSet { + l.Order = order + return l +} + +func (l *LineDataSet) SetPointBackgroundColor(pointBackgroundColor Color) *LineDataSet { + l.PointBackgroundColor = pointBackgroundColor + return l +} + +func (l *LineDataSet) SetPointBorderColor(pointBorderColor Color) *LineDataSet { + l.PointBorderColor = pointBorderColor + return l +} + +func (l *LineDataSet) SetPointBorderWidth(pointBorderWidth float64) *LineDataSet { + l.PointBorderWidth = pointBorderWidth + return l +} + +func (l *LineDataSet) SetPointHitRadius(pointHitRadius float64) *LineDataSet { + l.PointHitRadius = pointHitRadius + return l +} + +func (l *LineDataSet) SetPointHoverBackgroundColor(pointHoverBackgroundColor Color) *LineDataSet { + l.PointHoverBackgroundColor = pointHoverBackgroundColor + return l +} + +func (l *LineDataSet) SetPointHoverBorderColor(pointHoverBorderColor Color) *LineDataSet { + l.PointHoverBorderColor = pointHoverBorderColor + return l +} + +func (l *LineDataSet) SetPointHoverBorderWidth(pointHoverBorderWidth float64) *LineDataSet { + l.PointHoverBorderWidth = pointHoverBorderWidth + return l +} + +func (l *LineDataSet) SetPointHoverRadius(pointHoverRadius float64) *LineDataSet { + l.PointHoverRadius = pointHoverRadius + return l +} + +func (l *LineDataSet) SetPointRadius(pointRadius float64) *LineDataSet { + l.PointRadius = pointRadius + return l +} + +func (l *LineDataSet) SetPointRotation(pointRotation float64) *LineDataSet { + l.PointRotation = pointRotation + return l +} + +func (l *LineDataSet) SetPointStyle(pointStyle string) *LineDataSet { + l.PointStyle = pointStyle + return l +} + +func (l *LineDataSet) SetShowLine(showLine bool) *LineDataSet { + l.ShowLine = showLine + return l +} + +func (l *LineDataSet) SetSpanGaps(spanGaps bool) *LineDataSet { + l.SpanGaps = spanGaps + return l +} + +func (l *LineDataSet) SetSteppedLine(steppedLine bool) *LineDataSet { + l.SteppedLine = steppedLine + return l +} + +func (l *LineDataSet) SetXAxisID(xAxisID string) *LineDataSet { + l.XAxisID = xAxisID + return l +} + +func (l *LineDataSet) SetYAxisID(yAxisID string) *LineDataSet { + l.YAxisID = yAxisID + return l +} + +func Line() *LineChart { + return &LineChart{ + Chart: &Chart{ + BaseComponent: &template2.BaseComponent{ + Name: "chartjs", + HTMLData: List["chartjs"], + }, + dataSetIndex: -1, + }, + JsContent: LineJsContent{ + JsContent: JsContent{ + Type: "line", + }, + Data: LineAttributes{ + Attributes: Attributes{ + Labels: make([]string, 0), + }, + DataSets: make(LineDataSets, 0), + }, + }, + } +} + +func (l *LineChart) SetID(s string) *LineChart { + l.ID = s + return l +} + +func (l *LineChart) SetTitle(s template.HTML) *LineChart { + l.Title = s + return l +} + +func (l *LineChart) SetHeight(s int) *LineChart { + l.Height = s + return l +} + +func (l *LineChart) SetLabels(s []string) *LineChart { + l.JsContent.Data.Labels = s + return l +} + +func (l *LineChart) AddDataSet(s string) *LineChart { + l.dataSetIndex++ + l.JsContent.Data.DataSets = l.JsContent.Data.DataSets.Add(&LineDataSet{ + Type: "line", + Label: s, + }) + return l +} + +func (l *LineChart) DSLabel(s string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetLabel(s) + return l +} + +func (l *LineChart) DSData(data []float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetData(data) + return l +} + +func (l *LineChart) DSType(t string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetType(t) + return l +} + +func (l *LineChart) DSBackgroundColor(backgroundColor Color) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBackgroundColor(backgroundColor) + return l +} + +func (l *LineChart) DSBorderCapStyle(borderCapStyle string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderCapStyle(borderCapStyle) + return l +} + +func (l *LineChart) DSBorderColor(borderColor Color) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderColor(borderColor) + return l +} + +func (l *LineChart) DSBorderDash(borderDash []int) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderDash(borderDash) + return l +} + +func (l *LineChart) DSBorderDashOffset(borderDashOffset float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderDashOffset(borderDashOffset) + return l +} + +func (l *LineChart) DSBorderJoinStyle(borderJoinStyle string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderJoinStyle(borderJoinStyle) + return l +} + +func (l *LineChart) DSBorderWidth(borderWidth float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderWidth(borderWidth) + return l +} + +func (l *LineChart) DSCubicInterpolationMode(cubicInterpolationMode string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetCubicInterpolationMode(cubicInterpolationMode) + return l +} + +func (l *LineChart) DSFill(fill bool) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetFill(fill) + return l +} + +func (l *LineChart) DSHoverBackgroundColor(hoverBackgroundColor Color) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBackgroundColor(hoverBackgroundColor) + return l +} + +func (l *LineChart) DSHoverBorderCapStyle(hoverBorderCapStyle string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderCapStyle(hoverBorderCapStyle) + return l +} + +func (l *LineChart) DSHoverBorderColor(hoverBorderColor Color) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderColor(hoverBorderColor) + return l +} + +func (l *LineChart) DSHoverBorderDash(hoverBorderDash float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderDash(hoverBorderDash) + return l +} + +func (l *LineChart) DSHoverBorderDashOffset(hoverBorderDashOffset float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderDashOffset(hoverBorderDashOffset) + return l +} + +func (l *LineChart) DSHoverBorderJoinStyle(hoverBorderJoinStyle string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderJoinStyle(hoverBorderJoinStyle) + return l +} + +func (l *LineChart) DSHoverBorderWidth(hoverBorderWidth float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderWidth(hoverBorderWidth) + return l +} + +func (l *LineChart) DSLineTension(lineTension float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetLineTension(lineTension) + return l +} + +func (l *LineChart) DSOrder(order float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetOrder(order) + return l +} + +func (l *LineChart) DSPointBackgroundColor(pointBackgroundColor Color) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointBackgroundColor(pointBackgroundColor) + return l +} + +func (l *LineChart) DSPointBorderColor(pointBorderColor Color) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointBorderColor(pointBorderColor) + return l +} + +func (l *LineChart) DSPointBorderWidth(pointBorderWidth float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointBorderWidth(pointBorderWidth) + return l +} + +func (l *LineChart) DSPointHitRadius(pointHitRadius float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointHitRadius(pointHitRadius) + return l +} + +func (l *LineChart) DSPointHoverBackgroundColor(pointHoverBackgroundColor Color) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointHoverBackgroundColor(pointHoverBackgroundColor) + return l +} + +func (l *LineChart) DSPointHoverBorderColor(pointHoverBorderColor Color) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointHoverBorderColor(pointHoverBorderColor) + return l +} + +func (l *LineChart) DSPointHoverBorderWidth(pointHoverBorderWidth float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointHoverBorderWidth(pointHoverBorderWidth) + return l +} + +func (l *LineChart) DSPointHoverRadius(pointHoverRadius float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointHoverRadius(pointHoverRadius) + return l +} + +func (l *LineChart) DSPointRadius(pointRadius float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointRadius(pointRadius) + return l +} + +func (l *LineChart) DSPointRotation(pointRotation float64) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointRotation(pointRotation) + return l +} + +func (l *LineChart) DSPointStyle(pointStyle string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetPointStyle(pointStyle) + return l +} + +func (l *LineChart) DSShowLine(showLine bool) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetShowLine(showLine) + return l +} + +func (l *LineChart) DSSpanGaps(spanGaps bool) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetSpanGaps(spanGaps) + return l +} + +func (l *LineChart) DSSteppedLine(steppedLine bool) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetSteppedLine(steppedLine) + return l +} + +func (l *LineChart) DSXAxisID(xAxisID string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetXAxisID(xAxisID) + return l +} + +func (l *LineChart) DSYAxisID(yAxisID string) *LineChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetYAxisID(yAxisID) + return l +} + +func (l *LineChart) GetContent() template.HTML { + buffer := new(bytes.Buffer) + tmpl, defineName := l.GetTemplate() + + if l.JsContentOptions != nil { + l.JsContent.Options = l.JsContentOptions + } + + jsonByte, _ := json.Marshal(l.JsContent) + l.Js = template.JS(string(jsonByte)) + + err := tmpl.ExecuteTemplate(buffer, defineName, l) + if err != nil { + fmt.Println("ComposeHtml Error:", err) + } + return template.HTML(buffer.String()) +} diff --git a/template/chartjs/pie.go b/template/chartjs/pie.go new file mode 100644 index 0000000000000000000000000000000000000000..0e5605f9deab6ee434f3c2ec7590df1412de1c93 --- /dev/null +++ b/template/chartjs/pie.go @@ -0,0 +1,226 @@ +package chartjs + +import ( + "bytes" + "encoding/json" + "fmt" + "html/template" + + template2 "github.com/GoAdminGroup/go-admin/template" +) + +type PieChart struct { + *Chart + + JsContent PieJsContent +} + +type PieJsContent struct { + JsContent + + Data PieAttributes `json:"data"` +} + +type PieAttributes struct { + Attributes + + DataSets PieDataSets `json:"datasets"` +} + +type PieDataSets []*PieDataSet + +func (l PieDataSets) Add(ds *PieDataSet) PieDataSets { + return append(l, ds) +} + +type PieDataSet struct { + Label string `json:"label"` + Data []float64 `json:"data"` + Type string `json:"type,omitempty"` + BackgroundColor []Color `json:"backgroundColor,omitempty"` + BorderColor Color `json:"borderColor,omitempty"` + + BorderWidth float64 `json:"borderWidth,omitempty"` + BorderAlign string `json:"borderAlign,omitempty"` + + HoverBackgroundColor Color `json:"hoverBackgroundColor,omitempty"` + HoverBorderColor Color `json:"hoverBorderColor,omitempty"` + HoverBorderWidth float64 `json:"hoverBorderWidth,omitempty"` + + Weight int `json:"weight,omitempty"` +} + +func (l *PieDataSet) SetLabel(label string) *PieDataSet { + l.Label = label + return l +} + +func (l *PieDataSet) SetData(data []float64) *PieDataSet { + l.Data = data + return l +} + +func (l *PieDataSet) SetType(t string) *PieDataSet { + l.Type = t + return l +} + +func (l *PieDataSet) SetBackgroundColor(backgroundColor []Color) *PieDataSet { + l.BackgroundColor = backgroundColor + return l +} + +func (l *PieDataSet) SetBorderAlign(align string) *PieDataSet { + l.BorderAlign = align + return l +} + +func (l *PieDataSet) SetBorderColor(borderColor Color) *PieDataSet { + l.BorderColor = borderColor + return l +} + +func (l *PieDataSet) SetBorderWidth(borderWidth float64) *PieDataSet { + l.BorderWidth = borderWidth + return l +} + +func (l *PieDataSet) SetWeight(weight int) *PieDataSet { + l.Weight = weight + return l +} + +func (l *PieDataSet) SetHoverBackgroundColor(hoverBackgroundColor Color) *PieDataSet { + l.HoverBackgroundColor = hoverBackgroundColor + return l +} + +func (l *PieDataSet) SetHoverBorderColor(hoverBorderColor Color) *PieDataSet { + l.HoverBorderColor = hoverBorderColor + return l +} + +func (l *PieDataSet) SetHoverBorderWidth(hoverBorderWidth float64) *PieDataSet { + l.HoverBorderWidth = hoverBorderWidth + return l +} + +func Pie() *PieChart { + return &PieChart{ + Chart: &Chart{ + BaseComponent: &template2.BaseComponent{ + Name: "chartjs", + HTMLData: List["chartjs"], + }, + dataSetIndex: -1, + }, + JsContent: PieJsContent{ + JsContent: JsContent{ + Type: "pie", + }, + Data: PieAttributes{ + Attributes: Attributes{ + Labels: make([]string, 0), + }, + DataSets: make(PieDataSets, 0), + }, + }, + } +} + +func (l *PieChart) SetID(s string) *PieChart { + l.ID = s + return l +} + +func (l *PieChart) SetTitle(s template.HTML) *PieChart { + l.Title = s + return l +} + +func (l *PieChart) SetHeight(s int) *PieChart { + l.Height = s + return l +} + +func (l *PieChart) SetLabels(s []string) *PieChart { + l.JsContent.Data.Labels = s + return l +} + +func (l *PieChart) AddDataSet(s string) *PieChart { + l.dataSetIndex++ + l.JsContent.Data.DataSets = l.JsContent.Data.DataSets.Add(&PieDataSet{ + Type: "pie", + Label: s, + }) + return l +} + +func (l *PieChart) DSLabel(s string) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetLabel(s) + return l +} + +func (l *PieChart) DSData(data []float64) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetData(data) + return l +} + +func (l *PieChart) DSType(t string) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetType(t) + return l +} + +func (l *PieChart) DSBackgroundColor(backgroundColor []Color) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBackgroundColor(backgroundColor) + return l +} + +func (l *PieChart) DSBorderColor(borderColor Color) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderColor(borderColor) + return l +} + +func (l *PieChart) DSBorderWidth(borderWidth float64) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetBorderWidth(borderWidth) + return l +} + +func (l *PieChart) DSWeight(weight int) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetWeight(weight) + return l +} + +func (l *PieChart) DSHoverBackgroundColor(hoverBackgroundColor Color) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBackgroundColor(hoverBackgroundColor) + return l +} + +func (l *PieChart) DSHoverBorderColor(hoverBorderColor Color) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderColor(hoverBorderColor) + return l +} + +func (l *PieChart) DSHoverBorderWidth(hoverBorderWidth float64) *PieChart { + l.JsContent.Data.DataSets[l.dataSetIndex].SetHoverBorderWidth(hoverBorderWidth) + return l +} + +func (l *PieChart) GetContent() template.HTML { + buffer := new(bytes.Buffer) + tmpl, defineName := l.GetTemplate() + + if l.JsContentOptions != nil { + l.JsContent.Options = l.JsContentOptions + } + + jsonByte, _ := json.Marshal(l.JsContent) + l.Js = template.JS(string(jsonByte)) + + err := tmpl.ExecuteTemplate(buffer, defineName, l) + if err != nil { + fmt.Println("ComposeHtml Error:", err) + } + return template.HTML(buffer.String()) +} diff --git a/template/chartjs/radar.go b/template/chartjs/radar.go new file mode 100644 index 0000000000000000000000000000000000000000..f8147af815802dd1509e11ffad1b48decca47b98 --- /dev/null +++ b/template/chartjs/radar.go @@ -0,0 +1 @@ +package chartjs diff --git a/template/chartjs/template.go b/template/chartjs/template.go new file mode 100644 index 0000000000000000000000000000000000000000..217326ce004872d36a508dc7e85fb9e04ddf54c6 --- /dev/null +++ b/template/chartjs/template.go @@ -0,0 +1,17 @@ +package chartjs + +var List = map[string]string{ + "chartjs": `{{define "chartjs"}} + {{if ne .Title ""}} +

    + {{langHtml .Title}} +

    + {{end}} +
    + +
    + +{{end}}`, +} diff --git a/template/color/color.go b/template/color/color.go new file mode 100644 index 0000000000000000000000000000000000000000..f98abe64b2a23a4ce22cd697da7e8329e1db8322 --- /dev/null +++ b/template/color/color.go @@ -0,0 +1,154 @@ +package color + +const ( + IndianRed = "#CD5C5C" + LightCoral = "#F08080" + Salmon = "#FA8072" + DarkSalmon = "#E9967A" + LightSalmon = "#FFA07A" + Crimson = "#DC143C" + Red = "#FF0000" + FireBrick = "#B22222" + DarkRed = "#8B0000" + + Cornsilk = "#FFF8DC" + BlanchedAlmond = "#FFEBCD" + Bisque = "#FFE4C4" + NavajoWhite = "#FFDEAD" + Wheat = "#F5DEB3" + BurlyWood = "#DEB887" + Tan = "#D2B48C" + RosyBrown = "#BC8F8F" + SandyBrown = "#F4A460" + Goldenrod = "#DAA520" + DarkGoldenrod = "#B8860B" + Peru = "#CD853F" + Chocolate = "#D2691E" + SaddleBrown = "#8B4513" + Sienna = "#A0522D" + Brown = "#A52A2A" + Maroon = "#800000" + + Pink = "#FFC0CB" + LightPink = "#FFB6C1" + HotPink = "#FF69B4" + DeepPink = "#FF1493" + MediumVioletRed = "#C71585" + PaleVioletRed = "#DB7093" + + GreenYellow = "#ADFF2F" + Chartreuse = "#7FFF00" + LawnGreen = "#7CFC00" + Lime = "#00FF00" + LimeGreen = "#32CD32" + PaleGreen = "#98FB98" + LightGreen = "#90EE90" + MediumSpringGreen = "#00FA9A" + SpringGreen = "#00FF7F" + MediumSeaGreen = "#3CB371" + SeaGreen = "#2E8B57" + ForestGreen = "#228B22" + Green = "#008000" + DarkGreen = "#006400" + YellowGreen = "#9ACD32" + OliveDrab = "#6B8E23" + Olive = "#808000" + DarkOliveGreen = "#556B2F" + MediumAquamarine = "#66CDAA" + DarkSeaGreen = "#8FBC8F" + LightSeaGreen = "#20B2AA" + DarkCyan = "#008B8B" + Teal = "#008080" + + Coral = "#FF7F50" + Tomato = "#FF6347" + OrangeRed = "#FF4500" + DarkOrange = "#FF8C00" + Orange = "#FFA500" + + White = "#FFFFFF" + Snow = "#FFFAFA" + Honeydew = "#F0FFF0" + MintCream = "#F5FFFA" + Azure = "#F0FFFF" + AliceBlue = "#F0F8FF" + GhostWhite = "#F8F8FF" + WhiteSmoke = "#F5F5F5" + Seashell = "#FFF5EE" + Beige = "#F5F5DC" + OldLace = "#FDF5E6" + FloralWhite = "#FFFAF0" + Ivory = "#FFFFF0" + AntiqueWhite = "#FAEBD7" + Linen = "#FAF0E6" + LavenderBlush = "#FFF0F5" + MistyRose = "#FFE4E1" + + Gold = "#FFD700" + Yellow = "#FFFF00" + LightYellow = "#FFFFE0" + LemonChiffon = "#FFFACD" + LightGoldenrodYellow = "#FAFAD2" + PapayaWhip = "#FFEFD5" + Moccasin = "#FFE4B5" + PeachPuff = "#FFDAB9" + PaleGoldenrod = "#EEE8AA" + Khaki = "#F0E68C" + DarkKhaki = "#BDB76B" + + Gainsboro = "#DCDCDC" + LightGrey = "#D3D3D3" + Silver = "#C0C0C0" + DarkGray = "#A9A9A9" + Gray = "#808080" + DimGray = "#696969" + LightSlateGray = "#778899" + SlateGray = "#708090" + DarkSlateGray = "#2F4F4F" + Black = "#000000" + + Aqua = "#00FFFF" + Cyan = "#00FFFF" + LightCyan = "#E0FFFF" + PaleTurquoise = "#AFEEEE" + Aquamarine = "#7FFFD4" + Turquoise = "#40E0D0" + MediumTurquoise = "#48D1CC" + DarkTurquoise = "#00CED1" + CadetBlue = "#5F9EA0" + SteelBlue = "#4682B4" + LightSteelBlue = "#B0C4DE" + PowderBlue = "#B0E0E6" + LightBlue = "#ADD8E6" + SkyBlue = "#87CEEB" + LightSkyBlue = "#87CEFA" + DeepSkyBlue = "#00BFFF" + DodgerBlue = "#1E90FF" + CornflowerBlue = "#6495ED" + MediumSlateBlue = "#7B68EE" + RoyalBlue = "#4169E1" + Blue = "#0000FF" + MediumBlue = "#0000CD" + DarkBlue = "#00008B" + Navy = "#000080" + MidnightBlue = "#191970" + + Lavender = "#E6E6FA" + Thistle = "#D8BFD8" + Plum = "#DDA0DD" + Violet = "#EE82EE" + Orchid = "#DA70D6" + Fuchsia = "#FF00FF" + Magenta = "#FF00FF" + MediumOrchid = "#BA55D3" + MediumPurple = "#9370DB" + Amethyst = "#9966CC" + BlueViolet = "#8A2BE2" + DarkViolet = "#9400D3" + DarkOrchid = "#9932CC" + DarkMagenta = "#8B008B" + Purple = "#800080" + Indigo = "#4B0082" + SlateBlue = "#6A5ACD" + DarkSlateBlue = "#483D8B" +) diff --git a/template/components/alert.go b/template/components/alert.go new file mode 100644 index 0000000000000000000000000000000000000000..93afbfcd208a6c40cacd0ac501bb8f518c875a31 --- /dev/null +++ b/template/components/alert.go @@ -0,0 +1,43 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/modules/errors" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/template/types" +) + +type AlertAttribute struct { + Name string + Theme string + Title template.HTML + Content template.HTML + types.Attribute +} + +func (compo *AlertAttribute) SetTheme(value string) types.AlertAttribute { + compo.Theme = value + return compo +} + +func (compo *AlertAttribute) SetTitle(value template.HTML) types.AlertAttribute { + compo.Title = value + return compo +} + +func (compo *AlertAttribute) SetContent(value template.HTML) types.AlertAttribute { + compo.Content = value + return compo +} + +func (compo *AlertAttribute) Warning(msg string) template.HTML { + return compo.SetTitle(errors.MsgWithIcon). + SetTheme("warning"). + SetContent(language.GetFromHtml(template.HTML(msg))). + GetContent() +} + +func (compo *AlertAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "alert") +} diff --git a/template/components/base.go b/template/components/base.go new file mode 100644 index 0000000000000000000000000000000000000000..17648ad2272a928a9a5b77bb732cbadb8a2a9d1b --- /dev/null +++ b/template/components/base.go @@ -0,0 +1,158 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/menu" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +type Base struct { + Attribute types.Attribute +} + +func (b Base) Box() types.BoxAttribute { + return &BoxAttribute{ + Name: "box", + Header: template.HTML(""), + Body: template.HTML(""), + Footer: template.HTML(""), + Title: "", + HeadBorder: "", + Attribute: b.Attribute, + } +} + +func (b Base) Col() types.ColAttribute { + return &ColAttribute{ + Name: "col", + Size: "col-md-2", + Content: "", + Attribute: b.Attribute, + } +} + +func (b Base) Form() types.FormAttribute { + return &FormAttribute{ + Name: "form", + Content: []types.FormField{}, + Url: "/", + Method: "post", + HiddenFields: make(map[string]string), + Layout: form.LayoutDefault, + Title: "edit", + Attribute: b.Attribute, + CdnUrl: config.GetAssetUrl(), + HeadWidth: 2, + InputWidth: 8, + } +} + +func (b Base) Image() types.ImgAttribute { + return &ImgAttribute{ + Name: "image", + Width: "50", + Height: "50", + Src: "", + Attribute: b.Attribute, + } +} + +func (b Base) Tabs() types.TabsAttribute { + return &TabsAttribute{ + Name: "tabs", + Attribute: b.Attribute, + } +} + +func (b Base) Alert() types.AlertAttribute { + return &AlertAttribute{ + Name: "alert", + Attribute: b.Attribute, + } +} + +func (b Base) Label() types.LabelAttribute { + return &LabelAttribute{ + Name: "label", + Type: "", + Content: "", + Attribute: b.Attribute, + } +} + +func (b Base) Link() types.LinkAttribute { + return &LinkAttribute{ + Name: "link", + Content: "", + Attribute: b.Attribute, + } +} + +func (b Base) Popup() types.PopupAttribute { + return &PopupAttribute{ + Name: "popup", + Attribute: b.Attribute, + } +} + +func (b Base) Paginator() types.PaginatorAttribute { + return &PaginatorAttribute{ + Name: "paginator", + Attribute: b.Attribute, + } +} + +func (b Base) Row() types.RowAttribute { + return &RowAttribute{ + Name: "row", + Content: "", + Attribute: b.Attribute, + } +} + +func (b Base) Button() types.ButtonAttribute { + return &ButtonAttribute{ + Name: "button", + Attribute: b.Attribute, + } +} + +func (b Base) Table() types.TableAttribute { + return &TableAttribute{ + Name: "table", + Thead: make(types.Thead, 0), + InfoList: make([]map[string]types.InfoItem, 0), + Type: "table", + Style: "hover", + Layout: "auto", + Attribute: b.Attribute, + } +} + +func (b Base) DataTable() types.DataTableAttribute { + return &DataTableAttribute{ + TableAttribute: *(b.Table(). + SetStyle("hover"). + SetName("data-table"). + SetType("data-table").(*TableAttribute)), + Attribute: b.Attribute, + } +} + +func (b Base) Tree() types.TreeAttribute { + return &TreeAttribute{ + Name: "tree", + Tree: make([]menu.Item, 0), + Attribute: b.Attribute, + } +} + +func (b Base) TreeView() types.TreeViewAttribute { + return &TreeViewAttribute{ + Name: "treeview", + Attribute: b.Attribute, + } +} diff --git a/template/components/box.go b/template/components/box.go new file mode 100644 index 0000000000000000000000000000000000000000..09088182cd7318dd41b09e71ab62684882f95307 --- /dev/null +++ b/template/components/box.go @@ -0,0 +1,121 @@ +package components + +import ( + "fmt" + "html/template" + + "github.com/GoAdminGroup/go-admin/template/types" +) + +type BoxAttribute struct { + Name string + Header template.HTML + Body template.HTML + Footer template.HTML + Title template.HTML + Theme string + HeadBorder string + Attr template.HTMLAttr + HeadColor string + Class string + SecondHeaderClass string + SecondHeader template.HTML + SecondHeadBorder string + SecondHeadColor string + Style template.HTMLAttr + Padding string + types.Attribute +} + +func (compo *BoxAttribute) SetTheme(value string) types.BoxAttribute { + compo.Theme = value + return compo +} + +func (compo *BoxAttribute) SetClass(value string) types.BoxAttribute { + compo.Class = value + return compo +} + +func (compo *BoxAttribute) SetHeader(value template.HTML) types.BoxAttribute { + compo.Header = value + return compo +} + +func (compo *BoxAttribute) SetBody(value template.HTML) types.BoxAttribute { + compo.Body = value + return compo +} + +func (compo *BoxAttribute) SetStyle(value template.HTMLAttr) types.BoxAttribute { + compo.Style = value + return compo +} + +func (compo *BoxAttribute) SetAttr(attr template.HTMLAttr) types.BoxAttribute { + compo.Attr = attr + return compo +} + +func (compo *BoxAttribute) SetIframeStyle(iframe bool) types.BoxAttribute { + if iframe { + compo.Attr = `style="border-radius: 0px;box-shadow:none;border-top:none;margin-bottom: 0px;"` + } + return compo +} + +func (compo *BoxAttribute) SetFooter(value template.HTML) types.BoxAttribute { + compo.Footer = value + return compo +} + +func (compo *BoxAttribute) SetTitle(value template.HTML) types.BoxAttribute { + compo.Title = value + return compo +} + +func (compo *BoxAttribute) SetHeadColor(value string) types.BoxAttribute { + compo.HeadColor = value + return compo +} + +func (compo *BoxAttribute) WithHeadBorder() types.BoxAttribute { + compo.HeadBorder = "with-border" + return compo +} + +func (compo *BoxAttribute) SetSecondHeader(value template.HTML) types.BoxAttribute { + compo.SecondHeader = value + return compo +} + +func (compo *BoxAttribute) SetSecondHeadColor(value string) types.BoxAttribute { + compo.SecondHeadColor = value + return compo +} + +func (compo *BoxAttribute) SetSecondHeaderClass(value string) types.BoxAttribute { + compo.SecondHeaderClass = value + return compo +} + +func (compo *BoxAttribute) SetNoPadding() types.BoxAttribute { + compo.Padding = "padding:0;" + return compo +} + +func (compo *BoxAttribute) WithSecondHeadBorder() types.BoxAttribute { + compo.SecondHeadBorder = "with-border" + return compo +} + +func (compo *BoxAttribute) GetContent() template.HTML { + + if compo.Style == "" { + compo.Style = template.HTMLAttr(fmt.Sprintf(`style="%s"`, compo.Padding)) + } else { + compo.Style = template.HTMLAttr(fmt.Sprintf(`style="%s"`, string(compo.Style)+compo.Padding)) + } + + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "box") +} diff --git a/template/components/button.go b/template/components/button.go new file mode 100644 index 0000000000000000000000000000000000000000..204ceef119b3dc6ebc5202908daf4157fe0cc50f --- /dev/null +++ b/template/components/button.go @@ -0,0 +1,129 @@ +package components + +import ( + "fmt" + "html/template" + + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/template/icon" + "github.com/GoAdminGroup/go-admin/template/types" +) + +type ButtonAttribute struct { + Name string + Content template.HTML + Orientation string + LoadingText template.HTML + Theme string + Type string + Size string + Href string + Class string + ID string + Style template.HTMLAttr + MarginLeft int + MarginRight int + types.Attribute +} + +func (compo *ButtonAttribute) SetContent(value template.HTML) types.ButtonAttribute { + compo.Content = value + return compo +} + +func (compo *ButtonAttribute) SetOrientationRight() types.ButtonAttribute { + compo.Orientation = "pull-right" + return compo +} + +func (compo *ButtonAttribute) SetOrientationLeft() types.ButtonAttribute { + compo.Orientation = "pull-left" + return compo +} + +func (compo *ButtonAttribute) SetMarginLeft(px int) types.ButtonAttribute { + compo.MarginLeft = px + return compo +} + +func (compo *ButtonAttribute) SetSmallSize() types.ButtonAttribute { + compo.Size = "btn-sm" + return compo +} + +func (compo *ButtonAttribute) SetMiddleSize() types.ButtonAttribute { + compo.Size = "btn-md" + return compo +} + +func (compo *ButtonAttribute) SetMarginRight(px int) types.ButtonAttribute { + compo.MarginRight = px + return compo +} + +func (compo *ButtonAttribute) SetLoadingText(value template.HTML) types.ButtonAttribute { + compo.LoadingText = value + return compo +} + +func (compo *ButtonAttribute) SetThemePrimary() types.ButtonAttribute { + compo.Theme = "primary" + return compo +} + +func (compo *ButtonAttribute) SetThemeDefault() types.ButtonAttribute { + compo.Theme = "default" + return compo +} + +func (compo *ButtonAttribute) SetThemeWarning() types.ButtonAttribute { + compo.Theme = "warning" + return compo +} + +func (compo *ButtonAttribute) SetHref(href string) types.ButtonAttribute { + compo.Href = href + return compo +} + +func (compo *ButtonAttribute) AddClass(class string) types.ButtonAttribute { + compo.Class += " " + class + return compo +} + +func (compo *ButtonAttribute) SetID(id string) types.ButtonAttribute { + compo.ID = id + return compo +} + +func (compo *ButtonAttribute) SetTheme(value string) types.ButtonAttribute { + compo.Theme = value + return compo +} + +func (compo *ButtonAttribute) SetType(value string) types.ButtonAttribute { + compo.Type = value + return compo +} + +func (compo *ButtonAttribute) GetContent() template.HTML { + + if compo.MarginLeft != 0 { + compo.Style = template.HTMLAttr(fmt.Sprintf(`style="margin-left:%dpx;"`, compo.MarginLeft)) + } + + if compo.MarginRight != 0 { + compo.Style = template.HTMLAttr(fmt.Sprintf(`style="margin-right:%dpx;"`, compo.MarginRight)) + } + + if compo.LoadingText == "" { + compo.LoadingText = icon.Icon(icon.Spinner, 1) + language.GetFromHtml(`Save`) + } + + if compo.ID == "" { + compo.ID = utils.Uuid(15) + "_btn" + } + + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "button") +} diff --git a/template/components/col.go b/template/components/col.go new file mode 100644 index 0000000000000000000000000000000000000000..b56846e1e41d77b74fc3b72ad0878f26e7473443 --- /dev/null +++ b/template/components/col.go @@ -0,0 +1,36 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/template/types" +) + +type ColAttribute struct { + Name string + Content template.HTML + Size string + types.Attribute +} + +func (compo *ColAttribute) SetContent(value template.HTML) types.ColAttribute { + compo.Content = value + return compo +} + +func (compo *ColAttribute) AddContent(value template.HTML) types.ColAttribute { + compo.Content += value + return compo +} + +func (compo *ColAttribute) SetSize(value types.S) types.ColAttribute { + compo.Size = "" + for key, size := range value { + compo.Size += "col-" + key + "-" + size + " " + } + return compo +} + +func (compo *ColAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "col") +} diff --git a/template/components/composer.go b/template/components/composer.go new file mode 100644 index 0000000000000000000000000000000000000000..45eb00282e4b066f981a781cd6d7983e881e5857 --- /dev/null +++ b/template/components/composer.go @@ -0,0 +1,53 @@ +package components + +import ( + "bytes" + "html/template" + + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/logger" + "github.com/GoAdminGroup/go-admin/modules/utils" + template2 "github.com/GoAdminGroup/go-admin/template" +) + +func ComposeHtml(temList map[string]string, separation bool, compo interface{}, templateName ...string) template.HTML { + + tmplName := "" + if len(templateName) > 0 { + tmplName = templateName[0] + " " + } + + var ( + tmpl *template.Template + err error + ) + + if separation { + files := make([]string, 0) + root := config.GetAssetRootPath() + "pages/" + for _, v := range templateName { + files = append(files, root+temList["components/"+v]+".tmpl") + } + tmpl, err = template.New("comp").Funcs(template2.DefaultFuncMap).ParseFiles(files...) + } else { + var text = "" + for _, v := range templateName { + text += temList["components/"+v] + } + tmpl, err = template.New("comp").Funcs(template2.DefaultFuncMap).Parse(text) + } + + if err != nil { + logger.Panic(tmplName + "ComposeHtml Error:" + err.Error()) + return "" + } + buf := new(bytes.Buffer) + + defineName := utils.ReplaceAll(templateName[0], "table/", "", "form/", "") + + err = tmpl.ExecuteTemplate(buf, defineName, compo) + if err != nil { + logger.Error(tmplName+" ComposeHtml Error:", err) + } + return template.HTML(buf.String()) +} diff --git a/template/components/form.go b/template/components/form.go new file mode 100644 index 0000000000000000000000000000000000000000..5e4c897ea0b07cc1db475e4cdfc57c90d9312e16 --- /dev/null +++ b/template/components/form.go @@ -0,0 +1,232 @@ +package components + +import ( + "fmt" + "html/template" + "strings" + + "github.com/GoAdminGroup/go-admin/modules/config" + "github.com/GoAdminGroup/go-admin/modules/language" + "github.com/GoAdminGroup/go-admin/modules/utils" + form2 "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form" + "github.com/GoAdminGroup/go-admin/template/types" + "github.com/GoAdminGroup/go-admin/template/types/form" +) + +type FormAttribute struct { + Name string + Id string + Header template.HTML + Content types.FormFields + ContentList []types.FormFields + Layout form.Layout + TabContents []types.FormFields + TabHeaders []string + Footer template.HTML + Url string + FieldsHTML template.HTML + Method string + PrimaryKey string + Ajax bool + AjaxSuccessJS template.JS + AjaxErrorJS template.JS + HeadWidth int + InputWidth int + HiddenFields map[string]string + Title template.HTML + OperationFooter template.HTML + Prefix string + CdnUrl string + Horizontal bool + types.Attribute +} + +func (compo *FormAttribute) SetHeader(value template.HTML) types.FormAttribute { + compo.Header = value + return compo +} + +func (compo *FormAttribute) SetPrimaryKey(value string) types.FormAttribute { + compo.PrimaryKey = value + return compo +} + +func (compo *FormAttribute) SetHorizontal(value bool) types.FormAttribute { + compo.Horizontal = value + return compo +} + +func (compo *FormAttribute) SetContent(value types.FormFields) types.FormAttribute { + compo.Content = value + return compo +} + +func (compo *FormAttribute) SetId(id string) types.FormAttribute { + compo.Id = id + return compo +} + +func (compo *FormAttribute) SetAjax(successJS, errorJS template.JS) types.FormAttribute { + if successJS != template.JS("") && errorJS != template.JS("") { + compo.Ajax = true + compo.AjaxErrorJS = errorJS + compo.AjaxSuccessJS = successJS + } + return compo +} + +func (compo *FormAttribute) SetTabContents(value []types.FormFields) types.FormAttribute { + compo.TabContents = value + return compo +} + +func (compo *FormAttribute) SetTabHeaders(value []string) types.FormAttribute { + compo.TabHeaders = value + return compo +} + +func (compo *FormAttribute) SetHeadWidth(width int) types.FormAttribute { + if width > 0 { + if width > 12 { + width = 12 + } + compo.HeadWidth = width + } + return compo +} + +func (compo *FormAttribute) SetInputWidth(width int) types.FormAttribute { + if width > 0 { + if width > 12 { + width = 12 + } + compo.InputWidth = width + } + return compo +} + +func (compo *FormAttribute) SetFieldsHTML(html template.HTML) types.FormAttribute { + compo.FieldsHTML = html + return compo +} + +func (compo *FormAttribute) SetFooter(value template.HTML) types.FormAttribute { + compo.Footer = value + return compo +} + +func (compo *FormAttribute) SetLayout(layout form.Layout) types.FormAttribute { + compo.Layout = layout + return compo +} + +func (compo *FormAttribute) SetPrefix(value string) types.FormAttribute { + compo.Prefix = value + return compo +} + +func (compo *FormAttribute) SetUrl(value string) types.FormAttribute { + compo.Url = value + return compo +} + +func (compo *FormAttribute) SetHiddenFields(fields map[string]string) types.FormAttribute { + compo.HiddenFields = fields + return compo +} + +func (compo *FormAttribute) SetMethod(value string) types.FormAttribute { + compo.Method = value + return compo +} + +func (compo *FormAttribute) SetTitle(value template.HTML) types.FormAttribute { + compo.Title = value + return compo +} + +func (compo *FormAttribute) GetDefaultBoxHeader(hideBack bool) template.HTML { + if hideBack { + return template.HTML(fmt.Sprintf(`

    %s

    `, language.GetFromHtml(compo.Title))) + } + return template.HTML(fmt.Sprintf(`

    %s

    +
    +
    +  %s +
    +
    `, language.GetFromHtml(compo.Title), compo.HiddenFields[form2.PreviousKey], language.Get("Back"))) +} + +func (compo *FormAttribute) GetDetailBoxHeader(editUrl, deleteUrl string) template.HTML { + + var ( + editBtn string + deleteBtn string + ) + + if editUrl != "" { + editBtn = fmt.Sprintf(` +
    +  %s +
    `, editUrl, language.Get("Edit")) + } + + if deleteUrl != "" { + deleteBtn = fmt.Sprintf(` +
    +  %s +
    `, language.Get("Delete")) + } + + return template.HTML(`

    `) + language.GetFromHtml(compo.Title) + template.HTML(`

    +
    + `+deleteBtn+editBtn+` + +
    `) +} + +func (compo *FormAttribute) GetBoxHeaderNoButton() template.HTML { + return template.HTML(fmt.Sprintf(`

    %s

    `, language.GetFromHtml(compo.Title))) +} + +func (compo *FormAttribute) SetOperationFooter(value template.HTML) types.FormAttribute { + compo.OperationFooter = value + return compo +} + +func (compo *FormAttribute) GetContent() template.HTML { + compo.CdnUrl = config.GetAssetUrl() + if compo.Id == "" { + compo.Id = utils.Uuid(10) + } + + if col := compo.Layout.Col(); col > 0 { + compo.ContentList = make([]types.FormFields, col) + index := 0 + for i := 0; i < len(compo.Content); i++ { + ii := index % col + compo.ContentList[ii] = append(compo.ContentList[ii], compo.Content[i]) + if i < len(compo.Content)-1 { + if strings.Contains(compo.Content[i+1].Field, "__goadmin_operator__") { + compo.ContentList[ii] = append(compo.ContentList[ii], compo.Content[i+1]) + i++ + } + } + index++ + } + } + + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "form", + "form/default", "form/file", "form/multi_file", "form/textarea", "form/custom", "form/rate", "form/slider", + "form/selectbox", "form/text", "form/table", "form/radio", "form/switch", "form/checkbox", "form/checkbox_single", + "form/checkbox_stacked", "form/password", "form/code", "form/array", "form/select", "form/singleselect", + "form/richtext", "form/iconpicker", "form/datetime", "form/number", "form/number_range", + "form/email", "form/url", "form/ip", "form/color", "form/currency", "form_components", "form/datetime_range", + "form_layout_default", "form_layout_two_col", "form_layout_tab", "form_components_layout", "form_layout_flow", "form_layout_filter") +} diff --git a/template/components/image.go b/template/components/image.go new file mode 100644 index 0000000000000000000000000000000000000000..679c2298458eb84283b02c2dd96d7e7181a8f4d3 --- /dev/null +++ b/template/components/image.go @@ -0,0 +1,43 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/plugins/admin/modules" + "github.com/GoAdminGroup/go-admin/template/types" +) + +type ImgAttribute struct { + Name string + Width string + Height string + Uuid string + HasModal bool + Src template.URL + types.Attribute +} + +func (compo *ImgAttribute) SetWidth(value string) types.ImgAttribute { + compo.Width = value + return compo +} + +func (compo *ImgAttribute) SetHeight(value string) types.ImgAttribute { + compo.Height = value + return compo +} + +func (compo *ImgAttribute) WithModal() types.ImgAttribute { + compo.HasModal = true + compo.Uuid = modules.Uuid() + return compo +} + +func (compo *ImgAttribute) SetSrc(value template.HTML) types.ImgAttribute { + compo.Src = template.URL(value) + return compo +} + +func (compo *ImgAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "image") +} diff --git a/template/components/label.go b/template/components/label.go new file mode 100644 index 0000000000000000000000000000000000000000..831530d7d531d7cf8ed21feef3f3bd579e505579 --- /dev/null +++ b/template/components/label.go @@ -0,0 +1,34 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/template/types" +) + +type LabelAttribute struct { + Name string + Color template.HTML + Type string + Content template.HTML + types.Attribute +} + +func (compo *LabelAttribute) SetType(value string) types.LabelAttribute { + compo.Type = value + return compo +} + +func (compo *LabelAttribute) SetColor(value template.HTML) types.LabelAttribute { + compo.Color = value + return compo +} + +func (compo *LabelAttribute) SetContent(value template.HTML) types.LabelAttribute { + compo.Content = value + return compo +} + +func (compo *LabelAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "label") +} diff --git a/template/components/link.go b/template/components/link.go new file mode 100644 index 0000000000000000000000000000000000000000..63466d8218e35d849b51289ed47346a1ff94da82 --- /dev/null +++ b/template/components/link.go @@ -0,0 +1,56 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/template/types" +) + +type LinkAttribute struct { + Name string + URL string + Class template.HTML + Title template.HTML + Attributes template.HTMLAttr + Content template.HTML + types.Attribute +} + +func (compo *LinkAttribute) OpenInNewTab() types.LinkAttribute { + compo.Class += " new-tab-link" + return compo +} + +func (compo *LinkAttribute) SetURL(value string) types.LinkAttribute { + compo.URL = value + return compo +} + +func (compo *LinkAttribute) SetClass(class template.HTML) types.LinkAttribute { + compo.Class = class + return compo +} + +func (compo *LinkAttribute) SetAttributes(attr template.HTMLAttr) types.LinkAttribute { + compo.Attributes = attr + return compo +} + +func (compo *LinkAttribute) NoPjax() types.LinkAttribute { + compo.Class += " no-pjax" + return compo +} + +func (compo *LinkAttribute) SetTabTitle(value template.HTML) types.LinkAttribute { + compo.Title = value + return compo +} + +func (compo *LinkAttribute) SetContent(value template.HTML) types.LinkAttribute { + compo.Content = value + return compo +} + +func (compo *LinkAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "link") +} diff --git a/template/components/paninator.go b/template/components/paninator.go new file mode 100644 index 0000000000000000000000000000000000000000..62fd768f951ccf489b3634af0b00434c47776982 --- /dev/null +++ b/template/components/paninator.go @@ -0,0 +1,94 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/template/types" +) + +type PaginatorAttribute struct { + Name string + CurPageStartIndex string + CurPageEndIndex string + Total string + PreviousClass string + PreviousUrl string + Pages []map[string]string + NextClass string + NextUrl string + PageSizeList []string + Option map[string]template.HTML + Url string + ExtraInfo template.HTML + EntriesInfo template.HTML + types.Attribute +} + +func (compo *PaginatorAttribute) SetCurPageStartIndex(value string) types.PaginatorAttribute { + compo.CurPageStartIndex = value + return compo +} + +func (compo *PaginatorAttribute) SetCurPageEndIndex(value string) types.PaginatorAttribute { + compo.CurPageEndIndex = value + return compo +} + +func (compo *PaginatorAttribute) SetTotal(value string) types.PaginatorAttribute { + compo.Total = value + return compo +} + +func (compo *PaginatorAttribute) SetExtraInfo(value template.HTML) types.PaginatorAttribute { + compo.ExtraInfo = value + return compo +} + +func (compo *PaginatorAttribute) SetEntriesInfo(value template.HTML) types.PaginatorAttribute { + compo.EntriesInfo = value + return compo +} + +func (compo *PaginatorAttribute) SetPreviousClass(value string) types.PaginatorAttribute { + compo.PreviousClass = value + return compo +} + +func (compo *PaginatorAttribute) SetPreviousUrl(value string) types.PaginatorAttribute { + compo.PreviousUrl = value + return compo +} + +func (compo *PaginatorAttribute) SetPages(value []map[string]string) types.PaginatorAttribute { + compo.Pages = value + return compo +} + +func (compo *PaginatorAttribute) SetPageSizeList(value []string) types.PaginatorAttribute { + compo.PageSizeList = value + return compo +} + +func (compo *PaginatorAttribute) SetNextClass(value string) types.PaginatorAttribute { + compo.NextClass = value + return compo +} + +func (compo *PaginatorAttribute) SetNextUrl(value string) types.PaginatorAttribute { + compo.NextUrl = value + return compo +} + +func (compo *PaginatorAttribute) SetOption(value map[string]template.HTML) types.PaginatorAttribute { + compo.Option = value + return compo +} + +func (compo *PaginatorAttribute) SetUrl(value string) types.PaginatorAttribute { + compo.Url = value + return compo +} + +func (compo *PaginatorAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "paginator") +} diff --git a/template/components/popup.go b/template/components/popup.go new file mode 100644 index 0000000000000000000000000000000000000000..b1c2c792ee9ac40bd15b837fbdebe209426e668d --- /dev/null +++ b/template/components/popup.go @@ -0,0 +1,76 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/template/types" +) + +type PopupAttribute struct { + Name string + ID string + Body template.HTML + Footer template.HTML + FooterHTML template.HTML + Title template.HTML + Size string + HideFooter bool + Height string + Width string + Draggable bool + types.Attribute +} + +func (compo *PopupAttribute) SetID(value string) types.PopupAttribute { + compo.ID = value + return compo +} + +func (compo *PopupAttribute) SetTitle(value template.HTML) types.PopupAttribute { + compo.Title = value + return compo +} + +func (compo *PopupAttribute) SetFooter(value template.HTML) types.PopupAttribute { + compo.Footer = value + return compo +} + +func (compo *PopupAttribute) SetFooterHTML(value template.HTML) types.PopupAttribute { + compo.FooterHTML = value + return compo +} + +func (compo *PopupAttribute) SetWidth(width string) types.PopupAttribute { + compo.Width = width + return compo +} + +func (compo *PopupAttribute) SetHeight(height string) types.PopupAttribute { + compo.Height = height + return compo +} + +func (compo *PopupAttribute) SetDraggable() types.PopupAttribute { + compo.Draggable = true + return compo +} + +func (compo *PopupAttribute) SetHideFooter() types.PopupAttribute { + compo.HideFooter = true + return compo +} + +func (compo *PopupAttribute) SetBody(value template.HTML) types.PopupAttribute { + compo.Body = value + return compo +} + +func (compo *PopupAttribute) SetSize(value string) types.PopupAttribute { + compo.Size = value + return compo +} + +func (compo *PopupAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "popup") +} diff --git a/template/components/product.go b/template/components/product.go new file mode 100644 index 0000000000000000000000000000000000000000..bc820f5c718b7abad780b1f33995033d4678d14f --- /dev/null +++ b/template/components/product.go @@ -0,0 +1 @@ +package components diff --git a/template/components/row.go b/template/components/row.go new file mode 100644 index 0000000000000000000000000000000000000000..e496e0b8cfa8bf90d834fa84853155c5ed01854a --- /dev/null +++ b/template/components/row.go @@ -0,0 +1,27 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/template/types" +) + +type RowAttribute struct { + Name string + Content template.HTML + types.Attribute +} + +func (compo *RowAttribute) SetContent(value template.HTML) types.RowAttribute { + compo.Content = value + return compo +} + +func (compo *RowAttribute) AddContent(value template.HTML) types.RowAttribute { + compo.Content += value + return compo +} + +func (compo *RowAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "row") +} diff --git a/template/components/table.go b/template/components/table.go new file mode 100644 index 0000000000000000000000000000000000000000..0f7e85afca79ca9ca3b1fb12fdcbbe822ca5ca37 --- /dev/null +++ b/template/components/table.go @@ -0,0 +1,246 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/template/types" +) + +type TableAttribute struct { + Name string + Thead types.Thead + InfoList []map[string]types.InfoItem + Type string + PrimaryKey string + Style string + Class string + HideThead bool + NoAction bool + Action template.HTML + EditUrl string + MinWidth string + DeleteUrl string + DetailUrl string + SortUrl string + UpdateUrl string + Layout string + IsTab bool + ExportUrl string + ActionFold bool + types.Attribute +} + +func (compo *TableAttribute) SetThead(value types.Thead) types.TableAttribute { + compo.Thead = value + return compo +} + +func (compo *TableAttribute) SetInfoList(value []map[string]types.InfoItem) types.TableAttribute { + compo.InfoList = value + return compo +} + +func (compo *TableAttribute) SetType(value string) types.TableAttribute { + compo.Type = value + return compo +} + +func (compo *TableAttribute) SetName(name string) types.TableAttribute { + compo.Name = name + return compo +} + +func (compo *TableAttribute) SetHideThead() types.TableAttribute { + compo.HideThead = true + return compo +} + +func (compo *TableAttribute) SetStyle(style string) types.TableAttribute { + compo.Style = style + return compo +} + +func (compo *TableAttribute) SetSticky(sticky bool) types.TableAttribute { + if sticky { + compo.Class = "sticky_table" + } + return compo +} + +func (compo *TableAttribute) SetMinWidth(value string) types.TableAttribute { + compo.MinWidth = value + return compo +} + +func (compo *TableAttribute) SetLayout(value string) types.TableAttribute { + compo.Layout = value + return compo +} + +func (compo *TableAttribute) GetContent() template.HTML { + if compo.MinWidth == "" { + compo.MinWidth = "1000px" + } + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "table") +} + +type DataTableAttribute struct { + TableAttribute + EditUrl string + NewUrl string + UpdateUrl string + Class string + HideThead bool + DetailUrl string + SortUrl template.URL + DeleteUrl string + PrimaryKey string + IsTab bool + ExportUrl string + InfoUrl string + Buttons template.HTML + ActionJs template.JS + IsHideFilterArea bool + IsHideRowSelector bool + NoAction bool + HasFilter bool + Action template.HTML + ActionFold bool + types.Attribute +} + +func (compo *DataTableAttribute) GetDataTableHeader() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "table/box-header") +} + +func (compo *DataTableAttribute) SetThead(value types.Thead) types.DataTableAttribute { + compo.Thead = value + return compo +} + +func (compo *DataTableAttribute) SetSticky(sticky bool) types.DataTableAttribute { + if sticky { + compo.Class = "sticky_table" + } + return compo +} + +func (compo *DataTableAttribute) SetLayout(value string) types.DataTableAttribute { + compo.Layout = value + return compo +} + +func (compo *DataTableAttribute) SetIsTab(value bool) types.DataTableAttribute { + compo.IsTab = value + return compo +} + +func (compo *DataTableAttribute) SetHideThead() types.DataTableAttribute { + compo.HideThead = true + return compo +} + +func (compo *DataTableAttribute) SetButtons(btns template.HTML) types.DataTableAttribute { + compo.Buttons = btns + return compo +} + +func (compo *DataTableAttribute) SetHideFilterArea(value bool) types.DataTableAttribute { + compo.IsHideFilterArea = value + return compo +} + +func (compo *DataTableAttribute) SetActionJs(aj template.JS) types.DataTableAttribute { + compo.ActionJs = aj + return compo +} + +func (compo *DataTableAttribute) SetActionFold(fold bool) types.DataTableAttribute { + compo.ActionFold = fold + return compo +} + +func (compo *DataTableAttribute) SetHasFilter(hasFilter bool) types.DataTableAttribute { + compo.HasFilter = hasFilter + return compo +} + +func (compo *DataTableAttribute) SetInfoUrl(value string) types.DataTableAttribute { + compo.InfoUrl = value + return compo +} + +func (compo *DataTableAttribute) SetAction(action template.HTML) types.DataTableAttribute { + compo.Action = action + return compo +} + +func (compo *DataTableAttribute) SetStyle(style string) types.DataTableAttribute { + compo.Style = style + return compo +} + +func (compo *DataTableAttribute) SetExportUrl(value string) types.DataTableAttribute { + compo.ExportUrl = value + return compo +} + +func (compo *DataTableAttribute) SetHideRowSelector(value bool) types.DataTableAttribute { + compo.IsHideRowSelector = value + return compo +} + +func (compo *DataTableAttribute) SetUpdateUrl(value string) types.DataTableAttribute { + compo.UpdateUrl = value + return compo +} + +func (compo *DataTableAttribute) SetDetailUrl(value string) types.DataTableAttribute { + compo.DetailUrl = value + return compo +} + +func (compo *DataTableAttribute) SetSortUrl(value string) types.DataTableAttribute { + compo.SortUrl = template.URL(value) + return compo +} + +func (compo *DataTableAttribute) SetPrimaryKey(value string) types.DataTableAttribute { + compo.PrimaryKey = value + return compo +} + +func (compo *DataTableAttribute) SetInfoList(value []map[string]types.InfoItem) types.DataTableAttribute { + compo.InfoList = value + return compo +} + +func (compo *DataTableAttribute) SetEditUrl(value string) types.DataTableAttribute { + compo.EditUrl = value + return compo +} + +func (compo *DataTableAttribute) SetDeleteUrl(value string) types.DataTableAttribute { + compo.DeleteUrl = value + return compo +} + +func (compo *DataTableAttribute) SetNewUrl(value string) types.DataTableAttribute { + compo.NewUrl = value + return compo +} + +func (compo *DataTableAttribute) SetNoAction() types.DataTableAttribute { + compo.NoAction = true + return compo +} + +func (compo *DataTableAttribute) GetContent() template.HTML { + if compo.MinWidth == "" { + compo.MinWidth = "1600px" + } + if !compo.NoAction && compo.EditUrl == "" && compo.DeleteUrl == "" && compo.DetailUrl == "" && compo.Action == "" { + compo.NoAction = true + } + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "table") +} diff --git a/template/components/tabs.go b/template/components/tabs.go new file mode 100644 index 0000000000000000000000000000000000000000..170b019b828016a50dbce247566f8f83c53c989e --- /dev/null +++ b/template/components/tabs.go @@ -0,0 +1,22 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/template/types" +) + +type TabsAttribute struct { + Name string + Data []map[string]template.HTML + types.Attribute +} + +func (compo *TabsAttribute) SetData(value []map[string]template.HTML) types.TabsAttribute { + compo.Data = value + return compo +} + +func (compo *TabsAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "tabs") +} diff --git a/template/components/tree.go b/template/components/tree.go new file mode 100644 index 0000000000000000000000000000000000000000..9b110ab6ff0b31d386dd961f0895b64b9ccdd8c8 --- /dev/null +++ b/template/components/tree.go @@ -0,0 +1,51 @@ +package components + +import ( + "html/template" + + "github.com/GoAdminGroup/go-admin/modules/menu" + "github.com/GoAdminGroup/go-admin/template/types" +) + +type TreeAttribute struct { + Name string + Tree []menu.Item + EditUrl string + DeleteUrl string + UrlPrefix string + OrderUrl string + types.Attribute +} + +func (compo *TreeAttribute) SetTree(value []menu.Item) types.TreeAttribute { + compo.Tree = value + return compo +} + +func (compo *TreeAttribute) SetEditUrl(value string) types.TreeAttribute { + compo.EditUrl = value + return compo +} + +func (compo *TreeAttribute) SetUrlPrefix(value string) types.TreeAttribute { + compo.UrlPrefix = value + return compo +} + +func (compo *TreeAttribute) SetDeleteUrl(value string) types.TreeAttribute { + compo.DeleteUrl = value + return compo +} + +func (compo *TreeAttribute) SetOrderUrl(value string) types.TreeAttribute { + compo.OrderUrl = value + return compo +} + +func (compo *TreeAttribute) GetContent() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "tree") +} + +func (compo *TreeAttribute) GetTreeHeader() template.HTML { + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "tree-header") +} diff --git a/template/components/treeview.go b/template/components/treeview.go new file mode 100644 index 0000000000000000000000000000000000000000..cc265da736ac60d2d4b16f3878d6b4d2f266a3a5 --- /dev/null +++ b/template/components/treeview.go @@ -0,0 +1,42 @@ +package components + +import ( + "encoding/json" + "html/template" + + "github.com/GoAdminGroup/go-admin/modules/utils" + "github.com/GoAdminGroup/go-admin/template/types" +) + +type TreeViewAttribute struct { + Name string + ID string + Tree types.TreeViewData + TreeJSON template.JS + UrlPrefix string + types.Attribute +} + +func (compo *TreeViewAttribute) SetID(id string) types.TreeViewAttribute { + compo.ID = id + return compo +} + +func (compo *TreeViewAttribute) SetTree(value types.TreeViewData) types.TreeViewAttribute { + compo.Tree = value + return compo +} + +func (compo *TreeViewAttribute) SetUrlPrefix(value string) types.TreeViewAttribute { + compo.UrlPrefix = value + return compo +} + +func (compo *TreeViewAttribute) GetContent() template.HTML { + if compo.ID == "" { + compo.ID = utils.Uuid(10) + } + b, _ := json.Marshal(compo.Tree) + compo.TreeJSON = template.JS(b) + return ComposeHtml(compo.TemplateList, compo.Separation, *compo, "treeview") +} diff --git a/template/icon/icon.go b/template/icon/icon.go new file mode 100644 index 0000000000000000000000000000000000000000..bd7a6d1cd8893eeef5916c4686adf7574e024d08 --- /dev/null +++ b/template/icon/icon.go @@ -0,0 +1,768 @@ +package icon + +import ( + "html/template" + + "github.com/GoAdminGroup/html" +) + +func Icon(class string, num ...int) template.HTML { + space := template.HTML("") + if len(num) > 0 { + for i := 0; i < num[0]; i++ { + space += " " + } + } + return html.IEl().SetClass("icon fa", class).Get() + space +} + +func IconWithStyle(class string, style html.Style, num ...int) template.HTML { + space := template.HTML("") + if len(num) > 0 { + for i := 0; i < num[0]; i++ { + space += " " + } + } + + i := html.IEl().SetClass("icon fa", class) + for k, s := range style { + i.SetStyle(k, s) + } + + return i.Get() + space +} + +const ( + Adjust = "fa-adjust" // https://fontawesome.com/icons/adjust + Adn = "fa-adn" // https://fontawesome.com/icons/adn + AlignCenter = "fa-align-center" // https://fontawesome.com/icons/align-center + AlignJustify = "fa-align-justify" // https://fontawesome.com/icons/align-justify + AlignLeft = "fa-align-left" // https://fontawesome.com/icons/align-left + AlignRight = "fa-align-right" // https://fontawesome.com/icons/align-right + Amazon = "fa-amazon" // https://fontawesome.com/icons/amazon + Ambulance = "fa-ambulance" // https://fontawesome.com/icons/ambulance + AmericanSignLanguageInterpreting = "fa-american-sign-language-interpreting" // https://fontawesome.com/icons/american-sign-language-interpreting + Anchor = "fa-anchor" // https://fontawesome.com/icons/anchor + Android = "fa-android" // https://fontawesome.com/icons/android + Angellist = "fa-angellist" // https://fontawesome.com/icons/angellist + AngleDoubleDown = "fa-angle-double-down" // https://fontawesome.com/icons/angle-double-down + AngleDoubleLeft = "fa-angle-double-left" // https://fontawesome.com/icons/angle-double-left + AngleDoubleRight = "fa-angle-double-right" // https://fontawesome.com/icons/angle-double-right + AngleDoubleUp = "fa-angle-double-up" // https://fontawesome.com/icons/angle-double-up + AngleDown = "fa-angle-down" // https://fontawesome.com/icons/angle-down + AngleLeft = "fa-angle-left" // https://fontawesome.com/icons/angle-left + AngleRight = "fa-angle-right" // https://fontawesome.com/icons/angle-right + AngleUp = "fa-angle-up" // https://fontawesome.com/icons/angle-up + Apple = "fa-apple" // https://fontawesome.com/icons/apple + Archive = "fa-archive" // https://fontawesome.com/icons/archive + AreaChart = "fa-area-chart" // https://fontawesome.com/icons/area-chart + ArrowCircleDown = "fa-arrow-circle-down" // https://fontawesome.com/icons/arrow-circle-down + ArrowCircleLeft = "fa-arrow-circle-left" // https://fontawesome.com/icons/arrow-circle-left + ArrowCircleODown = "fa-arrow-circle-o-down" // https://fontawesome.com/icons/arrow-circle-o-down + ArrowCircleOLeft = "fa-arrow-circle-o-left" // https://fontawesome.com/icons/arrow-circle-o-left + ArrowCircleORight = "fa-arrow-circle-o-right" // https://fontawesome.com/icons/arrow-circle-o-right + ArrowCircleOUp = "fa-arrow-circle-o-up" // https://fontawesome.com/icons/arrow-circle-o-up + ArrowCircleRight = "fa-arrow-circle-right" // https://fontawesome.com/icons/arrow-circle-right + ArrowCircleUp = "fa-arrow-circle-up" // https://fontawesome.com/icons/arrow-circle-up + ArrowDown = "fa-arrow-down" // https://fontawesome.com/icons/arrow-down + ArrowLeft = "fa-arrow-left" // https://fontawesome.com/icons/arrow-left + ArrowRight = "fa-arrow-right" // https://fontawesome.com/icons/arrow-right + ArrowUp = "fa-arrow-up" // https://fontawesome.com/icons/arrow-up + Arrows = "fa-arrows" // https://fontawesome.com/icons/arrows + ArrowsAlt = "fa-arrows-alt" // https://fontawesome.com/icons/arrows-alt + ArrowsH = "fa-arrows-h" // https://fontawesome.com/icons/arrows-h + ArrowsV = "fa-arrows-v" // https://fontawesome.com/icons/arrows-v + AslInterpreting = "fa-asl-interpreting" // https://fontawesome.com/icons/asl-interpreting + AssistiveListeningSystems = "fa-assistive-listening-systems" // https://fontawesome.com/icons/assistive-listening-systems + Asterisk = "fa-asterisk" // https://fontawesome.com/icons/asterisk + At = "fa-at" // https://fontawesome.com/icons/at + AudioDescription = "fa-audio-description" // https://fontawesome.com/icons/audio-description + Automobile = "fa-automobile" // https://fontawesome.com/icons/automobile + Backward = "fa-backward" // https://fontawesome.com/icons/backward + BalanceScale = "fa-balance-scale" // https://fontawesome.com/icons/balance-scale + Ban = "fa-ban" // https://fontawesome.com/icons/ban + Bank = "fa-bank" // https://fontawesome.com/icons/bank + BarChart = "fa-bar-chart" // https://fontawesome.com/icons/bar-chart + BarChartO = "fa-bar-chart-o" // https://fontawesome.com/icons/bar-chart-o + Barcode = "fa-barcode" // https://fontawesome.com/icons/barcode + Bars = "fa-bars" // https://fontawesome.com/icons/bars + Battery0 = "fa-battery-0" // https://fontawesome.com/icons/battery-0 + Battery1 = "fa-battery-1" // https://fontawesome.com/icons/battery-1 + Battery2 = "fa-battery-2" // https://fontawesome.com/icons/battery-2 + Battery3 = "fa-battery-3" // https://fontawesome.com/icons/battery-3 + Battery4 = "fa-battery-4" // https://fontawesome.com/icons/battery-4 + BatteryEmpty = "fa-battery-empty" // https://fontawesome.com/icons/battery-empty + BatteryFull = "fa-battery-full" // https://fontawesome.com/icons/battery-full + BatteryHalf = "fa-battery-half" // https://fontawesome.com/icons/battery-half + BatteryQuarter = "fa-battery-quarter" // https://fontawesome.com/icons/battery-quarter + BatteryThreeQuarters = "fa-battery-three-quarters" // https://fontawesome.com/icons/battery-three-quarters + Bed = "fa-bed" // https://fontawesome.com/icons/bed + Beer = "fa-beer" // https://fontawesome.com/icons/beer + Behance = "fa-behance" // https://fontawesome.com/icons/behance + BehanceSquare = "fa-behance-square" // https://fontawesome.com/icons/behance-square + Bell = "fa-bell" // https://fontawesome.com/icons/bell + BellO = "fa-bell-o" // https://fontawesome.com/icons/bell-o + BellSlash = "fa-bell-slash" // https://fontawesome.com/icons/bell-slash + BellSlashO = "fa-bell-slash-o" // https://fontawesome.com/icons/bell-slash-o + Bicycle = "fa-bicycle" // https://fontawesome.com/icons/bicycle + Binoculars = "fa-binoculars" // https://fontawesome.com/icons/binoculars + BirthdayCake = "fa-birthday-cake" // https://fontawesome.com/icons/birthday-cake + Bitbucket = "fa-bitbucket" // https://fontawesome.com/icons/bitbucket + BitbucketSquare = "fa-bitbucket-square" // https://fontawesome.com/icons/bitbucket-square + Bitcoin = "fa-bitcoin" // https://fontawesome.com/icons/bitcoin + BlackTie = "fa-black-tie" // https://fontawesome.com/icons/black-tie + Blind = "fa-blind" // https://fontawesome.com/icons/blind + Bluetooth = "fa-bluetooth" // https://fontawesome.com/icons/bluetooth + BluetoothB = "fa-bluetooth-b" // https://fontawesome.com/icons/bluetooth-b + Bold = "fa-bold" // https://fontawesome.com/icons/bold + Bolt = "fa-bolt" // https://fontawesome.com/icons/bolt + Bomb = "fa-bomb" // https://fontawesome.com/icons/bomb + Book = "fa-book" // https://fontawesome.com/icons/book + Bookmark = "fa-bookmark" // https://fontawesome.com/icons/bookmark + BookmarkO = "fa-bookmark-o" // https://fontawesome.com/icons/bookmark-o + Braille = "fa-braille" // https://fontawesome.com/icons/braille + Briefcase = "fa-briefcase" // https://fontawesome.com/icons/briefcase + Btc = "fa-btc" // https://fontawesome.com/icons/btc + Bug = "fa-bug" // https://fontawesome.com/icons/bug + Building = "fa-building" // https://fontawesome.com/icons/building + BuildingO = "fa-building-o" // https://fontawesome.com/icons/building-o + Bullhorn = "fa-bullhorn" // https://fontawesome.com/icons/bullhorn + Bullseye = "fa-bullseye" // https://fontawesome.com/icons/bullseye + Bus = "fa-bus" // https://fontawesome.com/icons/bus + Buysellads = "fa-buysellads" // https://fontawesome.com/icons/buysellads + Cab = "fa-cab" // https://fontawesome.com/icons/cab + Calculator = "fa-calculator" // https://fontawesome.com/icons/calculator + Calendar = "fa-calendar" // https://fontawesome.com/icons/calendar + CalendarCheckO = "fa-calendar-check-o" // https://fontawesome.com/icons/calendar-check-o + CalendarMinusO = "fa-calendar-minus-o" // https://fontawesome.com/icons/calendar-minus-o + CalendarO = "fa-calendar-o" // https://fontawesome.com/icons/calendar-o + CalendarPlusO = "fa-calendar-plus-o" // https://fontawesome.com/icons/calendar-plus-o + CalendarTimesO = "fa-calendar-times-o" // https://fontawesome.com/icons/calendar-times-o + Camera = "fa-camera" // https://fontawesome.com/icons/camera + CameraRetro = "fa-camera-retro" // https://fontawesome.com/icons/camera-retro + Car = "fa-car" // https://fontawesome.com/icons/car + CaretDown = "fa-caret-down" // https://fontawesome.com/icons/caret-down + CaretLeft = "fa-caret-left" // https://fontawesome.com/icons/caret-left + CaretRight = "fa-caret-right" // https://fontawesome.com/icons/caret-right + CaretSquareODown = "fa-caret-square-o-down" // https://fontawesome.com/icons/caret-square-o-down + CaretSquareOLeft = "fa-caret-square-o-left" // https://fontawesome.com/icons/caret-square-o-left + CaretSquareORight = "fa-caret-square-o-right" // https://fontawesome.com/icons/caret-square-o-right + CaretSquareOUp = "fa-caret-square-o-up" // https://fontawesome.com/icons/caret-square-o-up + CaretUp = "fa-caret-up" // https://fontawesome.com/icons/caret-up + CartArrowDown = "fa-cart-arrow-down" // https://fontawesome.com/icons/cart-arrow-down + CartPlus = "fa-cart-plus" // https://fontawesome.com/icons/cart-plus + Cc = "fa-cc" // https://fontawesome.com/icons/cc + CcAmex = "fa-cc-amex" // https://fontawesome.com/icons/cc-amex + CcDinersClub = "fa-cc-diners-club" // https://fontawesome.com/icons/cc-diners-club + CcDiscover = "fa-cc-discover" // https://fontawesome.com/icons/cc-discover + CcJcb = "fa-cc-jcb" // https://fontawesome.com/icons/cc-jcb + CcMastercard = "fa-cc-mastercard" // https://fontawesome.com/icons/cc-mastercard + CcPaypal = "fa-cc-paypal" // https://fontawesome.com/icons/cc-paypal + CcStripe = "fa-cc-stripe" // https://fontawesome.com/icons/cc-stripe + CcVisa = "fa-cc-visa" // https://fontawesome.com/icons/cc-visa + Certificate = "fa-certificate" // https://fontawesome.com/icons/certificate + Chain = "fa-chain" // https://fontawesome.com/icons/chain + ChainBroken = "fa-chain-broken" // https://fontawesome.com/icons/chain-broken + Check = "fa-check" // https://fontawesome.com/icons/check + CheckCircle = "fa-check-circle" // https://fontawesome.com/icons/check-circle + CheckCircleO = "fa-check-circle-o" // https://fontawesome.com/icons/check-circle-o + CheckSquare = "fa-check-square" // https://fontawesome.com/icons/check-square + CheckSquareO = "fa-check-square-o" // https://fontawesome.com/icons/check-square-o + ChevronCircleDown = "fa-chevron-circle-down" // https://fontawesome.com/icons/chevron-circle-down + ChevronCircleLeft = "fa-chevron-circle-left" // https://fontawesome.com/icons/chevron-circle-left + ChevronCircleRight = "fa-chevron-circle-right" // https://fontawesome.com/icons/chevron-circle-right + ChevronCircleUp = "fa-chevron-circle-up" // https://fontawesome.com/icons/chevron-circle-up + ChevronDown = "fa-chevron-down" // https://fontawesome.com/icons/chevron-down + ChevronLeft = "fa-chevron-left" // https://fontawesome.com/icons/chevron-left + ChevronRight = "fa-chevron-right" // https://fontawesome.com/icons/chevron-right + ChevronUp = "fa-chevron-up" // https://fontawesome.com/icons/chevron-up + Child = "fa-child" // https://fontawesome.com/icons/child + Chrome = "fa-chrome" // https://fontawesome.com/icons/chrome + Circle = "fa-circle" // https://fontawesome.com/icons/circle + CircleO = "fa-circle-o" // https://fontawesome.com/icons/circle-o + CircleONotch = "fa-circle-o-notch" // https://fontawesome.com/icons/circle-o-notch + CircleThin = "fa-circle-thin" // https://fontawesome.com/icons/circle-thin + Clipboard = "fa-clipboard" // https://fontawesome.com/icons/clipboard + ClockO = "fa-clock-o" // https://fontawesome.com/icons/clock-o + Clone = "fa-clone" // https://fontawesome.com/icons/clone + Close = "fa-close" // https://fontawesome.com/icons/close + Cloud = "fa-cloud" // https://fontawesome.com/icons/cloud + CloudDownload = "fa-cloud-download" // https://fontawesome.com/icons/cloud-download + CloudUpload = "fa-cloud-upload" // https://fontawesome.com/icons/cloud-upload + Cny = "fa-cny" // https://fontawesome.com/icons/cny + Code = "fa-code" // https://fontawesome.com/icons/code + CodeFork = "fa-code-fork" // https://fontawesome.com/icons/code-fork + Codepen = "fa-codepen" // https://fontawesome.com/icons/codepen + Codiepie = "fa-codiepie" // https://fontawesome.com/icons/codiepie + Coffee = "fa-coffee" // https://fontawesome.com/icons/coffee + Cog = "fa-cog" // https://fontawesome.com/icons/cog + Cogs = "fa-cogs" // https://fontawesome.com/icons/cogs + Columns = "fa-columns" // https://fontawesome.com/icons/columns + Comment = "fa-comment" // https://fontawesome.com/icons/comment + CommentO = "fa-comment-o" // https://fontawesome.com/icons/comment-o + Commenting = "fa-commenting" // https://fontawesome.com/icons/commenting + CommentingO = "fa-commenting-o" // https://fontawesome.com/icons/commenting-o + Comments = "fa-comments" // https://fontawesome.com/icons/comments + CommentsO = "fa-comments-o" // https://fontawesome.com/icons/comments-o + Compass = "fa-compass" // https://fontawesome.com/icons/compass + Compress = "fa-compress" // https://fontawesome.com/icons/compress + Connectdevelop = "fa-connectdevelop" // https://fontawesome.com/icons/connectdevelop + Contao = "fa-contao" // https://fontawesome.com/icons/contao + Copy = "fa-copy" // https://fontawesome.com/icons/copy + Copyright = "fa-copyright" // https://fontawesome.com/icons/copyright + CreativeCommons = "fa-creative-commons" // https://fontawesome.com/icons/creative-commons + CreditCard = "fa-credit-card" // https://fontawesome.com/icons/credit-card + CreditCardAlt = "fa-credit-card-alt" // https://fontawesome.com/icons/credit-card-alt + Crop = "fa-crop" // https://fontawesome.com/icons/crop + Crosshairs = "fa-crosshairs" // https://fontawesome.com/icons/crosshairs + Css3 = "fa-css3" // https://fontawesome.com/icons/css3 + Cube = "fa-cube" // https://fontawesome.com/icons/cube + Cubes = "fa-cubes" // https://fontawesome.com/icons/cubes + Cut = "fa-cut" // https://fontawesome.com/icons/cut + Cutlery = "fa-cutlery" // https://fontawesome.com/icons/cutlery + Dashboard = "fa-dashboard" // https://fontawesome.com/icons/dashboard + Dashcube = "fa-dashcube" // https://fontawesome.com/icons/dashcube + Database = "fa-database" // https://fontawesome.com/icons/database + Deaf = "fa-deaf" // https://fontawesome.com/icons/deaf + Deafness = "fa-deafness" // https://fontawesome.com/icons/deafness + Dedent = "fa-dedent" // https://fontawesome.com/icons/dedent + Delicious = "fa-delicious" // https://fontawesome.com/icons/delicious + Desktop = "fa-desktop" // https://fontawesome.com/icons/desktop + Deviantart = "fa-deviantart" // https://fontawesome.com/icons/deviantart + Diamond = "fa-diamond" // https://fontawesome.com/icons/diamond + Digg = "fa-digg" // https://fontawesome.com/icons/digg + Dollar = "fa-dollar" // https://fontawesome.com/icons/dollar + DotCircleO = "fa-dot-circle-o" // https://fontawesome.com/icons/dot-circle-o + Download = "fa-download" // https://fontawesome.com/icons/download + Dribbble = "fa-dribbble" // https://fontawesome.com/icons/dribbble + Dropbox = "fa-dropbox" // https://fontawesome.com/icons/dropbox + Drupal = "fa-drupal" // https://fontawesome.com/icons/drupal + Edge = "fa-edge" // https://fontawesome.com/icons/edge + Edit = "fa-edit" // https://fontawesome.com/icons/edit + Eject = "fa-eject" // https://fontawesome.com/icons/eject + EllipsisH = "fa-ellipsis-h" // https://fontawesome.com/icons/ellipsis-h + EllipsisV = "fa-ellipsis-v" // https://fontawesome.com/icons/ellipsis-v + Empire = "fa-empire" // https://fontawesome.com/icons/empire + Envelope = "fa-envelope" // https://fontawesome.com/icons/envelope + EnvelopeO = "fa-envelope-o" // https://fontawesome.com/icons/envelope-o + EnvelopeSquare = "fa-envelope-square" // https://fontawesome.com/icons/envelope-square + Envira = "fa-envira" // https://fontawesome.com/icons/envira + Eraser = "fa-eraser" // https://fontawesome.com/icons/eraser + Eur = "fa-eur" // https://fontawesome.com/icons/eur + Euro = "fa-euro" // https://fontawesome.com/icons/euro + Exchange = "fa-exchange" // https://fontawesome.com/icons/exchange + Exclamation = "fa-exclamation" // https://fontawesome.com/icons/exclamation + ExclamationCircle = "fa-exclamation-circle" // https://fontawesome.com/icons/exclamation-circle + ExclamationTriangle = "fa-exclamation-triangle" // https://fontawesome.com/icons/exclamation-triangle + Expand = "fa-expand" // https://fontawesome.com/icons/expand + Expeditedssl = "fa-expeditedssl" // https://fontawesome.com/icons/expeditedssl + ExternalLink = "fa-external-link" // https://fontawesome.com/icons/external-link + ExternalLinkSquare = "fa-external-link-square" // https://fontawesome.com/icons/external-link-square + Eye = "fa-eye" // https://fontawesome.com/icons/eye + EyeSlash = "fa-eye-slash" // https://fontawesome.com/icons/eye-slash + Eyedropper = "fa-eyedropper" // https://fontawesome.com/icons/eyedropper + Fa = "fa-fa" // https://fontawesome.com/icons/fa + Facebook = "fa-facebook" // https://fontawesome.com/icons/facebook + FacebookF = "fa-facebook-f" // https://fontawesome.com/icons/facebook-f + FacebookOfficial = "fa-facebook-official" // https://fontawesome.com/icons/facebook-official + FacebookSquare = "fa-facebook-square" // https://fontawesome.com/icons/facebook-square + FastBackward = "fa-fast-backward" // https://fontawesome.com/icons/fast-backward + FastForward = "fa-fast-forward" // https://fontawesome.com/icons/fast-forward + Fax = "fa-fax" // https://fontawesome.com/icons/fax + Feed = "fa-feed" // https://fontawesome.com/icons/feed + Female = "fa-female" // https://fontawesome.com/icons/female + FighterJet = "fa-fighter-jet" // https://fontawesome.com/icons/fighter-jet + File = "fa-file" // https://fontawesome.com/icons/file + FileArchiveO = "fa-file-archive-o" // https://fontawesome.com/icons/file-archive-o + FileAudioO = "fa-file-audio-o" // https://fontawesome.com/icons/file-audio-o + FileCodeO = "fa-file-code-o" // https://fontawesome.com/icons/file-code-o + FileExcelO = "fa-file-excel-o" // https://fontawesome.com/icons/file-excel-o + FileImageO = "fa-file-image-o" // https://fontawesome.com/icons/file-image-o + FileMovieO = "fa-file-movie-o" // https://fontawesome.com/icons/file-movie-o + FileO = "fa-file-o" // https://fontawesome.com/icons/file-o + FilePdfO = "fa-file-pdf-o" // https://fontawesome.com/icons/file-pdf-o + FilePhotoO = "fa-file-photo-o" // https://fontawesome.com/icons/file-photo-o + FilePictureO = "fa-file-picture-o" // https://fontawesome.com/icons/file-picture-o + FilePowerpointO = "fa-file-powerpoint-o" // https://fontawesome.com/icons/file-powerpoint-o + FileSoundO = "fa-file-sound-o" // https://fontawesome.com/icons/file-sound-o + FileText = "fa-file-text" // https://fontawesome.com/icons/file-text + FileTextO = "fa-file-text-o" // https://fontawesome.com/icons/file-text-o + FileVideoO = "fa-file-video-o" // https://fontawesome.com/icons/file-video-o + FileWordO = "fa-file-word-o" // https://fontawesome.com/icons/file-word-o + FileZipO = "fa-file-zip-o" // https://fontawesome.com/icons/file-zip-o + FilesO = "fa-files-o" // https://fontawesome.com/icons/files-o + Film = "fa-film" // https://fontawesome.com/icons/film + Filter = "fa-filter" // https://fontawesome.com/icons/filter + Fire = "fa-fire" // https://fontawesome.com/icons/fire + FireExtinguisher = "fa-fire-extinguisher" // https://fontawesome.com/icons/fire-extinguisher + Firefox = "fa-firefox" // https://fontawesome.com/icons/firefox + FirstOrder = "fa-first-order" // https://fontawesome.com/icons/first-order + Flag = "fa-flag" // https://fontawesome.com/icons/flag + FlagCheckered = "fa-flag-checkered" // https://fontawesome.com/icons/flag-checkered + FlagO = "fa-flag-o" // https://fontawesome.com/icons/flag-o + Flash = "fa-flash" // https://fontawesome.com/icons/flash + Flask = "fa-flask" // https://fontawesome.com/icons/flask + Flickr = "fa-flickr" // https://fontawesome.com/icons/flickr + FloppyO = "fa-floppy-o" // https://fontawesome.com/icons/floppy-o + Folder = "fa-folder" // https://fontawesome.com/icons/folder + FolderO = "fa-folder-o" // https://fontawesome.com/icons/folder-o + FolderOpen = "fa-folder-open" // https://fontawesome.com/icons/folder-open + FolderOpenO = "fa-folder-open-o" // https://fontawesome.com/icons/folder-open-o + Font = "fa-font" // https://fontawesome.com/icons/font + FontAwesome = "fa-font-awesome" // https://fontawesome.com/icons/font-awesome + Fonticons = "fa-fonticons" // https://fontawesome.com/icons/fonticons + FortAwesome = "fa-fort-awesome" // https://fontawesome.com/icons/fort-awesome + Forumbee = "fa-forumbee" // https://fontawesome.com/icons/forumbee + Forward = "fa-forward" // https://fontawesome.com/icons/forward + Foursquare = "fa-foursquare" // https://fontawesome.com/icons/foursquare + FrownO = "fa-frown-o" // https://fontawesome.com/icons/frown-o + FutbolO = "fa-futbol-o" // https://fontawesome.com/icons/futbol-o + Gamepad = "fa-gamepad" // https://fontawesome.com/icons/gamepad + Gavel = "fa-gavel" // https://fontawesome.com/icons/gavel + Gbp = "fa-gbp" // https://fontawesome.com/icons/gbp + Ge = "fa-ge" // https://fontawesome.com/icons/ge + Gear = "fa-gear" // https://fontawesome.com/icons/gear + Gears = "fa-gears" // https://fontawesome.com/icons/gears + Genderless = "fa-genderless" // https://fontawesome.com/icons/genderless + GetPocket = "fa-get-pocket" // https://fontawesome.com/icons/get-pocket + Gg = "fa-gg" // https://fontawesome.com/icons/gg + GgCircle = "fa-gg-circle" // https://fontawesome.com/icons/gg-circle + Gift = "fa-gift" // https://fontawesome.com/icons/gift + Git = "fa-git" // https://fontawesome.com/icons/git + GitSquare = "fa-git-square" // https://fontawesome.com/icons/git-square + Github = "fa-github" // https://fontawesome.com/icons/github + GithubAlt = "fa-github-alt" // https://fontawesome.com/icons/github-alt + GithubSquare = "fa-github-square" // https://fontawesome.com/icons/github-square + Gitlab = "fa-gitlab" // https://fontawesome.com/icons/gitlab + Gittip = "fa-gittip" // https://fontawesome.com/icons/gittip + Glass = "fa-glass" // https://fontawesome.com/icons/glass + Glide = "fa-glide" // https://fontawesome.com/icons/glide + GlideG = "fa-glide-g" // https://fontawesome.com/icons/glide-g + Globe = "fa-globe" // https://fontawesome.com/icons/globe + Google = "fa-google" // https://fontawesome.com/icons/google + GooglePlus = "fa-google-plus" // https://fontawesome.com/icons/google-plus + GooglePlusCircle = "fa-google-plus-circle" // https://fontawesome.com/icons/google-plus-circle + GooglePlusOfficial = "fa-google-plus-official" // https://fontawesome.com/icons/google-plus-official + GooglePlusSquare = "fa-google-plus-square" // https://fontawesome.com/icons/google-plus-square + GoogleWallet = "fa-google-wallet" // https://fontawesome.com/icons/google-wallet + GraduationCap = "fa-graduation-cap" // https://fontawesome.com/icons/graduation-cap + Gratipay = "fa-gratipay" // https://fontawesome.com/icons/gratipay + Group = "fa-group" // https://fontawesome.com/icons/group + HSquare = "fa-h-square" // https://fontawesome.com/icons/h-square + HackerNews = "fa-hacker-news" // https://fontawesome.com/icons/hacker-news + HandGrabO = "fa-hand-grab-o" // https://fontawesome.com/icons/hand-grab-o + HandLizardO = "fa-hand-lizard-o" // https://fontawesome.com/icons/hand-lizard-o + HandODown = "fa-hand-o-down" // https://fontawesome.com/icons/hand-o-down + HandOLeft = "fa-hand-o-left" // https://fontawesome.com/icons/hand-o-left + HandORight = "fa-hand-o-right" // https://fontawesome.com/icons/hand-o-right + HandOUp = "fa-hand-o-up" // https://fontawesome.com/icons/hand-o-up + HandPaperO = "fa-hand-paper-o" // https://fontawesome.com/icons/hand-paper-o + HandPeaceO = "fa-hand-peace-o" // https://fontawesome.com/icons/hand-peace-o + HandPointerO = "fa-hand-pointer-o" // https://fontawesome.com/icons/hand-pointer-o + HandRockO = "fa-hand-rock-o" // https://fontawesome.com/icons/hand-rock-o + HandScissorsO = "fa-hand-scissors-o" // https://fontawesome.com/icons/hand-scissors-o + HandSpockO = "fa-hand-spock-o" // https://fontawesome.com/icons/hand-spock-o + HandStopO = "fa-hand-stop-o" // https://fontawesome.com/icons/hand-stop-o + HardOfHearing = "fa-hard-of-hearing" // https://fontawesome.com/icons/hard-of-hearing + Hashtag = "fa-hashtag" // https://fontawesome.com/icons/hashtag + HddO = "fa-hdd-o" // https://fontawesome.com/icons/hdd-o + Header = "fa-header" // https://fontawesome.com/icons/header + Headphones = "fa-headphones" // https://fontawesome.com/icons/headphones + Heart = "fa-heart" // https://fontawesome.com/icons/heart + HeartO = "fa-heart-o" // https://fontawesome.com/icons/heart-o + Heartbeat = "fa-heartbeat" // https://fontawesome.com/icons/heartbeat + History = "fa-history" // https://fontawesome.com/icons/history + Home = "fa-home" // https://fontawesome.com/icons/home + HospitalO = "fa-hospital-o" // https://fontawesome.com/icons/hospital-o + Hotel = "fa-hotel" // https://fontawesome.com/icons/hotel + Hourglass = "fa-hourglass" // https://fontawesome.com/icons/hourglass + Hourglass1 = "fa-hourglass-1" // https://fontawesome.com/icons/hourglass-1 + Hourglass2 = "fa-hourglass-2" // https://fontawesome.com/icons/hourglass-2 + Hourglass3 = "fa-hourglass-3" // https://fontawesome.com/icons/hourglass-3 + HourglassEnd = "fa-hourglass-end" // https://fontawesome.com/icons/hourglass-end + HourglassHalf = "fa-hourglass-half" // https://fontawesome.com/icons/hourglass-half + HourglassO = "fa-hourglass-o" // https://fontawesome.com/icons/hourglass-o + HourglassStart = "fa-hourglass-start" // https://fontawesome.com/icons/hourglass-start + Houzz = "fa-houzz" // https://fontawesome.com/icons/houzz + Html5 = "fa-html5" // https://fontawesome.com/icons/html5 + ICursor = "fa-i-cursor" // https://fontawesome.com/icons/i-cursor + Ils = "fa-ils" // https://fontawesome.com/icons/ils + Image = "fa-image" // https://fontawesome.com/icons/image + Inbox = "fa-inbox" // https://fontawesome.com/icons/inbox + Indent = "fa-indent" // https://fontawesome.com/icons/indent + Industry = "fa-industry" // https://fontawesome.com/icons/industry + Info = "fa-info" // https://fontawesome.com/icons/info + InfoCircle = "fa-info-circle" // https://fontawesome.com/icons/info-circle + Inr = "fa-inr" // https://fontawesome.com/icons/inr + Instagram = "fa-instagram" // https://fontawesome.com/icons/instagram + Institution = "fa-institution" // https://fontawesome.com/icons/institution + InternetExplorer = "fa-internet-explorer" // https://fontawesome.com/icons/internet-explorer + Intersex = "fa-intersex" // https://fontawesome.com/icons/intersex + Ioxhost = "fa-ioxhost" // https://fontawesome.com/icons/ioxhost + Italic = "fa-italic" // https://fontawesome.com/icons/italic + Joomla = "fa-joomla" // https://fontawesome.com/icons/joomla + Jpy = "fa-jpy" // https://fontawesome.com/icons/jpy + Jsfiddle = "fa-jsfiddle" // https://fontawesome.com/icons/jsfiddle + Key = "fa-key" // https://fontawesome.com/icons/key + KeyboardO = "fa-keyboard-o" // https://fontawesome.com/icons/keyboard-o + Krw = "fa-krw" // https://fontawesome.com/icons/krw + Language = "fa-language" // https://fontawesome.com/icons/language + Laptop = "fa-laptop" // https://fontawesome.com/icons/laptop + Lastfm = "fa-lastfm" // https://fontawesome.com/icons/lastfm + LastfmSquare = "fa-lastfm-square" // https://fontawesome.com/icons/lastfm-square + Leaf = "fa-leaf" // https://fontawesome.com/icons/leaf + Leanpub = "fa-leanpub" // https://fontawesome.com/icons/leanpub + Legal = "fa-legal" // https://fontawesome.com/icons/legal + LemonO = "fa-lemon-o" // https://fontawesome.com/icons/lemon-o + LevelDown = "fa-level-down" // https://fontawesome.com/icons/level-down + LevelUp = "fa-level-up" // https://fontawesome.com/icons/level-up + LifeBouy = "fa-life-bouy" // https://fontawesome.com/icons/life-bouy + LifeBuoy = "fa-life-buoy" // https://fontawesome.com/icons/life-buoy + LifeRing = "fa-life-ring" // https://fontawesome.com/icons/life-ring + LifeSaver = "fa-life-saver" // https://fontawesome.com/icons/life-saver + LightbulbO = "fa-lightbulb-o" // https://fontawesome.com/icons/lightbulb-o + LineChart = "fa-line-chart" // https://fontawesome.com/icons/line-chart + Link = "fa-link" // https://fontawesome.com/icons/link + Linkedin = "fa-linkedin" // https://fontawesome.com/icons/linkedin + LinkedinSquare = "fa-linkedin-square" // https://fontawesome.com/icons/linkedin-square + Linux = "fa-linux" // https://fontawesome.com/icons/linux + List = "fa-list" // https://fontawesome.com/icons/list + ListAlt = "fa-list-alt" // https://fontawesome.com/icons/list-alt + ListOl = "fa-list-ol" // https://fontawesome.com/icons/list-ol + ListUl = "fa-list-ul" // https://fontawesome.com/icons/list-ul + LocationArrow = "fa-location-arrow" // https://fontawesome.com/icons/location-arrow + Lock = "fa-lock" // https://fontawesome.com/icons/lock + LongArrowDown = "fa-long-arrow-down" // https://fontawesome.com/icons/long-arrow-down + LongArrowLeft = "fa-long-arrow-left" // https://fontawesome.com/icons/long-arrow-left + LongArrowRight = "fa-long-arrow-right" // https://fontawesome.com/icons/long-arrow-right + LongArrowUp = "fa-long-arrow-up" // https://fontawesome.com/icons/long-arrow-up + LowVision = "fa-low-vision" // https://fontawesome.com/icons/low-vision + Magic = "fa-magic" // https://fontawesome.com/icons/magic + Magnet = "fa-magnet" // https://fontawesome.com/icons/magnet + MailForward = "fa-mail-forward" // https://fontawesome.com/icons/mail-forward + MailReply = "fa-mail-reply" // https://fontawesome.com/icons/mail-reply + MailReplyAll = "fa-mail-reply-all" // https://fontawesome.com/icons/mail-reply-all + Male = "fa-male" // https://fontawesome.com/icons/male + Map = "fa-map" // https://fontawesome.com/icons/map + MapMarker = "fa-map-marker" // https://fontawesome.com/icons/map-marker + MapO = "fa-map-o" // https://fontawesome.com/icons/map-o + MapPin = "fa-map-pin" // https://fontawesome.com/icons/map-pin + MapSigns = "fa-map-signs" // https://fontawesome.com/icons/map-signs + Mars = "fa-mars" // https://fontawesome.com/icons/mars + MarsDouble = "fa-mars-double" // https://fontawesome.com/icons/mars-double + MarsStroke = "fa-mars-stroke" // https://fontawesome.com/icons/mars-stroke + MarsStrokeH = "fa-mars-stroke-h" // https://fontawesome.com/icons/mars-stroke-h + MarsStrokeV = "fa-mars-stroke-v" // https://fontawesome.com/icons/mars-stroke-v + Maxcdn = "fa-maxcdn" // https://fontawesome.com/icons/maxcdn + Meanpath = "fa-meanpath" // https://fontawesome.com/icons/meanpath + Medium = "fa-medium" // https://fontawesome.com/icons/medium + Medkit = "fa-medkit" // https://fontawesome.com/icons/medkit + MehO = "fa-meh-o" // https://fontawesome.com/icons/meh-o + Mercury = "fa-mercury" // https://fontawesome.com/icons/mercury + Microphone = "fa-microphone" // https://fontawesome.com/icons/microphone + MicrophoneSlash = "fa-microphone-slash" // https://fontawesome.com/icons/microphone-slash + Minus = "fa-minus" // https://fontawesome.com/icons/minus + MinusCircle = "fa-minus-circle" // https://fontawesome.com/icons/minus-circle + MinusSquare = "fa-minus-square" // https://fontawesome.com/icons/minus-square + MinusSquareO = "fa-minus-square-o" // https://fontawesome.com/icons/minus-square-o + Mixcloud = "fa-mixcloud" // https://fontawesome.com/icons/mixcloud + Mobile = "fa-mobile" // https://fontawesome.com/icons/mobile + MobilePhone = "fa-mobile-phone" // https://fontawesome.com/icons/mobile-phone + Modx = "fa-modx" // https://fontawesome.com/icons/modx + Money = "fa-money" // https://fontawesome.com/icons/money + MoonO = "fa-moon-o" // https://fontawesome.com/icons/moon-o + MortarBoard = "fa-mortar-board" // https://fontawesome.com/icons/mortar-board + Motorcycle = "fa-motorcycle" // https://fontawesome.com/icons/motorcycle + MousePointer = "fa-mouse-pointer" // https://fontawesome.com/icons/mouse-pointer + Music = "fa-music" // https://fontawesome.com/icons/music + Navicon = "fa-navicon" // https://fontawesome.com/icons/navicon + Neuter = "fa-neuter" // https://fontawesome.com/icons/neuter + NewspaperO = "fa-newspaper-o" // https://fontawesome.com/icons/newspaper-o + ObjectGroup = "fa-object-group" // https://fontawesome.com/icons/object-group + ObjectUngroup = "fa-object-ungroup" // https://fontawesome.com/icons/object-ungroup + Odnoklassniki = "fa-odnoklassniki" // https://fontawesome.com/icons/odnoklassniki + OdnoklassnikiSquare = "fa-odnoklassniki-square" // https://fontawesome.com/icons/odnoklassniki-square + Opencart = "fa-opencart" // https://fontawesome.com/icons/opencart + Openid = "fa-openid" // https://fontawesome.com/icons/openid + Opera = "fa-opera" // https://fontawesome.com/icons/opera + OptinMonster = "fa-optin-monster" // https://fontawesome.com/icons/optin-monster + Outdent = "fa-outdent" // https://fontawesome.com/icons/outdent + Pagelines = "fa-pagelines" // https://fontawesome.com/icons/pagelines + PaintBrush = "fa-paint-brush" // https://fontawesome.com/icons/paint-brush + PaperPlane = "fa-paper-plane" // https://fontawesome.com/icons/paper-plane + PaperPlaneO = "fa-paper-plane-o" // https://fontawesome.com/icons/paper-plane-o + Paperclip = "fa-paperclip" // https://fontawesome.com/icons/paperclip + Paragraph = "fa-paragraph" // https://fontawesome.com/icons/paragraph + Paste = "fa-paste" // https://fontawesome.com/icons/paste + Pause = "fa-pause" // https://fontawesome.com/icons/pause + PauseCircle = "fa-pause-circle" // https://fontawesome.com/icons/pause-circle + PauseCircleO = "fa-pause-circle-o" // https://fontawesome.com/icons/pause-circle-o + Paw = "fa-paw" // https://fontawesome.com/icons/paw + Paypal = "fa-paypal" // https://fontawesome.com/icons/paypal + Pencil = "fa-pencil" // https://fontawesome.com/icons/pencil + PencilSquare = "fa-pencil-square" // https://fontawesome.com/icons/pencil-square + PencilSquareO = "fa-pencil-square-o" // https://fontawesome.com/icons/pencil-square-o + Percent = "fa-percent" // https://fontawesome.com/icons/percent + Phone = "fa-phone" // https://fontawesome.com/icons/phone + PhoneSquare = "fa-phone-square" // https://fontawesome.com/icons/phone-square + Photo = "fa-photo" // https://fontawesome.com/icons/photo + PictureO = "fa-picture-o" // https://fontawesome.com/icons/picture-o + PieChart = "fa-pie-chart" // https://fontawesome.com/icons/pie-chart + PiedPiper = "fa-pied-piper" // https://fontawesome.com/icons/pied-piper + PiedPiperAlt = "fa-pied-piper-alt" // https://fontawesome.com/icons/pied-piper-alt + PiedPiperPp = "fa-pied-piper-pp" // https://fontawesome.com/icons/pied-piper-pp + Pinterest = "fa-pinterest" // https://fontawesome.com/icons/pinterest + PinterestP = "fa-pinterest-p" // https://fontawesome.com/icons/pinterest-p + PinterestSquare = "fa-pinterest-square" // https://fontawesome.com/icons/pinterest-square + Plane = "fa-plane" // https://fontawesome.com/icons/plane + Play = "fa-play" // https://fontawesome.com/icons/play + PlayCircle = "fa-play-circle" // https://fontawesome.com/icons/play-circle + PlayCircleO = "fa-play-circle-o" // https://fontawesome.com/icons/play-circle-o + Plug = "fa-plug" // https://fontawesome.com/icons/plug + Plus = "fa-plus" // https://fontawesome.com/icons/plus + PlusCircle = "fa-plus-circle" // https://fontawesome.com/icons/plus-circle + PlusSquare = "fa-plus-square" // https://fontawesome.com/icons/plus-square + PlusSquareO = "fa-plus-square-o" // https://fontawesome.com/icons/plus-square-o + PowerOff = "fa-power-off" // https://fontawesome.com/icons/power-off + Print = "fa-print" // https://fontawesome.com/icons/print + ProductHunt = "fa-product-hunt" // https://fontawesome.com/icons/product-hunt + PuzzlePiece = "fa-puzzle-piece" // https://fontawesome.com/icons/puzzle-piece + Qq = "fa-qq" // https://fontawesome.com/icons/qq + Qrcode = "fa-qrcode" // https://fontawesome.com/icons/qrcode + Question = "fa-question" // https://fontawesome.com/icons/question + QuestionCircle = "fa-question-circle" // https://fontawesome.com/icons/question-circle + QuestionCircleO = "fa-question-circle-o" // https://fontawesome.com/icons/question-circle-o + QuoteLeft = "fa-quote-left" // https://fontawesome.com/icons/quote-left + QuoteRight = "fa-quote-right" // https://fontawesome.com/icons/quote-right + Ra = "fa-ra" // https://fontawesome.com/icons/ra + Random = "fa-random" // https://fontawesome.com/icons/random + Rebel = "fa-rebel" // https://fontawesome.com/icons/rebel + Recycle = "fa-recycle" // https://fontawesome.com/icons/recycle + Reddit = "fa-reddit" // https://fontawesome.com/icons/reddit + RedditAlien = "fa-reddit-alien" // https://fontawesome.com/icons/reddit-alien + RedditSquare = "fa-reddit-square" // https://fontawesome.com/icons/reddit-square + Refresh = "fa-refresh" // https://fontawesome.com/icons/refresh + Registered = "fa-registered" // https://fontawesome.com/icons/registered + Remove = "fa-remove" // https://fontawesome.com/icons/remove + Renren = "fa-renren" // https://fontawesome.com/icons/renren + Reorder = "fa-reorder" // https://fontawesome.com/icons/reorder + Repeat = "fa-repeat" // https://fontawesome.com/icons/repeat + Reply = "fa-reply" // https://fontawesome.com/icons/reply + ReplyAll = "fa-reply-all" // https://fontawesome.com/icons/reply-all + Resistance = "fa-resistance" // https://fontawesome.com/icons/resistance + Retweet = "fa-retweet" // https://fontawesome.com/icons/retweet + Rmb = "fa-rmb" // https://fontawesome.com/icons/rmb + Road = "fa-road" // https://fontawesome.com/icons/road + Rocket = "fa-rocket" // https://fontawesome.com/icons/rocket + RotateLeft = "fa-rotate-left" // https://fontawesome.com/icons/rotate-left + RotateRight = "fa-rotate-right" // https://fontawesome.com/icons/rotate-right + Rouble = "fa-rouble" // https://fontawesome.com/icons/rouble + Rss = "fa-rss" // https://fontawesome.com/icons/rss + RssSquare = "fa-rss-square" // https://fontawesome.com/icons/rss-square + Rub = "fa-rub" // https://fontawesome.com/icons/rub + Ruble = "fa-ruble" // https://fontawesome.com/icons/ruble + Rupee = "fa-rupee" // https://fontawesome.com/icons/rupee + Safari = "fa-safari" // https://fontawesome.com/icons/safari + Save = "fa-save" // https://fontawesome.com/icons/save + Scissors = "fa-scissors" // https://fontawesome.com/icons/scissors + Scribd = "fa-scribd" // https://fontawesome.com/icons/scribd + Search = "fa-search" // https://fontawesome.com/icons/search + SearchMinus = "fa-search-minus" // https://fontawesome.com/icons/search-minus + SearchPlus = "fa-search-plus" // https://fontawesome.com/icons/search-plus + Sellsy = "fa-sellsy" // https://fontawesome.com/icons/sellsy + Send = "fa-send" // https://fontawesome.com/icons/send + SendO = "fa-send-o" // https://fontawesome.com/icons/send-o + Server = "fa-server" // https://fontawesome.com/icons/server + Share = "fa-share" // https://fontawesome.com/icons/share + ShareAlt = "fa-share-alt" // https://fontawesome.com/icons/share-alt + ShareAltSquare = "fa-share-alt-square" // https://fontawesome.com/icons/share-alt-square + ShareSquare = "fa-share-square" // https://fontawesome.com/icons/share-square + ShareSquareO = "fa-share-square-o" // https://fontawesome.com/icons/share-square-o + Shekel = "fa-shekel" // https://fontawesome.com/icons/shekel + Sheqel = "fa-sheqel" // https://fontawesome.com/icons/sheqel + Shield = "fa-shield" // https://fontawesome.com/icons/shield + Ship = "fa-ship" // https://fontawesome.com/icons/ship + Shirtsinbulk = "fa-shirtsinbulk" // https://fontawesome.com/icons/shirtsinbulk + ShoppingBag = "fa-shopping-bag" // https://fontawesome.com/icons/shopping-bag + ShoppingBasket = "fa-shopping-basket" // https://fontawesome.com/icons/shopping-basket + ShoppingCart = "fa-shopping-cart" // https://fontawesome.com/icons/shopping-cart + SignIn = "fa-sign-in" // https://fontawesome.com/icons/sign-in + SignLanguage = "fa-sign-language" // https://fontawesome.com/icons/sign-language + SignOut = "fa-sign-out" // https://fontawesome.com/icons/sign-out + Signal = "fa-signal" // https://fontawesome.com/icons/signal + Signing = "fa-signing" // https://fontawesome.com/icons/signing + Simplybuilt = "fa-simplybuilt" // https://fontawesome.com/icons/simplybuilt + Sitemap = "fa-sitemap" // https://fontawesome.com/icons/sitemap + Skyatlas = "fa-skyatlas" // https://fontawesome.com/icons/skyatlas + Skype = "fa-skype" // https://fontawesome.com/icons/skype + Slack = "fa-slack" // https://fontawesome.com/icons/slack + Sliders = "fa-sliders" // https://fontawesome.com/icons/sliders + Slideshare = "fa-slideshare" // https://fontawesome.com/icons/slideshare + SmileO = "fa-smile-o" // https://fontawesome.com/icons/smile-o + Snapchat = "fa-snapchat" // https://fontawesome.com/icons/snapchat + SnapchatGhost = "fa-snapchat-ghost" // https://fontawesome.com/icons/snapchat-ghost + SnapchatSquare = "fa-snapchat-square" // https://fontawesome.com/icons/snapchat-square + SoccerBallO = "fa-soccer-ball-o" // https://fontawesome.com/icons/soccer-ball-o + Sort = "fa-sort" // https://fontawesome.com/icons/sort + SortAlphaAsc = "fa-sort-alpha-asc" // https://fontawesome.com/icons/sort-alpha-asc + SortAlphaDesc = "fa-sort-alpha-desc" // https://fontawesome.com/icons/sort-alpha-desc + SortAmountAsc = "fa-sort-amount-asc" // https://fontawesome.com/icons/sort-amount-asc + SortAmountDesc = "fa-sort-amount-desc" // https://fontawesome.com/icons/sort-amount-desc + SortAsc = "fa-sort-asc" // https://fontawesome.com/icons/sort-asc + SortDesc = "fa-sort-desc" // https://fontawesome.com/icons/sort-desc + SortDown = "fa-sort-down" // https://fontawesome.com/icons/sort-down + SortNumericAsc = "fa-sort-numeric-asc" // https://fontawesome.com/icons/sort-numeric-asc + SortNumericDesc = "fa-sort-numeric-desc" // https://fontawesome.com/icons/sort-numeric-desc + SortUp = "fa-sort-up" // https://fontawesome.com/icons/sort-up + Soundcloud = "fa-soundcloud" // https://fontawesome.com/icons/soundcloud + SpaceShuttle = "fa-space-shuttle" // https://fontawesome.com/icons/space-shuttle + Spinner = "fa-spinner" // https://fontawesome.com/icons/spinner + Spoon = "fa-spoon" // https://fontawesome.com/icons/spoon + Spotify = "fa-spotify" // https://fontawesome.com/icons/spotify + Square = "fa-square" // https://fontawesome.com/icons/square + SquareO = "fa-square-o" // https://fontawesome.com/icons/square-o + StackExchange = "fa-stack-exchange" // https://fontawesome.com/icons/stack-exchange + StackOverflow = "fa-stack-overflow" // https://fontawesome.com/icons/stack-overflow + Star = "fa-star" // https://fontawesome.com/icons/star + StarHalf = "fa-star-half" // https://fontawesome.com/icons/star-half + StarHalfEmpty = "fa-star-half-empty" // https://fontawesome.com/icons/star-half-empty + StarHalfFull = "fa-star-half-full" // https://fontawesome.com/icons/star-half-full + StarHalfO = "fa-star-half-o" // https://fontawesome.com/icons/star-half-o + StarO = "fa-star-o" // https://fontawesome.com/icons/star-o + Steam = "fa-steam" // https://fontawesome.com/icons/steam + SteamSquare = "fa-steam-square" // https://fontawesome.com/icons/steam-square + StepBackward = "fa-step-backward" // https://fontawesome.com/icons/step-backward + StepForward = "fa-step-forward" // https://fontawesome.com/icons/step-forward + Stethoscope = "fa-stethoscope" // https://fontawesome.com/icons/stethoscope + StickyNote = "fa-sticky-note" // https://fontawesome.com/icons/sticky-note + StickyNoteO = "fa-sticky-note-o" // https://fontawesome.com/icons/sticky-note-o + Stop = "fa-stop" // https://fontawesome.com/icons/stop + StopCircle = "fa-stop-circle" // https://fontawesome.com/icons/stop-circle + StopCircleO = "fa-stop-circle-o" // https://fontawesome.com/icons/stop-circle-o + StreetView = "fa-street-view" // https://fontawesome.com/icons/street-view + Strikethrough = "fa-strikethrough" // https://fontawesome.com/icons/strikethrough + Stumbleupon = "fa-stumbleupon" // https://fontawesome.com/icons/stumbleupon + StumbleuponCircle = "fa-stumbleupon-circle" // https://fontawesome.com/icons/stumbleupon-circle + Subscript = "fa-subscript" // https://fontawesome.com/icons/subscript + Subway = "fa-subway" // https://fontawesome.com/icons/subway + Suitcase = "fa-suitcase" // https://fontawesome.com/icons/suitcase + SunO = "fa-sun-o" // https://fontawesome.com/icons/sun-o + Superscript = "fa-superscript" // https://fontawesome.com/icons/superscript + Support = "fa-support" // https://fontawesome.com/icons/support + Table = "fa-table" // https://fontawesome.com/icons/table + Tablet = "fa-tablet" // https://fontawesome.com/icons/tablet + Tachometer = "fa-tachometer" // https://fontawesome.com/icons/tachometer + Tag = "fa-tag" // https://fontawesome.com/icons/tag + Tags = "fa-tags" // https://fontawesome.com/icons/tags + Tasks = "fa-tasks" // https://fontawesome.com/icons/tasks + Taxi = "fa-taxi" // https://fontawesome.com/icons/taxi + Television = "fa-television" // https://fontawesome.com/icons/television + TencentWeibo = "fa-tencent-weibo" // https://fontawesome.com/icons/tencent-weibo + Terminal = "fa-terminal" // https://fontawesome.com/icons/terminal + TextHeight = "fa-text-height" // https://fontawesome.com/icons/text-height + TextWidth = "fa-text-width" // https://fontawesome.com/icons/text-width + Th = "fa-th" // https://fontawesome.com/icons/th + ThLarge = "fa-th-large" // https://fontawesome.com/icons/th-large + ThList = "fa-th-list" // https://fontawesome.com/icons/th-list + Themeisle = "fa-themeisle" // https://fontawesome.com/icons/themeisle + ThumbTack = "fa-thumb-tack" // https://fontawesome.com/icons/thumb-tack + ThumbsDown = "fa-thumbs-down" // https://fontawesome.com/icons/thumbs-down + ThumbsODown = "fa-thumbs-o-down" // https://fontawesome.com/icons/thumbs-o-down + ThumbsOUp = "fa-thumbs-o-up" // https://fontawesome.com/icons/thumbs-o-up + ThumbsUp = "fa-thumbs-up" // https://fontawesome.com/icons/thumbs-up + Ticket = "fa-ticket" // https://fontawesome.com/icons/ticket + Times = "fa-times" // https://fontawesome.com/icons/times + TimesCircle = "fa-times-circle" // https://fontawesome.com/icons/times-circle + TimesCircleO = "fa-times-circle-o" // https://fontawesome.com/icons/times-circle-o + Tint = "fa-tint" // https://fontawesome.com/icons/tint + ToggleDown = "fa-toggle-down" // https://fontawesome.com/icons/toggle-down + ToggleLeft = "fa-toggle-left" // https://fontawesome.com/icons/toggle-left + ToggleOff = "fa-toggle-off" // https://fontawesome.com/icons/toggle-off + ToggleOn = "fa-toggle-on" // https://fontawesome.com/icons/toggle-on + ToggleRight = "fa-toggle-right" // https://fontawesome.com/icons/toggle-right + ToggleUp = "fa-toggle-up" // https://fontawesome.com/icons/toggle-up + Trademark = "fa-trademark" // https://fontawesome.com/icons/trademark + Train = "fa-train" // https://fontawesome.com/icons/train + Transgender = "fa-transgender" // https://fontawesome.com/icons/transgender + TransgenderAlt = "fa-transgender-alt" // https://fontawesome.com/icons/transgender-alt + Trash = "fa-trash" // https://fontawesome.com/icons/trash + TrashO = "fa-trash-o" // https://fontawesome.com/icons/trash-o + Tree = "fa-tree" // https://fontawesome.com/icons/tree + Trello = "fa-trello" // https://fontawesome.com/icons/trello + Tripadvisor = "fa-tripadvisor" // https://fontawesome.com/icons/tripadvisor + Trophy = "fa-trophy" // https://fontawesome.com/icons/trophy + Truck = "fa-truck" // https://fontawesome.com/icons/truck + Try = "fa-try" // https://fontawesome.com/icons/try + Tty = "fa-tty" // https://fontawesome.com/icons/tty + Tumblr = "fa-tumblr" // https://fontawesome.com/icons/tumblr + TumblrSquare = "fa-tumblr-square" // https://fontawesome.com/icons/tumblr-square + TurkishLira = "fa-turkish-lira" // https://fontawesome.com/icons/turkish-lira + Tv = "fa-tv" // https://fontawesome.com/icons/tv + Twitch = "fa-twitch" // https://fontawesome.com/icons/twitch + Twitter = "fa-twitter" // https://fontawesome.com/icons/twitter + TwitterSquare = "fa-twitter-square" // https://fontawesome.com/icons/twitter-square + Umbrella = "fa-umbrella" // https://fontawesome.com/icons/umbrella + Underline = "fa-underline" // https://fontawesome.com/icons/underline + Undo = "fa-undo" // https://fontawesome.com/icons/undo + UniversalAccess = "fa-universal-access" // https://fontawesome.com/icons/universal-access + University = "fa-university" // https://fontawesome.com/icons/university + Unlink = "fa-unlink" // https://fontawesome.com/icons/unlink + Unlock = "fa-unlock" // https://fontawesome.com/icons/unlock + UnlockAlt = "fa-unlock-alt" // https://fontawesome.com/icons/unlock-alt + Unsorted = "fa-unsorted" // https://fontawesome.com/icons/unsorted + Upload = "fa-upload" // https://fontawesome.com/icons/upload + Usb = "fa-usb" // https://fontawesome.com/icons/usb + Usd = "fa-usd" // https://fontawesome.com/icons/usd + User = "fa-user" // https://fontawesome.com/icons/user + UserMd = "fa-user-md" // https://fontawesome.com/icons/user-md + UserPlus = "fa-user-plus" // https://fontawesome.com/icons/user-plus + UserSecret = "fa-user-secret" // https://fontawesome.com/icons/user-secret + UserTimes = "fa-user-times" // https://fontawesome.com/icons/user-times + Users = "fa-users" // https://fontawesome.com/icons/users + Venus = "fa-venus" // https://fontawesome.com/icons/venus + VenusDouble = "fa-venus-double" // https://fontawesome.com/icons/venus-double + VenusMars = "fa-venus-mars" // https://fontawesome.com/icons/venus-mars + Viacoin = "fa-viacoin" // https://fontawesome.com/icons/viacoin + Viadeo = "fa-viadeo" // https://fontawesome.com/icons/viadeo + ViadeoSquare = "fa-viadeo-square" // https://fontawesome.com/icons/viadeo-square + VideoCamera = "fa-video-camera" // https://fontawesome.com/icons/video-camera + Vimeo = "fa-vimeo" // https://fontawesome.com/icons/vimeo + VimeoSquare = "fa-vimeo-square" // https://fontawesome.com/icons/vimeo-square + Vine = "fa-vine" // https://fontawesome.com/icons/vine + Vk = "fa-vk" // https://fontawesome.com/icons/vk + VolumeControlPhone = "fa-volume-control-phone" // https://fontawesome.com/icons/volume-control-phone + VolumeDown = "fa-volume-down" // https://fontawesome.com/icons/volume-down + VolumeOff = "fa-volume-off" // https://fontawesome.com/icons/volume-off + VolumeUp = "fa-volume-up" // https://fontawesome.com/icons/volume-up + Warning = "fa-warning" // https://fontawesome.com/icons/warning + Wechat = "fa-wechat" // https://fontawesome.com/icons/wechat + Weibo = "fa-weibo" // https://fontawesome.com/icons/weibo + Weixin = "fa-weixin" // https://fontawesome.com/icons/weixin + Whatsapp = "fa-whatsapp" // https://fontawesome.com/icons/whatsapp + Wheelchair = "fa-wheelchair" // https://fontawesome.com/icons/wheelchair + WheelchairAlt = "fa-wheelchair-alt" // https://fontawesome.com/icons/wheelchair-alt + Wifi = "fa-wifi" // https://fontawesome.com/icons/wifi + WikipediaW = "fa-wikipedia-w" // https://fontawesome.com/icons/wikipedia-w + Windows = "fa-windows" // https://fontawesome.com/icons/windows + Won = "fa-won" // https://fontawesome.com/icons/won + Wordpress = "fa-wordpress" // https://fontawesome.com/icons/wordpress + Wpbeginner = "fa-wpbeginner" // https://fontawesome.com/icons/wpbeginner + Wpforms = "fa-wpforms" // https://fontawesome.com/icons/wpforms + Wrench = "fa-wrench" // https://fontawesome.com/icons/wrench + Xing = "fa-xing" // https://fontawesome.com/icons/xing + XingSquare = "fa-xing-square" // https://fontawesome.com/icons/xing-square + YCombinator = "fa-y-combinator" // https://fontawesome.com/icons/y-combinator + YCombinatorSquare = "fa-y-combinator-square" // https://fontawesome.com/icons/y-combinator-square + Yahoo = "fa-yahoo" // https://fontawesome.com/icons/yahoo + Yc = "fa-yc" // https://fontawesome.com/icons/yc + YcSquare = "fa-yc-square" // https://fontawesome.com/icons/yc-square + Yelp = "fa-yelp" // https://fontawesome.com/icons/yelp + Yen = "fa-yen" // https://fontawesome.com/icons/yen + Yoast = "fa-yoast" // https://fontawesome.com/icons/yoast + Youtube = "fa-youtube" // https://fontawesome.com/icons/youtube + YoutubePlay = "fa-youtube-play" // https://fontawesome.com/icons/youtube-play + YoutubeSquare = "fa-youtube-square" // https://fontawesome.com/icons/youtube-square + IonIosGearOutline = "ion-ios-gear-outline" // https://fontawesome.com/icons/ion-ios-gear-outline + IonIosCartOutline = "ion-ios-cart-outline" // https://fontawesome.com/icons/ion-ios-cart-outline + IonIosPeopleOutline = "ion-ios-people-outline" // https://fontawesome.com/icons/ion-ios-people-outline +) diff --git a/template/installation/Makefile b/template/installation/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..2134aa7009b9c573e2295f4ff85ec75b1ea4bd19 --- /dev/null +++ b/template/installation/Makefile @@ -0,0 +1,5 @@ +all: + find ./ -name ".DS_Store" -depth -exec rm {} \; + adm combine js --path=./assets/src/js/combine/ --out=./assets/login/dist/all.min.js + adm combine css --path=./assets/src/css/ --out=./assets/login/dist/all.min.css + adm compile asset --path=./assets/login/dist/ --out=./ --pa=login \ No newline at end of file diff --git a/template/installation/assets.go b/template/installation/assets.go new file mode 100644 index 0000000000000000000000000000000000000000..6923506b9fb8db832158e1bdbf92cb8a7018506f --- /dev/null +++ b/template/installation/assets.go @@ -0,0 +1,285 @@ +// Code generated by go-bindata. DO NOT EDIT. +// sources: +// assets/login/dist/all.min.css +// assets/login/dist/all.min.js +// assets/login/dist/respond.min.js +package login + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _assetsLoginDistAllMinCss = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xbd\x6b\xb7\xe3\xb6\xb1\x20\xfa\xfd\xfc\x0a\xc6\x5e\xbd\xdc\xed\x16\xd9\x24\xf5\xda\x92\x56\xfa\xc6\x76\xe2\x19\xdf\x39\x19\xdf\x35\x27\x9e\x49\xc6\xf1\x9d\x05\x91\x90\x44\x37\x45\x32\x24\xb5\x1f\xd6\x68\x7e\xfb\x2c\xe2\x41\xe2\x51\x00\x49\xed\xdd\xaf\x64\x9f\x3e\x49\xb4\x51\x85\x42\xa1\xaa\x50\x05\x14\xf1\xf8\xc3\x2e\xcf\x6a\x77\x87\x22\x7c\x66\xbf\x8e\x49\xfa\xb0\xce\x0b\x9c\x39\x15\xca\xaa\x0d\x29\xad\xea\x87\x14\xaf\xb3\xbc\x3c\xa2\x94\x96\xdc\xe1\x64\x7f\xa8\xd7\x53\xdf\xdf\x54\x65\xb4\x4e\xf3\x08\xa5\x2f\xbf\xfa\xb1\xa9\xf6\x1f\x28\xab\x9c\x7f\x6f\xc0\x5f\xbd\x9a\x08\x80\xa6\xdc\xe5\xe5\xa7\x32\x7d\x79\xa8\xeb\xa2\x5a\xbf\x79\xd3\x10\xac\xbc\x7d\x55\xa3\x3a\x89\xbc\x28\x3f\xbe\xa9\xde\x34\x0c\x34\xed\xbf\xb9\x0d\x96\x6f\x8e\xf8\x38\xff\x1b\xfa\x4f\x55\x10\x2e\xfe\x9c\xfc\xcf\xe2\xdb\x6f\xdc\x9f\xfe\xeb\xff\x2a\x6f\x7e\xfc\xab\x7b\x28\xfe\xcb\x7f\xf9\x8f\xbf\xfc\x3a\xff\xff\xfe\x87\x77\x97\xef\x76\xe1\xab\x5d\xc3\x63\xfd\xf2\x2b\xf2\xd7\x57\xaf\x36\xa7\x2c\x89\xf2\x18\xbb\x25\xca\xf6\x78\xfd\xd3\x6b\x7f\xb6\xf0\x5d\x7f\x1e\x7e\x3f\xf9\xe9\x75\xf0\xdd\x8d\xef\x06\xdf\xdd\xdc\x4c\x7e\x7a\x1d\xfa\xdf\xce\x9a\xff\xf9\xe3\x9f\x7c\x37\xfc\xe3\xf7\x0d\xf8\x9b\xc5\xcc\x77\xbf\x59\xac\x9a\xdf\xdf\xff\x29\xfc\x93\xfb\xfd\x9f\xc2\xef\x2f\x9f\xb5\xc0\xfe\xfb\xe9\x0a\x81\xf9\xbe\xeb\xcf\xe6\x8d\x14\xfc\xd9\xaa\xf9\xbd\x0a\xc8\xef\x6f\x9b\xdf\xdf\x36\xbf\xc3\x20\x58\x7c\xde\x82\xf9\xeb\x78\xc1\x04\xdf\xfb\xbe\x1b\x7c\xff\xfd\x67\x6e\x12\x3f\xe1\xf1\x26\x31\x5d\xfa\xae\x3f\xfd\xdc\x7b\xfe\xd7\x2b\x7a\x1e\xf8\xa1\xeb\x07\xfe\xb4\x19\x00\x41\xe0\xbb\x7e\x10\x34\x03\x20\xf8\xd3\x37\xbe\x1b\xfc\xe9\xfb\x15\xf1\x24\xdf\x7c\xfb\x99\x0b\xe6\xc7\x6b\x04\xe3\xbb\x7e\x38\x23\x5e\x22\x9c\xaf\x88\x4c\x7c\x22\x93\xef\x89\x4c\x42\x9f\x8a\xc6\x77\x1b\xf9\xd0\xdf\x7f\x74\x43\xff\xbb\xef\xa9\xff\x68\x24\x1a\x7e\xb7\xf0\xdd\xf0\xbb\x25\x71\xbe\xcb\xd0\x77\xbf\x59\x7e\xee\x36\xf6\x13\xf7\x2b\x77\x43\x05\xe9\x37\x82\xf4\x89\xd4\xfc\x60\x4a\x3c\x6d\x30\x6f\x8c\x6e\x4e\x8c\x2e\xfc\xf6\x5b\xd7\x0f\xbf\xfd\x8e\xfc\xfe\x6e\x41\xfe\xe7\x8f\xdf\xd0\xff\xf9\x8e\x88\xd5\x6f\x44\xbc\xa0\x52\x5f\xce\xa8\xa4\x09\x24\x08\x43\xf2\x3f\x2b\xea\xb2\x57\x44\xe4\x61\x10\xd2\xff\x99\x93\x28\x47\xda\xfd\xfe\xfb\xef\xff\xf8\x58\xb1\xcf\x4c\x62\xff\x6f\x78\x7f\x4a\x51\x09\x08\xbe\x83\x8c\x15\xfd\x8d\x22\xfa\xef\xff\xc7\xff\xeb\x6f\x77\x37\xc5\xbb\x6f\x8a\x05\xfa\x4c\x26\x06\x1f\x57\x60\x3f\xfd\xcf\x2b\x04\xf6\x61\x26\x06\x1f\xd9\x92\xc6\x0b\xe6\xc9\x26\x06\x1f\xb7\xe7\xff\xbd\x18\x6f\x12\x4f\x35\x31\xf8\xc8\x3a\xbf\xa2\xe7\x1f\x68\x62\xf0\x91\x05\x33\xbf\x46\x30\x9f\xe8\xc4\xe0\x23\x8f\x2e\xee\x57\xf6\xff\x62\x13\x83\xa5\x49\xec\xdf\xe6\x69\x0c\xc8\x9c\x15\x3f\x7a\x36\xb6\x2c\xf7\x9f\x63\xbe\xe0\x63\x8a\xeb\x13\xce\x16\x7c\x54\x2b\xfa\x88\xb9\x82\x8f\xd9\xef\x8f\x99\x29\xf8\xa8\xfa\xfe\x74\xf3\x04\x1f\x55\x2c\xff\x4c\x59\x82\x8f\x3a\xae\x3e\xdb\x1c\xc1\x9b\xaf\x7f\xf7\xb5\xf3\x6d\x9e\xd7\x55\x5d\xa2\xc2\xb9\x9d\x7a\x53\x6f\xe9\x10\x89\xac\xdf\xbc\xd9\xe3\x7a\xcb\x61\x8d\x3c\x5e\xfd\xdb\xd7\xce\x77\x79\xf1\x50\x36\x12\x77\x42\x3f\x08\xdc\xd0\x0f\x16\xce\x5f\xee\x92\xba\xc6\xe5\xc4\xf9\x21\x8b\xbc\x7f\xfb\xda\xf9\xf7\x24\xc2\x59\x85\x63\xe7\x94\xc5\xb8\x74\xfe\xfc\xc3\x5f\x9c\x56\xc8\xfb\xa4\x3e\x9c\xb6\x44\xba\xf5\xdd\xb6\x7a\xd3\x36\xf0\x66\x9b\xe6\xdb\x37\x47\x54\xd5\xb8\x7c\xf3\xef\x3f\x7c\xf7\xa7\xff\xfa\x1f\x7f\x7a\xf5\xf5\x9b\x37\x5f\xff\x8e\x6a\x3b\xf9\x0d\x7b\x51\x55\x35\x3c\xfa\xde\xd4\xf9\xdf\x84\x2c\x6b\xc9\xf9\xdf\x8e\x40\x37\xc3\x51\x9e\xa2\xea\x8d\x54\xef\xeb\x37\x87\xfa\x98\x4a\xa6\xd5\xe8\xd5\xad\x70\x99\xec\x36\xee\x1d\xde\xbe\x4b\x6a\xb7\xc6\xf7\xb5\x5b\x25\xbf\x61\x17\xc5\xbf\x9e\xaa\x7a\x1d\xf8\xfe\x8b\x8d\x7b\xac\x60\xc8\x65\x9b\xc7\x0f\xe7\x23\x2a\xf7\x49\xb6\xf6\x2f\xa8\xac\x93\x28\xc5\x13\x54\x25\x31\x9e\xc4\xb8\x46\x49\x5a\x4d\x76\xc9\x3e\x42\x45\x9d\xe4\x59\xf3\xf3\x54\xe2\xc9\x2e\xcf\x1b\x71\x1d\x30\x8a\x9b\xff\xd9\x97\xf9\xa9\x98\x1c\x51\x92\x4d\x8e\x38\x3b\x4d\x32\x74\x3b\xa9\x70\x44\x6a\x54\xa7\xe3\x11\x95\x0f\xe7\x38\xa9\x8a\x14\x3d\xac\xb7\x69\x1e\xbd\xbb\xa0\x53\x9c\xe4\x93\x08\x65\xb7\xa8\x9a\x14\x65\xbe\x2f\x71\x55\x4d\x6e\x93\x18\xe7\x2d\x66\x92\xa5\x49\x86\x5d\x52\x61\x73\x8b\x1b\xd6\x50\xea\xa2\x34\xd9\x67\xeb\x2d\xaa\x70\x03\xa5\x84\xd6\x59\x5e\xbf\xfc\x39\xca\xb3\xba\xcc\xd3\xea\x97\x57\x2d\x89\x2c\xcf\xf0\xe6\x40\xc7\x97\x7f\xf9\xf9\x90\xc4\x31\xce\x7e\x99\xd4\xf8\x58\xa4\xa8\xc6\x12\xde\x05\x9d\xb7\x28\x7a\xd7\xf4\x25\x8b\xdd\x28\x4f\xf3\x72\x5d\x97\x28\xab\x0a\x54\xe2\xac\xbe\xa0\x35\x8a\xea\xe4\x16\x4f\xd0\xfa\x90\xdf\xe2\xf2\x9c\x9f\xea\x86\x85\x46\x6c\xdb\x6d\xf9\x73\x9d\xd4\x29\xfe\xe5\xbc\xcd\xcb\x18\x97\xee\x36\xaf\xeb\xfc\xb8\x0e\x8a\x7b\x27\xce\xeb\x1a\xc7\x97\xed\xa4\xaa\xcb\x3c\xdb\x9f\x95\x41\x7f\x89\x77\xd9\x59\x70\x0d\x49\x8d\xd2\x24\xba\x1c\x02\xae\x16\x6f\xb1\xc4\x47\xc7\x67\xee\x23\xf9\x0d\xaf\x43\x7c\xbc\x1c\x51\xf9\xee\x4c\xb9\xfc\xd2\xf7\xfd\x4d\xc7\xfb\xfa\xcb\xdd\xce\xbf\x54\x47\x94\x32\x6b\x21\x75\x6e\xfc\x17\x97\xea\xb4\x9d\x54\xa7\xe2\x5c\xe4\x55\xd2\x28\x67\x5d\xe2\x14\x35\x7d\x12\x68\x2f\xe7\x2f\x36\x44\xee\x5c\x6c\x46\xd1\x37\x94\xea\xbc\x58\xbb\xde\x1c\x1f\x1b\xda\x67\xd6\x69\xd7\x0b\x9b\x92\xe4\xb8\x67\xd2\x58\xfb\x97\xea\x76\x4f\xb4\xb4\x2e\xf3\xbc\x7e\x75\x6e\x04\xb8\x4b\xf3\xbb\x35\x55\xc9\x85\xda\x15\xef\x71\x80\x8f\xce\xcc\x2f\xee\x2f\x87\xf2\xdc\xb2\xc1\x2d\x7c\x9b\xdf\x37\x9c\x26\xd9\x7e\xdd\x68\x1c\x67\xa4\x68\xe3\x1e\xf3\xdf\x4c\x30\xb8\xf8\x52\x94\xb8\x63\x04\x9d\xea\xfc\xd2\xf8\xb8\xc9\xbb\x6d\x3c\x29\x4a\x3c\xa9\xd0\xb1\x90\x86\xdb\x31\xcf\xf2\xaa\x40\x11\x9e\xb4\xbf\x04\xc1\x05\xf8\x78\xd9\x9e\xea\x3a\xcf\x26\x49\x56\x9c\xea\x49\x5e\xd4\x74\x60\x54\x38\xc5\x51\x3d\x69\x06\x20\x2a\x31\x6a\x87\x1b\xa9\xbc\x4e\xb2\x03\x2e\x93\x7a\x43\x75\xc9\xfe\x62\x94\x3a\xf6\x6e\x93\x2a\xd9\xa6\x98\xb7\x40\x49\x9e\xc9\x98\x26\x46\xda\x38\x6c\x6a\xc6\x0c\xa3\x71\x16\x0e\x61\xe4\xe7\xfa\xa1\xc0\xbf\xa7\xc5\xbf\x4c\x84\xa2\x12\x57\xb8\x96\x4a\xaa\xd3\xf6\x98\xd4\xbf\x9c\xb9\xac\x51\x51\x60\x54\xa2\x2c\xc2\x6b\x5a\x7f\x13\x9d\xca\x2a\x2f\xd7\x45\x9e\x64\x35\x2e\x59\x63\x3f\xc7\x49\x85\xb6\x29\x8e\x7f\x11\x9b\x6d\x0b\xcf\xac\x52\x8c\x77\xe8\x94\xf2\xbe\xad\xd7\x44\x65\xbb\x3c\x3a\x55\x6e\x92\x65\xb8\xa4\x9c\xe8\xe5\xe7\x02\xc5\x71\xa3\x3c\x7f\xd3\xda\x13\x41\x3d\x8b\x86\x4a\x9d\xe5\x45\xe8\x4d\x74\xc0\xd1\xbb\x6d\x7e\x2f\x77\x1a\xc5\x49\xde\xf5\x50\x30\x8d\x76\xe4\xea\xc6\x24\x80\xe0\xd2\x96\x43\xb1\xfd\xec\x74\xdc\xe2\xf2\x97\xf5\x9a\x37\x46\x7a\xe3\x56\x45\x92\xb9\xa2\xa5\x18\xb0\xf3\x53\x2d\x63\xf3\xb1\x40\x4c\x55\xd4\x1a\x46\x65\x74\x00\xfb\xf4\xb8\x11\xb2\x01\xec\xa0\x31\xb9\x5d\x82\xd3\x18\xe0\xa0\xe3\x9d\x16\xb8\x51\x53\x25\x05\x3a\x6b\xaa\x10\xe3\x28\x2f\x51\xe3\x9b\x20\x1b\x24\xf6\x4d\x1a\xaf\x70\xdd\x5a\x85\x37\x9d\xe3\xa3\xe3\x2d\x42\xf2\x3f\xcb\x39\x3e\x6e\xf8\x08\x73\xc2\xe2\x9e\xdb\x4c\xe3\x8a\xab\x3c\x4d\x62\xa7\x4a\xd2\x5b\x5c\x5e\x52\xbc\xc7\x59\x0c\x19\x57\x3b\x52\x65\xef\xc0\x07\xb4\xe6\xc1\xeb\xc6\xce\xb9\xe7\x6f\xfc\x82\x48\xaf\x09\x25\x29\x2a\x2a\xbc\xe6\x3f\x2e\x75\x3c\xa9\x0f\x5d\xc3\xcd\x24\xe6\x3f\xf2\x53\x19\xe1\xb5\x03\xcc\x32\x0e\xf3\x6d\x41\x62\xff\xdc\xdd\xe6\x49\x8a\x4b\x12\xbb\xa4\xd9\x46\x55\x46\x6f\xa2\xaa\x7a\xd3\x84\x60\x3a\x57\xf8\xc3\x11\xc7\x09\x72\x8a\x32\xc9\xea\xf3\xd7\x93\x35\xda\x35\x01\x7b\xbd\xc5\xbb\xbc\xc4\x42\xdc\xf8\x5d\x72\x2c\xf2\xb2\x46\x59\xbd\xa1\x13\x84\x03\x8a\xf3\x3b\x22\x69\x01\x24\x04\x17\xdf\x11\xeb\x48\x26\x07\x57\x35\x41\x2e\x68\x82\x88\x5b\xab\x71\x4c\x1d\x59\xa7\xfc\x35\x99\x77\xd1\x00\xff\xf3\xa1\xc4\xbb\x5f\x68\x07\xce\xcc\x38\xd7\x5f\x38\x2f\xbf\x70\x50\x5d\x97\x2f\x1b\xe8\xab\x2f\x5e\x7d\x21\x06\x61\x23\x32\x01\x53\x6c\x42\xf6\xff\xff\xfd\x17\xbf\xa2\x5b\x54\x45\x65\x52\xd4\xeb\x2f\x58\xc5\x49\x0b\xfc\xf2\x0b\x8d\xd6\x17\x17\x32\x21\xf9\xc7\x29\xaf\x71\x13\x26\xce\x9a\x79\x7d\xb9\x5a\xad\x36\x05\xda\x63\x77\x5b\x62\xf4\xce\x4d\xb2\x66\x36\xb5\x46\xb7\x79\x12\x5f\xea\x66\xce\xd4\xce\x3b\x88\xe1\xb8\x74\x1a\xe5\x12\xdb\x6a\xa2\xe6\xa4\x6e\x5c\x1e\x5c\xbf\x09\xaa\x47\x74\xef\xde\x25\x71\x7d\x20\x53\x38\x41\xa2\x87\x70\x72\x98\x4e\x8a\x73\x5e\x16\x07\x94\x55\xeb\xe9\xe6\x2e\x89\xf3\xbb\x6a\x3d\xa5\x20\x91\x2a\xe9\x16\x23\xea\x65\xe8\x76\x8b\x4a\x79\x3a\xe4\x6d\xeb\xec\xad\x17\xa1\x12\xd7\x13\x2f\x2e\xf3\xe2\x54\xbc\x15\xca\xb8\xb9\xd7\x79\xe1\x42\xe6\x74\xf1\x52\xb4\xc5\x29\x20\x1e\xdf\xf7\x2f\x9e\x34\x64\xb4\x11\x22\x92\x21\x98\x4e\x1d\x4f\xf8\xaf\x83\x3e\x4f\xfb\x72\xb7\xdb\x69\x75\x5c\x4a\x1d\xc7\x5d\x65\xa1\xe8\x00\x70\x16\xc7\xb1\x40\xc5\xb4\xa0\xdb\xa7\x0f\xc5\x21\x89\xf2\xac\x72\x0e\x28\xdd\xa5\x49\xb6\xaf\xc8\xca\x8d\x2f\xc5\xd6\x6f\xde\x3c\xa0\xac\x3a\x24\x5e\x75\x8a\x50\x72\x38\xe5\x64\x1c\x1f\xf3\x18\xa5\xd5\x9b\x70\xf1\x26\x5c\x2c\x67\x6f\x62\x7c\xcc\xe9\x8a\xed\x4d\x47\xd1\x6d\x29\xba\x25\xcd\xf4\x7a\x38\xaf\x5f\xbd\x5f\xf2\xff\x4f\xbb\xdc\xc3\xc7\x2d\x8e\x63\x1c\xbb\xcd\xaa\xb1\x71\xd4\xc2\x02\xf3\x69\x9b\x05\x97\x99\xef\xb1\x2d\xa9\xa9\xf7\xd6\x52\x5d\x77\x0d\xd5\xe5\x09\xbf\x57\x11\x56\xb7\xfb\xb6\xb1\xea\x76\xff\xd5\xab\x8b\xd7\xe2\x03\x53\xfc\x66\xaa\x1e\x14\xf7\x1b\x70\x79\xd5\x6b\xdf\xfd\x1f\x32\xc4\xb9\x58\xd0\x06\x06\x5a\xef\x98\xe7\xf5\xa1\x89\x75\x28\xab\x13\x94\x26\xa8\xc2\x31\x9d\x8f\xe4\xd5\xbd\x8a\xb3\x2f\xd1\x43\x15\xa1\x14\x0b\xfd\x71\x49\x9c\x4b\xaa\x77\x5d\x04\x63\xfe\xf8\xef\xbe\x1f\xa2\x2f\x44\xd4\x22\x3d\x55\x20\xda\x56\x42\xc3\xa7\x92\x61\x4d\xe4\xd2\x5c\xaf\x1c\xfa\x28\x92\x2a\x1f\x93\x0c\x6a\x24\x0c\x83\x50\xc2\x8b\xd2\xfc\x14\x03\x78\x0b\x3f\x90\x99\xc9\x6e\x71\x9a\x17\x18\x40\x5d\xfa\x2b\xb9\x7b\x38\x8b\x92\x14\x44\xdc\x49\x88\xfb\x14\x55\x00\x8f\xd8\x57\xda\x3e\x9e\xaa\x24\x02\xf1\xe4\xbe\xd0\x29\x1a\x88\x38\x95\x10\x0f\x18\x95\x35\x88\x37\x97\x09\xd6\xa8\x04\xd1\x16\x1a\x9a\x8b\x8f\x45\xfd\x00\x22\x2f\x25\xe4\x53\x85\x61\x9a\x37\x12\xda\x2e\x49\x8f\x20\x9a\x2c\xeb\xfa\xe0\xa6\xa8\xdc\x03\x6a\xc1\x7e\xe0\x2b\xa8\x20\x52\xa0\xd1\x4b\x2a\x50\x36\x8a\xe1\xe4\x80\xa5\x63\x3f\x90\x05\x5d\xe2\x63\x7e\x0b\x33\x37\x93\x10\x7f\xcb\xf3\xa3\x9b\x64\x20\xe6\x5c\xc7\xcc\x4f\x30\x8b\xb2\x5e\xf2\xdd\x0e\xc4\x92\x15\x52\x25\xfb\x0c\x01\xe6\x8a\xfd\x40\x56\x49\x94\xef\x41\x2c\x45\x23\x25\xaa\x40\x49\x87\xb2\x3a\x0e\xf9\x11\x14\x4c\x18\xa8\x76\x00\xa3\xc9\xda\xa8\x13\x03\x35\x45\x1f\x39\x02\x06\x3b\xf6\x43\x59\x1b\x71\x7e\x97\xa5\x39\x8a\x5d\x94\x82\x72\x0e\xe7\x20\x3a\x88\x2a\xab\xe4\x54\x18\x11\x65\xad\x24\xd9\x36\xbf\x07\xf1\x6e\x14\x5f\x8a\x1e\xdc\x28\x29\x23\x83\x98\x56\x8a\x3d\x16\x18\x81\x5d\x9a\xfa\x0a\xe2\xae\xc4\xb0\x1e\xa7\xb2\x82\x9a\xe1\x62\x92\xd3\x54\x56\x52\x13\xc8\x40\x34\x59\x49\xbb\x14\x81\x86\x36\x9d\xa9\x4e\x2c\x2e\x0e\x79\x86\x41\x17\x3a\x95\x55\x74\x9b\xa7\xa7\x23\x36\x8d\x88\xe9\x02\x42\x6e\xd4\x0a\x62\x2f\x21\xec\x53\x01\xe2\xca\xda\xfa\x47\x19\xe5\x31\xa8\xa8\xa9\xac\xa8\x2d\x32\x62\xce\x14\xb7\x06\x0b\x6b\x16\xa8\x58\xa0\x98\x66\xb2\x86\xb6\x39\xec\xd6\x66\x53\x0d\xed\x88\x4a\x18\x55\xd6\x12\x59\xdf\x82\x78\xb2\x82\x22\x74\xc4\x25\x02\x11\x65\xe5\x90\x94\x1c\x84\xb6\x54\x58\x4c\xc1\x61\x36\x93\x15\x42\x73\xb9\x20\xa2\xe2\xd6\x9a\xf5\x2f\x9b\x3c\x01\xd8\x73\x5f\xc7\xa6\x2b\x40\x08\x59\xd6\x0d\xc9\xda\xba\x29\xde\xc1\x94\x43\x00\x39\xc2\x59\x0d\x87\xd1\xf9\x14\x40\x2f\x8d\x6c\xcf\x00\xec\x5f\x4f\x55\x9d\xec\xc0\x58\x3e\x9f\x6b\x63\x1f\x44\x5b\x28\xbe\x2c\xc6\x59\x6d\xee\xa1\xea\xf9\x08\xb6\x99\x67\x65\xa2\x80\x22\xdc\x78\x7f\x97\x7c\x9b\x00\x2b\x28\xd3\xb3\x24\xaa\x4f\x25\x38\xb4\x16\xb2\x16\x8f\xa8\x70\x1b\x33\x87\x25\xbd\x50\x14\x43\xbf\xd9\x40\x88\x53\x25\x54\xc1\x06\xbc\x90\x75\x81\xe3\x04\x46\x53\xa6\x68\x07\x64\xe8\x8b\xac\x03\x92\x6a\x05\xf1\x64\xe9\x9b\xe6\x2b\x8b\x1b\x65\xca\x87\x0b\xb7\x59\xe5\xdf\xa1\x12\x1c\x67\x8b\x95\xa2\xa5\xaa\xb6\xe2\x2f\x7d\xc5\xff\x59\x50\x03\x2d\x02\x82\x68\xb2\x7e\x0a\x74\xaa\xc0\x9e\x2d\xa7\x4a\xcf\x72\xd0\x93\x2f\x67\x8a\x1b\x2a\x8d\xfc\xcd\xf5\xae\xdb\xd0\xd5\xc9\x34\x2e\xac\xe8\xb2\xbe\xf0\xaf\x38\x02\xed\x64\x79\xa3\xea\xff\xb6\xcc\xcd\x6e\x66\xb9\x02\xd1\x8d\xa3\xf0\xc6\xd7\x96\x74\x64\x26\x09\xe2\x06\xfa\xd2\xcc\x8c\x1c\x02\x33\x68\x33\xf6\x54\x99\x94\x9b\x31\x65\xfd\xfd\xe3\x84\xab\x66\xf9\x6d\xc6\x9f\x2b\x5e\x69\x97\x9b\x71\x15\x15\x46\x25\xc6\x59\x75\xc8\x61\xc9\x2d\xa1\x0e\x9a\xa7\x70\x37\x37\x6a\x17\x2d\xb8\xea\x2c\x22\xb3\x20\xaf\x64\x15\xa2\xb2\xcc\xef\x8c\xf6\xb1\x0a\x00\x64\xa3\x75\xac\x42\x00\x1b\x9e\x21\xad\xa6\x00\xaa\x69\xea\xb5\x9a\xe9\xce\xcf\x34\xf9\x5c\xcd\x15\x39\x93\x4f\xeb\xbb\x53\x0a\xae\x75\x56\x0b\x08\x9b\x7c\xa3\x05\xd1\x95\x51\x78\x1f\xa5\xe8\x88\x6c\x06\x15\x28\x8b\xfa\x7d\x02\x0a\x3a\x50\xd6\xf4\x29\x46\xd0\x94\x35\x50\x56\xf4\xbb\x04\x8c\x02\x81\xaf\x04\x95\x07\x4c\x32\x87\x20\xea\x5c\x43\x8d\xd2\x1c\xf4\x99\x81\x92\x00\xb8\x43\x65\x96\x64\x7b\x73\xd7\x97\xaa\xc7\xce\x60\xb2\x8a\xcf\x42\x29\xce\x62\x30\x05\x11\x28\x79\x80\x12\x65\x71\x0e\x25\x0c\x02\x25\x0b\x10\xe5\xc7\x23\x06\x03\x70\xa0\xa4\x02\x8e\x68\x9f\x61\x18\x31\x04\x7d\x25\x68\xdf\x81\x92\x11\xe0\xc8\x06\x0b\x0f\x94\xbc\x40\x89\xeb\x3b\x6c\xe0\x42\x9d\x08\xe4\x45\xd1\x28\x21\x82\x73\x3b\x41\xa0\xce\xa3\x53\x92\xd9\x37\xa9\x58\xc9\x12\x30\x74\x93\xf1\x28\xa9\x02\x36\x7c\xf8\xc6\x04\xb0\x86\xba\x32\x25\x35\x0e\x79\x99\xfc\x96\x67\x35\x5c\x47\x4d\x21\xc4\x50\x84\x0c\x94\x0c\xc2\xf6\x94\xa6\x87\xbc\x04\xd9\x56\xb2\x08\x5b\x0c\x8e\xf6\x40\xc9\x22\x44\x4d\xb7\x76\x49\x84\x6a\x50\x72\x4a\x32\xa1\x3e\x9c\x8e\xdb\xca\x60\x1d\x4a\x26\x81\xe1\x9a\x8c\x43\x49\x26\x1c\x50\x16\x1b\x7d\x70\xa0\x24\x14\x08\xb2\xc1\xbb\x07\x4a\x52\x81\xe0\x1a\x18\x5e\xe9\x98\x26\x76\x95\x9c\x02\x8d\x44\x3d\xa1\x23\x50\xd2\x0b\x52\x25\x13\xfb\x4a\x9e\x41\xaa\x03\x77\x43\x49\x39\x48\x35\x8c\xdd\x91\xf5\xba\x4f\xf3\x2d\xa8\x7f\x25\xf5\x70\x57\xe2\x0c\xcc\xca\x06\x4a\xda\xa1\x46\xd5\x3b\x68\x91\x1e\x28\x09\x87\x5d\x92\xc2\x8b\xbf\x40\xc9\x36\x6c\xcb\x04\xef\x22\x04\x8f\x6f\x25\xe1\xd0\xc4\x45\x3a\x6f\x81\x90\x95\x9c\x43\x8c\xaa\xc3\x36\x87\x27\xa8\x81\x92\x79\x28\x50\x81\xcb\x28\x4d\x40\x35\x28\xe9\x07\x92\x97\x36\x66\x92\x03\x25\x0b\x91\x26\x19\xb4\xa2\x09\xd4\x0c\xc4\x21\x87\xa3\x8d\x92\x81\x28\x4e\xd5\xa1\x00\x53\xb0\x81\x92\x82\x38\x55\x70\xc7\x65\xe9\xef\xb7\x70\x97\x65\xb9\x57\x39\xec\xad\x95\x84\x42\x83\xe6\x6e\x1f\x5c\x94\x16\x07\xb4\x85\x03\x82\x92\x56\x50\xab\x18\xe6\x49\x81\x92\x60\xe0\xd5\xe8\xb7\x57\x08\x7f\x6a\xc6\x37\xb6\x31\x83\x59\xab\xeb\x32\xd9\x9e\x6a\x30\x85\x17\x28\xc9\x06\xbd\x92\xb1\x35\x45\x5d\x19\x59\xfc\x62\x50\x69\x73\x75\x22\x57\xa0\x0c\x46\x54\x93\xe1\xf4\x43\xb8\xd1\x5b\x28\x59\x87\x16\x1f\xf6\x47\x4a\xe6\x21\xcd\xf7\xf0\xd7\x80\x60\x11\xa8\xb9\x52\x30\x4b\x1b\x2c\xd4\xd4\xeb\xde\xf0\xd1\x20\x50\xd2\x13\x19\xbe\x73\xef\x92\x2c\xce\xef\x40\x64\x75\x7a\x12\xe5\xb0\x17\x50\xd3\x14\x08\x4c\x2b\x04\x4a\x96\xc2\x34\xbd\x50\x92\x14\x0d\x35\xb8\x55\x25\xbb\x47\xb6\x0a\x80\x88\x2b\x55\xed\x06\x44\x25\x2f\x51\x61\xd8\x3a\x96\xaa\x5a\xf2\xa2\x78\x70\x63\xf0\x7b\x28\x0e\x94\xd4\x04\xc3\x36\xf6\x6a\xa9\xe6\xc7\x09\xba\xf1\xdb\x52\xa0\xa6\x2a\x3a\xf2\x20\xf6\x1c\xc2\x36\x69\x42\xc9\x56\x44\x25\x8e\x93\xba\x99\x73\xc2\x9c\xcb\x7a\xa3\x1b\x21\x61\xb7\xa2\xe6\x2b\x4e\x75\x8a\x4b\x30\x0c\x28\xa9\x0a\xba\x39\x07\x42\xbc\xd1\xa6\xfe\x45\x89\xab\x0a\x16\xb2\x92\xa4\xc0\xa8\x34\x06\x0e\x25\x45\x41\xf0\x4c\xbe\x48\x49\x50\xd4\xf9\x9d\x81\x57\xc5\x43\xd6\xa8\x06\x9d\xa2\x92\x96\xa8\x62\x63\xde\x33\x50\xb2\x12\x07\x1b\xaa\x32\xbe\x4e\x5b\xb2\x11\x0b\xe6\x40\xc9\x04\x92\x5d\x3e\x55\x8d\x4b\x03\x69\x35\xde\x9d\xc8\x8c\x31\xdd\x82\xba\x5d\xa9\x61\xaf\xc1\x9e\xbb\x01\x88\xab\xc6\xbb\x06\x77\x61\xc0\x55\x83\x5c\x83\xbb\x34\xe0\x2a\x73\x43\x7e\x1c\xc1\x35\x7c\xf2\x08\x56\xaa\x53\xdc\x27\x55\x4d\xf7\xc9\x99\xeb\x28\x9f\x3f\xd2\xfc\x14\xdb\x3e\x24\x06\x4a\xc6\x81\x56\x30\x7e\x4e\x0c\x56\x37\xca\xc8\xc3\xd8\x8d\xf2\x2c\x31\x8c\xbe\x95\xfa\x11\x17\x63\x37\xc6\x51\x12\x9f\x72\x68\x1b\x05\x0e\x7d\x65\x6c\x41\x4c\x84\x4a\xca\xa3\xf1\x40\xa6\x0f\xba\xa1\x92\xf7\x68\xfc\x8f\x19\x57\x99\x08\xe2\x5b\x9c\xc2\x81\x35\x54\x12\x20\x8d\x32\x41\x34\x65\x2e\x88\x2a\x70\x6d\x17\x2a\x89\x0f\x94\x62\x30\x6c\x84\x4a\x7a\x02\xff\xe3\x44\x8e\x89\x40\xb2\x0f\x95\x0c\xc5\x3b\xb2\x73\x19\x40\x0b\xd4\x04\x26\xe8\xa1\xd5\x0d\x2e\x05\x02\xe7\x27\xa1\x92\x97\xd8\x26\xd5\x01\x4c\x7c\x87\x4a\x46\xe2\x5d\x66\x58\xb9\x85\x4a\x42\x62\x8b\xb6\x0f\xee\x2e\x2f\x8f\xa7\x14\xfa\xae\x17\x2a\xf9\x88\x1a\xcc\xca\x84\x8b\x9d\xbc\x77\x68\x9b\xa2\xe8\x9d\x69\xed\x11\x2a\x69\x88\x2d\xe8\xea\x43\x25\xf5\x80\x8a\x02\x32\xb3\xdd\xcd\x4e\xde\xae\x83\x4b\x78\x29\x15\x2a\x09\x87\x43\x7e\x2a\x0d\x5b\x7b\xc2\x69\x20\xef\x71\x4a\xd1\x11\x14\xba\x92\x71\x88\x4f\x45\x6a\xca\x37\x84\x4a\xbe\xa1\x48\xf6\xfb\x07\x77\x8b\xc0\xc5\x51\xa8\x24\x1c\xaa\x28\xa9\xaa\xbc\x04\x87\xb8\x92\x6d\xd8\x26\x75\x94\x83\x93\xd2\x50\x49\x35\x6c\x6b\xe8\x8b\xaa\x8a\x75\xbf\x05\xad\x48\xc1\x7a\x80\x8c\xdc\xf7\x91\xdc\x8d\x5f\xa1\x51\xad\x61\x95\xa7\x2d\xa4\xe8\xd0\xdf\xc6\x2a\xde\x00\x2c\xb2\x03\x0e\xea\x81\x92\xf6\x48\x22\xec\xa6\x79\x9a\x82\x7e\x47\xc9\x76\xb4\xb8\x6e\xdd\x78\x20\xd0\x7a\x95\x64\x07\x8e\x4f\x11\xdd\x92\x0d\xe1\x2a\x9f\x47\xc8\x19\x31\x7b\x92\x2d\x54\xd2\x1c\xac\x8e\x25\x95\x17\x2a\x09\x8f\x23\xce\x4e\xee\x01\x1d\xb7\xa7\x72\x0f\x7b\x3c\x25\xf1\x41\xb6\x57\x9a\x17\x1d\xa1\x92\xff\xc8\xa1\xcd\x75\x38\x54\x92\x1f\xfb\x12\xc1\xc6\xaa\x24\x3e\xaa\x53\x46\x06\x2b\x38\xd9\x09\xd5\xcd\x16\xfc\x88\x1e\x88\x1b\xe8\xb8\x74\xf7\x33\x84\x1c\xea\xc8\xc2\x26\x7e\xa8\x86\xa2\xcb\xed\xaf\x38\xaa\xd9\x27\x7b\xf8\x9b\x65\xa8\x64\x42\xa4\x2a\xec\x28\x18\x54\x6b\x6e\xae\x65\x37\x1d\x25\x4f\x22\xd5\x34\xe4\xee\x42\x65\xdf\x86\x54\xc7\x66\x74\x4a\xb6\x45\xaa\x67\x4a\x2e\x86\xea\xa6\x8e\x32\x41\xd9\x3e\xc5\xe6\x0a\xea\xbe\x0e\x5e\xc1\xd4\x1b\x25\x07\xd3\xe2\x9b\xa5\xad\xa4\x5f\xda\x1a\x06\x95\xce\xd5\xc9\x69\x56\xe5\xb0\x1b\x52\x73\x2e\xa7\x02\x97\xec\x1c\x05\x84\x3d\x57\x57\x00\x16\xdc\x85\x3e\xde\x8d\x02\x59\xea\xb8\x66\x69\xdf\xe8\xc8\x86\xfc\x4a\xa8\xe4\x57\x08\x2e\x3c\x05\x5c\xf8\x5f\x5c\xbe\x7e\xe2\xf3\x64\x17\xe5\xbc\xce\x13\x53\xef\x0e\x12\xd3\x93\x8b\x7e\xd1\x1d\xf5\xaa\x51\xe1\x1e\x92\xfd\x21\x25\x6b\x12\xfd\x18\x2c\x39\x30\x2c\x6e\x17\x3f\xe0\xf4\x16\x37\xe3\xc8\xc9\xf0\x09\x4f\xfe\x33\xff\x73\xf2\x4d\x99\xa0\x74\x22\x9c\x51\x16\x5a\x9c\x15\xf7\xf2\x86\x71\x6f\x16\xde\xcc\x97\xc1\x6c\xca\xce\x41\x7e\x39\x9d\x4e\x37\xe0\x39\x0f\xf9\x90\xa5\x7a\xb6\x52\xe4\x8c\x9f\xac\xec\xda\xe5\x25\x62\xd3\xfc\xc4\x25\x3a\xb7\x2d\x2f\xd1\x76\xb9\x51\x8f\x24\xd1\x33\xc2\x6b\x72\x2e\xb1\x3d\x03\xcc\xaa\x84\xd3\x79\xb8\x8c\xb4\x2a\xc2\x29\x26\x5a\xaf\x3d\x33\x3c\x2f\xee\x1d\x74\xaa\x73\xa7\xdb\x27\x1f\x9d\x2a\xb7\x24\x5f\xd8\x1a\x9a\x1b\x86\xe9\xe6\xbb\x5d\x85\xeb\xb5\x1b\x16\xf7\xca\x29\x59\x9f\x1c\x06\x52\x4e\xe7\x1e\x93\x38\x4e\xf1\xc5\x8b\x50\x99\x9f\x2a\x9c\xd2\x33\x87\x6f\xbd\xa4\xc6\xc7\xb7\xe8\x6d\x72\xdc\x4f\x60\x18\x81\x24\xc7\xbd\x5b\xe2\xaa\xc8\xb3\x2a\xb9\xc5\x13\x8f\x7c\x30\xca\x50\x92\x3a\xac\x6a\x5b\xd0\xfc\x29\x9f\xe9\xde\xc8\xc7\x92\x36\xe2\x61\x45\x4a\xb8\x51\x24\x8e\xf9\xa1\x9f\x12\xc5\xc9\xa9\x5a\x2f\x8a\x7b\x0a\x6e\x49\xc3\x07\xc0\xcd\xd4\xdb\xc3\x97\x16\xa3\x02\x2d\x49\x3f\x19\xf8\x65\x1c\xc7\x1b\x99\xbf\x99\x38\x36\x9a\x71\x40\x8f\x55\xa0\x34\x75\xbc\xb0\x72\x30\xaa\xb0\x9b\x64\x6e\x7e\xaa\x37\x6e\xde\x87\x61\x07\x53\x39\xd0\x8f\x45\x8a\x94\xe6\xfe\x8b\xcb\xa1\x64\x9a\x27\xce\x3b\x6c\x06\x2d\xfb\x9b\xb9\x7f\x52\xc4\x0f\x31\x6e\xba\xf3\x59\x62\x07\x31\xc6\x17\xaf\x2a\xdd\x3c\x4b\x1f\xba\x43\x22\x68\x5b\xe5\xe9\xa9\xc6\x1b\x26\xe1\xe2\x9e\x0b\xb8\xf9\xd9\x9d\x90\x64\x96\xe7\x36\xa5\xca\x11\xee\x0d\xf9\x08\x53\xe2\xa8\x7e\xe9\x4f\xc8\xbf\x57\xdd\x81\x4a\xde\x22\x35\x73\xd4\xcc\x93\xd9\x61\x7a\x00\x42\x47\x4a\xcb\x1b\xbd\xcd\x82\x71\x46\xf4\x2d\xea\xbe\x3d\x4b\xad\x9e\x94\xa6\xfc\x10\xeb\xfb\xb9\xcc\xd3\xf6\x00\xf4\x59\x39\xc1\xec\x1d\x82\x89\x77\x08\x27\xde\x61\x3a\xf1\x0e\xb3\x89\x77\x98\x4f\xbc\xc3\x62\x72\x08\x26\xf4\x38\xdd\x61\x36\x39\xcc\x27\x87\x85\xd9\xb5\xb0\x43\x30\x73\xf5\x10\x8c\x17\x28\x87\xba\xbd\x43\xe0\x78\x64\xd3\xc7\xa4\xf9\xc9\x7f\x85\x5d\x61\xd8\x16\x4e\xbb\xc2\x69\x5b\x38\xeb\x0a\x67\x6d\xe1\xbc\x2b\x9c\xb7\x85\x8b\xae\x70\xc1\x0a\xbb\xc6\xdb\xb6\xbb\xa6\xdb\x96\xbb\x86\xdb\x76\xbb\x66\xdb\x56\xbb\x46\xdb\x36\xbb\x26\x79\x8b\x67\xfb\x11\x21\x36\x10\x97\xcb\xa5\xa4\x04\x2e\xf8\x1e\x63\x6f\x82\xd6\x63\x05\x7a\xad\x44\x84\xd8\xb9\x98\xbf\xb8\x48\x66\xc3\xad\x45\xe0\x3e\x30\x72\xff\x38\x7d\x3e\x4a\x2d\xfc\xb2\x07\x22\xfb\x43\x20\x14\x4e\x89\x4b\x6e\x74\x10\x8a\xa5\x94\xe3\x69\xa3\x19\xe1\x26\x8a\x19\xed\xc7\xe4\x30\x13\x27\x14\x37\xa4\x74\x3e\x39\xcc\xcf\x72\xd0\xbf\x10\x19\x2d\xc4\xd2\x26\xb0\x15\x6d\x4c\x73\x7c\x87\xca\x26\xc5\x28\x3e\x03\xfe\x4d\xa8\xb9\xe0\x7f\x0a\xf7\xec\x2a\xfe\xff\x42\xcf\x41\xbf\x3c\x26\x19\x8b\x1e\xcb\xc5\x4d\x71\xff\xea\x4c\xe9\x0b\x1d\x09\x8a\xfb\xcb\x85\x49\x4a\xbb\x3b\xa3\x11\xd3\x11\x95\xef\x26\xe4\xd6\x8d\xf6\xe0\x79\x88\x8f\x50\x64\x89\x76\x37\x78\x7a\xf1\xc8\x6c\xa0\x99\xb8\xd2\xd3\xcd\x34\x3e\x37\x7f\x33\x10\x99\xa7\x8a\x30\x52\xc0\x80\x74\x0b\xb6\x08\xa5\x25\x0c\xcc\x36\x51\x8b\x70\x56\xc4\x10\xb2\xfc\xae\x44\xc5\xf9\xee\x90\xd4\x98\x1c\x49\xc7\x6b\x5a\xc4\xf9\xca\xef\x70\x19\xa1\x0a\xab\x77\x48\xb4\x00\x86\x78\x2a\x0a\x18\xb1\x05\x70\x8e\x51\x41\xb6\xbb\xff\xa6\x61\x76\x10\x86\x7a\x3c\xd5\x38\x3e\x8b\xe3\x9f\x14\x17\x65\x42\xae\x8a\x91\xe6\x61\x17\x24\x01\xf9\xfc\x4b\x2e\x94\x27\x63\x37\x0b\x7f\xe5\x33\x9a\xd5\x29\x8a\x70\x55\xb5\x34\xa3\xe5\x62\x1a\x73\x9a\x0c\x28\xd3\xe4\x85\x32\xcd\xed\x7c\x16\x46\x8c\x66\x92\xed\xf2\x96\x60\xb0\xf4\x6f\x76\x9c\x60\x03\x91\xa9\x91\x12\x99\xd4\x6c\x1e\x2e\x56\x8c\x14\xdb\xda\xc6\x61\x37\x68\x11\x4f\xb7\x9c\x1a\x03\xca\x04\x79\xa1\x44\x73\xb1\x98\x07\x2d\x7b\x31\xca\xf6\x1d\x08\xad\x66\xb3\x59\xc8\x49\x52\x98\x4c\x91\x95\x49\x04\x6f\x66\xd3\xf9\x74\x76\xf1\xb6\x7b\x55\x2b\x64\xde\xa4\xd9\x7c\xab\xab\xae\x42\xdb\x88\x50\x44\xdb\xd0\xab\x73\x95\x6d\xf7\xad\xc2\x74\xa4\x78\xb7\xf3\xe3\x1b\xda\x86\xaa\x39\xa1\xc8\xd4\x46\x14\xe0\x70\x3b\x25\x6d\x10\x05\x02\x0d\xac\x70\xbc\x63\x9d\x90\x34\xc9\xff\x36\x91\x46\xbb\x78\xd5\xcc\xab\xb6\xfb\x56\xa1\x46\xb7\x80\x04\x2c\xb1\x01\x59\xaf\x40\xf5\x25\x8e\xb6\x73\xd2\x06\x53\x30\x80\x13\xc6\x38\xc6\xb4\x09\x45\xd3\x5d\x89\xa9\x01\x3c\xdb\xae\xb6\xab\x8b\x47\xee\x08\xa0\x5f\x3d\xb9\xa7\xe3\x0e\x78\xd5\xc6\xb1\xf5\xcc\x2f\xee\x1d\xdf\x11\xa6\x9c\xe2\x95\x47\xc2\x64\x33\x4f\x27\xa7\x54\x8c\x86\x3e\x14\x0a\xf3\xd4\xc9\xd3\x49\x9e\x3a\xa7\x06\xdd\x21\x95\x9c\xae\x1e\x43\xf5\x2f\x1e\x39\x0f\x76\xca\xc8\x49\xe4\xf6\xee\x0e\x9a\x1e\x68\x9c\x7f\xd5\x1d\x52\xce\x30\xc3\xa6\xcb\x08\x15\x97\x51\x26\x7f\xb9\x73\xb2\x72\x30\x57\x7e\x9b\x26\xf0\xaa\x84\x13\xa5\x39\x87\x79\x37\x59\xa6\x84\xe7\xc5\xfd\x25\xb6\xf6\xbe\x11\xe0\x25\x8e\x27\xb1\x7c\x99\x4d\xb7\x74\xb9\xc4\xb5\x7e\x63\x54\x1b\x19\x69\x67\xcc\x51\x2e\x4e\x85\x9c\x9a\xd3\x90\x4a\x73\x54\x93\x28\xc4\xe7\xfa\x0b\x1f\x9c\xcc\x63\x54\x52\x34\x35\x40\xd1\x82\xb6\x02\x4e\xd3\xa4\xa8\x92\x6a\x03\x85\x1a\xa5\x79\x99\xed\xe0\xa6\xe9\x3b\xbd\xb7\x23\x46\x35\x72\xf3\x32\xd9\x27\x19\x4a\x5d\x7a\x8b\xc7\x44\xbc\x56\x8b\xcd\xda\x0f\x38\x2d\x00\x7b\xa3\x57\x6c\x39\x34\x96\x24\x59\x42\x0e\x99\x57\x47\x21\x84\xaf\xfc\x17\x1b\x63\x00\xeb\x6e\xf4\x68\x63\x7b\x63\x95\x8e\x30\xed\x24\x33\x13\x75\x02\xb2\xf4\xe6\x9d\xf9\x73\x85\x8b\xc6\xdf\x11\x76\xf2\x74\x9d\xa2\xaa\x76\xa3\x43\x92\xc6\x13\x01\x50\x18\xca\x4f\x62\x05\x6d\x20\x08\x88\x6c\xd2\x22\x94\xb0\xeb\xd9\x84\x12\x3a\xa3\x91\xd7\xeb\xd2\xdd\x60\x3d\xd9\x98\x46\xb0\x5a\x93\x2c\x3b\xa5\xb7\x0c\x00\xc0\xad\xee\x5f\xfd\x3d\xf4\x83\x99\xf3\x77\xdf\xff\xc6\xff\xea\xe2\x75\xe8\x6e\x89\x6f\x71\x59\x89\x14\xbc\xe2\x94\xa6\x6c\xce\x24\x8f\xba\x40\x1b\x76\xbe\x6e\xb4\x7c\x39\xcd\xc7\xa9\xa0\x25\x49\x81\x3e\xc4\x86\xd2\x5f\x08\x43\xee\x38\x84\x61\x10\x99\xd0\x2f\xa3\x58\x45\x1c\x93\x84\x45\x1c\x58\xd8\xa0\x84\x79\x9b\x34\xdf\x68\xe9\x99\x19\x41\x24\x60\xed\x97\x0d\x45\x6a\xc5\xd6\x2b\xf9\x3a\x9e\xaf\x88\xed\x38\xc4\x8e\xbe\xba\xa0\x38\x2e\x9b\xb9\x83\x71\xd9\x20\xde\x5f\x61\x70\xb7\xf6\x2b\xe0\xfe\x8c\xb3\x34\x9f\xfc\x39\xcf\x50\x94\x4f\xbe\x23\xd9\x71\x54\x4d\xa2\xfc\x54\x26\xb8\x74\x32\x7c\xd7\x5d\x0d\x47\x28\xb5\xfe\x24\x2c\xee\x9d\x99\xe4\x3d\x1a\x8f\xc4\xa7\x26\xcb\x70\x3e\xc3\xd0\x52\x62\xb5\x0b\x77\x33\x3d\x23\x75\x79\xb7\x8d\x87\x91\x36\x4d\xd7\xa6\x0a\xd1\xa9\x90\xe6\x12\xae\x6c\x4a\xb2\x0a\xd7\x8e\xef\xb8\x01\x09\xf7\xe5\x7e\x8b\x58\x7a\xc7\x0b\xe7\xaf\x36\x83\x31\x1b\x86\x1d\x91\x69\xf1\x3a\x43\x92\xd0\x53\xaf\x42\x35\xdc\x2c\xa5\xde\x27\x45\x2e\xf1\x93\xfd\x1a\x6f\x62\x45\xbc\xb3\xb2\xb0\x14\x9b\x9d\x0e\x4a\x42\xdf\xe5\x65\x4c\xef\x4c\x5a\xb3\x9b\x93\xd2\x94\x16\x36\x31\x8e\x95\x35\x7f\x43\xfa\x9b\x37\xff\x80\x3c\x63\x14\x45\x80\x56\x8b\x12\x3b\x92\xd5\xf8\x40\xee\x5a\x4a\x29\x49\x51\xb7\x28\x31\xe1\x49\x67\x44\x48\xe0\x2b\xcd\xfa\x17\xaf\xa9\x56\x45\x65\x9e\xa6\xe4\x4e\xa6\x23\xba\xe7\x02\x99\xce\xc4\xb9\x81\xfb\xb0\xa6\x68\x17\xaf\x19\x7e\x28\x11\xae\xe6\x33\xba\xe2\xa0\xd3\x01\xc3\x11\xd2\x76\x14\x85\xe4\xe8\x8c\x33\x98\xae\x29\x56\x3e\x27\xb3\x06\x0d\x7f\xb5\x0a\x41\xfc\xd5\x12\xc6\x0f\x42\xdf\x07\x2b\x04\x01\xad\xd1\x01\xdc\x5d\x7a\x4a\xe2\x27\xeb\xaa\x57\xe6\x77\x67\x09\xcf\x15\xab\xd2\x19\x69\x53\xd2\xb0\x90\xba\xe9\xde\x0d\x26\xed\x2f\xbf\xfb\x29\x94\x86\xed\xcf\xee\xd7\xb4\xfd\x35\x6b\x7f\xcd\xdb\x5f\x8b\xf6\xd7\xb2\xfd\x75\xd3\xfe\x5a\xd1\x5f\xc7\x98\x37\xdd\xfc\xf2\xbb\x9f\x42\x69\xd8\xfe\xec\x7e\x4d\xdb\x5f\xb3\xf6\xd7\xbc\xfd\xb5\x68\x7f\x2d\xdb\x5f\x37\xed\x2f\xd6\x74\x75\xe4\x4d\x37\xbf\xfc\xee\xa7\x50\x1a\xb6\x3f\xbb\x5f\xd3\xf6\xd7\xac\xfd\x35\x6f\x7f\x2d\xda\x5f\xcb\xf6\xd7\x4d\xfb\x8b\x35\x7d\x5f\xf1\xa6\x9b\x5f\x7e\xf7\x53\x28\x0d\xdb\x9f\xdd\xaf\x69\xfb\x6b\xd6\xfe\x9a\xb7\xbf\x16\xed\xaf\x65\xfb\xeb\xa6\xfd\xb5\x02\x2e\x6c\x6a\x6c\x55\xcf\xc2\x5b\xcd\xef\xf2\x11\x3b\xd0\xad\x2c\x3a\x2e\xc2\x73\xf7\xc9\xa6\x2b\x0d\xf8\xd0\x0c\xbc\x05\xfd\xbf\xa5\x00\xf5\x19\xf4\x66\xea\x4d\xd9\xff\x75\xd0\x55\xeb\x05\xba\xb2\x1b\x56\xb6\x58\x00\xe4\x96\x0c\x38\xbf\x01\xa8\x2d\x38\x50\xe0\x6e\xce\xca\x66\x10\x73\x33\x06\x9c\x42\xbc\x4d\x19\x30\x14\x78\x6b\x05\x00\xf1\xc6\xe5\x00\xb1\x46\xe6\x3d\x41\x78\x66\xda\x16\xe5\x47\x41\x01\x03\x81\x42\xa4\x28\x3e\x43\x01\x25\x49\x50\x56\x0c\x43\x14\x27\x01\xdc\x30\x00\x28\x53\x82\xb1\x64\x18\xa0\x60\x09\xc6\x82\x63\xa8\xbc\xcf\x19\x00\x14\x31\xc1\x98\x31\x0c\x50\xce\x04\x63\xca\x30\x42\x95\xf3\x56\x64\x46\xce\xb9\xe4\x8c\x8c\x73\xb9\x51\x6f\xdd\x42\xaa\x43\xa3\x10\x3a\xd6\x64\x7d\x34\x90\x80\x42\x0c\xea\x68\x30\x7c\x8a\x61\xd0\x46\x75\x70\x57\x14\x41\x56\x46\x75\x70\x6f\x68\xb9\x41\x17\xd5\xc1\x5d\x52\x04\x83\x2a\xaa\x83\xbb\x60\x08\x2a\xd7\x73\x5a\x6e\x50\x44\x75\x70\x67\x14\xc1\xa0\x87\xea\xe0\x4e\x29\x42\xa8\xf2\xcc\x05\x65\xe4\x99\xc9\xcb\xc8\x32\x93\x96\xa4\x03\xfa\x2d\xbc\xd1\x82\x94\x47\x10\x95\xc1\x51\x02\x09\x05\xd4\x0a\x47\xf5\x25\x54\x50\x3d\x0c\x75\x25\x61\x8a\x7a\x62\x08\x37\x12\x02\xa8\x30\x86\xb9\x94\x30\x41\xcd\x31\xcc\x85\x8c\xa9\xf7\x75\x2e\x21\x80\xba\x64\x98\x33\x09\x13\x54\x2a\xc3\x9c\x4a\x98\xa1\xde\x53\x45\x05\x96\x9e\xca\x9a\xb0\x74\xd4\x1f\x9a\xd4\xfa\x68\xf3\x03\x2d\xc6\x91\x56\xb4\x18\x47\xd8\x30\xc6\x38\xc2\xaf\x31\xc6\x91\x66\x94\x18\xd7\x30\x61\x8c\x71\x0d\xaf\xc6\x18\xd7\x74\x49\x8d\x71\x4d\x87\x8d\x31\xae\x91\x8b\x31\xc6\x35\xe2\x53\x63\x5c\x23\x5c\x63\x8c\x6b\xba\x6a\x8a\x71\xd5\xd1\x18\xe3\x5a\x90\x39\xc6\xb5\x28\xe6\x18\xc7\x51\xb4\x18\xc7\x01\xe6\x18\xc7\x31\xcc\x31\x8e\x63\x68\x31\x8e\x03\xcc\x31\x8e\x63\x98\x63\x1c\xc7\xd0\x62\x1c\x07\x98\x63\x5c\x2b\x17\x53\x8c\xe3\x08\x7a\x8c\x23\x10\x30\xc6\xb5\x10\x63\x8c\x6b\x31\x8c\x31\x8e\x63\xa8\x31\x8e\x97\x1b\x63\x1c\x47\x30\xc6\x38\x8e\xa0\xc6\x38\x5e\x6e\x8c\x71\x1c\xc1\x18\xe3\x38\x82\x1a\xe3\x78\xb9\x31\xc6\xb5\xe2\x30\xc4\x38\x0e\xd7\x62\x5c\x75\xec\x8d\x71\x02\x4a\x5f\x8c\x13\x50\xfb\x62\x5c\x87\x6a\x88\x71\x1d\x42\x5f\x8c\xeb\x30\xfb\x62\x5c\x87\x69\x88\x71\x1d\x42\x5f\x8c\xeb\x30\xfb\x62\x5c\x87\x69\x88\x71\x1d\x42\x5f\x8c\x13\xe4\x6b\x8f\x71\x1d\xa2\x1a\xe3\x6c\x79\x8c\x8f\xb4\xfe\xd6\x82\x1c\x69\x45\x0b\x72\x84\x0d\x63\x90\x23\xfc\x1a\x83\x1c\x69\x46\x09\x72\x0d\x13\xc6\x20\xd7\xf0\x6a\x0c\x72\x4d\x97\xd4\x20\xd7\x74\xd8\x18\xe4\x1a\xb9\x18\x83\x5c\x23\x3e\x35\xc8\x35\xc2\x35\x06\xb9\xa6\xab\xa6\x20\x77\x8c\x8d\x41\xae\x05\x99\x83\x5c\x8b\x62\x0e\x72\x1c\x45\x0b\x72\x1c\x60\x0e\x72\x1c\xc3\x1c\xe4\x38\x86\x16\xe4\x38\xc0\x1c\xe4\x38\x86\x39\xc8\x71\x0c\x2d\xc8\x71\x80\x39\xc8\xb5\x72\x31\x05\x39\x8e\xa0\x07\x39\x02\x01\x83\x5c\x0b\x31\x06\xb9\x16\xc3\x18\xe4\x38\x86\x1a\xe4\x78\xb9\x31\xc8\x71\x04\x63\x90\xe3\x08\x6a\x90\xe3\xe5\xc6\x20\xc7\x11\x8c\x41\x8e\x23\xa8\x41\x8e\x97\x1b\x83\x5c\x2b\x0e\x43\x90\xe3\x70\x2d\xc8\x1d\xe3\xde\x20\x27\xa0\xf4\x05\x39\x01\xb5\x2f\xc8\x75\xa8\x86\x20\xd7\x21\xf4\x05\xb9\x0e\xb3\x2f\xc8\x75\x98\x86\x20\xd7\x21\xf4\x05\xb9\x0e\xb3\x2f\xc8\x75\x98\x86\x20\xd7\x21\xf4\x05\x39\x41\xbe\xf6\x20\xd7\x21\xf6\x07\x39\x21\xf9\xfe\x91\x12\xdc\x5a\x94\x23\xad\x68\x51\x8e\xb0\x61\x8c\x72\x84\x5f\x63\x94\x23\xcd\x28\x51\xae\x61\xc2\x18\xe5\x1a\x5e\x8d\x51\xae\xe9\x92\x1a\xe5\x9a\x0e\x1b\xa3\x5c\x23\x17\x63\x94\x6b\xc4\xa7\x46\xb9\x46\xb8\xc6\x28\xd7\x74\xd5\x14\xe5\xd2\xbd\x31\xca\xb5\x20\x73\x94\x6b\x51\xcc\x51\x8e\xa3\x68\x51\x8e\x03\xcc\x51\x8e\x63\x98\xa3\x1c\xc7\xd0\xa2\x1c\x07\x98\xa3\x1c\xc7\x30\x47\x39\x8e\xa1\x45\x39\x0e\x30\x47\xb9\x56\x2e\xa6\x28\xc7\x11\xf4\x28\x47\x20\x60\x94\x6b\x21\xc6\x28\xd7\x62\x18\xa3\x1c\xc7\x50\xa3\x1c\x2f\x37\x46\x39\x8e\x60\x8c\x72\x1c\x41\x8d\x72\xbc\xdc\x18\xe5\x38\x82\x31\xca\x71\x04\x35\xca\xf1\x72\x63\x94\x6b\xc5\x61\x88\x72\x1c\xae\x45\xb9\x74\xdf\x1b\xe5\x04\x94\xbe\x28\x27\xa0\xf6\x45\xb9\x0e\xd5\x10\xe5\x3a\x84\xbe\x28\xd7\x61\xf6\x45\xb9\x0e\xd3\x10\xe5\x3a\x84\xbe\x28\xd7\x61\xf6\x45\xb9\x0e\xd3\x10\xe5\x3a\x84\xbe\x28\x27\xc8\xd7\x1e\xe5\x3a\x44\x2d\xca\xb1\x27\x8d\x6c\x6f\x48\xb2\x67\x34\xdb\x4f\xc9\x75\x5e\xac\x6f\x84\x0f\x79\x6c\xc7\x4a\x53\xd4\x6d\xbc\xda\xa8\xdb\xc7\xeb\x03\xb0\xa3\x9c\x34\x2e\x1c\x90\x52\xce\x4b\x01\xbb\x0e\x69\x9d\xb7\xf5\x36\x8f\x1f\xde\xd6\xe5\xdb\xf6\x95\x24\xa1\xe8\xd0\x16\xed\xf2\xbc\x56\xb0\xda\xa2\x0e\xeb\x80\x51\xac\x60\xb5\x45\xdd\x8b\x67\x37\xe6\x8d\x17\xca\x71\xb6\x3a\x2f\x0c\x07\x99\xe2\x38\xbe\x00\x4d\xa8\x8f\x55\x92\xfe\x2a\x3b\x06\x43\x90\x0a\xd3\xcd\x6b\x4e\x6d\xbd\x4b\x4a\xbe\xfd\x4e\xe8\x8f\x1d\xad\x95\x44\x94\xa7\xe4\x3d\xaf\x5e\x72\x76\x3c\x59\xb2\x32\xcc\x44\xb2\x1f\xf5\x20\x3c\xde\xb5\xf6\x25\x43\x78\x4d\xfe\x5b\x84\x83\xd2\x72\x3c\x83\xb5\x93\x13\x9a\xec\xb1\xad\x28\xcf\x62\xf2\x8c\x2e\x60\x63\x20\xf0\x00\x00\x35\xbb\x03\x81\x50\x4d\xcd\x16\x41\x60\x67\x95\xf3\x76\x4c\xb4\xcf\x84\xc1\x6f\x84\xa9\x58\x50\xf7\x00\xd8\x41\x87\xe9\x9d\x03\x60\x40\x3d\xbd\x6b\x00\xcc\xf0\xc2\x99\xce\xfd\x28\x6a\x6c\x10\x31\xdf\x12\x76\x32\xab\xea\x32\x29\x84\x0e\xaf\xb3\xfa\xe0\xe6\x3b\xb7\x7e\x28\xf0\xcb\x3c\x8e\x5f\x41\xc6\xb2\x6a\xfe\x71\x0a\x64\x67\x7a\x57\xdf\xb8\x13\x9e\x6c\xaa\xa2\xee\xd6\x89\xf2\xf4\xe7\x28\x45\x55\xf5\xf5\xef\x1b\xf7\xfc\x8b\x76\x6e\x50\x7e\x6a\x2f\xca\xd3\xd3\x31\xdb\xd0\xc9\x3f\xd9\x3f\xc6\x9f\x97\x93\xa8\x4c\xf8\x53\x73\xa3\x68\xe3\x34\x15\x29\x2b\xce\xd4\xa3\x27\x1e\x01\x37\xdb\x42\x0e\xba\x03\x8e\x3d\x7e\x50\x52\x73\xcd\x2a\x84\x19\x0c\xd0\x8e\x0a\x81\xbc\xba\x81\x1a\xd0\x0e\x33\x09\xa0\x1d\x15\x02\xc5\x05\x03\xb5\xae\x1d\xb3\xc6\x41\x33\x61\xb5\xd6\xac\xb4\x35\x61\x2b\xd6\x01\xc6\x62\x60\x99\x45\x05\xa7\xeb\x02\xc5\x36\x61\x1d\x24\x2c\xe8\xbc\xc5\x4d\xf3\x4f\xb3\x12\x76\x8c\x05\x32\x93\x16\x04\xda\x09\x83\x42\x86\xa2\x82\xb8\x3d\x00\x6d\x69\x20\xd0\x56\x0c\x04\xa1\xb6\xb8\x4d\x00\x6d\x69\x20\xd0\x5e\x0c\x04\x85\xb6\xcc\x67\x85\x60\x5b\x90\x4e\x0a\x99\x4d\x46\x41\xeb\xb1\x19\x99\x4d\xc0\x68\x24\x72\x66\xab\xe9\x3b\xc6\x14\xfb\x78\x15\x2d\x34\xb3\x49\xb2\x5d\x0e\xd9\x0c\x2d\x07\x0d\xa6\x01\x41\xd6\x22\x95\x73\x7b\x50\xe9\xcb\xe5\xa0\x91\x40\x74\x34\xfa\xdc\x06\x54\xfa\x72\x39\x68\x18\x10\x1d\x4e\xdf\x7c\xba\x0b\xd6\x75\x77\xbc\xcb\x6c\x0f\x22\x4e\x8f\x31\x08\xac\x01\x96\xd0\x11\x32\x9b\x81\xf5\xbc\x59\x34\xc3\xd3\xdd\x54\xb3\x01\x76\x84\x0c\x32\x83\x16\x04\x5a\x02\x83\x42\xc6\xa0\x82\xb8\xde\x81\xb6\x34\x10\x68\x15\x06\x82\x50\x5b\xdc\x06\x80\xb6\x34\x10\x68\x21\x06\x82\x42\x5b\xe6\x73\x7a\xb0\x0d\x48\xa7\xf4\xcc\xa6\xa2\xa0\xf5\x58\x8b\xcc\x26\x60\x30\x12\x39\xb3\xcd\xf4\x1e\x21\x44\xbb\x30\x8a\x34\xb3\xa1\xe7\x02\x21\xab\xe1\x10\xd0\x68\x28\x10\xb2\x19\x05\xc2\xed\x42\x6f\x47\x85\x80\x06\x03\x53\x03\xda\xe1\x36\xa1\xb7\xa3\x42\x40\x63\x81\xa9\x75\xed\x98\xcf\x5b\xc2\x36\x20\x1e\xb7\x34\x5b\x8a\x8c\xd5\x63\x28\x12\x8b\x80\x9d\x88\xc4\xcc\x66\xd2\x77\x0e\x74\x1b\x45\xad\x95\x08\xb7\xc1\x9c\x85\xfd\xc8\x9e\x1f\xbc\xe8\xce\x05\xdc\x8b\x5b\xf8\x1d\x7a\xcd\xbb\x83\xb2\xd8\x79\xd9\x25\x21\x96\x8b\x25\x49\xf8\x6b\x54\x8d\x39\x0a\xb2\xc3\x59\x38\x7b\xc0\x4e\x26\xba\xc7\xaa\x3d\x7c\xc8\x0e\xf4\x34\x45\x0d\x07\x87\x84\x24\x51\xe8\x21\x85\x2d\x2a\xe1\xfb\x5d\xf4\x9e\xbd\x65\x8b\x59\xed\xb0\xa9\x01\x11\x5a\xef\xd9\x90\x0e\x16\x24\x7d\x05\x68\x43\xb2\x51\xd2\x57\x71\x36\xa4\x03\x7c\x35\x00\x5c\x4f\x5b\x0f\x9b\x65\x03\x2e\x8a\xc5\xec\x83\x91\x39\x70\xc9\x3c\xb6\x66\x27\xce\xab\x6b\x8e\x6e\xb3\x13\xfc\xd5\x35\xa5\x36\xcf\xca\x79\xc4\x51\x92\x16\xce\x92\x8e\x13\xf4\xb8\x8a\x82\x9c\xaf\xac\x38\xb6\x45\x41\xca\x57\x56\x14\x5b\x3c\x4b\xe7\x41\xc7\x08\x59\x20\x62\x1b\x6a\x3d\x15\xcd\x03\x59\x97\xd5\xf8\x16\xa1\x8a\x4a\xfe\x66\xed\x5f\x2e\xbb\x04\xa7\x71\x85\xeb\x73\xf7\x61\xd6\xd7\x2f\x7b\xf2\xbb\x6b\x9c\x52\xbc\xc7\x59\xac\x1c\xb7\x13\x1c\xb8\x5a\xd7\x70\x71\x4b\x18\x28\xf9\x5f\xf9\x68\x9b\x70\x3a\xb1\xbb\xc6\x0a\xb8\x5c\x60\xde\xfc\xbb\xa4\x68\x8b\x87\x5d\x19\x26\xf3\x34\x57\x2e\x8f\x59\xfa\xfe\x85\x5c\x29\xf7\x73\xfd\x50\xe0\xdf\xd3\xc7\xae\x7f\x79\xea\x9b\xf7\x84\x16\xc8\xab\x11\xdb\xfc\xfe\x97\x89\x50\x58\xa2\x38\xc9\x7f\xe1\xb7\xe1\xcc\xc8\x51\xca\x56\x9a\x2c\x03\xfe\xf7\x95\x24\x3c\x7a\x9c\x55\xa4\xbc\x4b\x52\xfc\x8b\xac\xa5\x8b\xd4\x46\xb6\x57\xe1\x82\x16\x2f\xf4\x4a\xbd\x9f\x8f\xa7\xb4\x4e\x8a\x14\xff\xc2\xee\xd8\xfb\xb9\xd1\xdd\x2f\x67\xf1\x5a\x37\xb5\x4d\x76\xdb\x04\xd4\x49\x1d\x44\xbb\xfa\x24\xf7\xe2\xe5\xa7\xba\x38\xd5\xf0\x31\x50\x22\xb5\xa5\x7c\xf0\xb3\xff\xf6\xc1\xf9\x7c\x7e\xf1\x76\x79\x79\x74\xa3\x3c\xab\xcb\x5c\x3d\x3b\xaf\xdf\x44\x37\x9d\x09\x37\xa5\x2d\x8a\x7b\x27\x08\xaf\x68\xd4\x74\x51\x5d\x57\x9a\x1c\xd1\x1e\xf3\xa3\xb0\x83\x8e\x95\xda\xce\xf5\x36\x75\x9b\xff\x88\xc7\x75\xfd\x25\x7c\xb2\xd7\x88\x0b\x5c\x8f\xc7\x98\x20\x5d\x10\xaf\xb8\x73\xbc\x60\x5e\x4d\x74\x86\x34\x1c\xe5\x32\x3d\x3b\x3d\x1b\x9d\xa7\x20\x22\x9b\x02\x33\x59\x91\xda\xfa\xcb\xc5\x02\xed\xf0\x8a\x5b\xe7\x1a\x3c\xbf\xdc\x27\xc8\x89\xef\xf8\xce\x0d\x07\x04\x7e\x38\x09\x96\xf3\x49\x38\x9d\x4e\xbc\xc5\x28\x8d\x58\x09\x29\x9d\x59\x13\x1f\x56\xa4\x28\xc2\x07\xf2\x08\x1b\xbf\xe8\x67\xb5\x5a\x6d\xf2\x02\x45\x49\xfd\xb0\x0e\x94\x4a\xcd\x8c\x9b\x0c\x65\x43\x45\xad\x0d\x26\x8c\x51\x75\x8e\x15\x7b\x2f\xc7\xfa\x4d\x55\xb8\x64\x50\xac\xff\x73\x9c\x90\xdb\x04\xe3\x5f\x26\x72\x79\x89\x51\x9c\x67\xe9\xc3\x2f\x13\x1e\xfe\x3a\x54\x47\x1e\xf2\xc0\xda\x08\x63\x93\x4c\x84\x06\x7b\x09\xb3\xcb\x4c\xb2\xbc\x76\x51\x9a\xe6\x77\x38\xbe\xf0\x0b\x4c\x65\x44\x83\xb3\x55\x83\x13\x2a\x0a\x8c\x4a\x94\x45\xec\xea\x1a\x60\x25\xc6\x51\x9b\x50\x1f\xe3\xdb\x24\xc2\x6e\x91\xdc\xe3\xd4\x25\x57\x95\xae\xfd\x57\x67\x81\x7e\x8c\x6a\xfc\x8b\xc4\x89\xe8\xb8\xeb\xe4\x68\x81\x36\x75\xc9\x83\xc9\x69\x1e\xa1\xd4\x8c\x77\xcc\xb3\xfa\x20\x83\xa5\x1b\x70\xa6\xe4\xb6\x38\x6a\x30\xe4\x0b\xa9\x5b\x1d\x1d\x95\xc7\x89\x05\x81\xb0\x69\x43\x50\x38\xb5\xa1\x52\x66\xd5\x6e\xfe\xc2\x6a\x54\x47\x5d\x3c\x10\x44\x15\x0d\x84\xc3\xc4\xc2\x41\xb2\x48\x7c\x55\x24\xe9\xbe\x47\x24\x32\x02\x20\x12\x9d\x82\x51\x24\x32\xaa\x5d\x24\xe9\xde\x24\x12\x19\x02\x8b\x44\xc6\x91\x44\x92\xee\x25\x91\xcc\x16\xe4\xa8\x3e\xb1\x22\xc2\xe5\x59\x4f\x22\x5c\x3c\x3e\x0b\x99\x78\x64\xd2\x01\x9c\xb5\x56\x6f\xb2\xed\xbf\xbb\x91\xd3\x74\xc8\x04\x94\x51\xa6\x7f\x88\x99\x12\x32\xf5\x95\xce\x69\x03\x77\x59\xfa\x1b\xf5\xa6\x4c\xf5\x92\xd2\xb6\x35\x70\x02\xd9\x82\xd9\x75\x55\x06\x2c\xca\xa2\x36\x05\x63\x00\xa0\x2e\x9b\x8d\xea\xd7\xc4\x0a\x02\x9a\x91\x09\xa9\x74\x77\x41\x28\x09\xe8\xb5\x2a\xfe\xd7\x4c\x0b\x02\x11\x57\x52\x13\xe3\x44\xe6\xcb\xa2\x34\xf0\x46\xae\xe1\xb2\x06\x6f\x50\x36\x6a\x80\xb1\xf3\xda\xce\xed\x6b\x99\x77\xe8\x02\x30\xb6\x19\x8c\x5c\xeb\xac\xc5\x0b\x50\x81\x76\x3c\xa6\x4c\xa8\xa6\xc7\x2b\x80\x50\x21\x72\x69\xe4\xc0\x9a\x14\xd4\x55\x83\xe2\x99\x2a\x9e\x8e\x90\x24\x99\xae\x18\x0a\x9a\xaa\x88\x21\x1c\x49\xce\x36\x46\xda\xa6\xa4\x11\xab\x96\xda\xd8\xb0\xa0\x88\xa3\x1f\x62\x42\x0c\x73\x2e\xdd\x3d\x20\x7a\x09\x71\xf1\xd0\x2e\x58\x94\x4d\x61\x4b\xdd\x94\x41\xba\x9d\x0f\xb5\x40\xab\xa3\x72\x9d\x89\xaf\xdc\xa0\x75\xe9\x10\x85\xe8\xb3\x11\xf6\xca\x68\xd7\xe9\x84\xda\x02\x67\xae\x5f\x32\xc4\x56\x97\x30\x75\x2d\xd8\xa9\x4b\xd1\x2e\x64\xb6\xf3\x25\x95\x10\xdd\x81\xd9\x05\x84\x26\x92\x83\x93\xaa\x27\xea\x8f\xd2\x14\xeb\x9e\xb1\x41\x3d\x9e\x43\xf5\x85\x1e\x4b\xf3\x27\x05\xb9\x7f\xd2\x68\x13\x04\xb7\x43\x91\x3d\xd1\x26\x43\x75\x41\xdb\x2f\x9f\x4b\x17\xa5\x85\x00\xbd\x91\x2e\xd5\xeb\xae\xed\x6d\xef\x0a\x56\xc8\xb0\x1d\x97\x1b\xfd\xa2\x78\xc9\x78\x94\x46\xb4\x69\x81\xc1\x78\xd2\xbd\x6a\x3c\x1d\x21\x4d\x66\xe9\x1e\x36\x9e\xa7\xed\x96\xd2\xa2\xc5\x86\xe0\x9e\x82\xf5\x87\xd8\x50\xba\x1f\x69\x43\xaa\x3c\x14\x1b\x22\xec\x89\x36\x74\x23\x8a\x29\x18\x23\xa6\x8b\x77\x40\x95\xbb\xc3\x38\x6e\x96\x61\x7a\xf4\x97\xe1\x8a\x96\x64\xdf\x36\x0b\xbd\x79\x2b\x25\xce\xb8\x4e\xb9\x9d\xdd\xd0\x30\xcd\xfd\xe2\x6f\x6e\x92\xc5\xf8\x7e\x1d\x6e\xa0\x14\x10\xf1\xdc\xa2\x17\x57\xd7\x30\x1b\xed\x0a\xe7\x0d\x9b\x53\xb8\xf8\x16\x67\x75\xc5\x36\x8b\x59\x84\xfc\x1a\xe6\x5c\x9d\x9d\xf7\xa0\x19\x11\xf8\x09\x92\x45\xd7\x93\x7e\x33\x53\x9d\xc9\x20\x1e\xab\x63\x0f\x9a\x11\x81\x1f\x64\xf1\x05\x69\xc3\xee\xb4\xb1\x0a\xb6\x87\xc6\x11\x66\x9e\x60\x71\x3b\x75\x93\xa1\xac\x6d\x36\x4d\x90\x60\x07\x9c\x16\x74\xa2\xa9\x00\xc8\x04\x00\x2a\x83\xda\xd0\x16\x0f\x10\x8c\xcf\xc7\x01\x14\x61\xb6\x01\x00\xa4\x8a\xca\xc5\xd7\x32\x83\x72\xb6\x43\xca\x67\x51\xfc\xf7\x9d\x3a\xb4\xf0\x03\x66\xd9\xe8\x35\xdc\x57\xe7\xd6\x9a\x28\xf6\xe5\x62\xb9\x0d\x16\x37\xa3\xd3\x69\x42\x5d\x85\x6b\xd1\xc2\x51\x1c\xe7\x99\x2c\x73\x20\xa5\x4b\x77\xa7\x6d\x20\x89\x5b\x24\xd2\x0d\x06\xa0\x06\xdb\xfb\xa1\x9a\xbc\x56\x2c\x99\x63\x07\xd5\x4d\xbe\x85\xa9\x26\xdf\x02\x04\x93\x97\xcb\xa0\x36\x40\x93\x57\x61\x80\xc9\x73\x14\xcd\xe4\x25\x00\x68\xf2\xec\x32\x75\x99\x41\x8b\xc9\x53\xfc\x0f\x63\xf2\x20\x3f\x86\xc4\xf2\x3c\x78\xac\xc9\x47\x3e\x0a\x16\xdb\xeb\x4c\x9e\xd6\x55\xb8\x36\x9a\x3c\x93\xa1\x69\x57\xd5\x06\x92\xb8\x45\x22\x9a\xc9\x8b\x35\x70\x59\xe6\xa5\x6a\xf0\x4a\xa1\x64\x8a\x1c\xa6\x1b\x3b\x83\xa8\xa6\xce\x8a\x05\x43\x17\x4b\x74\xda\xa0\x91\xcb\x10\xc0\xc4\x29\x82\x66\xe0\x42\x31\x68\xde\xec\x62\x7f\x91\x2d\x8b\x71\x53\xec\x0f\x63\xdc\x00\x37\xa0\x69\xd3\x47\x06\x1e\x69\xda\xf8\x66\x76\x33\xbd\xd2\xb4\x49\x5d\x89\x67\xa3\x61\x33\xf9\x99\xf6\x80\x6d\x20\x69\x1b\xa5\xa1\x99\xb5\x88\xdf\x4e\x69\x89\xb6\xff\x8f\xa1\x22\x39\x99\x33\xe7\x13\x1e\xb9\x0e\x7f\x53\xc9\x56\xd7\xbf\x08\xd6\xae\x3d\xe4\xd5\x66\xab\xe6\x60\xf6\xb3\x3d\x9c\x36\x6d\xfe\x99\x2f\xea\x22\xcd\x33\xdb\x15\x73\xb4\x86\xaf\xf5\x72\x9a\xce\xf0\xb0\x99\x4e\x53\xfd\x42\x2b\x51\x15\x9e\x8d\x1a\x4b\x90\xaf\x6d\x20\xba\x4a\x35\xc1\x6c\x54\x74\xb2\x37\x63\x50\xdb\x02\x11\x07\x5a\xb2\x81\x78\x9a\xc1\x0e\x45\xde\xd6\xd9\xb9\x13\x8e\x99\x95\xb7\xb2\x90\xc5\x03\xf2\x52\x15\xc9\xab\xaa\x5b\xe9\x06\xf5\xbf\x73\xe4\x52\x31\x4d\x16\xdb\x4c\x06\x7a\x57\x61\x5c\x93\xdc\xef\xea\x0d\x33\x9f\xab\x26\xc7\x60\x2a\x70\xda\x1d\x20\x6a\xc9\xb1\x77\xd7\xbf\x4a\xa7\x4b\x65\x2a\xe6\xa5\xaf\x3a\xca\x59\x45\xe1\x15\x06\x55\xce\x10\xa8\x0d\x6d\x1a\x06\x8b\x85\x70\xb9\xfa\xd4\x06\xcf\x64\xda\xf4\x34\x8e\x41\xf6\xe5\x40\xf8\xbe\xb2\x6c\x17\xa6\x22\x9e\xfe\x41\xc8\x7e\xd3\xb2\xdd\x85\x49\xcc\x49\x76\x6e\xe8\x69\x6b\x84\xda\xeb\x4e\x00\xaf\x43\x94\xd9\x5d\xfa\x3b\x82\x55\x25\xa5\x60\xe6\x3c\x08\xb4\xa4\x0c\x70\x69\xc7\x90\x76\xaa\xa3\xad\x9d\x85\x96\x40\xbc\x5c\xbc\xc6\x11\xd9\xbe\xe4\x74\x9b\x69\xc0\x0f\x39\xdd\xe6\x1a\xeb\x6b\x73\xdd\x66\x1b\x3d\x1f\xa3\xef\x84\x35\x7c\x13\x72\x8f\x95\x5b\xe7\xa7\xe8\xe0\xa2\x88\x0c\xd6\x23\xca\x92\xe2\x94\x92\xd7\x3e\x37\x66\x88\xfc\x2d\xa9\x9d\xf0\x9c\x2a\x5c\xba\x34\x59\x47\x37\xf4\x90\xad\x18\x40\x69\xa5\x17\x6a\x05\x03\xb7\x08\x99\x6f\x82\x27\xdf\xdb\xb7\x75\xc6\xce\xad\x79\x74\xc3\x96\x50\xb2\x16\x4a\xba\x9f\x6b\x0d\x7d\xad\xa1\x3f\xc9\x26\x2f\xb5\x5d\xe1\xa7\xf4\x74\xd5\x74\x3a\x85\xdf\x6e\x15\xba\x22\xf2\x79\x86\x25\x37\x68\x27\xcf\xb4\xb8\x77\xe6\xca\x1c\x33\x30\x3c\x8c\x60\xc2\xa5\x7c\x75\x9f\xc7\xb6\x75\xd6\xb3\x97\xa4\x19\x31\xfa\x47\xa6\xcd\x2e\x49\xc9\x13\x1a\x69\x71\x40\x2f\xd9\x2e\x95\xdf\x2f\x84\xfd\x59\x3d\x4f\x29\xb4\x3b\x5b\xbc\xc5\xfc\x82\x64\xae\x00\x36\x08\xc6\x19\xcc\x65\x6e\xeb\xcc\x8d\xf1\x0e\x9d\xd2\xfa\xdc\xf7\x9c\xaf\x32\x73\x26\x47\x0f\x84\xfa\x82\xc6\x79\x11\xb3\x26\x2b\x61\xbc\x68\xfe\xa9\xcb\xcd\xa8\xf9\x27\x91\xd7\x4d\x67\x20\x2d\x14\x37\xff\x64\x56\x05\xdb\x6a\xe9\xf3\xb2\xbc\xc0\xd9\x5b\x2f\x2e\xf3\x22\xce\xef\x9a\x50\xb8\xdf\xa7\x78\xb8\xa0\x46\xf2\x00\x48\x4d\x1f\xc4\x2a\x84\x1d\x1b\xd1\xb9\x87\x74\x60\xa4\xb6\x96\xa9\xf5\xf6\x9b\x13\xef\x45\x5c\x0f\x46\x1c\xa0\xd3\x78\xd6\xfc\xeb\xb7\x8f\x47\xea\x14\x74\x2d\x72\x03\x7c\x40\x41\x2a\xe3\x30\x48\x69\x2d\x4c\x57\x5b\x37\x48\x01\xa2\x1d\x10\xa0\x2a\x00\x29\x59\x83\xfb\x51\x94\xd7\x83\xb5\x1e\x86\x65\x3c\xb6\xd6\xeb\x24\x1c\x6f\x8b\xe2\x3d\xee\x7b\xa2\x70\x4a\x2b\x8d\x78\xd0\x50\x69\x37\xc4\x8b\x18\xcd\x24\x2a\xa2\x84\xa5\x87\x0f\xed\xe4\xe9\x83\x87\x0a\xf9\x20\x0c\xb7\x33\x5f\x22\x2f\xdb\xf2\x08\x5a\xa1\x3f\x8b\x97\x0a\xab\xa2\x2d\x73\xfa\xfd\xb6\x3c\x48\x5c\x23\x79\x00\xa4\x06\xf8\x27\x05\x22\x18\xba\xcc\x3d\xa4\x03\x23\xb5\xc1\xfe\x49\x51\x70\x2f\x62\xbf\x7f\x1a\xa3\x53\x22\xba\x7e\xfb\x78\xa4\x4e\x2d\xfe\x89\x37\x00\xf9\x27\x15\x06\x29\x0d\xf2\x4f\x0c\x06\xfb\x27\x0d\x08\x50\x1d\xec\x9f\x64\xe5\xf5\x60\xf5\xf8\xa7\xbe\xc7\x4b\x87\xba\x0a\xc5\x4b\xf1\x6a\xf0\x0d\x3a\x4d\x3d\xe5\xed\x5a\xd8\x52\xe6\xd1\xf6\x66\x1e\x29\xad\xcf\x22\x84\x67\x91\x44\x45\x14\xb5\xf4\x7a\xaa\x9d\xfc\x6c\xb6\x8a\x67\xaa\x21\x86\xf3\xf9\x22\x9c\x4b\xe4\x87\x18\x35\x48\x6b\xba\xba\x99\x4d\x57\x32\xab\xa2\x51\x73\xfa\xfd\x46\x3d\x48\x5c\x23\x79\x00\xa4\x06\x38\x2a\x05\x22\x58\xbc\xcc\x3d\xa4\x03\x23\xb5\xc1\x8e\x4a\x51\x70\x2f\x62\xbf\xa3\x1a\xa1\x53\x2a\xba\x7e\xfb\x78\xa4\x4e\x2d\x8e\x8a\x37\x00\x39\x2a\x15\x06\x29\x0d\x72\x54\x0c\x06\x3b\x2a\x0d\x08\x50\x1d\xec\xa8\x64\xe5\xf5\x60\xf5\x38\xaa\xbe\xab\x43\x86\xba\x0a\xc5\x51\xf1\x6a\x66\x47\x25\x3e\x88\x6d\xf0\x52\xdb\xc8\xd7\xbe\x92\xcc\x16\xdb\x9b\x18\x75\x24\x44\x21\x77\xef\x2f\xf7\xd8\x5f\xb0\xf5\xe3\xb9\x1a\x28\xb7\x8b\xf8\x66\xde\x11\x1e\x64\xc8\x10\xa1\x70\xb1\x42\xdb\x48\xe0\x50\xb4\x62\x42\xb9\xdf\x84\xfb\x85\x33\xa6\x69\x55\x46\x80\x37\x12\x8b\x05\x9b\x16\xd8\xd5\x04\x0d\x13\x19\xec\x81\x44\xe5\xd9\xb1\xfa\x7d\xcf\x50\x7d\x51\xf9\xf4\x28\xfe\x6a\x7d\x59\xfc\x0d\xa1\x0b\x39\x1b\x09\xa0\x29\x04\x72\x33\x0d\x00\xf6\x31\x32\x44\x25\x36\xd8\xbb\x08\x8a\xb1\xa1\xf4\xf8\x15\xeb\x5d\x34\x83\x46\xb6\xea\x51\x58\x1d\xb3\x47\x51\xde\xc5\x87\x4d\x60\xe7\xa3\x78\xa6\x36\x8d\x31\x0a\xa7\x0b\x89\x8a\x28\x58\xe9\xe5\x75\x3b\x79\x1c\xad\x96\x81\xba\xf4\x5c\xdd\xcc\x77\x7e\x2c\x91\x1f\x62\xad\x20\xad\x78\x7e\x33\x0f\x42\x99\x55\xd1\x60\x39\xfd\x7e\x9b\x1d\x24\xae\x91\x3c\x00\x52\x03\x9c\x8d\x02\x11\x8c\x5b\xe6\x1e\xd2\x81\x91\xda\x60\xc7\xa3\x28\xb8\x17\xb1\xdf\xfd\x8c\xd0\x29\x15\x5d\xbf\x7d\x3c\x52\xa7\x16\x57\xc4\x1b\x80\xbc\x91\x0a\x83\x94\x06\xf9\x24\x06\x83\xdd\x92\x06\x04\xa8\x0e\x76\x4e\xb2\xf2\x7a\xb0\x7a\x5c\x54\xef\xd5\x47\x03\x5d\x85\x9a\x49\x62\xd5\xcc\x8e\x8a\xdd\x0d\x64\x37\x94\xd5\x7c\x3a\xd3\x06\xde\x6c\xba\x9b\x22\x91\x88\x94\xac\xa3\x77\xf4\x0c\xf0\x52\xd1\x6a\xea\x87\x6a\x1c\x5c\x2e\x82\x28\x58\x89\xc4\x87\x18\x34\x48\x0a\x45\xe1\x8a\xcf\xe5\x19\x9f\x52\x4e\x94\x52\x1f\x90\x12\x1d\x20\xa8\x71\x0c\xe8\xf2\x82\x72\xdc\x12\x40\xcc\x95\x8a\x8c\x03\xa2\x37\x91\x1a\x9e\xdf\x96\x94\xda\x87\x37\x20\xbb\x3d\x58\x8f\x54\x62\xbd\x26\xf1\x28\x3d\xda\x32\xdb\x94\x3a\x98\xd8\x96\x41\x80\xa2\xc0\xb4\x36\x01\x19\xb2\xda\x0a\x4c\x27\x39\x3c\xa7\x2d\x2a\xcc\x8e\xd4\x97\xd1\xb6\xdf\xb0\x35\xd0\x1f\x28\xbe\x88\xd7\x32\xfb\xa2\x34\xc9\xde\x9d\xb5\x93\xa4\x50\x92\xaa\x7b\xfc\x9b\xd7\x9b\xb4\xbf\x24\xb3\x68\x0a\xd6\x6a\x41\xff\xe7\x49\xca\x8a\xf5\xd8\xfe\xd0\xf7\xdd\x01\x0e\x35\x86\x44\xa5\x93\xbf\x99\xe4\x45\x01\x8b\xd7\xb0\xf7\x55\xe4\x4b\x8c\xe9\x3c\x5c\x46\xda\xc7\xe4\x53\x16\xe3\x32\x4d\x32\x20\x2e\x80\x8d\xc0\xc6\xa9\x40\xec\xa6\x29\xf0\x6a\x45\x91\xd8\x6f\xaf\x8e\x87\x3e\x84\xb7\xfb\x42\xde\x36\x7f\x31\x96\xf6\xe7\xa7\x3b\xd2\xd4\xb5\x51\x1d\x85\x36\xba\x83\x87\x8f\x39\x73\xd7\x11\xbf\xaf\x04\xe2\xf7\x55\xd7\x01\xfa\xb5\xfd\x4a\xda\xd0\xae\x48\x71\xe7\x5b\x8b\xf3\x5a\x40\x97\xb7\x4d\x8a\xf7\x35\x6c\x4f\x75\x9d\x67\xbf\x74\xb8\xd2\x69\x5a\x5c\xe1\xda\x00\xab\x4e\xdb\x63\x22\x02\xe5\xfd\x77\x28\xc6\x67\xfe\xd1\xde\x87\xae\x61\x61\x40\x72\x89\x89\xd3\xf4\x1d\x95\xca\xc5\x2a\x10\x86\x1d\x4c\xdb\xf5\x92\xec\x2c\xdc\x84\x11\xe5\x69\x8a\x8a\x0a\xb7\x32\xa3\x86\xc6\x8b\x1b\x6c\xf9\x3e\xa2\xba\x04\x81\xec\x6e\xab\xfc\xee\x42\xae\xcd\xb2\xe3\x50\x13\x68\x5b\x69\x26\xcb\xfa\x36\x3a\xa6\x6c\xbf\xbd\x4a\xb0\xbd\x48\x50\x13\x97\x5b\x27\xc7\x24\xdb\xbb\xbb\x53\x46\x37\xf1\x60\x54\x61\x59\x5e\x30\x4a\x2f\x09\xbd\xa9\xf8\xc4\x46\xa4\x37\x55\x2f\xbb\x51\x60\xe6\x4a\x3a\xd5\xa2\xcc\x0b\x5c\xd6\x0f\x6b\xda\xeb\xc9\x6d\x52\x25\xdb\x24\x4d\xea\x07\xa5\x09\x0b\xe2\x20\xac\x8b\x17\xa1\x12\xd7\xb6\x3d\xb7\x7e\x27\x7a\xe9\x71\x8e\xe2\xde\xb0\xbb\x4a\x78\x73\x60\x56\xdc\x3b\x31\xaa\x0e\x38\x56\x4b\xc9\x2e\xa6\xbf\xb7\x09\x67\x76\xc0\xcf\xb6\xbd\x89\x3e\x33\x02\x61\x5c\xda\x09\xce\x84\xfc\x3a\x15\xd0\x31\x43\x65\x12\xa4\xec\x65\xf2\x05\x84\x23\xce\x4e\x86\xe3\x84\xe4\xa6\x28\xba\x95\xb3\x3d\x50\x18\xf8\xbe\xbf\x11\xc7\xcb\xa6\x7b\x93\x6a\x23\xbc\x95\xb5\x50\x0f\x2a\xb7\x57\xb3\x85\xec\x7e\x30\x65\x23\x9c\xf2\x38\xc8\x26\x4d\xaa\x9a\x5d\x95\xa9\x6e\x15\x13\xe6\x91\x6d\x40\x16\xa0\x69\x52\xac\xbb\x63\xe8\xf7\x1b\x2b\xcc\x72\x1b\x95\x50\x2a\x6d\x7f\x22\x3b\xa5\x06\x5c\x57\x45\x37\xec\x37\x1e\x5c\xae\xaf\x1c\x4f\xb0\xa0\x29\x6a\xf2\xc8\xcb\x49\xc4\x7a\xce\xfc\xa0\xa7\xf0\x80\x8f\x84\xeb\x78\x71\x72\x9b\xc4\xb8\xe4\xa7\x5c\x83\x76\x2f\xe2\x7a\x45\xd4\xa1\xba\x16\x20\xf7\x42\x2f\xab\x93\x09\xbf\x4d\x93\xb7\x08\xbe\xb5\xac\x09\x45\x0e\xb9\xbd\x22\x4a\x31\x2a\xd7\xdb\xbc\x3e\x0c\xdd\xe0\x28\x6c\x7c\x81\xee\xf8\xd4\x59\xe0\xd3\x12\x00\x22\xcf\x87\x16\xcd\x3f\x70\x4e\x01\xd9\x14\xbb\xb4\x5f\xa6\xca\xdf\x06\x40\x6a\x73\x2d\x00\xe6\xa6\x03\x6b\x0b\xa0\x81\xfc\xb0\x19\xb0\x69\xe0\xbe\x6d\x97\x1e\x00\x6f\x1d\xc8\xc0\x9d\x80\xa0\xce\xc1\x2c\xed\x0c\x25\x06\xf6\x10\xd8\x01\x68\xbf\x27\x0b\xdc\xe6\xc8\x36\x0d\x16\x65\xbe\x4f\xe2\xf5\x1f\xff\xfa\x43\x03\xfa\x4b\x53\x6d\x97\x97\x47\xef\xcf\x49\x54\xe6\x55\xbe\xab\xbd\x7d\x33\x42\x71\x56\xbf\xc4\x19\x61\xee\xf7\x3b\x94\x56\xf8\xd5\x45\x5d\x2a\x12\x27\x28\xc7\x7a\x8a\x82\x8c\x3e\x73\xe0\x38\x24\x9e\x5c\x78\xeb\x6c\xc3\xb7\xc6\xb7\x58\x07\x8c\x9a\x61\xda\x33\xa2\xac\x53\x42\x75\x14\x35\x93\x68\xeb\x28\x6a\xc4\xda\xfc\xd1\x39\xfe\x5d\x72\x8f\x63\xe5\x10\x79\xbb\x67\x59\x89\x01\xab\x95\x7f\x11\x7c\x91\x2a\x47\x83\x48\x4e\x85\x43\xe3\xef\xc4\xcb\xd0\xed\x16\x95\x2e\x69\x93\xed\x8c\x76\x5a\x22\x0c\xeb\x1c\xe5\x59\x8d\xb3\x7a\xfd\xc5\x17\x62\x38\x55\xef\xd6\xd4\x83\xae\x00\x60\x71\xb7\x6b\x5f\x62\xb4\x97\x0f\xb9\x5b\x4d\xeb\x44\x83\xed\x99\x22\xfd\x85\x2a\xdb\x4e\x77\xd6\x18\x91\x8e\x4a\x1b\x10\x99\x05\xdd\x64\x53\xc2\x12\x63\x22\xac\x36\xf8\xc4\x65\xe8\x65\x44\x86\x33\x28\x3a\x41\x61\x11\xc3\x4e\xdd\xd0\xcd\xb5\x6a\x2b\xe2\xab\x95\x06\x2a\xd2\xfa\x1d\x80\xaf\xfb\xe0\xc2\x22\x15\x02\x0b\x89\x99\x8e\x55\xbd\x51\x43\x5b\x70\x13\x02\xe5\x73\x7b\xdf\x82\xd0\x43\xb2\xc4\x7d\xad\xc8\xa8\x2b\xd4\x14\xe5\x74\x3f\xc1\x5a\x02\x48\x3e\x25\xc2\x8e\x84\xf0\x75\x60\x9d\xe7\xe9\x16\x95\x32\x74\xae\x40\x9d\xae\x05\xb1\x44\x64\xaa\x2d\x17\x0f\x8a\xa9\xba\x64\x48\x6f\x35\x72\x6f\x0d\xe4\xde\x4a\xe4\xa4\x67\xf7\xa4\x45\x32\x95\x6e\x96\xd7\x2f\xc5\x6b\xa6\x5f\xd1\x92\xee\x8e\x60\x5a\xa0\xce\x77\x5f\x9d\xc1\x9c\x91\xa8\x4c\xe1\xea\x6a\xe5\xd0\x92\x19\x73\x64\xe3\x75\x5e\xd0\xf1\xdb\xb2\x21\xfb\x28\x05\xa8\xb5\xdc\x35\xa4\xcb\x41\xb2\x43\x75\xb6\xaf\x61\x8b\x1c\x35\xdd\x34\x31\x24\xc1\x54\x7e\x0c\x16\xa0\x22\x0c\xd0\x19\x75\x14\x3d\x2a\x62\xd4\x6c\xe2\x57\xc5\xa4\x0e\xce\x41\x24\x14\xe1\x3d\x91\xf2\x58\xd3\x36\x15\xea\x86\xf8\x48\x2d\x39\x9a\x21\x68\x9e\x8c\xcc\x6a\x34\x3c\x71\x92\x23\x77\xe3\xb5\x86\x2a\xdf\x52\x73\xa3\x5e\x14\x78\xa3\x0f\x62\x72\x89\x8b\x9d\x4c\x10\xaa\x74\x82\x50\x22\x64\xe0\xfb\x43\x9c\x72\xb1\x31\xd0\xa5\x8d\xc7\x64\x86\xf9\x0c\x07\xf0\x3b\xe4\xc8\x1b\x01\x32\x95\xb3\xc7\x93\x19\x83\xaa\x25\xb0\xa4\x45\x37\xc1\xb1\xd0\xf0\x1d\x46\x65\x48\x28\x97\x01\xe6\x39\xc5\x5b\x35\xfa\xcb\xd3\xd8\xee\x61\xb9\x8d\xf1\x95\x4f\x23\x3b\x22\x59\xf1\x85\x3a\x03\xba\x1a\x33\x01\xe8\xa0\x7e\xd8\xe9\x18\x83\x30\xb9\x11\x32\x50\xce\x69\xfa\x46\x6e\x07\x78\x49\x8b\x83\x54\x27\x3f\x16\x07\x67\x72\x2a\xb3\xe2\x7e\x63\x72\x75\x02\x0c\x74\x76\x03\xfd\x91\xc2\xa5\xcd\x17\xf6\xba\xbe\x71\xee\x58\xef\x80\xd2\xf5\x1e\xa3\x7b\x82\x18\x06\x92\xbd\x32\x98\x5d\x41\xcb\x10\xd5\x9e\x4e\x9d\xef\x25\xc0\xe9\x5a\x16\xdb\xff\xf5\x54\xd5\xc9\x2e\xc1\xb1\x9c\x55\x17\x5d\x0b\x4d\xb3\xa7\xe8\x21\x3f\xd5\x6c\x4d\xdb\x7d\x50\x23\x49\xf9\x75\x85\x0b\x54\xa2\x1a\x83\x94\x35\x3f\x28\x43\x94\xdb\x12\x68\x6b\xca\x13\x9a\x9c\x9d\x17\xe6\x06\x84\x49\xfd\x19\x76\x84\x30\xbe\xbc\x68\xec\x16\x8b\x3f\xc7\xa8\x46\x4c\xd3\xec\xbb\x4d\xf5\x0b\xa9\x09\x9f\xee\x1f\x86\xcf\xee\x66\x35\x23\x0b\x0e\x7a\x6c\x3b\x86\xaa\xc6\xbb\x7b\x49\xe2\xb6\xc4\x51\xcd\xc2\xb3\xff\x0a\xbe\x90\x4e\x5c\x5b\x98\x97\xbb\xd4\x6c\xcc\x86\x21\x50\x91\xdf\x52\x15\xb4\x3c\xec\x26\xd2\xbd\x7e\x31\x06\xc0\x57\x77\x5b\x9f\x90\x4c\x37\x3e\x02\x62\x23\xce\x12\xfd\x9c\xe0\x54\xbd\x79\xfb\xad\x72\x47\x87\x0a\x05\x6e\xe6\xb0\xa1\x6c\xeb\x8c\xfa\xc1\xf7\x7f\x71\xa6\xa1\x07\x06\x1c\xbd\x1f\x03\x10\xc1\xde\x0c\xbd\xa1\xd3\xca\x9f\x05\xdb\xc4\xe9\xc0\x2a\x9c\x67\xf5\x7a\x50\x03\x3b\x46\x2c\x9d\x8d\x41\xa8\xaa\xc8\x68\xee\x4a\xbe\x3e\xd1\x66\x73\x0d\xb4\xc7\xe6\x54\x14\xb5\xc9\xf7\x71\xc5\xaf\x81\x75\x03\xce\x20\x63\x1b\xd4\x8d\xa1\x77\x09\x5b\xf9\xb3\x60\x8f\x34\x36\x13\xcf\xb0\x6d\x68\xec\x18\xb1\x06\x1a\x5b\x9f\xc8\x34\x63\x53\x6f\x20\xea\xb1\x2c\x71\xbd\xd2\x85\x72\x9b\x77\xed\x9f\x16\x02\x8d\x8e\xae\x75\xed\xf2\x60\x40\x77\xf9\xdc\x64\xf0\xf5\x21\x00\xd5\x73\xdf\x83\x41\xd6\x4f\x7e\xe2\xbb\x41\xfa\xad\x26\xf0\xeb\x25\x03\xdf\x0c\x02\x78\xd5\x6e\x0e\x37\x7a\x08\x60\x53\x8d\x89\xda\x90\x2d\x47\xc0\xe6\x22\x8d\x9c\xe1\xea\x25\x2b\x9e\xf4\xd6\x96\xcb\x2e\x45\xb3\x4c\x07\xc4\xb7\xfd\x74\xcb\x34\x82\x95\x89\x3b\x9b\x16\xf7\x61\x08\xb3\xba\x1e\x64\x65\x79\xa2\x63\x0b\xaf\xc0\xa9\xcb\x32\x75\xcd\xd4\x5b\xf9\x7d\xa7\x6d\xad\x72\xd5\x1e\xed\x33\x2b\x4b\x5c\xfb\xe9\x34\x4d\x50\xb3\x22\x0c\x2b\xb2\xfe\xea\x50\xba\xb9\x47\xc6\x03\x35\x38\x00\xd7\x92\x8f\x1d\x91\x14\xb5\xc9\x4f\x7d\xab\x52\x75\x92\xc0\x17\xac\x76\x70\xfb\xe0\x87\x55\x30\x4a\x01\xdb\x72\x20\x3c\x92\x4b\x02\x3e\xe5\x40\xb8\x6d\x42\x17\x04\xb2\x0f\x54\x20\x4c\xfb\x4c\xf5\x54\xa3\x5d\xbd\xaa\x0c\x60\xfd\x4a\x5b\xe9\xb8\xdd\xe8\xd2\xc9\xd0\xad\x72\xbf\x9d\x76\xd3\x96\xb2\x83\x88\xd4\x79\x9b\x26\x7d\x0f\xe9\x70\xbc\xb7\xa8\xf7\xc9\x1d\x39\x0a\xcc\x19\x5f\xd2\xf6\x94\xf6\x6f\xcb\xa6\x08\x30\xe8\x71\x52\xc2\xc6\x0a\x69\x7f\x86\x06\x95\x5b\xb4\x6d\xee\x80\x37\x9f\x8c\xdc\x9a\x41\x38\x70\xd8\x2e\x89\x89\xf8\x87\xc0\x48\x5b\x64\x7c\xb7\x19\xab\xe7\x5b\xe8\x8e\x17\x46\x3d\x43\xb7\xee\xd3\xed\x63\xe2\xba\x78\x9b\x1c\xf7\xe7\x2e\x09\xdd\x1a\x87\x5b\xa3\x6d\x75\x36\xbe\xdd\x49\x9e\x61\xe6\x68\x8d\x21\x89\x3b\xdf\x24\xdb\x6b\x4d\x94\xa3\xbe\x45\xf2\x30\xb1\x6c\xe1\x18\x77\xf9\x99\xc3\x5e\xdb\x54\x5a\x83\xf6\x92\x37\xb2\x76\xe8\x7f\xa9\x3d\x11\xf6\x37\x41\xa5\x82\x42\x75\x98\x64\x5c\xcd\xa4\x8e\xd9\x11\xbb\xe5\xc6\x7a\x7f\x96\x22\x5d\xc5\xa3\xc3\x26\x47\x38\x20\x3f\xba\x74\xa0\x90\x2a\x91\xb5\xe7\x9b\xaa\x74\xea\x93\xb5\xaf\x61\x75\x9a\x13\x9e\x60\xd5\xe6\xad\x46\x02\xfd\xbb\x49\xba\x2c\x9e\x6d\xe7\x88\xa1\x0b\x40\x22\xb2\xcb\x3e\x0e\xef\x93\x7f\x19\x84\xdd\xed\x0b\xd2\x26\xde\xa6\xee\xeb\x86\x65\xc2\x50\x8d\xcc\x88\x27\x9a\xb6\x3a\x3c\xc7\x8b\xf0\x2d\xb2\x0e\xf8\x21\x23\xee\x03\xf7\x59\x1e\x1e\xe4\x20\x0d\xe5\xa5\x48\xd2\x54\xf1\x4b\x32\xa0\xeb\xab\xaa\x3a\x8e\xf1\x3a\x4d\xce\xca\xbe\x67\x19\x41\xe9\x9c\x56\x2c\xf6\x48\x07\x0e\xb9\xd1\xa0\x73\xff\x6e\x55\xa3\xe8\x1d\x3c\x56\x3b\x90\xc0\x32\xb9\x5e\x5a\xff\x02\x67\xf2\x15\x97\x7e\x97\x70\xad\x27\x78\x0f\x0e\x60\xdc\xb8\x1f\x3c\xdc\x05\xc9\x18\x5d\xe7\xf5\xee\xc0\x3e\x2c\x86\x0c\x89\xa7\x77\x01\x4f\x3d\xfc\x3f\x40\x1f\xc1\x21\x5f\xa3\xad\xcb\xf6\x48\x92\xc7\xd4\xdd\x02\x65\xea\x51\x16\x09\x87\xdd\x1c\xaa\x4f\x76\x09\x13\xaa\x7d\xaa\x9f\xb5\xaf\xf9\x74\x47\xf7\x30\x42\xd7\x44\x77\x77\x21\xcf\xf5\xd7\x0e\xc9\x86\x57\xdb\x3c\xa8\x6f\x93\x25\xe0\xe6\x7a\xf7\x65\xb2\x6d\xb8\x82\xef\x6c\xf7\x60\xb6\xe7\x84\x94\x4d\x2b\x73\x6d\xd3\x4a\x53\xc2\xe7\xa3\xee\xfd\x9a\x1c\x3d\x49\xbb\xa3\x34\x2d\xa8\x8a\xca\x3c\x4d\x9b\xa5\x03\xb9\x7f\x57\xdc\xe0\x0a\x4f\xfc\x7a\x9e\x1b\xf0\xe9\xd6\x95\x70\x3e\x9f\xf0\xff\x78\x81\xf1\x11\x04\x18\x5b\xeb\x2e\x39\x2d\xc5\x39\x7e\xe8\x77\x51\x92\xa8\x84\x2b\xec\xa5\xdd\xbb\x83\xf7\xc9\xa8\xcc\x68\x87\xb5\x88\xfd\xfe\x2e\x39\x16\x79\x59\xa3\xac\xde\x08\x49\x61\xa1\x54\x79\x8f\x50\x58\x2e\x30\xe5\x74\xb8\x7d\x02\x60\x15\x2e\xf0\xb6\x61\xa5\xae\xb2\xb9\xb8\xce\x0b\x33\x0a\xbd\xb2\x1f\xc4\xe9\x7b\xf0\xf0\xc9\x98\x21\xeb\xa1\xf6\x05\x30\x5f\xd8\xc8\x8c\xee\xf9\xdb\xcb\xec\xc9\xad\x1b\xbf\xb8\x7f\x45\x1f\x68\xce\xcb\x04\x67\x35\x5d\x44\xa6\x28\x8b\xab\x08\x15\xb8\xb3\x86\xa7\x64\x2a\xf4\x7d\x72\xf3\x76\xe3\xce\x50\x92\xe1\xd2\xdd\xa5\xa7\x24\x7e\xab\x93\x35\x61\xd0\x11\x2e\xc0\x6d\x75\x95\x5a\x8f\xbc\x90\xfd\x53\x60\xda\x57\xe6\x45\x17\xdd\xfc\xce\xd2\x29\x2f\x65\xf7\x18\xf1\x1d\xbd\x0e\x40\x20\xa6\x7e\x9f\x00\x6d\x55\xb7\x02\xf5\x4c\x82\xb4\x37\x5e\x3c\x88\x36\xf5\x7b\xb9\xe9\x69\xa9\x87\xc3\x06\x45\x3a\x73\x20\x8b\x02\x6a\xe4\xdc\xfa\x19\x35\x1b\x25\x91\x08\x84\x49\x44\x43\x62\x5b\xa2\x2c\x16\xf3\x09\x62\x70\x6c\xb3\x4c\x73\xfb\xa7\x74\xfa\x82\xb1\x48\x51\x98\x68\x74\x65\xe6\x44\x94\x5c\x99\x64\x49\xe4\xc9\x42\x8f\xb8\xdf\x76\x96\xe8\x48\xa4\x26\x3a\x02\x35\x73\x19\xed\xac\x8f\xa8\x96\x25\xbe\x6d\xd4\x70\xb8\x80\x18\x49\x2b\xa9\x15\xff\xc6\x23\x4c\x62\x6e\xba\x3f\x85\xf0\x2d\x6b\x89\x7c\xb8\x19\x7f\x1c\x69\xec\x7d\xf5\x52\x97\xf4\xa3\x99\x12\xd8\xf1\x92\x28\xcf\xdc\x66\x52\x03\x9d\xec\x0e\xc3\xee\x6d\x41\xfd\x2b\x56\xa0\xb5\xd6\x91\x7b\xdd\x11\x96\x1f\xc6\xee\x1d\x54\x4c\x15\xd2\x4c\xb3\x6d\x26\x43\xb7\x8c\xde\x7a\xe9\xcd\x8b\x7b\xc7\x6d\xf3\xa2\x0c\xcc\x52\xab\xe2\x4b\x0e\xbe\xfe\x70\x70\xa0\x7e\x0e\x0f\x95\x98\xc4\x59\x5b\x8a\xac\xb5\x29\x47\x75\x36\xdb\xda\x0d\xf5\x4f\xfa\x76\x2d\x32\x53\x91\x9e\xfc\xb0\x1b\x02\x55\xf9\xf8\xf9\x8c\x89\x43\x47\x3d\x10\x36\xe9\xab\x21\xc9\x91\x7e\xd6\xe4\xdb\x84\x43\x45\xe4\x96\xfa\x46\x07\x62\xad\xa6\x38\x16\x3b\xae\x96\xfb\x15\xee\x5e\xe9\x35\xb6\xc6\x9e\xb4\x3c\xab\x30\x4a\x58\x4e\x5f\x49\x79\x98\x4d\x6d\x0e\x98\x9a\xe4\x68\x76\x79\x79\x3c\x6b\x89\x7d\xab\x27\x71\x0d\xae\x44\x73\x67\xfd\xf3\x7c\xd3\x62\xf4\xd1\x6b\x81\xc9\xd3\x2d\x14\xac\xa4\xfa\x43\x72\x5e\x1e\x9f\xec\x5d\x2d\x9d\xe6\x23\xdf\xd5\x32\x12\xb4\xbf\xab\x25\x55\xbb\xf6\x5d\x2d\x13\x11\x75\x57\x8b\x19\x0f\xd8\x00\x32\x0c\x59\x7d\x57\xcb\x54\xcb\xf2\xae\x96\x54\xe5\xaa\x77\xb5\x64\x0a\xed\x73\x4a\x52\xf1\x13\xbf\xab\x05\x36\xc9\xdf\xd5\xd2\x1b\x36\xbc\xab\x05\x53\x81\x37\x77\x00\x44\xaf\x78\x57\x4b\xa2\x32\xe2\x5d\xad\xbe\xf8\xa9\x0d\x4e\x2d\xe3\x09\x0d\x11\x75\x27\xb8\x9e\x6e\x1c\xe2\x14\xc4\x94\x81\xe8\xb2\x7d\x7d\x0d\x6f\x5b\xd7\x8c\x0f\xcd\x6a\xc4\x30\x27\xc2\xfc\x47\x64\xc1\xe0\xc5\xb0\xb5\x49\x75\xed\xf0\xe1\x8e\x70\xf0\x59\x79\xb7\x31\x41\x09\x7c\x5d\x94\x13\x91\x3d\x76\x59\x92\x50\x27\xd0\xd3\x7b\x81\x0f\xd4\xba\xaf\xa4\x5a\x33\xbd\x96\x34\x75\xc6\xf7\xb5\x84\x0f\x3c\xf9\x68\x5d\x90\x8b\x74\xf4\xcf\xb8\xfa\x02\xa1\x4d\xef\xf5\xdb\x32\x39\x9c\xdd\xd1\x04\xf2\x4b\xf4\xe6\x00\x61\xe1\x22\xe4\xab\xf4\x89\x85\x5c\xed\xff\xc8\x44\xe4\x91\xd0\x99\x32\xf0\x96\x0b\x4f\x1e\xdf\x34\xff\xd4\x4b\x25\x97\xcd\x3f\xb5\xb6\xb2\x3a\x53\x76\x1f\x18\x11\x95\xc9\x21\x8c\x23\x7f\x36\x26\x5f\xe8\xfb\xb7\x1b\x40\xe4\x88\x0a\x07\xb0\xd6\x4e\x07\x47\xe0\xf6\xf4\x44\xd9\xdb\x21\x5c\x61\x72\x55\x4f\x1a\x72\xf2\x17\x84\x5e\xac\x01\x0c\x5a\xbe\xd4\x03\x3b\x25\xec\x76\x40\xe8\x89\x37\x8e\x0c\xc3\x1b\xc2\xa5\x69\xbb\x0a\xd9\xe8\x79\x95\x5d\x48\xbb\xc7\xf8\x75\x84\x6c\xe3\x83\xb9\x42\x0f\xaf\x0c\xc9\x78\x49\x62\x2f\x7d\x61\x3d\xaf\x57\xbf\xb9\xb9\x31\x56\xd7\xd2\xa6\x2a\x02\x89\xa1\xa3\x86\x35\x11\xbc\xb0\x81\xa7\x07\x67\x88\x1a\xa5\xed\x3e\x43\x4c\xad\x6f\x46\x02\xb4\x62\x59\xcd\x0e\x1b\xdb\x83\xd7\xb5\xe3\xea\x3e\x99\x17\x80\xdb\x18\xe4\x1a\x7a\xaa\x5e\xdb\xbf\x27\x76\x22\x86\x46\x86\x79\x96\xde\xca\x57\x77\xf2\x6a\x1f\x64\xec\x2b\x39\x1a\x3e\xc0\x28\xf5\x7b\x30\xc9\xc3\x5f\x2a\x76\x7b\xda\xdc\x46\x52\xbd\x26\xd4\x08\x1f\xde\x9e\x7e\x19\xe8\x00\x4c\xf3\xe5\xa0\x3d\x1c\x8f\xaa\xa2\x2a\xaa\xed\x44\x92\xdd\xe2\xb2\xc2\x80\x97\x0d\x43\xf5\xfe\x73\xff\xa6\xf9\xa7\x56\x85\xa7\x3f\xab\xb8\xf9\x67\xc7\x55\xa4\x04\xe3\xf4\x6f\x86\x81\xdc\x85\x4a\x4b\x9c\xfe\xf4\xb0\xa6\xce\x80\x06\xa2\xf7\x74\x06\x9e\x04\x5d\xdd\x1f\x78\x12\x64\xc5\x1a\xc0\xe0\xa8\x2d\x48\x3d\xd6\x60\x9a\x04\xf5\xe0\x0d\xe1\xd2\xe4\x80\x66\xb3\xd9\x95\xd6\x01\x4d\x82\xc4\xa1\x0e\x57\xe8\xe1\xb5\x67\x12\xd4\x4f\xdf\x3a\x09\x22\x37\x43\x1b\xaa\x6b\x93\x20\x15\x01\x98\x04\x05\x7e\xf3\xcf\xae\x4e\x65\x12\x64\xc1\x19\xa2\x46\x68\x12\x64\x35\xb5\xbe\x49\x10\xd0\x8a\x29\x84\x29\xb7\xca\x8d\xf2\x73\x96\x6f\x13\x6c\x0f\xf6\x35\x83\xa5\x7f\xba\xd6\xef\x88\x06\xcf\xd8\xc6\xd5\x7d\x32\x97\x35\x74\xc6\x36\xbe\xea\xb5\xfd\x7b\x62\x8f\x37\x7c\xc6\x76\x4d\xe5\xab\x3b\x79\xb5\xc3\x34\xf6\x55\x9c\x5e\xf5\xd8\xa5\x3e\xff\x00\x5d\x97\x3a\x69\x33\x51\x35\xcc\xdb\x74\xf8\xf0\x26\x8d\xf3\x36\x1b\x66\xef\xbc\xcd\xc4\xf1\xa8\x2a\xaa\xba\x2e\xde\xb6\xc4\x28\x8e\xca\xd3\x71\xdb\x7e\x7e\xbb\x51\xbe\xbe\x89\xfb\x03\x07\xdc\x0f\x4c\xee\x72\x05\x1f\x8c\x6f\x9b\x12\x37\xd3\xca\x9f\x74\x24\x9c\xd7\x69\xb2\xde\xe2\x5d\x5e\xb6\x1b\xb2\xe8\x75\x4a\x1b\x61\x85\xd0\xde\x57\xf9\xe6\xef\xbe\x8f\xfc\x2f\x24\x12\x7c\xf3\xa5\x38\x71\x2f\xd0\x3e\xc9\xc8\xee\x0b\xf8\x83\x06\x78\xbc\x89\x74\xde\x01\x37\xdd\x76\xf4\xf4\x5e\x29\xd0\x66\xbc\xca\x05\x55\x81\xec\x57\x37\x6e\xb4\x53\xbf\xea\xa1\xac\xde\xab\x7d\xc9\x55\xb6\x43\x2f\xe2\x35\x1d\x12\x51\x7a\x22\x9d\x4a\x53\x7b\x25\x01\x49\x0f\xa1\x4f\x05\xb6\xbc\xba\xe9\xf6\x20\xb9\x19\xe1\xf8\x9a\xc6\x82\x00\x23\x1c\x5c\x99\xb0\xd7\x5b\xed\xdc\xa6\x5a\xcc\x2e\xbb\xd4\x15\x0c\x57\x20\x10\xe5\x7c\xe0\x46\x7e\x4d\x62\xc8\xc1\x2d\x55\x37\x42\xf4\x83\x4a\x01\x5e\x94\x98\x05\xc2\x1a\x66\x8d\x00\x0b\x4d\xa0\x93\xad\x65\x36\xb6\xd6\x7b\x6a\x09\x7c\x30\x97\x9f\x56\x10\x1b\x14\x63\x22\x5c\x0e\xb1\xa9\x46\x32\x03\x54\xef\xbe\x04\xb2\x52\x16\x44\x20\x9c\x05\x1c\x72\xec\x0f\x78\xcd\x5c\xd1\xb6\x9b\xee\x35\xb7\xc2\xca\xa8\x67\xb9\xea\x4e\x18\xbd\x05\xf3\x70\x07\xe0\xea\x78\x13\x47\xf1\xc2\x3a\xc6\x17\xf2\x68\x63\xb4\x4d\xc3\x5c\x07\x5b\x47\xba\xde\xb4\x0a\x96\xda\xae\x8e\xba\x64\x69\x99\x24\xd9\x81\x37\x9f\xe8\xa4\x2d\x22\xd5\xe1\x36\x91\x4e\xad\x22\x9d\x42\xdd\x32\x8b\x54\x03\x5b\x45\xaa\x37\xad\x82\x49\xdb\xb8\x84\xcf\x0c\xf3\xa0\xaa\xdf\x47\xa1\x9d\x21\x26\x54\x1c\x38\xbe\x12\x00\xeb\x07\xfd\x4d\x98\xb6\x05\x77\xaa\xb6\x19\xb4\xef\xb0\xff\x9c\x24\xdf\xe5\x37\x6f\xbb\xe7\x28\x61\x81\x97\x8c\x3f\x7c\x4c\x2b\x7b\x19\xbe\xaf\xbb\x1e\xd1\x3f\x49\xa7\x84\xaf\x97\x2d\x72\x51\xe2\xdb\x24\x3f\x55\x42\x85\xb6\x48\xa8\x44\x77\x69\x31\x04\xc5\x5b\xca\x45\x72\x4f\x60\x1f\x29\x01\x48\x2b\x57\x38\xb7\x8b\x47\x77\x76\xc8\xaa\x6a\x95\xe4\x85\xf8\xe8\x78\x8b\xe6\xbf\xa6\xf8\x28\x8c\xb0\xe5\xfc\x85\x74\xdb\xc9\xd2\x74\xdb\x49\x7b\xe5\xbf\x64\x5d\xfd\x97\xb0\x6c\x51\x85\xe9\xab\x4d\x92\xca\xbd\x70\x8e\x8f\x17\x44\xb9\x66\x52\xe2\x7f\x0d\x7b\x6a\x80\x49\x86\x5d\xa2\xc6\xfa\xbf\xc6\xc7\xa2\x7e\x50\xce\x10\x91\x0b\x4b\xd9\xc6\x17\x6d\x7e\xc8\x8f\x07\x31\x02\x96\xef\xce\x64\xca\x2b\x21\xfd\x7c\x28\xf1\xae\x5d\x99\x40\x20\xe3\xf3\xad\xe4\x7b\x31\x27\xc7\x5e\xaa\x37\x3e\x72\xaf\xe0\x41\xcd\xca\x20\x53\xb3\xe1\xcd\xc2\x5f\xf9\x9c\x1c\xf0\xca\x37\x67\x8f\xbc\x3d\xad\xe0\x41\xcd\xca\x20\x53\xb3\xf4\xf5\x77\x4e\x4e\x7d\xe9\x97\xb7\x49\x5e\xa7\x15\x91\xa0\x06\x85\x72\x63\xf6\x8e\xbc\xeb\xcc\x09\x01\xef\x79\x72\xcb\x22\xaf\x4c\x2a\x78\x50\x9b\x32\xc8\x78\x11\x00\x79\xe7\xb5\xb5\x10\xed\xbd\x3e\x3e\xed\x20\xef\xc9\xc9\x68\xa0\x1d\x89\x10\x53\x9b\xf4\xe5\xc6\x0b\x7b\xb1\x0e\xde\x54\xd6\xbd\x68\x23\x6e\xc5\x9f\x16\xf7\xce\x52\x8f\xb5\xef\xd5\x15\xf0\x07\x87\xa0\x91\xa5\x46\x04\xb2\xc3\x86\x74\xcb\x3c\xa4\x69\xaf\x6d\x43\x5a\x7b\xb0\x8c\x55\xe2\xef\x96\x71\x1a\xd2\x2e\x2d\xfe\x88\xd9\x05\x31\x06\xb8\x77\xa2\x7f\x5d\xe9\x9d\x9a\x30\x4c\x59\x49\x6a\x7c\xe4\x93\x7a\xce\x4e\x77\xda\xb8\x5d\x42\xbc\x95\xdf\x21\xe4\xb3\x77\x83\xf3\x97\xc9\xf3\xba\x52\x8c\x83\x71\x5e\x33\x54\x69\x1f\xce\x5c\x3d\x35\xdd\xb1\x23\xae\x3f\xc9\xac\xe4\xd7\xd3\x71\x9b\xd7\x65\x77\xab\x16\xd9\xcf\x34\x05\x76\xc4\x4f\xf5\x9d\x54\xa4\x88\x76\x24\xc9\x0e\xb8\x4c\xa0\x85\x0b\x09\xe7\x6d\x33\x8e\x77\x08\x26\xc2\x9f\x87\xe0\x2c\x11\x10\x51\xd5\xed\x7f\xca\x01\x94\x30\x50\x4c\x3e\xf4\x7d\xa1\xfa\xdb\x43\x29\xce\xd7\xf8\x00\x9e\x37\xff\x2e\xe2\x49\x91\xb6\x86\x76\x0a\xca\x01\xc4\x63\x3d\x7b\x09\xdc\xba\x25\xf4\xbc\xa5\x7e\x56\x6e\xa0\xa6\xf9\x77\xa7\x8a\x4a\x8c\x33\x7a\xb2\x4d\xdf\xde\x05\x6b\x6a\x76\xa3\x6b\x6a\x46\xb6\xc5\x3d\xb2\x87\xe2\x03\x5a\xb4\x87\x0b\x5f\xe9\x8f\xa6\xc9\x4e\x37\x8b\x29\xd9\xd3\x5e\x1f\x4e\xc7\x6d\x86\x92\xd4\xf0\xbc\x8c\xbe\xcd\x2e\x54\xcf\x5d\x88\x57\x93\x5c\x3d\x45\x15\x5f\xc8\x12\x1e\xea\xa3\x48\x8e\x17\x56\x0e\x46\x15\x76\x93\xcc\xcd\x4f\xb5\xf2\xd8\x9f\x01\xa9\x17\x43\xe8\xbd\x43\xae\x7c\x99\x74\x05\xec\x06\x18\x61\xcc\x8a\xe7\x3f\xba\x13\xf9\xa8\xab\xc2\x1f\x0b\x11\x8a\x5a\xd7\xd6\x95\x40\xf7\xae\xf0\x59\x48\xc7\x8e\x17\xa1\x82\xe4\xfd\x84\x03\x4b\x1b\xf1\xeb\x19\x4a\x71\x59\x9f\xa5\x83\x5f\x23\x8f\x48\x43\x09\x42\x42\xd5\x39\xcc\xe4\x2d\xad\xca\xf0\xa7\x48\xf4\x7f\xf4\xc7\x59\x97\xcd\x10\x27\xb0\xb7\xc5\x84\xfd\x38\x69\x3b\xbc\x5b\x94\xd7\x85\xfa\xc0\x24\x23\x1c\x27\xd5\x31\xa9\xc8\xac\x7d\x22\x17\x25\x5b\xed\x75\x80\x29\x5c\xd1\xf1\xa2\x34\xaf\xa0\xfa\x0c\x62\x0a\x6e\x4d\xa8\x66\x1b\x2c\x89\x0f\x83\x24\xd0\x4e\xef\xb8\x5a\xa2\xe5\x62\x0a\xad\x1e\xe2\xdd\xce\x8f\xd5\xfd\x94\xf1\x02\xaf\xa2\x85\x42\xca\x01\x1d\x62\xb4\xc2\xe1\x76\xaa\xa2\x8a\xf2\xe7\xb3\xcf\xed\x7c\xd6\xcc\x56\x28\x84\x4c\x03\xdb\x29\xdb\xd2\xbf\x81\xdf\xf0\xc6\xf1\x4e\xcd\x59\x6d\x23\x7c\xb3\x0b\x44\x3a\x30\x63\x68\x81\x03\x2c\xb5\x07\x72\x35\x9b\x87\x8b\x15\xc7\x52\x9e\xf4\xbf\x41\x8b\x78\xba\x85\xfc\x46\xb4\xbb\xc1\x53\x85\xb1\x1d\xc2\xdb\x28\x52\x48\xc1\xbc\xed\x96\x38\xd8\xce\x55\x54\x80\xbd\xc5\x62\x1e\x74\x42\x93\x9f\xf3\x46\xab\xd9\x6c\x16\x42\xdc\x85\x31\x8e\xb5\x77\xd7\xb7\x51\x14\x07\x32\x25\x98\x39\x3c\xdb\xae\x22\x5f\xc1\x04\x78\xbb\x99\x4d\xe7\xd3\xd9\xe5\x0f\xdc\x31\xbe\xc3\x0f\xbb\x12\x1d\x71\xe5\x14\x65\xbe\x2f\x71\x55\xb9\x5b\x72\xa2\xb6\x4c\x0a\x5c\x9d\x77\x65\x7e\x14\x27\xb1\xad\x71\xcf\x48\xf2\xe2\x52\xe7\x20\xd4\x77\xfc\xcb\xe5\x0f\x6e\xfe\x5e\xc9\xbf\x47\xda\x1e\xa7\x78\x16\x4e\xa9\x41\xde\xb0\xff\x1e\x30\xd3\x07\xa1\xbe\x13\x55\xda\xd3\x8c\xc6\x03\x53\x3a\x66\xc7\x3f\xd9\xee\xa0\xdd\xef\xdd\x3e\x38\x4a\x6e\xaf\xb2\xe4\xee\xc2\x6e\xae\x67\x58\x42\x18\x73\xd6\x96\xee\xb9\xc2\x49\x2e\xe9\x7d\xcb\xa1\x98\x40\x54\x27\x1d\x73\xbc\x05\x8d\xc6\x4a\x34\x57\x80\x46\x88\x2c\x37\x66\x49\xf1\xa4\x2b\x65\x25\x8e\x2c\x5f\xed\x94\x21\xe7\x8f\x3e\x03\xec\xb6\x0f\x02\xce\xe6\x31\xde\x4f\x80\x13\x6c\xf3\x57\x4e\x38\x7f\x31\x11\x42\xa9\xf6\xf7\xdc\x7f\x61\xa8\x69\x86\x2c\x15\x1a\xca\xdf\xaf\xf4\xa3\xc5\x6e\xfe\x19\x32\xfd\xc9\x73\x0c\x3c\x18\x4b\xc6\x1b\xf1\x44\x33\x5f\x4e\xc4\xca\x10\xd9\x24\xdb\xc7\xe3\x78\x21\x2b\x50\x0c\x92\xb7\x87\xb2\xe4\x48\x17\xb8\x90\x87\x74\x42\xfe\x50\xb5\x93\x64\xbb\x24\x4b\x6a\x32\x6e\xc6\x57\x1a\x5d\x43\x1d\x67\xbd\x49\x2d\xfb\x00\x84\x08\x3c\x0f\xc4\xe7\x81\xa8\x71\xac\xd8\x5d\x4f\x56\xb3\xc7\xe8\xd4\xda\xcf\x16\xf7\x6c\x71\x7d\x16\xd7\x9f\xd9\xee\x31\x3a\x80\xc0\xb3\xdd\x3d\xdb\x5d\x9f\xdd\xf5\x7e\xda\xe8\x31\x3b\xbd\xfe\xb3\xd5\x3d\x5b\x1d\x60\x75\x24\xa7\xad\x1e\x92\x66\xc5\xd0\xf3\xb7\xec\x2d\x09\x02\x9f\xd0\xff\x71\xb7\x79\xfc\x70\x56\x17\xd6\xbf\xe5\xf9\x71\x1d\x5c\x44\x14\x96\x4b\xc7\xb3\xb6\x05\x37\xdf\xfe\x8a\xa3\x5a\xbd\x6d\x52\x84\x79\xc9\x71\xef\x76\x19\x6a\xf5\x42\x6e\x8a\x4a\xf2\x74\x8c\x9d\xb7\xc2\xa3\xe3\xf2\x46\x0e\xfa\xc1\x8b\xd6\x68\x0a\xe4\x0a\xe4\x44\xb6\xf2\xfd\x40\xa8\xd0\xf4\x60\xa2\x57\x66\xed\x00\x57\xad\x2a\x1f\xe6\xea\xbc\xe0\xa4\xe8\x37\xba\xb3\xe1\xb6\x09\xde\x1c\xb9\x28\x4c\xfd\xd0\x4f\x4a\x39\xce\x01\xa3\x86\xd9\xb3\xe5\x4e\x8b\xb9\xd0\xe3\xa4\xaa\xd5\x8d\x2d\xda\xc6\x95\xee\xfb\x95\xfd\xde\x7c\x7a\x09\x90\xf2\xb5\x6b\xfc\x4d\xf8\xc0\x8d\xe8\xe3\x76\x79\x2a\x1c\x0c\x79\x46\xb0\xe7\x3e\x04\x9d\xa6\xf9\xca\x8a\xc7\x3d\x2e\x89\xd4\x96\x26\xf4\xf1\x3b\x4d\xac\xdd\x39\x4a\xbd\x8e\xa3\x16\x70\xab\x30\x10\x33\xe2\x8b\x47\xfc\xb4\x56\xba\xcd\x23\x4a\x39\xdd\x61\x03\x37\xc5\x2a\x19\x80\xda\x11\xd1\xa1\x5b\x7e\x49\x7a\xee\x62\x10\x95\xf8\xc8\x63\x97\xfa\xa2\x7b\x89\xd4\xcf\xc3\x7c\x3f\xd0\xc4\x08\x69\x77\x0b\x98\xe0\xd7\xed\x93\x24\xdf\x5a\x4d\x34\xcd\x0a\xed\x61\xf3\x9a\x8a\x84\xff\x3e\x93\x68\x3f\x79\x0c\x67\xb9\x91\xfd\x78\x7e\x7b\x6a\x19\x98\xd5\xae\x76\x80\xb7\x01\xe8\x84\x69\xb9\x49\xc7\x0c\x6a\xd8\xf1\x6c\xbb\x96\xdc\xb0\xfb\x17\x6e\x60\x84\xd6\x7a\x2a\xbc\xf5\xaa\x23\x4a\xd3\xf1\xf5\xac\xd5\x46\xdb\xd6\xa0\x6a\x3d\xbc\xf6\xd5\xb6\x57\xb6\x1b\xf5\x95\xd5\xfa\x38\xee\xa9\x4d\x2a\xf7\x8d\x27\x93\xa2\xe0\x71\x61\x17\x95\xb5\xce\x80\x91\x14\x2d\xe3\x18\xeb\x9b\x5e\x46\x7e\xf0\xd4\x23\x09\x27\x60\x08\x0b\x30\x7d\x33\x99\xb1\xe1\xaf\xb7\x9e\xa2\x23\x63\xc3\xa6\x88\xd8\xc2\x6d\x91\x51\x21\xd2\x83\x24\x9f\xc0\x37\x0a\xdc\x27\x1f\x93\x8d\x0c\x75\x3b\x13\xec\x08\x7d\x1d\x93\x6c\xa8\x87\x75\xde\xe6\x20\xac\x41\xc2\x90\xdd\xb2\xdd\x19\x33\x49\xc9\xce\x98\xda\x93\x46\x7e\xc4\x87\x72\x40\xc4\x4d\x6d\x13\xe3\x3a\x65\x03\x81\xd1\xa6\x6c\xad\xd4\x6b\xc7\x4d\x6d\xa3\xae\x09\xd0\xaa\x61\xa1\xba\x0d\x43\xb6\x5d\x93\x60\xa3\x19\x9e\xee\x80\x69\x27\xa1\x61\x36\x5c\x01\x6a\xed\xc9\x20\x93\x15\x9b\xea\x47\xe9\xef\xfa\x18\x4b\x65\x72\x91\x2d\x95\xda\x8a\x46\x7b\xdc\xe6\x09\x40\xa6\x8c\x80\x89\x77\x90\xbe\x99\xcc\x68\xab\xed\xab\xd7\x6b\xb8\x8c\x80\x51\xe3\x1c\x6e\xd5\xb6\x4c\xa4\x07\x49\x52\xa1\x59\xe0\x68\x17\x46\x91\x99\x61\xb3\x1d\xcb\x08\x7d\x1d\x1b\x64\xcd\x4a\x9b\x83\xb0\x06\x09\x63\x84\x59\x73\x49\x49\x66\xcd\xec\x49\x23\x3f\x6a\xd3\x0d\x20\x64\x5a\xdf\xc4\x3c\x44\xdd\x48\x64\xb4\x49\xf7\x54\xeb\xb5\x68\x5a\xdf\xa8\x77\x06\xb6\xea\x5b\x22\x61\xc7\x91\x34\x67\x14\x34\xde\x46\x11\x68\xcd\x94\x8a\xd9\x98\x25\x78\x4f\x9f\x06\x99\xb2\xdc\xe0\x10\xa4\x21\x62\x18\x61\xc7\x5c\x46\x92\x1d\x33\x1b\x32\xea\xbc\x2f\x27\x07\xce\xbd\xf5\xb7\x29\xa5\xe3\x8e\x17\xaf\x40\x99\x76\x53\x6e\xe8\x0f\x4f\x9a\x59\xb7\x7e\x42\x3b\x80\xe8\x86\xa5\x40\xd9\xb0\xe4\xcb\x9b\x7f\x8c\x48\x8c\x61\x9a\xfd\x15\xf7\xa9\x72\x00\x97\x96\x9e\x18\x1c\x72\xc5\x75\xff\x09\x43\xe8\x08\xa0\xd2\x36\xf8\xf6\x16\xbb\x11\x47\x59\xaf\xd1\x7a\x75\x52\xa7\xd8\xa6\x5f\x5f\xdc\xa0\xb5\xd0\x77\x8e\x0a\x64\xda\x55\xa5\x5e\x46\x8f\xba\x75\xa5\xca\x9f\x40\x3d\x56\x0d\xe6\x7a\x97\xe7\x75\x77\xb0\x51\x14\x74\xcf\x06\x38\xf9\x16\x72\xe0\x01\xc6\x9e\x23\x96\xc0\xe9\xce\x0c\xa7\x6f\x85\x01\x30\xe1\x45\x94\x53\x7e\x7b\x8f\x88\xa2\x6f\x1d\xd6\xa8\x68\xee\x77\x00\x59\xad\xce\x59\x7f\x7f\x63\xa3\x3d\xa0\xae\x35\x2d\x66\x9d\x35\x92\xf2\xeb\xd9\xbd\x2c\x0d\xa6\x75\x96\x1e\x4c\x7a\xd4\x50\x90\xda\xef\xb2\xdd\x7a\xf3\xe2\xe3\xd2\xfd\x3d\x19\x46\x49\x7d\x50\xee\x69\xac\x4b\x1a\xe2\xaf\xc7\x98\xc0\x90\x4f\x08\xd6\x8b\x9c\x95\xa6\xaf\x68\x8a\xed\xf9\x14\x83\xc4\x6b\x79\x18\x43\xc8\x06\x85\xd4\x74\xf7\x3c\x83\x02\x7f\xb9\x25\xae\x8a\x3c\xab\xc8\x51\x29\x52\x62\x1c\x6d\x20\x6d\x87\x1d\x53\x90\xa9\xc2\xa5\x7a\x5b\x8e\x72\xc8\xc1\x7a\x72\xe7\x62\x22\x26\xbf\x3a\x4d\xa0\xe0\xb8\xd3\x20\x03\x4e\xa9\x5b\x07\xce\x48\x46\xde\xd6\x4d\x28\x94\x4b\x4a\x33\xa7\x63\x08\x37\x06\x37\x98\xf0\x23\x78\x1a\xd5\xea\xa7\x26\x5e\xa7\x8e\x9f\x46\xda\xbd\xed\x1c\x3e\x88\x56\x9f\xac\x3f\xbd\xed\x58\xfa\xf3\x74\x1a\x78\x3a\x19\x3f\x9d\x14\x1f\x23\x27\x8b\xf9\xbf\x57\x13\x07\xe2\xf4\xfb\xb0\xf0\xa7\x68\x66\x80\x6a\x3e\x48\x33\xe6\xde\x3c\x99\xf4\x9f\x4c\xbe\x4f\x26\xc1\x47\xc8\xc8\x76\xc9\x8a\xd9\xb6\x85\x9b\x5a\x18\x07\x46\xd6\x4c\x33\xc4\x6b\x67\x84\xa3\x78\x61\xea\x11\x0b\xca\x41\x76\x68\x27\xda\xcc\xde\x06\x12\xbd\x96\x99\xe1\xed\x7d\x7a\x62\x1d\x1c\xcd\x1e\xd5\xc6\xc0\xc8\xfc\x08\x4d\x3e\x51\x3f\x7a\xda\xb0\x45\xbf\xa7\x11\xf9\x13\x49\xf5\x89\x04\x77\xb5\x6c\xce\x1f\xc7\x92\x1f\xef\x2e\x7a\x45\xfe\x9e\x3d\xd2\xd3\xf4\xa2\x57\x55\x8f\x77\x81\xd6\x18\xf7\x24\x12\x7d\x1a\xa1\x5d\x2b\x97\x1e\x57\xad\xac\xcf\x9b\x4e\xbe\x56\x56\xfa\x1a\x44\xd0\x9f\xcc\xe3\x6b\x01\xd7\xa8\x72\x11\xe9\x6c\x4c\x14\xca\x43\x6b\xc8\xdc\x69\x32\xb6\xc6\x41\x4a\x83\x29\x63\x99\x82\xb0\xd9\x72\x55\xc4\x33\x7f\x15\xcc\x40\x88\x32\xf4\xb6\x2e\xdf\x5a\x3d\x3c\x80\x6e\x0b\x3a\x1d\x7a\x63\x12\x23\xa8\xb7\xe8\xc3\xa8\x37\xf3\xbb\x11\xd4\x5b\xf4\x61\x01\xf3\x4a\x59\x0d\x22\x70\x2d\x07\x83\xe4\x39\x88\xc0\xb5\x1c\x0c\x92\xf9\x20\x02\x60\x48\xe3\x2f\xfd\xf5\x5b\xab\xd1\x8f\x83\xe2\x1e\x80\x2d\xc8\x76\x0c\xf6\x20\xda\x82\xd4\xc6\x60\x0f\x0a\x88\xd7\x49\x69\xa0\x99\x5e\x55\x7f\x88\x24\x07\x1a\xe9\x55\xf5\x87\x48\x7b\xa0\x89\xea\x21\x8b\xbf\x39\xd7\x63\x74\xb2\x93\xef\xb5\x51\x65\xe1\xda\x67\x1a\x43\xa9\x83\xe8\x2a\xf5\x01\xa6\x60\x6f\x6f\x34\x81\x31\x1c\x0c\xea\xf1\x68\x02\x07\xf5\x2b\x4d\xaf\x3e\xc5\x89\x4d\xbf\x3a\x45\x6c\xb3\x36\x99\xa1\x0f\xa4\x0d\x61\x5f\xa3\x4b\x5b\x6b\x63\xeb\x8f\x69\x7f\x48\x6f\xc7\xd6\xef\xd3\x63\x47\xcf\x70\xd4\xa5\xfb\xc0\x05\x7d\x97\xa5\xa7\x82\x04\xb8\x43\xff\x30\x9d\x9b\x91\x6f\x2d\x57\x6b\xbd\x96\x2b\xb7\x77\x43\xe9\x98\xed\x3e\x02\xb8\x77\x20\xae\xed\xa3\xe0\x64\x54\xbd\x11\x53\x61\x99\xa2\xf6\x31\xcf\xc0\x31\xc5\x53\x1b\x76\x80\x86\xb5\xed\x12\x62\xdb\xed\xad\xab\xf2\x25\x54\x1a\x82\xf2\xf5\xd4\xfa\x56\x9d\xb2\x59\x60\x28\xcd\x81\x42\x1c\x4c\xcf\x91\xef\x71\xe4\x4c\xc1\x8f\x0e\x81\x84\x60\x19\x43\x7c\xb1\x45\x98\xce\x5a\x7b\xc3\x2c\x78\xfc\x43\xc2\x31\x88\xf8\x8a\x53\x25\x36\xb2\x23\xa5\x3c\x84\xa4\x33\xe6\xc2\x4c\x90\xd0\xb5\x82\x96\xb9\x6b\x2f\x10\x01\x2f\x54\x93\x70\x4c\xe6\x7c\xd5\x55\x6d\x36\xca\x63\x8d\x7a\x00\x49\x45\xdc\x9c\x35\xc3\x5e\x77\x03\xad\xab\x4d\x5b\x62\x90\xde\x9e\x01\xde\x11\xd7\x21\x98\x64\x7d\xd5\xd5\x73\x46\xb2\x23\x05\xdd\x4b\x4f\x95\x32\x63\xca\xb0\x4f\x1b\x22\x74\xad\x88\x65\xd6\xda\xbb\x22\xc0\x0b\xef\x24\x1c\x83\xa0\xaf\xbb\x4a\xcf\x46\x79\xa4\xac\x87\x90\x54\x9d\x35\x63\xcd\xb0\x7f\xd8\x40\xeb\x5a\x89\xcb\x0c\xf2\x6b\x12\xc0\x3b\xfc\x44\x14\x83\xbc\xaf\xbb\x1c\xd0\x42\x78\xa4\xb8\x07\x50\x54\xa5\xcd\x18\x33\xec\x72\x85\x49\x5d\x2b\x6c\xce\x1e\x3e\x6e\x71\x2c\x4e\x2e\xfb\x0e\x8a\xb3\x3d\xaf\xdd\x75\xd1\xbe\x7a\x71\x9f\x4e\xd4\xd1\x4a\xd8\x36\x40\x0d\x91\x14\x00\xe5\x09\xb9\x9d\x10\x00\xd0\x9b\x08\x00\xc0\x6d\x12\xe3\xbc\xeb\x0d\xda\x56\x79\x7a\xaa\xe9\x05\xa2\xcd\x2c\x97\xef\xe3\xa5\x47\xe8\x85\xc3\xc9\xe2\x75\x7e\xdd\xcc\x5a\xeb\x40\xb0\xd8\x3e\xac\xce\xca\xc5\xc1\xf3\x85\x17\xce\x5f\x00\xd8\xb3\xed\xc3\x54\x45\x5e\x36\x98\x77\x38\x4d\xcf\xc7\x24\x93\x6e\x09\x6c\xb7\x82\xae\x0c\xf7\xc6\xda\xe7\x7a\xe2\x24\x13\x4f\x9b\x7f\xe3\xaf\x4c\xec\xd9\x57\xdc\x83\x4a\xfb\xe5\x10\x8b\xf9\xc7\x29\xaf\x81\x67\xbc\xe5\x01\xa8\xdc\x4a\x48\xeb\xbb\x69\xb7\xfd\x38\x9c\xc1\xb7\x43\x13\xbc\xea\x28\x5d\xc4\x2b\xa3\x91\x2c\x3c\xbd\x4e\x56\xb8\x0f\xdc\x76\x0d\xb6\xf1\xe6\x77\xdf\x67\x4f\x8c\x48\xbb\xab\x7d\x87\xcc\x0e\x77\x49\x5a\xe3\x72\x8d\xd2\xe2\x80\x5e\xe6\x05\x8a\x92\xfa\xe1\xf7\xa1\xff\x6a\xc3\x7e\xaf\xbd\x90\xf1\xc1\xcf\x35\xd3\x3f\xa4\x6d\xee\x6d\x0b\xf6\x0b\xd5\xe1\xc6\xe6\x62\x63\xed\x39\x7c\xda\xf5\xf6\x1a\xbb\xa2\xc0\xa8\x44\x59\xc4\x5e\x5d\xeb\x46\xb1\xd2\x42\x67\x63\x6b\xdf\x11\xd7\x98\xc7\x3c\x46\xa9\x9b\x17\x38\x53\x2f\x16\x61\xb0\x6e\xd0\xed\x92\x7b\x1c\xb3\x11\xc7\xb2\x4d\xda\xc8\xe3\xe7\xb7\x03\x7f\xee\x6f\xc4\x5b\xee\xb5\xfb\x40\x79\x17\x78\xb9\x5b\x45\x65\x9e\xa6\x0d\xf7\x75\x7e\x8a\x0e\x9b\xfc\x54\x37\x6a\x6b\x99\xf4\x76\x28\xc6\x0e\x63\x38\x4e\x50\x9a\xef\xcf\xc0\x75\x97\x52\xd1\x2e\x2f\x8f\x8e\x37\x65\x17\x51\xeb\x97\x59\xf3\xbf\x74\x3c\x01\xc9\x44\x49\x6d\x88\x22\xa6\xa8\xc6\x2f\xfd\x89\x1b\xce\x5f\xbc\xda\xb8\xc7\xca\x0e\xcf\xad\x60\x0b\x8c\x0b\x25\xc9\x6c\x22\xd1\xea\xfa\x36\x9e\x7c\x0b\x43\xbe\x89\x1b\xff\x95\x68\x44\x8c\x99\xd6\x96\xdc\x7b\xae\xef\xb6\xe4\x81\xde\xe5\x2d\x73\xad\xc7\x29\xea\xc3\x85\x7b\xc0\xf9\x25\x31\xa4\x1a\x7b\xdd\x0f\xa8\x07\x1f\xd0\x00\xee\x99\x8c\xd2\xa4\x58\x77\x4e\x5c\xf6\xc3\x1a\x4c\x73\xc5\xab\xd5\x4a\x2f\x15\x1d\x5f\xf8\x4a\xf7\x70\x9d\x51\xc3\x67\x40\xa6\xc5\xbd\xb3\x52\x1c\xb0\x7a\x04\x04\xc6\xe1\x82\x69\x3a\x11\x97\x79\x71\xed\xb0\x9d\xf9\xd0\x83\xa4\xbe\xaf\xd2\x27\xa3\xf1\x0c\x3a\x2e\xc1\x6f\xe9\xd5\x92\x0c\xae\xa4\x78\x3b\x56\x8d\x3d\x9b\x2b\xdd\xbc\x6e\xcc\xc0\x90\x57\x68\x94\xaa\xfc\xea\x71\x21\xb5\xe5\x86\x9d\x19\x89\x47\x4c\xb4\x63\x40\xfc\xa2\xfd\xb6\x0f\xe4\x70\x8d\x66\x70\xf2\x71\x1b\x8a\xaa\x1e\x02\x69\xf8\x16\xae\x49\xa1\x31\x0b\xce\x61\xc9\xbd\xa0\x84\xc8\x43\x9d\xaf\x9b\xff\xd2\x12\x7c\xe2\x1d\xf9\x1a\x03\x4e\xf7\x66\x88\x4e\xa2\x7d\x28\x12\xaa\x43\x62\xfd\xeb\xee\xa7\xfc\x52\x23\xaf\x41\x5d\xf6\x16\x95\xee\x11\xa3\xea\x54\x62\xc3\x0c\xcd\x5d\xad\x56\x4d\x28\xa7\x63\x7a\xde\x4c\x7a\x98\x94\xe7\xd2\x55\xd1\x94\x5e\xfb\xfe\xb2\xf6\xe6\x83\xe4\x32\x28\x68\xe1\x77\xf7\x4f\x93\x07\x38\x1c\xd1\xbb\x70\x37\x01\x0e\xb6\x39\x3b\x99\x63\x1d\x6d\x06\xa4\x56\x02\xfc\x26\x9c\x69\xc3\xc7\x45\x67\x7d\xb5\x0a\x05\xd6\x53\xce\xf6\x8a\xa2\x7b\x75\x9e\xa7\x75\x52\x00\x72\xeb\x86\xe4\xd2\x57\x26\xed\x64\x76\xb3\x43\xc7\x24\x7d\x58\x1f\x70\x7a\x8b\xeb\x24\x42\x4e\x86\x4f\x78\xf2\x9f\xf9\x9f\x93\x6f\xca\x04\xa5\x93\x0a\x65\x95\x5b\xe1\x32\xd9\x81\x8f\xe3\xf0\x1b\xa9\xca\x23\x4a\xa5\x49\xd3\x4c\x9d\x34\x75\xcf\x4e\x28\xd7\xfd\x88\x7f\x57\x35\x2a\x6b\x78\xc6\x23\x4e\xb4\xba\x82\x2e\xa4\x90\xb2\x14\xd7\x35\x2e\xc9\xb3\x3b\xcd\xb0\x61\x7c\xdd\xe5\x65\xec\x6e\x4b\x8c\xde\x49\x25\x10\xd6\x5d\x89\x8a\xb6\x40\x7a\xc2\x87\xf6\xb0\xcf\x5d\xd1\x3e\xd3\xb6\xa8\x1d\x31\x05\x19\xfd\xd6\x4a\xf4\x5b\xab\x0e\xbf\xce\x0b\xe9\x2d\xc0\x76\xa4\x92\xf1\x40\x66\xb1\x1c\x55\xba\x40\x8d\x3d\x64\xab\x3d\x4c\xc3\x91\xd9\x75\x65\x46\xd2\x12\xb2\x78\xd3\x1a\x40\x58\x62\xc3\x4d\x32\xf9\x29\x96\xd0\x57\x9f\x58\xba\x19\x7f\xe5\x79\x33\x03\x06\xbe\x41\xf0\x36\x51\x59\xe6\x77\x80\xf1\x2b\x97\xb1\xfb\xf2\xf2\x02\x38\x9a\x48\x0d\x99\xb8\x50\x49\x07\x8e\xd2\x94\x1c\xf8\xe6\xfe\x0b\x59\x20\x42\x7c\x61\xbe\x8a\xbe\x9e\xe4\x48\x07\x91\xc4\xa8\x28\xb4\x45\x68\xa8\x0d\xb6\x4f\x10\x6d\x54\x97\xcd\xaf\x62\x7b\x5c\x9b\x84\x7e\x4f\x2f\xf5\xeb\xdf\xae\x6f\x13\x6c\x8f\x7c\x31\xf2\x5f\x6c\xe4\x1b\xec\x88\xa5\x1b\x5b\x92\x5a\xa3\x1b\xad\xa0\xf6\x20\x99\xf2\xe6\xf8\x6c\xa6\xb7\x3d\x9f\xb7\xb8\x11\xb6\x73\x80\xcd\x51\x01\x41\x0d\x0e\x36\x19\xad\x31\x39\x55\x03\x34\x07\x1a\x8e\x38\x61\x13\x34\x08\x77\xf2\x8a\x36\x8d\x8a\xd4\xad\xe6\xfa\x36\x8b\xbc\x20\xeb\x61\x53\xe2\x46\x9b\x7e\x2e\x94\x55\xa3\xe0\x8e\x96\x0b\x31\x97\xc2\x23\xd8\x63\xc2\xe0\xec\x5f\x34\x0c\x7e\xa8\x25\x52\x14\x45\x57\x2c\x91\xcc\x73\x35\x5f\x99\x86\x85\xd0\x5c\x0d\x40\xd2\x82\x3a\x33\x4b\x12\xa4\x45\x1b\xa7\x2b\x4c\x0e\xa5\x71\x59\x1c\xec\x32\x9c\x85\x62\xf1\x4a\x57\x09\x4e\xa2\xaf\x3c\xd9\x16\xe1\x6c\xf1\x21\x3d\xae\xdf\x3d\x00\x26\x1f\x6e\x37\xbc\x48\xbb\x6c\xfe\x59\x16\x44\xdb\xe6\x9f\x22\xe2\xd6\xf7\x3a\xdd\xf8\xec\x96\xd3\x5d\xde\x8d\x30\xd3\x62\xbc\xf5\x88\x83\x98\x28\x7f\xaf\xd1\xae\x06\x87\xb7\x3c\x61\x7d\x5c\x38\x97\x9b\x54\x4e\x8e\x07\x3a\x93\x8c\x29\xd6\xa7\xf5\x17\x5f\xc8\x7e\x4b\x56\x52\x9d\x17\x1d\x61\x76\x37\x2a\x79\x3c\x1f\x74\xf6\x04\xa4\x47\xc8\x2e\x17\x20\x94\x4a\x46\x38\x7f\xa5\x78\xc9\xee\x34\xb1\xc6\x08\xe3\xbf\xd3\xe7\x46\x33\xa2\x4d\xdb\x39\xe7\x0b\x80\x1f\xe1\x7d\x70\x53\x73\xc4\xb8\x79\xcf\xa5\xf8\x4d\x3b\x29\x0d\x0b\xa1\xd7\x52\xa4\x16\xfa\x2d\x96\x1b\x7a\x4e\x82\xae\x95\x11\xb9\xe7\xb4\xa7\x74\xe0\xc1\x5d\x96\x98\x11\x3a\x0d\xb7\x44\xc9\x8a\x7d\x1e\xa3\x6a\x6e\xc3\x60\xb0\x13\x04\x21\x01\x14\x49\x18\x78\x61\xdd\x66\x89\x80\xe1\xda\xb6\xb2\x44\x37\x06\x08\xae\x48\x55\x36\x7b\x72\x6d\x80\xb6\x95\x76\xc4\xc9\x93\xd0\x71\xa1\xd8\xd8\x6d\x81\x0f\xd6\x69\x76\x0a\xbd\x9b\x1f\x1b\x7b\xdc\xcb\x0b\xe9\x71\x84\xca\xfc\x54\x41\x8f\x23\x77\x30\xb6\xd4\x31\xe5\x1c\xc9\xb7\x22\x2d\x19\x2e\x57\x7e\xeb\x25\xf6\x7b\x98\x49\x20\x07\xd2\xd3\xfc\x41\x25\xf6\x2a\xa2\x43\xe6\x0e\x72\x52\x1a\x44\xe9\x81\xc3\xfc\xbd\x65\x0f\x2d\x82\xb0\xe4\xb8\x3f\x4b\x73\x1b\xfe\xee\x26\x4a\x53\xfa\xe8\x66\x3b\x2d\x71\xa7\xf1\xab\xc9\x4b\x2d\xb3\xdc\x14\x9f\x61\xc1\x0c\x4b\xcc\x2f\x6c\x0f\x4d\xca\xb9\xf9\x85\xf1\xb9\x49\x0b\x3d\x61\x46\xb3\x43\x11\x76\x6f\x93\x2a\xd9\x26\x69\xb3\x56\x17\x5e\x25\x33\x80\x78\xed\x02\x97\x55\x81\xe9\x25\x46\x01\x9e\x36\x93\x50\xb5\x04\x96\x3e\xbb\xf9\xc8\x63\x77\xa6\x83\x28\x19\xbe\xaf\xcf\x6c\x16\x6c\xc9\xdc\x4f\xe3\x97\x8d\x55\x4e\x4c\x79\x78\x11\x6e\x67\x86\xde\xa8\x0e\x62\x14\x25\xbe\x1d\xc6\x8b\xdb\xc7\x8c\x3b\x8c\x1b\x8b\x50\xfa\x18\x65\xb3\xb3\x41\xec\xfa\x36\x56\x29\xf0\xa2\xf3\x69\x62\x31\x23\x17\xbe\xaa\xa5\x44\x78\xca\xed\xfa\x30\xc5\x33\xcf\xa1\x8e\x20\x6c\x5a\x3f\x09\x8f\xe6\x82\xe4\xce\x6c\xda\x0a\xc1\x3b\x75\xbb\x66\x02\x06\x35\x68\x1a\x30\x75\x96\x4e\x82\x6d\xad\x88\x83\x04\x64\xb7\x09\x05\x65\x9e\x8e\xfd\xf8\x3f\x17\x9f\xef\xeb\x7f\xad\x4f\xff\x10\xac\x3e\x1e\xb8\x78\xa5\x4f\xc1\xc5\xa9\xeb\xa0\xcf\x1a\x6a\xaf\xa8\x7c\x06\xbf\x1e\x42\xb4\x21\x67\xa2\x1d\x5f\x2a\xf0\x7d\x3f\x78\xe5\x34\x22\x1c\xf6\xb4\xc7\x63\x29\x32\x46\x3b\x7a\x84\xfe\x84\x64\x34\xea\xbc\x98\xd0\x34\x43\xf3\x6b\x57\xe6\xc7\x97\x72\x4b\xaf\x26\x75\xfe\x52\x6b\xeb\xd5\x80\xf7\x3d\xea\xdc\xa1\x6e\x75\x30\xeb\x4c\x3d\x45\x99\xef\x93\x78\xfd\xc7\xbf\xfe\xd0\xd0\xfd\x0b\x77\x09\xde\x9f\x93\xa8\xcc\xab\x7c\x57\x7b\x6d\x1b\x64\x19\xff\x5d\xa3\xe7\xaa\x2e\x7f\xff\xd5\x97\x37\x3e\xfd\xbf\xaf\x26\x38\x8b\x85\x72\xbf\x2d\xff\x4f\xac\xea\x5f\x1e\x0a\xfc\xfb\x40\xea\x46\x89\x0b\x8c\xea\x35\xfd\x1f\xf7\x1e\xb0\x04\x3a\x02\x78\x4e\xab\x7d\xf4\xd8\x2c\xf3\x5e\x55\xd2\xee\xfb\xaa\x8c\x1e\x61\x1c\x23\x29\x3e\xc2\x38\xa8\x25\xa8\xf6\x31\xbf\xde\x38\xac\xac\x3f\xde\x38\x7c\x83\x71\xdc\x3c\x89\x71\xb4\x7b\x4c\xd4\xf2\x61\xef\xf7\x9b\xbf\x5c\xb4\x9f\xa3\xc5\x6f\x18\x6a\x2b\x8e\xb7\x4f\x1f\x8a\x43\x12\xe5\x99\x1b\x1d\xf0\x6d\x99\x67\xae\x12\x17\x2c\x98\xea\xf4\xa7\x45\x25\x58\x4a\xd0\x93\x81\x96\xd8\xd7\xac\x60\x78\xda\x70\xde\xce\xba\x93\x8c\xe6\x7a\x48\xd6\x41\xcf\xec\x3c\xa6\x63\x1d\x43\x86\xf5\xe2\xe0\x16\xfa\x05\xc2\xbf\x1a\x74\x6d\xf0\xc5\x9a\xa1\x91\x81\xa2\xe4\x1f\x76\xba\xef\xaf\xe4\xb7\x98\x48\xa5\x49\x52\x79\x5d\x60\xa3\xb9\xde\xe2\x5d\x5e\xe2\x36\xdb\xf2\xd5\xdf\x43\x7f\xba\xfa\xca\xca\x24\x58\x07\x7d\x25\xcd\x0d\xe2\x24\x42\x75\x5e\x56\x80\xfa\x79\x62\xc4\x17\xd7\xee\x6d\x0a\x79\xbe\xe1\x1f\x86\x5f\x6c\xe0\x97\x75\xd8\xb7\x2f\xf9\xe5\x12\x36\x0d\x20\x47\x53\xc4\x77\x7a\x00\x96\x9c\x34\x39\x83\x06\xd7\xe5\x97\xba\x6d\x87\x5d\x42\x2f\xe0\x7b\x00\x1a\x46\xb3\x9a\x7c\x14\x6f\x66\x25\xa6\x8d\x5a\x42\x22\xfd\xef\x2b\xfb\xd4\x43\x4f\xbd\x0a\xc9\x10\x96\xf7\x53\x4c\x47\xe8\x0e\x9f\x97\x32\xf6\x43\x81\xfd\x50\xcc\x47\x1a\x0e\x1f\x74\x8a\xe6\x17\xcc\x69\x1a\xe3\x57\xcd\xbd\xd8\x88\xfb\x1e\xd9\x6d\x73\x82\xee\xda\x6d\xa8\x64\xd0\x8a\x9b\x27\xa5\x0d\x93\x8f\x9e\xc8\xe9\x4c\x93\x5d\x0f\x67\x35\x3b\xcf\x57\xc4\x55\x54\x62\x9c\xd1\x45\xb1\xbe\x2b\xe1\xd3\x72\x97\x7c\x3b\x42\xa7\xc5\xa9\xaf\x66\x78\xda\x41\x4f\x26\xc7\xd3\x27\xf4\x8d\xef\xcf\x25\xda\x1d\x21\x33\xbd\x92\x39\x35\x96\xca\x0c\x05\x1f\xc0\x0c\x68\x6a\x1a\x06\x67\xf1\x68\xdd\xa5\xdb\x38\xe3\xf2\x97\xc1\xde\x76\x65\x34\x77\x35\xe9\x41\xa2\x5e\x8e\x62\xd5\x79\x9e\x6e\x51\x29\x56\xe4\x45\x1c\x2d\x4a\x31\x2a\x77\xc9\x3d\xc7\x69\xff\x6e\x11\xf2\xac\x46\x49\x86\x4b\x77\x97\x9e\x92\xb8\xc5\x53\x8a\x35\x74\x0d\xb1\x45\x89\x53\xf7\x90\x97\xc9\x6f\x0d\x20\x75\xe2\x96\xa4\x56\xce\x2b\x90\xac\x8f\x00\xa2\x05\x92\x4c\x6c\x28\x9c\x8c\xb8\xdf\x88\xd7\x93\xca\x64\x44\xba\x95\x4b\x46\x64\x65\x1c\x31\x43\xb7\x1c\xde\xfc\x14\x8a\xb7\xa8\x6c\x37\xd2\x0b\x28\x52\xb1\x82\x2e\xb7\x27\x17\xca\xa8\x32\x4e\x0b\x2c\xd0\xbe\xab\x4f\xff\xe8\x40\x7c\x27\x7f\x07\x6f\x4b\x38\x52\x9b\x1e\xa5\x3f\x59\xb8\x94\x9e\xbe\x13\x33\xa4\x23\xac\x55\xb1\x43\xcd\xe6\x7a\x4c\xcc\x68\x20\x03\x94\x0f\xe9\x19\x52\xa9\xa0\x46\x83\xba\x40\xc5\xc8\x9a\x90\x64\xaf\x0b\x5a\x10\x2e\xfb\x58\xd4\x08\x62\xbd\xcd\xeb\xc3\xc5\xa3\x81\x84\xed\x7b\x93\x3f\x64\x49\x4e\x48\xd8\x9b\xea\xb6\xcb\xb5\x8b\xf8\x28\xa2\xb0\x43\xfd\x77\xc9\xb1\xc8\xcb\x1a\x65\xf5\x45\x78\x05\x91\x22\x34\x3f\x45\xf8\x21\x89\x3b\x6d\x37\x71\x48\x04\x56\x87\xfc\x4e\xe6\x4a\x84\x26\x19\x49\x64\xa6\xf8\xac\x25\x34\x2f\x1e\x09\x6f\x84\x78\xe3\xfc\xd7\xfe\x1b\xdf\x41\x1b\x7d\x36\xa1\x7d\xa3\x1e\x32\xf7\xf0\x09\xdb\x31\xce\x8c\x8c\xa3\xdd\x2e\xb9\x57\xb6\xa5\x5e\xfe\xe0\x1e\x2b\xf7\x36\xc1\x77\x0d\x1a\x8b\x5d\x31\xbe\x4d\x22\x4c\x83\xec\xc5\x63\xfd\x71\xd3\xfd\xa4\xfd\x7d\x8c\xbb\xdf\xd5\xb1\xfb\x7d\x5f\x19\x5b\xef\xc8\x50\xc5\x4e\xc4\x12\x3a\x8b\x03\x8a\x54\xdc\x63\x0c\x94\xa8\xb5\xdb\x22\x15\xb7\x3a\x02\x25\x6a\xed\xb6\x48\xc5\xbd\xaf\x80\x12\xb5\x76\x5b\xa4\x98\xaf\x22\x0e\xbe\x4d\xb1\xdd\x5d\xb1\x5c\x2c\xc9\x5c\x06\x90\xa4\x6a\x63\xc4\xfd\x40\x88\x04\x20\x22\x96\x46\x2c\xb7\xcc\xef\x44\xcc\x58\xc0\x9c\xd4\x07\x73\xbd\x08\xa7\xa9\x50\x71\x48\x47\xa0\x81\x3c\x96\x04\x95\xa9\x32\xeb\xbf\x8e\x88\xc2\x8e\x58\x08\x11\x54\x66\x9b\x6c\x0e\xda\x36\xb3\x5a\x05\x52\x33\xd5\x71\xa0\xde\x04\x44\x8b\xde\x54\x2c\xb3\xde\xaa\xa3\xa8\x37\xad\x9e\x49\x6f\xa3\xbb\x37\x54\x9b\xe3\x09\x0f\xd6\xf1\xb5\xa4\xaf\xd5\x3c\xdd\x42\xac\x36\x13\x04\xcd\xf2\x51\x68\xe7\x18\x0f\x54\xbd\x80\x68\x51\xbd\x8a\x65\x56\xfd\x31\x16\x55\xaf\xd5\xeb\x55\xfd\xe0\xfe\x8d\xd6\xfd\x70\xca\xe3\x95\x3f\x96\xf6\xb5\xda\x0f\xc8\x3e\x5c\x81\x62\xba\x1f\xa8\x67\x01\xd1\xa2\x67\x15\xcb\xac\xe7\x74\x2f\xea\x59\xab\xd7\xab\x67\xa0\x27\xa3\x35\x0a\xd1\x18\xaf\x3b\x33\x95\x91\x5a\xd2\xdc\x3d\x9d\x03\x59\x26\x22\x63\xbd\x08\x23\x28\x38\xd4\x5e\x82\x3d\x96\xc9\x28\x0a\xe3\xb4\x97\x62\x2b\x2f\x56\x57\xd0\xbd\x5a\xb7\x15\x69\x51\x26\x59\x6d\x9f\x7f\x38\x14\xc7\x50\xc5\x6e\xdc\x32\xae\xc5\xbe\x01\x44\xb3\x89\x13\x64\xd1\xca\xa1\xda\xaa\xa1\xcb\xc8\x43\x26\x5e\x60\xc7\xfb\xc6\x82\x82\xad\x58\xfd\x88\x76\x7a\xc7\x0b\x88\x7f\x7d\xbf\xae\x18\x59\x9c\x10\xb3\x37\xab\x2d\x5d\xfe\x10\x1d\x50\x59\xe1\xda\xf9\xe2\xa7\xbf\x7c\xef\xde\x7c\xb1\x79\xf3\xf5\xef\xbe\xc9\x92\x23\xaa\xb1\x17\x55\x95\xe3\x3a\x87\xba\x2e\xd6\x6f\xde\xc4\x28\xc3\x31\xce\xbc\x23\x7e\x83\x28\xfc\xdf\xfe\x3d\x89\x70\x56\xe1\xd8\x39\x65\x31\x2e\x9d\xfa\x80\x9d\x3f\xff\xf0\x17\x27\xa5\xc5\x5d\xd5\xbc\xc0\x59\x95\x9f\xca\x08\x7b\x79\xb9\x7f\xc3\xe0\xd5\x9b\x3f\xff\xf0\x97\x7f\xfb\x2e\x2f\x1e\xe8\x97\xac\x97\xd1\x2b\x27\xf4\x83\xb9\xf3\x47\x94\x25\x38\x75\xfe\x14\xe3\xec\xeb\x37\x1e\x6b\x2c\xee\x8e\xed\x92\x82\x24\xcf\xdc\xf8\xc4\xbe\xd3\x04\xd5\x06\x2e\xd5\xeb\xec\x92\x34\x75\x8f\x79\x8c\xc9\x7a\x75\x63\x02\x5c\xda\x76\xdd\x1d\xaa\x6a\x5b\xe3\xde\x1c\x6c\xbd\x29\x7e\x82\xe6\xbd\x24\xdb\x25\x59\x52\x63\x80\x85\xa4\xc6\xb4\x31\x37\xca\x4f\x59\xbd\xe6\xa8\x9b\x7e\x14\xa1\x81\x43\x92\xed\x21\xea\x6d\x4f\x42\xb0\x7f\x61\x25\xd0\xd8\xe6\xa7\x2c\xc2\x3f\x64\x13\xb5\xe8\xc7\x93\x5d\x76\x4b\x83\xf0\x96\x73\x91\xfc\x2e\x4d\x8a\x1f\x4f\xf5\x5f\x27\x5a\xd1\xdf\xae\xa6\xfe\x07\x5e\xf1\x1d\x7e\x20\x37\x2a\x54\x0e\x65\xf9\xbc\x2b\xf3\xe3\x24\xf4\x5f\x4c\xe6\xd3\x17\x93\x1b\xff\xc5\xa4\xce\x81\x56\xea\xe4\x98\x64\x7b\x77\x77\xca\x22\x42\x34\x3a\x6d\x93\xc8\xdd\xe2\xdf\x12\x5c\xbe\xf4\xbd\x30\x98\x4f\x7c\x6f\x11\xf8\x13\xdf\x9b\xce\xe7\x93\xc0\xf3\x7d\xff\xd5\xe6\xb1\xf5\x1f\xbf\x57\x67\xe6\xbf\x98\xcc\xa6\x2f\xc6\xf7\x68\x39\x6f\x38\xf2\xe7\x0d\x47\x37\xf4\xf7\x62\x4c\x8f\x0c\xf5\xfb\x7a\xe4\x4e\xfd\xe2\xde\xd6\x2b\x8e\x70\x59\xfa\x9f\x51\xaf\x82\x79\x4f\xaf\x18\xc2\x65\x25\xf4\xca\x88\x3c\xeb\x21\x46\xe1\x97\xcb\x1f\x9e\x8d\xfd\xd3\x36\x8b\x67\x63\x7f\x3a\x63\x67\x21\x08\x10\x53\x86\x8e\x4d\x9c\x6d\xa0\x1b\xb8\x54\xdf\xae\x9b\x97\xc9\x3e\xe1\x1f\x4a\x1d\xfa\xbd\x6b\x63\x07\x43\x31\x66\x97\xa2\xea\x40\x47\xdd\x9c\x8e\x36\xbe\x79\x24\xb8\x84\xf3\x17\x93\xe5\xfc\xc5\xb9\xbb\x00\x40\x1c\xb0\x23\x6b\x7a\x14\xdf\xd0\x79\x02\x54\xfb\x4e\x0a\x21\xa6\x8b\x53\x5a\x51\x57\x01\x68\xa7\x8a\x50\x4a\xf6\xd0\x4e\x82\x49\x20\xaa\x45\x06\x5c\xe6\xa0\x6e\x5b\x24\xcf\x27\xe3\x9e\xfe\x17\x48\x46\xc6\xb8\x08\x8e\x6a\x3c\x33\xa2\x60\xff\x09\x7b\xe7\xd1\x3e\x19\x94\x4f\x80\xaa\xf2\x49\x21\xa4\xfc\xf2\xb4\xdd\xe2\xf2\x5b\x94\xc5\x8f\x95\xd1\xb4\x47\x46\x61\xe3\x51\x96\x73\x03\x0d\x01\xdc\xf8\x75\x0b\x25\x8a\xd5\xe0\x83\x94\x44\x70\xaf\xde\x48\x64\xba\x31\xf2\xd4\x81\x2f\x8b\xb9\x8d\x92\xb7\xe2\xea\x85\x08\x09\xd0\xcb\xd2\x4a\x87\x60\x11\x74\xa3\x15\x51\xe8\xd3\x8d\x8f\x67\x03\xf8\x57\x34\x00\x4f\x50\xbb\xc1\x8f\x74\x18\xaa\x33\xe9\x20\x90\x47\xa9\x0e\xe8\x1d\x9b\x79\x82\x3c\x8e\x9d\xe5\x05\xfe\x8b\xc9\xb4\x99\xc2\xfa\x2f\x26\x4b\xff\xc5\xa4\x7f\x16\x41\xb6\xc0\xf4\x9c\xc1\x60\x08\x97\x66\x72\xdc\xcc\x23\x17\x3e\x99\x20\xf7\x50\xee\x23\xdc\xd1\x15\x87\xd8\xb3\x44\xe8\x8c\x8d\xca\xc1\x60\x6e\x04\xa8\x5a\x1a\x29\x04\x8d\xec\x2e\xc9\xf6\xe7\x10\x64\xaf\xcc\xeb\x4e\x5c\xc1\x24\x98\xc7\x78\x2f\xb2\x07\xc2\x0d\x3e\x47\xc1\x75\x03\xdf\x4e\x8c\x21\x5c\x16\x43\xa8\xf5\x30\x46\xf9\x82\x55\xa0\x36\xdb\x43\x8a\xc2\x61\x9f\xa1\xa0\xf6\x74\x90\x76\x4f\xb2\xee\x67\x55\x7c\x54\x55\x78\x54\x01\xc6\x35\x4d\x9d\x17\x0e\xdf\x00\x6a\x81\x99\x86\x65\x43\x5c\x1b\x96\x4d\x21\x34\x2c\x6b\x14\xa3\xc7\x4e\x23\x1a\xef\x06\x5b\x53\x17\x4e\x27\xe4\xff\x5f\xa9\xa2\x9d\x2a\x02\x1b\x56\xe1\xd2\xef\x4b\x85\x19\xc1\x84\xfd\x47\xa5\x65\x6a\xbb\xaf\xca\xc5\xee\x6b\x07\x90\x31\x76\xbb\xb7\xce\xd3\xcd\x22\x9f\x15\xff\xaf\xa4\x78\x8f\xa8\xdb\xe0\x31\x1a\x98\xea\x30\x9a\x32\xc8\x5f\xdc\xe5\xdb\x6d\x6a\x5c\x9d\xd3\x5b\x60\xa4\x3f\x2f\x01\x38\x7d\x96\xa6\x32\xe1\x9c\x9e\xaf\xed\x73\xcc\xc3\x2b\x19\x96\x36\x22\x81\xd0\x07\xeb\xab\x1a\x1a\x5a\xe5\x32\xeb\xef\x67\x60\x60\xd9\xd6\xa6\xbd\x92\x21\x56\xca\xf3\x39\xb0\x7e\x68\x69\xd3\x56\xc5\xb0\x1c\x92\x58\x36\x70\x1c\xd8\xba\x69\xab\x03\x1b\x3f\x64\x6c\xa2\x87\x7b\x36\xd5\x67\x53\xfd\x94\x4d\xd5\x63\x06\x6a\xf0\xca\x14\xaa\xfa\x65\x5a\x0a\x79\xe6\x5f\x71\x9a\xe6\x74\xcd\x1a\x04\x5e\xf0\x02\x5e\xb9\x42\x9c\x84\xa1\x17\x82\x01\xed\x1d\xbe\xfb\xeb\x4b\x37\x08\x3d\x62\xa8\xcd\x9f\x7f\xeb\xfe\xdc\x0c\x45\xbc\x4c\xa7\xde\xd4\x4c\x7f\xe1\x85\x42\x2d\xfe\x97\x46\x1d\x46\xbb\xcc\x66\xde\xcc\xc2\xfb\xd4\x0b\xc4\x6a\xdd\xdf\x3a\xf7\x46\xd4\xcb\x7c\xee\xc1\x29\x20\x52\x31\xf0\xe6\x0b\xb1\x62\xf7\xb7\xd6\x86\x19\xf5\xb2\x58\x78\x0b\x4b\x3f\x7c\x6f\x79\x23\xb3\x27\x94\xe8\x7d\xb1\xa2\x5f\x96\x4b\x6f\x69\x6e\xcb\xf7\xa6\x2b\x5f\x62\x53\x2c\xd1\xda\xb2\xa3\x5f\x6e\x6e\xbc\x1b\x6b\xbf\x82\xd5\x7c\xaa\xb2\x2a\x94\x41\x7d\xeb\xa9\x22\x05\x81\xe7\x51\xf1\x3c\x2a\x9e\x47\x05\x4d\x3b\xd0\xb1\x60\x08\x37\x04\xa8\x46\x1b\x5a\xd8\xf3\xf1\xd5\xf4\xd5\xd5\xbc\xa5\xe7\x87\xac\xdb\xe7\x20\xac\xa9\x3e\xa1\xbd\x0e\x17\x5f\xf8\x78\x0b\x7c\x50\x6f\x97\xab\xd3\x09\xf9\x7f\x70\x29\xcb\x61\x17\xfb\x1a\x59\x5c\xfb\xf5\x2c\x0d\x7b\xbe\xb3\xb4\x8b\x67\xdb\xc2\x9a\xcc\xc3\xda\x4f\xd5\x96\xbe\x05\x9e\x3f\x9d\xb4\xff\x65\xf8\xba\x21\x62\x18\xd2\x6d\x1d\x07\xcb\x09\xfb\x0f\xcc\x60\x07\xbe\x88\x5f\xd3\x6d\x2c\x8e\x48\x78\x3c\x9b\xde\xb3\xe9\x7d\x48\xd3\x6b\x37\x61\xda\x37\xbc\xfc\x90\xc1\x5b\x5e\x7e\xc8\x6c\xfe\xf3\x8f\xf9\x1d\x33\xe4\xc6\x80\x97\x73\x92\x0d\xfb\x8c\x8c\x58\x5c\x4f\x91\x5d\x54\x7e\xff\x46\x2b\x86\xd2\x6b\x44\x72\xc5\xb0\x67\xaf\x13\x83\x0f\x58\x2b\xfa\x13\xf6\xcd\xcd\xb6\x71\xca\x1f\xbe\x71\xaa\x87\x31\xc6\xd7\x55\xa9\x8f\x67\x33\x79\x36\x13\x63\xda\x41\x32\x8e\x1e\xdf\xd4\xe0\x98\xfc\x53\x03\xb3\xf9\xa8\x7f\x27\x87\xc2\xff\x39\x8c\xaf\xb5\x2b\xcb\x07\x79\x01\x65\x94\xf1\x51\xd3\x32\x13\x6e\xe1\x43\xf2\x59\x23\xb6\x0d\xf4\x1b\x5f\x0f\x63\x2d\x5f\x8f\xf2\x51\xcf\x66\xf2\x6c\x26\x46\x1f\x45\x8c\xa3\xc7\x47\x35\x38\x26\x1f\xd5\xc0\x6c\x3e\xea\xbf\xd1\xab\x2d\x3e\x61\xeb\x23\x9f\x2f\x86\xda\x5f\xbf\xf9\x5d\x6b\x7d\x6e\x9f\xf9\xb9\x63\xec\x6f\xf8\x1e\xa4\x01\x3b\xa5\xfa\x18\x7b\x1a\x37\xf5\x6c\x29\xcf\x96\x62\xf3\x54\xd4\x3e\x7a\x5c\x15\x41\x32\xf9\x2a\x02\xb4\x39\xab\x9f\x8a\x7f\x1e\xfb\xf3\x27\xfd\x93\xf9\x6b\xe7\xf2\x6e\xd8\x37\xff\x0e\xfd\xe1\xb3\xf9\xbe\xc9\xfc\x98\xb9\xbc\xdb\x77\xf0\xc6\x36\x9b\x97\x51\x07\xdc\x02\x0e\x78\xb1\x67\x13\x7a\x36\xa1\x51\x17\xc9\x0b\x86\xd3\xe3\xdb\x7e\x2a\x4c\x8e\xed\xa7\xc2\xec\xd5\x7e\x3c\xd5\x86\x9d\xa8\xe3\x32\x8b\x73\xff\xc5\x64\x3e\x1f\x9a\x5d\x1c\x9e\xf5\x14\xb2\x81\x8f\x4f\xc6\xea\x23\xf2\x5f\xab\xfb\xd6\x43\xe8\x82\xc9\xfc\x78\x32\x04\xc9\x1f\x4f\x96\x00\xf9\xe3\xa9\x26\x29\x0d\x58\x9e\xd7\x8d\x48\x72\x60\xb5\x47\xae\xd7\x7a\x8e\x1e\xd9\xca\xb5\xc2\x5e\x57\xd7\x62\xc0\x66\xf6\x2c\x1a\xd1\xa1\x71\x81\xf4\x99\xa1\x39\xff\xc5\x80\x56\x73\x24\xab\xd7\x70\xcc\x1a\xbf\x67\xb6\xdb\xc2\xc7\x48\xc8\x0d\xfb\x93\x12\x02\x0a\x6c\x3e\x9f\x69\x57\x3c\xb9\x03\x7d\xea\x36\xa7\x12\x18\xd0\xaa\x6e\xba\x04\x18\x23\x24\xb7\x4f\x4a\xee\x55\x62\xea\x97\x52\xaf\xbe\x3f\xd7\xbe\x78\x4a\x0f\xfa\x34\x6e\x59\x91\x71\xa8\x55\xe7\x3f\x15\x83\x9c\xea\xf0\x4f\x02\xe3\xdd\x6a\x9f\x57\xbd\xca\xa9\xba\xfd\x5e\xd5\xed\x89\x38\xcf\xa2\x51\x2c\xb2\x6f\x0e\x4d\x50\x8c\xb6\x08\xcf\xa2\x77\x28\xe6\x7b\x1a\xc4\x2e\x1c\x2b\x75\x96\xf6\xd2\xf7\x56\x73\xe8\x6e\x08\x11\x0a\x96\x2a\x5f\xc4\x01\xd2\x81\x07\xde\x3a\x21\x00\xa1\x42\xf9\x36\x85\xcf\xb9\x1f\x1e\xe3\xde\xa0\x5d\x0a\xd5\x6e\x77\x20\xa5\x66\x9d\xb6\xdf\x6e\xc7\x98\xe6\xbc\xcf\x30\xe7\xba\xc5\x43\xc3\xa8\x2f\x2b\xfa\xa9\xb2\xe8\x09\x8c\x59\xb5\x01\x4d\xec\x3a\x88\x5d\x2b\xdf\x26\xfb\xd1\xbd\x1e\xe5\x33\x9e\x4c\x39\x9f\x24\xa7\x9e\xcc\x5f\xaf\x9a\xbe\x4d\xb4\xe3\x8c\x12\xd0\xac\xac\xf6\xd3\xe2\xf0\x09\xe6\xbc\x6f\x12\x33\x87\x26\x31\xd7\x6b\xe9\xd3\x63\xd1\x13\x18\xb3\xea\x06\x9a\x2c\x77\x10\xbb\x56\x46\x1b\xe6\xd8\x99\xff\x93\x29\xe7\x93\xe4\xd4\x93\xf9\xeb\x55\x93\x71\x08\x31\xa0\x59\x59\xdd\x67\xaf\xc1\x02\xe8\xb3\xcf\x27\x1e\x41\x9f\x1c\x87\x9e\xc8\x97\x55\x35\xe0\xe2\x43\x00\xf5\xa8\x65\xb4\x69\x8e\x5b\x4b\x3d\x9d\x76\x3e\x41\x46\x3d\x85\xbd\x7e\x3d\x19\xc7\x10\x87\x9a\xb5\xc5\xbe\xba\x08\xdd\x37\xbf\xd0\x6b\x0a\xba\xb3\x9e\xa0\x3c\x7b\xca\xb9\xc3\x07\x66\x58\x20\xce\x6e\xdb\xdd\x5c\xab\x50\xf3\x1a\x8b\xc3\x61\x25\xda\x56\x57\x3f\x15\x7f\xc6\xd9\xe9\xf1\x12\xb9\x62\x35\x1a\x6c\x00\xd9\x5c\xaf\xd5\x0f\xde\x8f\x27\xd5\x2c\xe1\xbe\x47\xbb\x0d\x8e\x49\xc3\x0d\xcc\xa6\xe5\x2b\x66\xcb\x63\x72\xcf\x4f\x34\x32\x3f\x41\x2e\x3d\x91\xb7\x1e\x05\x19\xdd\x28\x01\x99\xd4\xf3\xe3\x49\x09\xf1\x81\x94\xb3\xd1\x04\xd5\x8b\xef\x71\x2c\x0b\xbb\xc0\x67\x28\x56\x6c\x61\x53\x5f\x13\x07\xe3\xd2\x4b\x43\xfd\x26\xd0\xe5\x0f\xd7\xb6\x27\xb6\x68\x17\xa1\x69\xa5\x6d\xf9\x80\x22\x80\x35\x6b\x1f\xd9\xa3\x6b\xbf\x9b\x7d\x2c\x16\x3c\xa5\xe1\x7e\xd9\x1a\x06\x54\x07\xb5\x48\x58\x5f\x7d\x8e\xea\xdb\x98\xa7\xf3\x01\xe1\x7e\xc8\xd6\x3d\xb1\x4d\xbb\x50\x4d\xeb\x5a\xcb\x27\x20\x01\xfc\x38\x6b\x79\xcc\xb7\xba\x8f\xc7\x84\xa7\x34\xdd\x2f\x5f\xb3\xd1\xf6\xac\x48\xdb\x0f\x3b\xd7\x77\xaf\xcf\x6c\xac\x36\xfb\x01\x1b\xf7\xa4\x26\xed\x32\x35\xae\x24\x6d\x1f\xb1\x44\xf8\xe3\x2c\xe6\xfa\x0f\x8e\x1f\x8d\x07\x4f\x6d\x79\x80\x80\xcd\x56\xdb\xb7\x06\xa4\x5f\x7f\x1e\x13\x45\x98\x33\xb3\x7e\x26\x23\x08\x90\x78\x3f\x5c\xdb\x5e\xd7\xa2\x5d\xa0\xf0\x42\xcc\xfa\x9d\x8b\x00\x1f\x1b\x8d\xaf\xff\xb0\xf9\xb1\x58\xf0\xa4\x86\xfb\xa4\x6a\xb6\x51\xf3\x04\x3b\x4d\x0a\xd3\x35\x49\x05\x2e\xab\x02\x93\x37\xb5\x5f\xce\xc8\xc3\x49\xc2\xb5\x37\xc1\xc4\x9f\xb8\xd3\x85\x7a\xeb\xe1\xf0\x3a\x7a\x67\xd4\x7d\xa3\x18\x55\xd8\xcd\xa5\x89\xb8\x09\xc5\x70\x42\x59\xe7\x46\x16\xb5\x3f\x09\xe6\x10\x8f\xc1\xaa\xbf\x5f\xe3\x28\x3d\x6d\x6f\xe1\x8b\x92\xaf\xe6\x71\xf9\x64\xbd\x5d\x8e\xea\x6d\x92\xf5\x75\x36\xc9\x0c\xa7\xbb\x75\x0e\xc5\xeb\x9b\xd9\x7f\xec\x5d\x02\x2b\x3c\x25\xe7\xe0\x36\x5c\x9d\x0f\x2b\x93\x4f\xca\x90\xfc\x84\xc1\xf3\xc0\x7f\x1e\xf8\xcf\x03\xff\x5f\x63\xe0\xcb\x8f\x75\xb5\xdc\x6d\x51\xf4\x6e\x87\x22\xec\x02\xb9\x5a\x1b\xcc\x34\x11\x49\x13\x7d\x66\x97\x26\xf0\xa4\x2e\x4d\x8a\x1f\xb2\xbf\x5e\xe1\x82\x1a\x83\xf3\x27\x03\x06\x2a\x5c\xe3\x09\xe5\xba\xe9\x92\x8a\x43\xdd\x90\xc2\x93\x1b\x8e\xee\x06\xaf\xf2\x94\xf6\x01\x5f\xcf\xd8\xcb\x8a\x76\xa9\xf7\xd0\x1a\xdd\xe4\x79\xe8\x20\x57\x85\xa0\x5e\x85\x35\xb0\xc6\x93\x0c\x4d\x2d\x8e\x3e\xdb\xf1\xb3\x1d\x7f\x96\x76\xec\x71\xeb\x1d\x10\x10\xba\x97\x4a\x6d\xa1\x41\xc0\xb2\x04\x89\x1f\xb2\xbf\x42\x71\xe2\x87\xec\xaf\xe6\x50\xf1\xb7\xab\x67\xab\x63\x86\x98\x54\xe3\x13\x19\x62\x6c\x8e\x35\x66\x88\xc9\x55\x3e\xea\x10\xa3\xac\x8c\x19\x62\x52\x8d\x47\x0c\x31\x26\x84\x11\x43\x4c\xac\xf1\xbe\x42\xc5\xb3\x1d\x3f\xdb\xf1\x67\x68\xc7\x1e\xb7\xde\x0f\x1e\x2a\xfe\x06\x87\x8a\xbf\x99\x42\xc5\x8f\xa7\x7a\xc4\x74\xcc\xde\x6d\xf8\x7e\xf6\xf7\x39\x1d\x92\xf2\xca\x1f\x72\x3e\x69\xd8\x50\xf1\x2c\xd0\x27\x10\x68\xfb\x6a\xb6\x31\x85\xcf\xe0\x90\xa9\x93\xf2\xf7\x30\xea\x2c\xe3\x67\x44\x8c\x7a\x62\x75\xf3\x7c\xd2\x78\x67\xc7\xaa\x5c\xaf\xee\x47\x06\x59\xeb\xf8\x79\x16\xe8\xa3\x04\xaa\x3f\x31\xff\x81\xa2\x4f\xd3\xa6\x61\x4c\x82\xf1\x27\x4d\xf6\x87\xfa\x3f\x0a\x8c\x63\x7e\xa0\x6d\xe0\x96\x00\x7e\xe5\xba\x2a\xac\x01\xc8\x9d\x9c\xe0\x39\x0d\xc5\xd6\x1c\xa8\x54\xdc\x37\x3b\x61\x2d\x1a\x2e\x20\x1f\x64\x28\xc0\xee\x41\xa1\x8a\x38\x66\x9e\x85\x78\xad\x10\x3d\x49\x74\x06\xbb\x16\x71\x54\xdb\x96\x60\x4f\xfb\xe9\xc2\x3a\x5a\xe0\x3d\x9b\xa3\xd4\x3e\x46\xeb\x9a\xd2\x61\xf3\xfb\xe8\x5c\x79\x32\x2f\xbd\x0a\x05\x76\xad\xca\xc0\xa7\x5c\x41\x41\xef\x53\x13\x47\x6e\x1c\xb5\xc3\xde\x0e\x00\xf6\x50\xa8\xcf\xed\x84\x7e\xdf\x73\x92\x1c\xa3\x13\x26\xa4\xb9\xab\x19\x1a\xee\xcc\x9e\x45\xc2\x5c\x53\x2b\x08\x83\x15\x73\xb8\xf6\x7e\x31\x2b\xb7\xd9\xdb\x1f\xf3\x3b\xe1\x34\xa5\xb1\x4f\x29\xde\xd5\xce\x36\xaf\xeb\xfc\xa8\x77\x4c\x04\xf6\x8b\x7b\xd6\xf7\x70\xe8\x6c\x3e\x42\xd8\x8f\x60\x6c\xbc\x25\x3e\x0b\xab\xd7\x46\x5b\x11\xf5\xd8\x2a\xc7\x33\xd9\x2c\x87\xf7\xd9\xae\xb0\xb3\xd4\xd8\xed\xb2\xc1\x31\xf6\x5b\x82\xf6\x6a\xa4\x4f\x21\xa3\xf4\xf1\x28\xc6\xae\x33\xdf\x67\x79\x0d\xb1\x60\xeb\xe6\x61\x0d\xd1\x66\xc3\xc6\x6d\xc4\x1c\xe9\xa7\xe2\x83\x7a\x94\x27\xd5\xc7\x07\x75\xbe\xcf\x82\xb2\x9a\x2d\x13\x4f\x8f\xcd\x52\x2c\x93\xc1\x52\xa8\xdd\x5a\x3f\xb4\x03\x71\xb5\x2c\x88\x01\xe1\xe3\xbb\x10\xd0\x68\x9f\xe5\x35\xc4\x76\x07\x39\x5c\x86\x66\xb6\xde\x1e\x67\xdb\x2e\x4a\xaf\x9d\xba\xdb\x17\xb3\x4f\xb5\x20\xe9\x5d\x8f\xe8\xcb\x11\xdd\xf4\xfe\x89\xfb\xea\x75\x3d\xb4\x1a\x0c\xb0\xa4\x6f\x01\x56\x23\x79\xea\x69\xfe\x30\x51\x7e\xc4\xd0\x05\x9a\xcf\xbf\x9e\x14\x3c\xbd\xef\x7d\x06\x66\x5f\xc6\x08\x08\xbd\x06\xf7\xc4\x71\x62\x98\xb4\x9f\x34\xf0\x8c\x5f\xab\x1a\xed\xee\x5f\x52\x18\x1e\x20\x82\x21\xf6\x67\x09\x8a\x22\x86\xd5\x02\x9f\x76\x72\xfd\xa1\x87\xfa\x53\x99\xde\xbf\x9e\x14\x3c\xb5\xef\x7d\x06\x67\x5b\x40\xb4\xe0\x1e\x53\xfb\xec\x47\x77\xdf\x14\xdb\xfa\x5d\xfb\x5f\x5b\x10\x9e\xd6\xfd\x7e\x8b\xb3\x3b\x38\xcb\xb4\xff\x90\x64\x7b\x7c\x86\xbe\x28\xf2\xee\xd6\x79\xe1\x34\x83\x4c\x17\x44\x0b\x19\xfc\xe1\x67\xc8\xe7\x3c\x8a\x75\x09\xe9\x1b\xba\xc0\x07\x31\x45\x80\x37\x3d\x02\xbe\x91\xb7\xf7\x7d\x2a\xfd\x9b\xd1\xf7\x81\xfb\xfb\xa7\x1d\x86\x83\xe1\x9f\x58\xff\xfa\xbe\x35\x93\x5f\xed\xc9\xaf\x65\xdf\xb1\xe5\x16\x01\x76\x18\xcf\x56\xfc\x6c\xc5\x9f\xa9\x15\x7b\xd4\x76\x0d\x2e\x9e\x00\x55\xbf\x4e\x0a\xe1\x09\x44\x9a\x02\x77\x5d\x5b\xd9\xee\xee\xb2\x51\xa7\x42\x81\xb6\x21\x65\x54\xbd\xc7\x5e\x4b\xf6\xf9\x77\xc6\x63\x5d\x30\x86\xef\x06\xaa\x07\xed\xa6\xd4\xa4\xdd\x9e\xeb\xc7\x86\xde\x11\xa3\xf4\xd2\x2e\x9c\x9e\x5a\x9a\xd2\x3e\x49\x1e\x3d\xce\x99\x45\x19\x60\x36\x8c\x14\x43\xea\xf8\x2d\xcf\x8f\x43\xec\x73\xdc\x53\x46\x73\xf1\x61\x0c\x49\xb4\x1f\xa4\x3d\x8f\xb5\x62\x90\x12\x85\xaa\x42\xa2\xa5\x66\x19\x0d\xbb\xcd\xbd\xe5\x2d\x98\x90\xff\x57\x0e\x87\x37\x43\x54\x73\xaf\xc3\xeb\xf4\x07\x07\xe5\x21\xb9\xf9\xdc\x9f\xf8\x9e\x3f\x27\xcf\xc9\x2d\x9b\xff\x0e\x56\x63\x1e\xa2\x83\xeb\xf7\xbe\x16\xd7\xf6\x68\xb6\x9c\x4f\xda\xff\x52\xfa\xb5\x30\x0b\x62\x48\xb5\xd1\xb2\x08\x08\xff\x37\x37\xcd\x7f\x4f\x43\x7f\x12\x8c\x90\x83\x5e\x17\xb0\xea\x67\x0b\x79\xb6\x10\xc9\x42\x3c\xc1\x2e\xac\xbe\x08\xba\x04\xb2\x83\x98\x7d\xd2\xb0\xeb\xf1\xad\xd6\xd3\xda\xce\x08\x8b\x93\xea\xfc\x13\x59\x1c\xf4\x9a\xee\xa8\x6a\x1f\xdd\xe2\x34\x9f\xf4\x6c\x21\xcf\x16\x02\xf9\x24\x5b\x0e\xb8\xc3\x80\x7d\x92\x29\xf3\x4b\xa1\x03\x1f\x1c\xb0\x9a\xcf\x15\x16\xf7\x4f\x6a\x70\xee\x95\x16\xe7\x7e\x42\x26\xa7\x39\xa5\x67\x13\x79\x36\x11\xd0\x2b\x59\x3f\x14\x08\x28\xb0\x5f\x32\x7e\x1e\xa0\x60\xfd\xd5\x86\xd1\x93\xf3\x2b\xe6\xe6\xff\xa4\x53\x73\xf7\xca\xb9\xb9\xfb\xc9\x4c\xce\x35\xaf\xf4\x6c\x1f\xcf\xf6\xa1\xbb\x24\xf3\x4d\xbd\x1c\x0e\x3b\x23\xf8\x9e\xde\x06\x06\x64\x16\xc5\xfc\xd5\xe3\xb3\x61\xc6\xa7\x1f\x3e\x42\xeb\x1e\x6f\xd3\x22\x44\x20\x63\xc9\x8a\x2d\x22\x24\x8b\xea\xd9\xe7\x69\xaa\xef\x69\xe0\x0e\x7d\x8d\x1e\x76\x44\xd0\xad\xcb\x83\xab\xf4\xec\x1b\x35\xee\x42\x90\xc1\x1f\x7d\xcc\xeb\x63\xe5\xd9\xcc\x9e\xcd\xec\xfd\x84\x96\x9e\xe7\x61\x04\x14\x83\x73\xb4\xa5\x06\xf9\x53\x1e\x4f\x60\xb9\xb3\xf0\xaa\xd5\x45\x5b\x6d\x90\xbd\xbc\x54\xf3\x47\xf0\x73\x05\xfd\xb8\xf6\xed\x77\xa6\x5d\xec\x02\x10\x72\x02\xcf\xa2\xbc\x52\x94\x9e\x28\x40\xbb\xa1\x9b\xd2\x4d\x96\x67\x65\x18\x98\x2e\x1a\x9f\x40\x3d\xee\x95\xfa\x71\x1f\xa7\xa0\x11\xfa\x19\xa4\x1e\xba\xb3\xcf\xa4\x1f\x11\x0a\xd9\xfa\xb3\x34\xaf\x97\xa6\x27\xc9\xd0\x6e\xef\xc6\x3c\x86\xed\x49\x1a\x06\xff\xa9\x78\x9a\x29\xc9\x87\xfe\xbe\xf6\x49\x4e\x48\xc0\xe7\x46\x86\xd7\xf9\x27\x99\x92\xe8\x8e\xe0\xd9\xc8\x9e\x8d\xec\x3d\xcd\x7b\xed\x39\x15\xf0\xf5\xa3\x16\x00\xb9\xc5\x2a\x4d\xf8\x83\xf7\x43\x2e\xc3\x1a\xf1\x62\x13\xf0\xbe\xec\x90\x3d\x94\x96\x57\xb6\x7c\xfd\x91\xaf\xcf\x91\x7d\x4f\x64\xda\xa0\x4c\x01\x45\x55\xa7\x00\xb2\x28\xd4\x72\x48\xe7\xda\xa7\x0e\x3f\xa4\x42\x3f\x2b\xf6\x3d\x91\x69\xbb\x42\xa1\xa9\xba\x00\xb2\x28\xd4\x76\x0e\xe6\xba\x77\x00\x3f\xa4\x3e\x3f\x27\xee\x3d\x89\x67\xbb\x3e\xc1\xa9\xa8\x08\xb3\x68\xf4\x27\xe3\x0b\x49\x32\x57\x7d\x1e\xeb\x23\xf8\xdb\xcf\x86\x75\xaf\x63\xd8\xae\x48\x3d\x6c\xb6\x00\xa3\x0a\xa5\x77\x91\x1f\xcf\xac\xfc\xa8\xf9\xc0\x17\xd3\x07\x4a\x58\x57\xe2\x67\xc5\xbc\x27\xb1\x6c\xd3\xa4\x21\xef\x27\xc2\x6c\xfa\x1c\x1a\x77\xde\x93\x48\xae\x7d\xf6\xf8\x73\x64\xdf\x93\x98\xee\xd1\xa9\x31\x6e\x5a\x72\x5c\x1c\x3e\x38\xf6\xbc\x27\xa9\x5c\xf7\x2a\xf0\x67\xc8\xbd\x27\xf3\xdc\xa3\x52\x73\xe8\xb4\xa5\x71\x38\xc2\xd0\x08\xf4\x9e\x3c\xd7\x75\x6f\xe6\x7e\x7e\xcc\x7b\x02\xcb\x3d\xfa\x34\x44\x50\xba\xf2\xdc\xe6\xf1\xc3\x79\x97\x67\xb5\xbb\x43\xc7\x24\x7d\x58\xe7\x05\xce\x9c\x0a\x65\xd5\xe4\x9b\x32\x41\xe9\xa4\xf9\xe9\x56\xb8\x4c\x76\x9b\x34\xc9\xb0\x7b\xc0\x8d\x05\xac\x03\x6f\xbe\x21\xd5\xaa\xe4\x37\xbc\x0e\x16\xc5\xfd\x26\xca\xd3\xbc\x5c\x7f\x79\x33\x6b\xfe\x91\x3b\xb8\xf7\x65\x7e\xca\x62\x97\x01\x76\x7e\xf3\x8f\xb4\xe8\x55\xf5\x43\x8a\xdd\xf0\x0c\x60\xed\x76\x62\x5d\x42\x3e\xca\x6f\x71\x29\x96\x16\x79\x95\x90\x05\xba\x3b\xf5\x5f\x88\x80\x12\x17\x18\xd5\xeb\x2c\x67\xbf\x44\x58\x72\x44\x7b\xbc\x3e\x95\xe9\x4b\xcf\x7b\x43\xfe\xa8\xde\x6c\xf7\xff\x2b\xf4\x7e\x2d\xf6\xaf\x36\xbc\x5f\xbe\xff\x42\x64\x71\x2a\xb0\x48\x98\x73\x64\x02\x7b\x9c\x1f\x71\x5d\x3e\x84\x5e\x91\xed\x5f\xd1\x46\x2f\xe8\xcc\x3a\x33\x8d\xa2\x8d\x7b\xcc\x7f\xa3\x1a\xa7\x2c\xa3\x34\x75\xbc\x69\xe5\x60\x54\xe1\x8d\x9b\x9b\x41\xa2\xb1\x40\xf0\x63\x65\x84\x19\xca\x2f\x68\x7d\x68\x44\xc9\xd9\x0b\x57\x68\x8a\xa6\x17\xef\x88\xb3\xd3\xb9\x40\x71\x9c\x64\xfb\xb5\xbf\x39\xa2\x72\x9f\x64\xeb\xa9\x5f\xdc\x3b\xbe\xe3\x53\xb8\x93\x26\xe7\x34\xa9\x6a\x97\xc8\x85\x1e\xab\xa3\x88\x2e\x4d\xc5\xac\x03\xbf\xb8\xdf\xc4\x49\x55\xa4\xe8\x61\x4d\x7a\x9d\x64\xc4\x64\xaa\x1a\x45\xef\x5a\x08\x2b\xdc\xa6\x79\xf4\x6e\xf3\x5b\xde\x54\xdc\x7c\x2d\x03\xdb\x16\x1d\xd4\xb2\x35\x2f\xee\xdb\x62\x0f\x91\x6b\xe4\x9d\x56\xce\xdb\x69\xf3\xef\xe2\xed\x0e\xf3\x28\x77\x9b\x81\xd3\xd6\x6b\x7a\xc1\x19\xad\xf3\x62\x3d\xc3\x5d\xd2\x68\x9b\xdf\xbb\xd5\x01\xc5\xf9\xdd\xda\x9d\x15\xf7\xce\xb2\xb8\x77\x66\x8b\xe2\xde\x09\x8b\x7b\xa7\xdc\x6f\x11\x1d\xcd\x13\x2f\x78\x45\xd5\x38\xaa\x42\x3e\x0a\x7d\x14\xae\x6c\x92\x17\x3e\x9c\x1c\x51\x00\x9f\x79\x27\x2f\x7f\x38\xe2\x38\x41\x4e\x15\x95\x18\x67\x0e\xca\x62\xe7\xe5\x11\xdd\xbb\x77\x49\x5c\x1f\xd6\xcb\xc5\x4d\x71\xff\xea\x0c\x29\x3c\x68\x2c\x45\x34\x05\xe7\x10\x9e\x6b\x7c\x2f\xba\xdd\x53\x51\xe0\x32\x6a\x46\x4a\x8a\xeb\x1a\x97\x6e\x55\xa0\xa8\xa9\x1c\x16\xf7\x82\x5b\x0b\x3b\xdb\x59\xfb\x8e\xef\x10\x5b\x62\x26\xe7\xfb\xbe\xd4\x88\x47\x72\x94\x8d\x56\x8a\xb3\x3c\x2e\x9a\x5a\x26\x54\xa7\x38\x0b\x5e\x74\xd6\x91\x5f\xed\x9a\x7f\x94\x97\x3b\xea\x9a\xa6\xe6\x16\x9d\xa2\x1b\x0b\x2a\x63\x29\xda\xe2\xf4\xac\x10\xda\x28\xad\xf6\xb7\x13\xe5\x59\x5d\xe6\xe9\x59\x71\xfa\x2a\x5d\xe6\x46\xe7\x8d\xa4\x98\x46\xdc\x14\xef\xea\xb5\xdf\xfe\x49\xbe\x2f\xad\xfd\xcd\x36\x2f\x63\x5c\x52\x4f\x42\x7f\xb7\x9e\xa4\xb8\x77\xaa\x3c\x4d\x62\xcd\xdc\x74\x9b\x26\xd5\x55\xbb\xa5\x85\xb9\x56\xa4\xa1\xb4\xf4\x48\xf3\x25\x8a\x93\x53\xb5\xf6\x39\x3d\xb5\xb0\xd2\xca\xc0\x8a\x9f\x88\xb3\x37\xea\x70\xbd\xe6\xcd\x25\x59\x71\xaa\xdd\x22\x45\x11\x3e\xe4\x69\xdc\x46\x06\x51\xec\xd3\x57\x1b\xd3\xe8\xb1\x36\xd1\x48\xe2\x7d\x50\x6e\x44\xf1\x68\xbe\xa3\xbc\x78\x20\x76\x58\x51\xd7\x90\x64\x31\xce\xea\xb5\xbb\x5a\xad\x56\xc5\x3d\xb7\x62\x5f\x9a\xf2\x88\x83\xc6\xdf\x34\x71\x74\x97\xe6\x77\x6c\x6e\x67\x65\xf8\x3d\x49\x62\x97\x47\xa7\x6a\x62\x86\xd3\x28\x79\x1e\x32\xb6\x66\xaf\x2e\xde\xb6\xce\xdc\xa2\x4c\x8e\xa8\x7c\x38\x43\xc3\x98\x8e\xdb\x50\x1b\xd9\xa4\x44\x1a\xcc\x42\x78\x6a\x66\x40\xc2\xcc\x0e\x0a\x4a\x8d\xef\x6f\xe6\x0e\x4e\x43\xc8\x71\x03\x1e\x0a\xe6\xc1\x24\xf4\x67\xe4\x3f\xde\x0c\x88\x4d\x43\xeb\xe5\xd7\xd4\x1a\x5f\x45\x12\x20\x9d\x67\x4d\xa4\x22\xa6\x2e\xb1\x88\x69\x08\x9c\xfa\xae\xbf\x9c\x2d\xe3\x20\x0e\x84\x17\x6b\xf2\x53\xdd\x18\x24\xbd\xc4\x80\x8c\x81\x49\x63\x36\xa8\xc4\xb2\xf7\xd7\x8d\xed\x4b\x84\xd0\xc5\xfb\xb5\x72\xf8\x8b\xd0\x8d\x4c\x84\x3d\xad\x5f\x16\xa8\xac\x93\x28\xc5\x95\xfb\x6b\x75\x6e\xa7\xd7\x68\x5b\xe5\xe9\xa9\xc6\x1b\x1a\x75\x9b\xc9\xb1\x38\x51\x36\x4c\xd4\x9b\x69\x96\xbf\x61\x5e\xff\x37\x32\xb6\xee\xd7\x6e\x40\xd7\x1a\x5d\x5f\x95\x07\x7b\xe4\x65\x40\x18\x34\xff\x84\x57\xab\xfe\x6f\x00\x00\x00\xff\xff\x71\x15\x71\x5b\x22\xdb\x02\x00") + +func assetsLoginDistAllMinCssBytes() ([]byte, error) { + return bindataRead( + _assetsLoginDistAllMinCss, + "assets/login/dist/all.min.css", + ) +} + +func assetsLoginDistAllMinCss() (*asset, error) { + bytes, err := assetsLoginDistAllMinCssBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/login/dist/all.min.css", size: 187170, mode: os.FileMode(420), modTime: time.Unix(1570965576, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _assetsLoginDistAllMinJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\xfd\x0b\x97\xa3\x36\xb6\x30\x0c\xff\x95\x32\xd3\x87\xa0\xf6\xb6\xcb\xee\x24\xf3\x3d\x83\x5b\xc5\xca\x75\x92\x49\xe7\x72\xd2\x3d\x93\xcc\xc1\xf4\x2c\x01\x02\x53\x85\xc1\x85\xa9\xae\xea\x18\xe6\xb7\x7f\x4b\x5b\x12\x08\x8c\x3b\x99\xf3\xbc\xef\x5a\xef\xea\xd5\x65\x10\xba\x6b\x6b\xdf\xb4\xf7\xd6\xf5\xf3\xd9\xed\x7f\x3f\xf0\xea\xfd\xd5\xbb\x17\xcb\xf5\xf2\x93\xab\xe6\xca\x89\xc8\xd5\x8b\xd5\xea\x53\xb8\x7a\xb1\x5a\x7f\x7a\xa5\x3e\x7f\x5d\x3e\x14\x31\xab\xb3\xb2\x80\xab\x6f\x8b\x68\x79\xd5\x5c\xdd\xde\x8b\x2f\xcb\xb2\x4a\xaf\xf3\x2c\xe2\xc5\x91\x3f\xbf\x9e\x25\x0f\x45\x24\x72\x39\x0c\x42\x72\xb2\xca\xf0\x96\x47\xb5\x45\x69\xfd\xfe\xc0\xcb\xe4\x6a\x5f\xc6\x0f\x39\xb7\xed\x0b\x1f\x96\xfc\xe9\x50\x56\xf5\xd1\x1b\xbe\x52\xb6\x8c\xcb\xe8\x61\xcf\x8b\xda\x0b\x1d\x06\xb3\x15\x71\xfb\x86\xc8\x29\x4b\x9c\x59\x9f\x85\xd4\xbb\xaa\x7c\xbc\x2a\xf8\xe3\xd5\x57\x55\x55\x56\x8e\xa5\xc6\x50\xf1\xfb\x87\xac\xe2\xc7\x2b\x76\xf5\x98\x15\x71\xf9\x78\xf5\x98\xd5\xbb\x2b\x76\xa5\x4b\x5a\x64\x53\xf1\xfa\xa1\x2a\xae\x42\x87\x91\xd6\xc5\xbf\x8e\xf5\x50\xc4\x3c\xc9\x0a\x1e\x5b\x33\xdd\x5d\x59\xde\x93\x3f\x6e\xbd\xcb\x8e\x30\x1c\xf9\x3b\x56\x5d\x45\xd4\x0f\x20\xa6\xd1\xf2\x28\xe6\x07\x38\x8d\x96\x51\x59\x44\xac\x86\x84\x46\xcb\xc3\xc3\x71\x07\x29\x8d\x96\x59\x11\xf3\xa7\x1f\x13\xd8\xd1\x53\x0b\x19\xdd\x2d\xeb\xf2\x75\x5d\x65\x45\x0a\xb7\x74\xb7\xdc\xb1\xe3\x8f\x8f\xc5\x4f\x55\x79\xe0\x55\xfd\x1e\xee\x44\xa6\xdc\x98\x10\xd8\x53\x0b\x97\xce\x82\x82\x0e\xfb\xa0\xc6\x22\x26\xa2\x58\x26\xc5\x32\x2b\xb2\x1a\xbf\xb4\x50\xd2\xeb\xb7\xfe\xf6\xb8\x7d\xf8\xfa\xab\xaf\xbf\xde\x3e\x7d\xb6\x0a\xe6\xcd\xe8\xfd\xd9\x75\x0a\x07\x7a\xfd\x76\xb1\x3f\x2e\xae\xe1\x9e\x5e\x2f\x1c\x7f\x1b\xb3\xc5\x6f\x01\xb9\x4e\x33\xa8\xa6\x1b\x0b\x97\x75\xf9\xf7\xc3\x81\x57\x5f\xb0\x23\x77\x48\xbb\x11\x2d\xd3\x62\x79\xa8\xca\xba\x14\x93\x47\x4f\x12\x6e\xdc\x3d\x44\x65\x71\xac\xab\x87\xa8\x2e\x2b\xb7\x80\x23\xcf\x39\x3e\x5a\x16\xe4\xbc\x48\xeb\x9d\xbb\x82\xba\xfc\xac\xaa\xd8\xfb\x7e\xb5\xbb\x86\xe2\x65\xc4\xf2\xdc\x11\x53\x4f\x5a\x48\x79\x3d\x80\x08\x3d\xf4\x87\x3c\x9f\x51\xe6\xad\x6e\x98\x27\x72\xfa\x6c\x2e\x7e\x96\xb2\xfe\xc0\x95\x69\x81\x3b\xac\x4c\xac\xcc\xeb\x9a\x45\x77\x83\x2a\xc5\x8a\x86\xb4\x58\xee\x79\x95\x72\xcc\xba\x34\x06\xe0\x10\x60\x3d\xf4\x2c\x0f\x15\x7f\xf7\x23\x82\x38\x45\xe0\x08\x45\xde\x9a\x3f\xc9\x57\xfd\x02\x61\x0b\x9c\x45\x3b\x77\x7a\xdd\x96\xe2\x1b\xb6\x04\x72\xd5\xf6\xec\x30\x35\x4a\xac\xb2\xeb\xb4\x53\x2c\xf7\xec\xe0\x0c\x61\x32\x84\xa8\xcb\xce\xe4\x60\x43\x88\x44\xa5\x84\xb4\x80\xf0\x39\x31\xc7\xa3\x8a\xe3\x25\x3b\x1c\xf2\xf7\xaa\x47\x55\x8a\xf0\x77\x14\x15\x24\x59\x75\xac\x2f\x55\xc0\xef\x9d\x15\x69\x21\x67\x1f\xcc\xb2\x58\x93\x16\xf8\xfd\xc4\x94\x1b\x2b\x06\x11\x9d\xb3\xb9\x23\x96\x33\x74\x57\xdd\x7c\x8f\xfa\x19\xdd\xd0\x95\x6d\x87\x37\x91\xe7\xe3\x02\x47\x41\xe0\xfa\x81\xa8\xbe\x88\x2f\x8e\xb2\x5b\xb0\xa6\x39\x5b\x5b\x01\x46\x0a\x2e\xdc\x04\x8e\x65\x55\xbb\xd1\x52\xfc\xc0\xf1\x80\x53\x17\x2d\xe5\x43\x0b\xc5\x92\x3f\xd5\xbc\x88\x29\xee\x38\xf5\x6c\xb4\x29\x86\xc4\x40\xcc\x7d\x0c\x1c\x12\x48\x69\x37\x91\xfe\x2a\x68\x9a\x53\x0b\x3b\xba\x86\xac\x4f\xd6\x43\xbf\xa5\xb3\xf5\x26\x11\xe8\x2c\x2c\xcb\x9c\xb3\xa2\x47\x9e\xa9\x6d\x3b\xb7\x34\x1d\x54\xb6\x53\x95\xcd\xe7\x04\xce\xb0\x6d\xda\x34\xc5\x32\x3b\x7e\xad\xfb\x95\x92\xa6\x71\x52\x7a\x6a\x09\xec\x28\xa5\x99\x6d\x3b\xa9\x04\xdc\xdd\x62\x41\x36\xd9\xcd\x6e\x23\x2a\xca\x12\x47\xee\x28\x87\x0d\x5a\x22\x44\xf4\x2b\xbc\xca\x8a\x2b\x46\x22\x9a\xfa\xa1\xc0\x7b\x4c\xfc\xa4\x33\x4a\x63\xd1\x3d\xdb\x16\x3f\xa2\xd5\x9f\x72\x96\x15\x72\xae\x9d\x58\x34\xcc\xa9\x48\xc6\x8d\xee\xc4\x84\x10\xcf\xe1\x9e\xc3\xe9\x6c\x2d\xf0\xa4\x6d\xf7\x1f\x23\xe2\x45\x62\x25\xdd\x2e\xdd\xac\x0b\xbf\x9e\x5a\x10\xcd\x53\x3d\xf7\xce\x2d\x24\x10\x13\xe2\xbe\x2b\xb3\xf8\x6a\xa5\x7a\x83\x59\x62\xd2\x01\x50\xda\x2f\x9c\x73\xe2\x4f\x07\x56\xc4\xa5\xab\xc8\x86\x35\x77\xf6\xf3\xef\x59\xbd\x5b\x56\x22\x79\xef\x10\xb2\xac\xf8\x21\x67\x11\x77\xae\xb7\x5f\x5e\xa7\x60\x59\x04\xb2\xe3\xcf\x9c\xc5\xef\xdd\xd9\x0a\xb8\x20\x3a\x03\x38\x1e\x13\x24\x46\x5a\x28\xca\xf2\x60\x02\x63\x0b\xfd\x7a\x4c\x6c\x72\x4b\x27\x59\x94\xd2\x62\x29\xd6\x11\xab\x51\x53\xe3\xe2\x5f\x3d\x51\x90\x1d\x7f\x91\x94\xe9\x32\x4e\xb4\x6d\x46\x29\x65\x4b\x49\xc2\x44\x45\x3f\x3c\xec\x79\x95\x45\x13\x65\x66\xfd\x12\x30\x62\xdb\x6c\x71\x60\xd5\x91\x7f\x9d\x97\xac\x76\x18\x99\xaf\x6f\xe8\x4a\x54\x60\x2c\xc6\xd4\x08\x14\x10\xce\x8c\xfe\x37\x0d\x5b\x16\x65\xcc\xdf\xbc\x3f\x70\x09\x92\xb2\xdf\x0e\x23\xde\x6c\xed\x32\x73\x17\xda\xf6\xec\x56\x62\xae\x41\x72\x4f\x59\xc0\xca\x8e\x3f\xe9\x97\x1f\x13\x0b\xab\x98\x61\xcf\xbe\xda\x1f\xea\xf7\x13\x3d\x43\x04\xb3\x31\x80\x57\x8d\x77\xad\x00\x43\x94\x16\xb5\x5d\x9a\x47\x4a\x99\xc7\xe6\x96\xe5\x9e\xed\x30\xd6\x34\xe6\x92\xe9\x54\x6f\xe7\x67\x6a\x10\x24\x68\x1a\x5d\xcc\xd5\xdf\x5b\x48\xf3\x32\x64\xf9\x57\xef\x58\x7e\xde\x53\x88\x28\x7f\xc7\xf2\x0d\x13\x33\x58\x65\x7b\x87\x11\x60\xb6\xed\xac\x71\x25\x15\x1f\xe1\x58\x0f\x47\x7e\x75\xac\xab\x2c\xaa\x2d\xe2\x39\x21\xcd\x97\x51\xc5\x59\xcd\xbf\xca\xb9\xd8\xb1\x8e\x75\x8c\xaa\xec\x50\x5b\x04\xc2\x25\xd2\x24\x06\xf9\x72\xc7\x19\xe2\x77\x5e\xc4\x5f\xec\xb2\x3c\x76\x42\xb2\x3c\xb0\x8a\x17\xf5\x0f\x65\xcc\x97\x15\xdf\x97\xef\xb8\xfe\x42\xdc\xc8\x61\x02\xf1\x47\x6c\xcf\x73\x41\xea\xa7\x66\x88\x75\xfb\xe4\x00\xd6\xfe\xb8\xb0\xfa\x8d\x73\x0f\x15\xee\x81\x98\xff\xc0\xf6\x7c\x9a\xfe\x49\xd0\x10\xdf\x6d\xbb\x7f\x5e\xd6\xe5\xab\xf2\x51\xf3\x17\x94\xd2\x70\x98\x32\x41\x51\x05\xed\x13\xf3\x17\x03\xa7\x2b\x48\x28\xd3\x28\x35\xa5\x47\x87\x91\x4d\x96\x38\x11\x32\x91\x29\x39\x09\x58\xd8\x24\x37\x7c\xc3\x25\xbe\x8b\x69\xa8\xa8\x1e\xf3\x79\x00\x11\x81\x98\x52\x3a\x5b\x93\xb0\xe2\xec\xae\xe5\xf9\x91\x5f\x89\x32\x5c\xc2\xcf\x1f\x2c\x71\xb9\x2d\x09\x1b\xa2\x20\x07\xf1\xf3\xc7\xda\xfb\x70\x29\x8d\xe5\x58\x0b\x02\x6a\x3e\x04\xcc\x96\xe5\x3a\x02\xa0\xfb\x95\x2a\x05\x7a\x13\xac\xc7\x1d\x1f\xb1\x62\x06\x9f\x1b\x36\x8d\x1f\x6c\xc6\x08\xc6\x39\x3a\x0a\x35\x33\x42\x3c\xcd\x39\x45\x60\x1d\x91\xbd\x35\xb7\x85\x60\xc2\x18\x71\x13\x39\x92\x08\x18\x21\x10\xb5\x90\x15\xe7\x6d\x1a\x9c\x8c\xec\x75\xe8\x2d\xd6\x6e\xaa\x99\x1a\x06\x91\xe8\xae\x68\x6a\xd4\x55\x31\x6d\xb2\xbb\xf3\x50\x43\x40\x4c\x57\xc0\x3b\x80\xd8\x44\x37\xf1\x26\x9e\xcf\x09\xf3\xf9\x7c\x1e\xd0\xd0\x8f\xbb\x51\xe9\x3c\x94\x83\xd8\xa5\x15\x3f\x9c\xf5\x4a\x37\x20\xe0\xcc\x0f\x20\xa1\x2b\x41\x91\x75\x53\x3b\x3a\x8b\x36\xe9\x4d\xb2\x49\xe6\x73\x12\xd3\x59\xe8\x30\x3f\x09\x20\x21\x10\xcf\x28\xdd\xd9\x36\x47\x16\x06\x53\x3b\xc2\xc4\xc7\x4c\x9f\x09\xcd\x67\x0d\x08\x68\x86\x8c\xfa\x81\x00\xe9\x1d\x12\x65\xa3\x45\xdd\xa0\x00\x49\xb9\x46\xb1\x6d\x67\xb2\xd1\x98\x6c\x3a\xd0\x4a\x24\x68\xfd\x6e\x01\xdd\x45\x05\xed\x7e\x00\x99\xe0\xc4\x1f\xb2\xd8\x5d\xc3\xa1\x2a\x9f\x26\x61\x45\x70\x3d\xaa\xe8\x19\x1c\x84\xb6\xed\x44\x92\x6f\x08\x29\x03\x46\x45\xc3\x26\x9f\xc2\x04\x6f\x40\x15\xb7\xde\x71\x20\xf0\x82\x40\x42\xcf\x19\x3b\xa6\x7a\x16\x4a\x96\x0e\xb8\x12\xbf\x9c\x71\x05\x04\x79\xd8\xa5\xe8\x3a\x65\xe6\x8f\xa0\x49\xe2\x77\x3e\x87\x44\xb3\x10\x02\x6d\x3d\xba\x5f\xb2\x9a\x2f\x8b\xf2\x11\x8e\x0f\x07\x21\xa3\xba\x77\xad\xe8\x2b\x72\xec\xd6\xe7\x92\x41\xbb\xfa\xe1\x61\x1f\xf2\xea\x4a\x4a\x73\x57\x7a\x14\x57\x08\xd3\x57\xa2\x86\xab\x9f\x79\xfa\xd5\xd3\xe1\x4a\x6e\x13\xc9\x1d\x58\xc8\x4b\xd6\x8e\x75\x65\x91\x91\x50\xb9\xf3\x2d\x5f\x52\x8c\x2b\x6b\x1e\xce\xad\xc0\x0a\xce\xd0\x1f\xd9\xe8\x32\x57\xc7\x9e\x87\xb6\x24\x8c\x58\x62\x65\x05\x36\xed\xd8\xe9\x8e\x14\x6f\x26\xf8\x8c\x68\x82\x26\x4b\x5a\xa3\x89\xb6\x6d\x87\xde\x6c\xe5\x5a\x4c\x0c\x49\x15\x59\x09\xa4\xdc\x34\x56\x81\xa3\x1f\xac\x6e\x78\x23\x18\xf3\xc5\x1a\x21\xac\x15\x5d\xab\xe9\x04\x95\xd3\xbc\x31\xec\x20\x83\x5b\xb8\x83\x1c\xf6\x50\x40\x09\x07\xb8\x87\x0a\x8e\x50\xc3\x03\xb5\x8e\xd9\x6f\xbf\xe5\xdc\x9a\xaf\x9f\x0b\xce\x4a\x4c\x27\xbc\x33\x05\xe3\x47\xba\x82\x27\xba\x82\xf7\x74\xc7\x1c\x02\xbf\xc9\x9f\xcf\xe4\xcf\xe7\xd3\xe2\xab\xe0\x89\x04\x14\xe6\x74\xb6\x22\xb0\x6a\xe1\x0b\xba\x7e\xf9\xf2\xe3\x35\x7c\x49\x4f\xed\x58\x16\xff\x4a\xec\xf2\xaf\xe9\x57\xcb\x43\x79\x80\xbf\x8a\x5f\x21\xd2\x7f\xa3\x1f\xbe\xa5\x5f\x29\xc9\xff\x6f\xf4\x12\x26\x5a\x09\x3e\x59\x63\x9f\xf8\x26\xda\x44\x92\x14\x30\x3f\x0a\x44\x57\x14\x43\x72\x15\xa9\xe5\x59\xac\x5b\xf8\x8e\x5a\xd1\x8e\x47\x77\x3c\x6e\xa4\xcc\xcc\xe3\x86\x1d\xdf\x17\x51\xc3\x1e\xea\x32\x29\xa3\x87\x23\x3e\x1d\x72\xf6\xbe\x11\x92\x66\x55\xe6\xc7\x26\xe6\x09\xaf\x9a\x38\x3b\xb2\x30\xe7\x71\xb3\xcb\xe2\x98\x17\x4d\x76\xdc\xb3\x43\x93\x97\xe5\xa1\xd9\x3f\xe4\x75\x76\xc8\x79\x53\x1e\x78\xd1\x54\x9c\xc5\x65\x91\xbf\x6f\x94\xd2\x24\x6e\x8e\x51\x79\xe0\xb1\x05\xaf\xa8\xe5\x6f\xb7\x4f\x2f\x56\xdb\x6d\xbd\xdd\x56\xdb\x6d\xb1\xdd\x26\x81\x05\xdf\x53\xcb\xf1\xdc\xed\x76\xbb\x5d\x36\xfe\x76\xfb\xb8\x08\x1a\xff\xed\x76\xfb\xb4\x5a\x2d\xb6\xdb\x27\xb6\x0a\xc8\xdc\x82\x1f\xe8\xf7\x1d\x65\xb1\x1e\x2d\xb0\x1e\xff\x64\x11\xf8\x91\x5a\xdb\xad\x6f\xcd\x5f\xcd\xad\xe7\x8e\x35\xff\x7e\x6e\x11\xc7\x73\xd5\xbb\xff\xfc\xed\xb3\x66\xf6\xef\xc0\xa3\x44\xa5\x78\xee\x47\x4e\xdf\xd4\x5b\xf1\xfb\x51\x40\x9e\x93\x8f\x9a\xad\x35\xfe\xb0\xb5\xc4\x97\xad\xd5\x38\xd6\xfc\x87\xb9\x45\x48\xa3\x6a\xd9\x6e\x03\x0b\x7e\xa2\x96\xdb\x37\xb8\xdd\x3a\x8e\xf3\x9f\x57\x4d\x9a\xf1\x17\x87\xf8\xdb\x6d\x10\x34\xd6\xfc\xc7\xb9\x45\x9e\x93\x66\xf9\x9c\x6c\xb7\xa2\x69\xf8\x6f\x2a\x80\x55\x6e\x7b\xe7\xd5\xdc\x9a\x5b\x60\xa5\x16\x81\x9f\xcd\x74\xeb\x2d\xf6\x71\x8e\x15\xbf\x55\x95\x06\x44\xb7\x42\x9e\xcb\x31\xcc\x9f\xa9\xc2\xaf\x27\x0a\x3f\x07\xf9\x63\x11\x78\x33\xf5\xd9\xf1\x6f\xe6\xff\x16\x5d\x7c\x35\xb7\x48\x97\xf5\xef\x83\xac\x54\x67\x7d\xbb\xdd\x06\x1f\x6d\xad\xe0\xb9\x67\xce\x1e\xb6\xfd\x0f\xb3\xc4\x4f\x04\x7e\x19\x37\xf6\xc3\xdc\x7a\x66\x11\xf8\x95\x9e\xbe\xfd\xd2\x1d\x7c\xfb\x93\x9a\x7a\x8b\xc0\x17\xaf\x3e\x7b\xfd\x7a\xf8\x75\xbb\x5d\xf6\xdf\xdf\x7c\xf6\xd7\xe1\x57\xf1\x69\x04\x49\xcf\x2d\x22\x33\x7f\xf6\xe6\xcd\xcf\xee\xa8\x17\x3f\x12\xf8\xe9\xf5\x57\x7f\xff\xf2\xc7\xf1\x87\x9f\x08\x7c\xf1\xcd\xb7\xaf\x46\x5d\x73\x1d\x04\x7e\xd4\x6b\x34\x39\x3b\xd6\x4d\x51\xef\xc4\xff\x85\x78\x21\x0b\x27\x12\x9c\x70\x53\x26\x0b\x81\xdc\x14\xf0\xa8\xd9\xe2\xef\x78\xd1\x94\x71\xdc\x38\x8e\x3f\x5f\x04\x0d\x71\xb6\xdb\xf8\x39\x29\x9a\x1e\x7e\xd5\x07\xf5\xbe\xdd\xc6\x73\xd2\x90\x6e\x6a\x11\x50\xac\x4c\xf0\xe7\x65\x99\x8f\xc6\x2d\xf6\xc5\x77\x73\x8b\x3c\x53\x59\x0a\xce\xe3\xe3\x17\x52\x9f\x34\x1e\x9b\xa8\x4e\x2e\xb3\xdb\xf7\x8a\xdf\x37\x69\xdd\xe4\x72\x44\xfd\x00\x87\x63\x70\x3c\x77\xb1\xdd\xc6\xc4\xc3\xae\x1b\x1d\x73\x3c\xea\xbf\x5d\x04\xcd\x33\xd5\xc5\x16\xfe\x49\xaf\x45\xaf\xb2\xe2\xf0\x50\x2b\x84\xd4\x88\xce\xb0\x8a\xb3\x26\x7c\xa8\xeb\xb2\x20\xcf\xae\x33\xf8\x1f\x7a\xfd\x76\xb7\x8d\xc5\xe3\x33\x7a\xfd\xd6\x7f\x7b\x0a\xe6\xdb\xd3\xf6\xf8\x7c\xeb\x17\xac\xce\xde\xf1\xab\xed\xe3\x35\xfc\x4b\xd6\xf6\x27\xc7\x17\x18\x64\x4e\x1a\x67\xfb\x38\x27\xcd\x76\xa9\x13\xc8\xb3\x6b\x60\x8c\x5e\xfb\xf3\x7f\x07\xd7\x10\x32\x7a\xfd\x51\xb3\xdd\x5e\xa7\x10\xb1\x01\xe4\xe1\x3e\xf4\xb7\xdb\x98\x2d\x92\xe0\xb4\x86\x3f\xb7\x38\x0a\xaf\x91\x43\x24\xcd\x12\x47\x20\x40\x38\x66\x74\x92\xc1\xa2\xd6\xea\xc9\x9a\x87\x8b\x3f\x7f\xfa\xe9\xc7\x7f\xd6\xec\x8e\x60\xd6\xe2\xa6\x89\xbc\xd0\x5d\xdd\xc4\x9e\xa4\xed\xcb\xa4\x2a\xf7\x5f\xec\x58\xf5\x45\x19\x73\x27\x9e\x63\x09\xe2\x4e\x7e\xbc\xb9\x59\xaf\x9a\x4f\x3f\x7d\xf1\x97\x3f\xc3\x7a\xf5\xe2\x63\x3b\x6e\x3e\xfd\xf3\xc7\x2f\x56\x28\xb8\x98\x4c\xcc\xde\x21\xed\xa6\xae\xde\x9f\xbe\x51\x6c\xcc\x57\xf4\x5b\xc9\xb7\xbc\x5b\x22\xf4\x09\xb9\xec\x48\x60\xf8\xf6\x95\x6f\xbe\x6b\x45\x67\x47\xaf\xdb\x88\xd5\xd1\xce\x49\x18\x39\x7d\x43\x4f\x58\xaf\xfb\x95\xca\xe5\x0d\x89\xd4\x5f\xb5\x14\x03\xaa\xd9\x90\x90\x76\x92\xfb\x67\x06\x37\xbd\x79\xdc\x65\x39\x17\xf4\x4b\x31\xd0\xf3\x79\x40\x36\x1d\xf3\x1c\x2d\xd6\x6d\xdb\x76\x1c\x4a\xca\x70\xc2\x63\xe0\xb2\xae\x04\x76\x8a\xde\x97\x48\xe7\x1f\xe1\x49\xb0\xb2\x4e\xe8\x85\xcb\xf2\xb1\xe0\xd5\x97\x8a\xb8\x37\x4d\xe8\xbe\x23\x33\x4a\x0b\xdb\xde\x3b\x21\x81\x50\xb0\x1c\x05\xc4\x62\x6d\xfc\x00\xee\x68\xd8\x8d\xb9\x93\x37\x66\x86\x70\x3e\x63\x4d\xb3\x9e\x51\x7a\x67\xdb\x7f\x91\x3f\x6b\x7c\xd5\x04\x37\x16\xed\xce\xb8\x6d\x1f\x50\x32\x5c\xab\xbc\x4e\x42\xff\xb5\xe4\x4f\x1c\xc5\x5f\x41\xa8\x6f\x69\xe2\xaf\x03\xcc\xf3\x17\x2a\xca\x8b\xa7\x1d\x0d\x97\x29\xaf\x95\xbc\xfd\xf9\xfb\x6f\x63\xe7\x96\xc0\x6c\xd7\x34\xb3\x9d\x21\x54\x0f\xda\xda\x2d\x33\x21\xb3\xdd\x76\x89\x92\xcf\xde\x11\x88\x3b\x99\x71\x34\x09\xb6\x8d\x2d\x0d\xd2\xce\xdb\x25\xb6\x5d\x3b\x21\xec\x88\x6d\xff\x5e\x1b\xa2\xef\x89\xff\x22\xd0\xdf\x35\xe4\xc5\x60\x8e\xe7\xf8\xf9\xfb\x37\x2c\x15\xb2\xb8\x98\x04\xc0\xde\xe3\x3c\x7c\x1c\x10\xdb\x8e\x86\x39\xbf\xc8\xd9\xf1\x28\xf2\xfe\x6e\x9d\x5d\x4e\xd1\x67\x88\x5b\x21\x95\x2f\xef\x8f\x42\x8e\x9c\xdd\x37\xcd\xec\x7e\x59\xf3\x23\x8a\x92\x38\xc7\x47\x5a\xd1\x07\x78\xa4\x21\x3c\x51\xb5\x38\x0c\x04\x73\x7a\xd7\x9f\x2e\xcd\xa8\x02\x83\x73\xc5\x01\x39\x95\x34\x15\xe2\x92\x53\xc9\xc5\xfa\xac\xae\xab\x2c\x7c\xa8\xb9\x63\x65\xb1\x45\x88\x77\xa4\x55\x47\x60\x42\x06\xd6\x76\xfb\xcc\xb6\x88\x1b\x2e\x8f\xe3\xcc\x70\x24\x70\xa4\x96\x9f\xc5\xf4\x23\x6b\x7e\x9c\x5b\x1f\x05\x57\x16\xe4\xb4\xd4\x8c\x9d\xdc\x13\xf9\x62\x41\x4a\x3f\x0f\xe8\x71\x5e\x31\x47\x3c\x91\xcd\x23\x65\x4c\x8f\xcb\xb6\x0f\xcc\x09\x4d\xf8\x68\x1a\x31\xba\x72\x79\x5b\x66\x85\x63\x81\x45\xc4\xa4\x3c\x11\x81\x14\xce\x66\xf3\x71\x89\xc7\x2a\xaf\xd5\x29\xca\x67\x79\xee\x3c\xe1\x3c\xca\x1d\xff\x9e\x9c\xda\x24\x2b\x58\x9e\xbf\x3f\x55\x4d\x13\x2a\x85\xce\x68\xd4\x6d\xdb\xaa\x8a\x33\xa7\xd7\xde\xfc\x0c\xd6\xb3\xb5\xa0\x46\xb8\x51\xfb\xdd\x2b\x18\x69\xa9\xd0\x16\x22\x67\x97\x1c\x3a\x91\xd8\xcf\x9d\x10\x86\x30\x16\xcd\x85\x3c\x73\x23\x24\xaf\x68\xc7\x5f\xe1\xbc\xd8\x76\xcc\x73\x5e\xf3\xab\xd0\x67\xcb\xe3\x2e\x4b\x6a\x87\x04\x10\xfa\x98\x37\xa0\x5c\xf7\x25\xec\x9b\xcc\x98\xa9\x5f\xf2\x1f\x02\x3a\x5b\x01\xeb\xbf\xdf\x32\xf3\xa8\x66\xa4\xf6\x8a\xb3\x77\x16\xd9\xf4\xb3\x37\x9b\x31\x27\x24\x6a\x82\x3a\xad\xc2\x6c\xdd\x4d\x94\xb9\x18\xb6\x1d\x5e\xd6\x87\x41\x48\x85\x80\x6c\x60\xb6\x3b\x36\xc4\x90\x4a\xa6\x6b\x2c\x62\xea\x1b\x24\x60\xf0\xc5\x82\xc4\x4b\x56\xd7\xd5\x37\xac\x88\x73\xee\x47\x3e\x0f\x02\x6a\x0c\x3b\x1f\xd4\x16\x0a\x50\x8f\x69\x64\xdb\x63\x61\x6c\x4d\xa9\x81\xf8\x6c\xdb\xf9\x77\xb8\x3c\x96\x0f\x55\xc4\xbf\x2d\x62\xfe\xd4\x34\x5f\x90\x85\xf3\x6f\x36\x4e\x13\x3b\x38\x1e\x60\xa3\x88\xc8\xae\x45\x34\x5a\x16\xfc\xa9\x7e\x9d\x85\x79\x56\xa4\x02\xe3\x45\x86\x5c\xb2\x58\x77\xea\x11\x6f\xed\x2e\xd6\x7d\x8f\xf7\xe6\x42\xf5\xc7\x50\xdd\x10\x2e\x6c\x4b\x2d\x85\x22\x37\x81\xf2\xa4\x98\x77\x3c\x37\xa4\x94\x19\xf3\x5b\xfc\x5f\xd5\xef\x18\x0d\x34\x8d\x25\xb9\x14\x7c\x23\x17\xda\x2b\xcd\xf6\x32\xe6\x98\x4d\x6a\x30\xa5\xf3\x10\xcc\x4f\x11\xc4\xb2\x3f\x1c\x12\xca\x1c\x3f\x80\x48\x53\xca\x90\x40\x4a\x93\x21\x18\xa4\x8b\x05\x89\x7c\x4e\x13\x3f\x0d\x02\xdb\x76\x04\x14\xd0\x99\x13\x8b\x1f\xf1\x4c\x48\x2b\xfe\x75\x5d\x3a\x0c\xf6\x82\x6d\x4f\x9d\x53\xb3\x49\xbc\x6d\xdb\xac\x8d\x68\xca\x96\x4a\x79\x41\x4f\x2d\x24\xe2\x3d\x3b\xfe\xfa\xfd\xab\x73\x89\x1c\x95\x79\x6c\x4c\x81\x19\xe9\x64\x6d\xd5\x42\x77\xfe\xe9\x59\xdf\xbc\xf9\xfe\xd5\x10\xff\xba\xb3\x75\x0b\x7b\x6c\x95\xd7\xba\x96\x09\xe9\x9f\x43\x4a\x99\x77\xde\x9a\xfb\xae\x3b\xad\x91\x74\x5f\xd0\xdb\xd4\x00\xf6\x74\xdc\x1d\xcf\x29\x68\x0a\x25\x3d\xfb\x00\x5c\xa4\xf1\x84\x3d\xe4\xf5\x3f\x32\xfe\x08\xdc\xb6\xf9\x8c\x52\x01\x2c\x07\xdb\x76\xf8\x92\xc5\xf1\x57\xef\x78\x51\xbf\xca\x8e\x35\x2f\x78\xe5\x9d\x27\x39\xd6\x43\x91\x97\x2c\xb6\x80\x33\x98\xad\x89\xcb\xc5\x16\x66\xd1\x0e\x73\xd9\xf6\xe0\xd5\xb1\xca\xa2\xcf\x4e\x08\x1c\xe8\x2c\x71\x52\x02\x11\xee\x7b\x44\xc1\x47\x7a\x6b\x00\x8f\xa9\x46\x8f\x34\x69\xa4\x56\x66\xc1\x8c\x8d\xe8\x55\xf7\xd9\x22\xad\xa8\x71\x6a\xc9\x2f\xd6\x6d\xaa\xfc\x53\x85\x31\xbf\x28\xf7\x12\x63\x5a\x84\xa8\xe6\xce\x69\xbf\x90\x17\x15\x00\x9f\xb7\xda\x11\x73\xfa\x4c\x92\xb7\xf4\x12\x5b\x20\x4b\x0a\x5e\xe5\x42\x17\xcb\x41\x17\x19\x11\x3c\xcc\x03\xcc\x46\x15\x8a\xba\x9a\x66\x2a\xd5\x79\x18\x77\x53\x34\xe6\x39\xf1\x32\xc9\x8a\x78\xf9\xed\x97\x23\xe5\x4c\x96\x4c\x5a\x7c\x8c\x39\x3a\xe4\x0c\x35\xb2\x19\x31\x5d\xbd\x25\x40\x24\x18\xa2\x9e\x6c\x78\x7e\x14\xb8\x7e\xd0\xb6\x20\x5a\xcf\x6b\x5e\x0d\xdb\xef\xf6\x5b\x47\x7b\x23\x06\x71\x5f\xdd\xe4\x0a\x9e\x33\x2f\x02\x43\xb7\x2d\x71\x1d\x45\x5f\xbb\xa1\xfe\x3f\xd0\xac\x1c\xf2\x45\x5c\xd3\xf5\x44\xd2\xcc\xf3\x34\xd9\xc3\xc1\xfc\xbc\x63\xf9\x03\x57\x7d\x06\xd5\xd7\x37\x9f\xfd\x95\x4e\x43\xb2\x37\xa5\xb8\xfb\xbd\x15\x33\x8a\x5f\x64\x65\x5d\x64\x37\xbd\xf0\x9c\x91\x62\x9d\xee\x77\x52\xa3\x1d\x53\x3f\x50\x47\x4b\x17\x2b\x17\x44\xd5\x7a\x2e\x48\x0c\x23\x27\x4d\x5b\x13\x3c\x66\x20\x82\x6a\x47\x06\x22\x53\x6c\x79\xd4\xcd\x52\xac\x39\xa1\xa4\xd5\xf3\x83\x0a\x93\xf1\x0c\x75\xbb\xca\xb6\x27\xb5\x9b\x87\xf1\xe0\x7b\x9e\xbb\x1f\x22\x54\x62\x38\xf7\xe2\x8f\x64\xc0\xfb\x2d\x3c\x9e\x18\x21\x5b\x38\xa3\x5d\x7b\xbe\x5d\x8b\x82\x57\x82\x1c\x50\xeb\x25\xbb\x92\x3c\xf2\xc3\xdc\xfa\xe8\xe6\xe5\x35\xbb\x79\x29\x15\x06\x7d\xf2\x62\x9b\x04\x1f\x5d\xed\x8f\x2c\xcf\xcb\xc7\x88\x1d\xea\x87\x8a\xd3\x8f\x3e\xba\x79\x59\x1e\xa4\x4e\x5b\x69\x3c\x31\xed\x5a\x26\xde\xbc\xbc\x96\xc9\x37\x16\xb0\xf3\xd5\xb3\xfc\x61\x75\x6f\xe9\x47\x1f\x05\x1d\xee\xb2\xed\x7b\x39\xdd\x96\xff\xfc\xed\xb3\x80\xf6\x3a\xc6\x8f\x9a\xad\xb5\x45\x85\xd2\x64\xa5\xba\x27\x7d\x55\x4d\xa3\xab\xea\xb5\x99\x9e\x8b\xd0\xdd\x48\xa5\xcd\xa5\xba\xb2\xf8\xdf\x54\x0e\x7f\xaa\xb6\x7f\xd3\x0b\xe5\x5c\xa5\x07\x9e\x28\xd3\x7f\x9a\x2c\xc9\xfe\x84\xcd\xcd\x9f\x4f\x14\x5d\xfe\x69\x39\xf7\xe7\xff\x0e\x90\x9a\x8c\x56\x57\xe2\x89\x74\xcc\x59\x4b\x6e\x8a\x6c\xc6\xc2\x91\xd8\x89\x16\x58\x52\xd9\x8c\x5d\x19\x9d\x30\x0f\xb3\x17\x82\x86\x81\xf5\xe5\xa5\x69\x12\xdf\x69\x3c\xb5\x76\x58\x52\xea\xbb\x3a\x75\xf1\xa5\x49\xe3\x05\xaa\xc0\xa7\x26\x4d\x7f\x02\xcb\xd5\x9a\xf2\x0b\xb5\x3c\x07\xf7\xc9\x22\xa0\x4b\xc2\xf2\xb9\x2b\xe6\x8b\x88\x3d\xb3\x17\x02\x05\x3f\xea\xfc\x7a\xff\x1c\x69\xa9\x3f\x35\x4d\xb9\x7c\xe4\xe1\x5d\x56\x7f\x3f\xcc\x2b\x3e\xec\xcb\xdf\x26\x52\xcb\xa9\x9c\xc7\x51\xa2\xd8\x90\xa3\x15\x8b\x96\x71\x76\x8c\xca\xa2\x40\x60\xc5\xfc\xf4\xa8\xce\xbf\x40\x8a\x44\xd0\xbf\xfb\xc7\x99\xd8\x1d\x38\xb6\x4a\x8d\x6d\x46\x2d\xf8\x49\xc0\xc2\x3d\xbd\xef\x26\xde\x50\xb5\xdd\x2b\xf9\xb4\x11\xdc\x42\x45\xab\xa9\x3c\x95\x99\x27\xd4\x33\x52\x2e\xa3\x72\x2f\xa8\xa3\x66\xf3\x7e\x2a\x8f\x99\xe8\x38\x81\x9a\x86\x4d\x63\x64\x2b\x6a\x96\x15\x47\xe2\x4d\xe9\x9f\xfe\x32\x90\x82\x3c\x36\x66\xf7\x5c\x21\x2d\x85\x43\x01\x6e\x63\x1c\xf8\xc4\x4d\x33\x73\x66\xb1\x54\x08\xc5\x86\x41\xca\xcc\x89\xba\xa6\xbd\xfe\xd1\x89\x09\x9a\xa6\x4c\x76\xdd\xb6\xd7\x7f\xb6\x2f\x7e\x45\xfb\xa6\x31\x15\xc9\x12\x27\x54\xf2\x56\x48\x07\x0a\x00\xf1\x05\x49\x86\x36\x47\xd9\x74\x32\x2a\x7c\x4e\x43\xef\xac\x1e\x66\x1e\x1a\xe5\x42\x38\x5e\x6d\xa4\x2e\x73\x76\xb1\x4f\x8b\x59\x78\xe9\x53\x47\x80\xbc\xd8\x75\x62\x3a\x25\x03\x50\x4a\xc7\x7a\xa9\xa6\x09\x89\x77\x79\x0a\x42\xe2\xae\x61\x6d\x8b\x59\x97\x56\x74\x5f\x72\xc1\x27\xf3\x58\xac\xd0\xa5\x42\xd8\x50\xec\x89\xf1\xa5\x4d\x33\xea\x07\xa5\xf4\x9d\x6d\xd7\xce\x3b\x60\xc4\x5b\xac\xdd\x50\xe6\x0a\x2f\xe5\x0a\x89\xb7\x76\xef\xbc\xbf\x39\x77\xc0\xc8\x42\xfc\x84\xc4\x5d\xb9\x9f\xd8\xb1\x28\xbd\x9e\x5a\xa0\x4b\x13\x1b\x75\x26\x04\xfd\xb2\x21\x1f\x60\xbc\xee\xa8\xcf\x02\xc8\xa8\x1f\x06\x52\xab\xd8\x34\xb3\x84\x18\x00\x98\x76\x9d\xf6\xd6\x2e\x17\x2f\xc9\x54\x07\x45\x61\xc1\x27\x75\x65\x95\x5a\x60\x13\x51\xb6\xe9\xe5\x75\x03\x7e\x76\xcb\x87\x42\x2a\x56\x22\x91\x2b\x9c\xce\x95\x99\xb9\x64\x8e\x9d\x1f\x07\x94\xd2\xcc\x8f\x03\x12\xcf\xe7\x3d\x1c\xe4\x0c\xbf\x01\x7e\x71\x55\xb6\x77\xa2\xcb\x99\x7e\x5e\xbb\xab\x16\x52\xe2\x16\x2d\xa4\x4c\x63\xbc\xe9\xe3\x56\xd4\xff\x16\x0f\x79\x2e\xff\x84\xc4\x2c\xd2\xe1\xcf\xb3\xc5\x98\x82\x43\xad\x0b\x66\xa8\x0b\xee\x18\xd9\xbf\x83\x45\x3f\x7a\xb6\x16\x04\x1f\x66\xce\xec\x0c\x39\x37\xcd\xec\xd0\x34\x95\x6d\x57\x12\xd7\x84\xa4\x69\xee\x05\x5d\x51\x6f\x04\xb5\x6d\x72\x0b\x75\x68\x32\x94\x1a\x93\xa6\x99\x40\xae\x02\x38\xe3\x4e\x41\x8b\x7a\xe3\x3e\xa1\xc3\x2d\x9d\xb2\x45\x69\x9f\x38\x39\xb5\xfd\x9c\x84\x50\xc8\x09\xf1\x59\xa0\xa9\xd4\xcd\x0a\xe7\x46\xe3\xa0\xc9\xf9\xfc\x9d\x79\xd1\xc6\xdf\x29\x43\xa1\x73\x54\xc5\x87\x0b\x23\xb0\x73\x3a\x50\x53\x8d\x4c\x0f\x02\x48\x28\xb7\xed\x2f\xe5\x2c\x99\x39\x61\x94\x93\x78\x1c\xf5\xfe\xb3\x83\xe6\x3a\x35\x80\x75\x56\x9a\x89\x97\xb8\xa6\x6c\x2c\xd6\xc9\x1b\xc9\x3a\x21\x71\x9d\x84\x4e\x88\x18\xa1\xa0\x83\xc9\xf2\x78\xe0\x51\x96\x64\x3c\xf6\x12\x29\x63\xb8\xa8\xa4\x13\xe3\x47\x1b\x4d\xfa\x21\x1b\x4d\xeb\xf5\xfb\xa2\x66\x4f\x57\x98\x13\xae\x1e\x8a\x8a\x47\x65\x5a\x64\xbf\xf1\xf8\x8a\x3f\x1d\x2a\x7e\x3c\x66\x65\xe1\x5e\x59\x73\x26\xa7\xf4\xa1\xc8\xee\x1f\xf8\xeb\xb2\x9a\x52\x6a\x18\x22\x02\x6e\xe3\x9c\xce\xa2\x65\xcc\x6b\x1e\xd5\x5f\x3e\x1c\xf2\x2c\x62\x35\x3f\xc2\x1d\x55\x18\xf1\x75\x2d\x78\x0f\x21\x3e\xa1\x01\x81\xb3\x12\x4c\x88\xf8\xe0\x7c\x4e\x20\xd7\x02\x44\x48\x99\x9f\x08\x01\x02\x69\x84\x9f\x04\xb6\x8d\xd6\x31\x48\xb6\x13\x42\x0c\xf5\x22\x53\x06\xca\xa8\x4d\x82\x35\xd1\xc0\x76\x87\x7a\x4b\x60\x2d\x70\x9a\xe2\x4c\xbe\xe1\x4f\x93\x36\x19\xd4\xb2\x10\xd5\x25\x06\xa9\x15\x23\x49\xe4\xf9\x88\x40\x4b\x4d\xf3\x17\xf9\xb3\xc6\x57\x29\x4a\x9f\xd9\x7a\xa1\xc9\x21\x1e\x58\x16\x75\x87\x04\xcd\x44\xb4\xc7\x64\x94\x2d\xf1\x70\x12\x59\xc5\x0d\xdb\x88\x04\x53\x13\x19\xcd\x29\x5a\xc0\xea\xb3\x91\x8f\x65\xd3\x9f\x98\xf8\x51\xf6\xf4\x1f\x62\xe9\x65\xbe\x7e\xde\xf0\x5c\x0a\xeb\x08\x7b\x61\xb4\x85\x58\xea\xa7\x24\x6e\x38\xd2\x93\xa1\xad\x76\x3f\x5d\x81\x64\x7b\x7f\x3a\xf2\x87\xb8\x74\x33\x06\x88\x4c\xdc\x5f\xa1\x07\x75\xf7\xd4\x82\x10\xd0\xc4\x6f\xc5\x73\x3c\xd8\x74\x4f\xd6\x8d\xe5\x9e\xe2\xac\x72\xad\x1e\xed\x5a\xca\x6a\x7e\xb6\x6a\xc1\xba\x9a\xf8\xde\x82\x35\xef\x92\x2b\xfe\x2e\x2b\x1f\x8e\x6a\xf4\x83\xb2\xff\xbe\x94\xa9\x6d\xe1\x50\xf1\xaf\x51\xe0\x77\x4f\x78\x2a\x3e\xa5\x40\xf0\xd7\x01\x15\x7f\x46\xc2\x3f\x30\xff\xe3\x80\x3a\xe2\x6f\xd3\x30\xff\x13\xfc\xfb\x69\xd0\x34\xa6\x75\xa0\xca\x2a\x44\x14\x84\xc1\x17\x02\x06\xb1\xa0\x25\x76\x86\xff\x71\x80\x7a\x7f\xe8\x00\x19\x3e\x21\xad\x3a\x70\xff\x60\x5f\x06\xf8\x02\xac\xa2\xde\xc9\x06\xd6\x41\x57\xd3\xc7\xc4\x53\xbd\xd3\x1b\xda\x61\xfe\x2a\x10\x1d\xff\x24\xa0\x73\x47\xfc\x78\xa2\xcb\xe2\xf1\xcf\x41\xd3\xac\x89\xfb\xe2\xb9\x63\xf1\x77\xbc\x90\x95\x7d\x8c\x26\xb8\x71\xac\xdf\x88\x28\xfb\xa9\x2c\xfb\xff\x0b\xe6\xcc\xff\x3f\x67\x19\x5c\xf1\x63\xdb\xe3\x16\x5b\x6d\x5d\x30\xb5\x73\x66\xa2\x79\xdb\x16\xb3\xa3\x41\xed\xd7\x25\xce\x81\x3a\xfa\x11\x75\x78\x62\x23\xba\x38\x20\x4f\xe4\xa4\xc3\x29\x77\x23\xdb\xfe\x87\xcc\x1e\x09\xa9\x3b\xa4\xa9\x13\xc1\x6c\x45\xe4\x4b\xd4\x5b\xff\x12\xab\x53\x33\x2f\x42\xb2\xd0\xcf\x04\x17\x66\x25\xea\x5d\xf5\x73\x18\x8a\x11\xbf\x08\xb4\x8f\x12\xa6\x98\xab\xf5\x31\x21\xad\x00\x68\x09\x42\x6f\x3e\xfb\xeb\x84\x7f\xc6\x58\x6b\x34\xad\xd1\x97\xba\x0f\xef\xcc\x4a\x6f\x36\x50\xaa\xfc\xc7\xc6\xbf\x6d\xab\x6c\x47\xce\xfb\xf5\xde\x67\x78\x90\xd4\x69\xa5\x9b\xc6\x09\x07\x36\x02\xce\xdb\xce\xfe\x85\xcd\x2d\x69\x18\xd0\x3c\x23\x96\x98\xd4\xf7\x0e\x83\x89\x7e\x85\x72\x0d\x26\xd0\x5a\xd4\x2b\x5f\x8c\x97\xa6\xf9\x7d\x55\xd9\x58\x4d\xa6\x34\xba\x16\xc1\xbd\xd6\x92\x16\x46\x7b\x77\x60\x04\xdb\x25\xeb\xc3\x06\xaa\xe8\xbb\x13\x1b\x2e\x49\xd2\x54\x96\x7b\x42\x9a\x13\xf3\xe6\x86\x9e\xc3\xe7\x02\xa9\x5b\x32\xc1\x13\x9c\x65\xe4\xea\xef\x1e\x9f\xe1\xeb\x5b\xf5\x1a\xd9\xf6\x8a\x52\xca\x3b\x38\x8b\x88\x6b\x3d\xef\x3f\x9a\x1f\x6e\x16\x6b\xd7\x7a\x66\x7e\x93\xe0\xd4\xc3\xa2\x6c\xea\xdf\x2a\x8b\x23\x70\x05\xef\xa0\xe8\xbf\x05\x3a\x24\x88\x37\xc6\x95\x36\x66\x5f\x9b\x86\x77\x70\xaa\x6b\x9e\xaf\xb1\xee\xb9\xb5\xb0\xdc\xd9\x9a\x08\x04\x79\x8e\x6e\xb4\xfb\x8d\xb2\x39\xa0\x88\x5d\x90\x4f\xeb\xc1\x1e\x52\x6a\xe5\xec\x58\x9b\xe9\x8b\x4f\x08\xec\xa8\xa5\x8c\x7e\xb0\x27\x7a\x7a\x05\xc1\x8b\xd5\x14\x79\x13\x0e\x14\x33\x53\x3e\x30\x00\x5e\xf4\x24\x93\xfd\x18\xd8\x39\xd2\x64\x26\xa4\x01\xcb\xa0\x78\xd6\x04\x15\xb8\x1f\x0a\x1a\x15\xdd\x09\x19\x6a\x7a\xb7\xc0\x91\xce\x32\xdb\x9e\xed\x04\xd5\xbe\x47\xe2\x9c\x68\x4e\xe2\x40\x4e\x79\x27\x1d\xe4\x34\xf7\x0f\x81\x90\x3d\x77\x5e\x7e\x79\xeb\x55\x68\x0a\x9a\x8f\x59\xda\xd9\x7a\x53\xd2\x03\xb5\xca\x22\x47\x83\x50\x66\xdb\xb3\xd2\xb6\x07\x23\x69\xbb\xad\x9f\x25\x4e\x49\xfd\xd4\xbb\x37\x88\xbd\x7b\xbf\x14\x33\x8f\xcf\x01\xa4\xb6\x7d\x24\xa7\x3b\x7a\xef\x3f\x04\x4d\xe3\x88\x1f\xf4\x53\xba\xa5\x77\x3e\x0b\xd0\xd8\xa3\xa0\xb7\x02\xb1\x51\xfa\x68\xdb\xb7\xfe\x3a\x80\xfd\x20\xe1\x45\x00\xb9\x60\x63\xef\x0d\xc3\x18\xbf\x08\xba\xd1\xce\xe7\x85\x6d\xe7\xb6\x2d\x46\xdd\x34\xce\x9e\x16\x74\x45\x9a\xa6\x5c\x1e\xca\x83\x83\x46\x1e\xc3\x81\xda\xf6\x7c\xbe\xb7\xed\x1c\x25\xc2\x93\xe8\x05\xf5\x1f\xa1\x80\x7d\xb0\x91\x46\xfa\x1d\x4f\x72\x44\xbf\x2c\x27\x94\x5d\x0f\x55\xd7\x89\xe0\xea\x45\xc7\x64\x17\x89\xe8\xed\x3a\xd8\x18\x0c\xca\x1f\xe9\xd3\x7f\xb8\x38\xaa\xd3\xd8\x25\x27\x97\x1d\xca\x8d\x0e\x89\x21\xec\x03\x02\x72\x54\x43\xbf\x81\xfd\x82\x72\xd8\x4b\x45\xc9\xfe\xbf\x62\x4a\xe9\xca\xb6\xf7\xd7\xf1\x0d\x5d\xb5\xed\x04\xe5\x33\x6c\xbd\x05\x37\x8a\xdc\xd2\x11\x17\x2b\x5e\x1e\x79\x2d\x19\x92\xa3\xcf\x46\xe2\x83\x41\xc7\xad\x87\x42\x1d\x4d\xf2\xf8\x4a\x56\x20\x39\xed\xce\xe8\xdc\x7f\x08\x3c\x94\x00\xb8\x96\x8f\xd6\x9e\x13\x51\x9f\x01\x03\xcb\x82\x30\x00\xb3\xad\x91\xe5\xae\xc3\xc6\xf2\x88\x79\x6c\xcb\x4c\x0b\x7b\x14\x54\x2e\x1c\xd6\xc6\xf4\x6f\x82\x48\xf8\x29\xf2\x1c\x71\x40\x67\x4e\x24\x7e\x30\xa5\x25\x53\x64\x4d\x54\xb7\x82\x48\x7c\xe5\x82\x3d\x93\x73\xe3\x9e\x8a\xb2\x76\xb3\x29\x55\xab\x1f\x80\x72\x23\xde\x9d\x5b\x64\xf4\x07\x04\x62\x3a\x86\x63\x10\x98\xa5\xb3\xaa\x4a\x69\xac\x05\x6d\x0e\x7e\x20\xd0\xd8\xc8\x06\x61\xb7\x58\x10\x27\xa1\xa9\xbf\x0b\x24\xa7\xb0\x13\xc3\x09\xc5\x4f\x42\x86\x83\x01\x0e\x49\x4f\x0f\x91\xa5\x80\x58\xc8\xac\xa2\x7a\x74\x16\xc0\x44\x7c\x9d\x45\x12\x62\xdb\x96\xc0\x8e\x1d\xc7\x63\x9c\x38\xbf\x37\x35\x03\xa1\x21\xfc\xb6\x04\xb4\xec\x7b\xa1\x16\x76\xc6\x89\xc0\x79\xc5\x4e\x68\x8a\x1a\x4d\x13\xca\xf3\x09\x21\xf5\x34\x0d\x4a\x8e\x1d\xcd\x61\x82\xe6\x88\x76\x73\x56\xa4\x17\xda\xfc\x45\x71\x70\x48\xa9\x2f\x01\x30\x96\x47\xf0\x85\xf3\x3e\x8e\x90\xf4\x99\x29\xc3\x26\x2e\xaf\xd0\xea\xe2\xe0\x85\x4b\xac\x69\x6c\xae\xf4\xb4\xcf\x5d\xf1\x41\x74\x60\xfc\x4d\xa6\x77\x56\xe4\x34\x1a\x35\x17\x09\x0c\x2d\x4d\xf6\x7b\x16\x92\x09\xf2\xa9\xa5\xc7\xb1\x4a\x72\x6c\x64\x42\x7a\x95\x64\x4b\xa0\x66\xd5\xc0\x15\xdb\xb4\x11\x2c\x23\x26\x15\xa4\xfd\xb3\xd8\x97\xbb\xc1\x59\xa0\xa4\xb4\x6b\xe9\xd8\x95\xc5\x2d\x54\x65\x39\xe9\xda\xcd\x28\xa5\x65\x0b\x68\xee\x7e\xe9\x7b\xb1\x64\x91\x10\xc0\x94\x1e\xd8\xb6\x9d\x19\x36\xf9\x35\xda\xc8\x37\xfd\xb3\x23\x38\xbe\xd9\x4c\xe0\x05\x54\xfc\xb2\xe5\xae\xe2\x49\xd3\xfc\x9b\x2d\x6b\x16\xa2\x9d\x0c\xba\x0f\xe3\x89\xc0\x34\xbb\xaa\xcf\x0b\xd0\xef\xaa\x05\xfd\xfa\xfb\x99\x57\x2d\xa8\xb3\x9a\x49\xde\xfa\x0f\xda\xc9\x84\xa2\xff\x6c\xa9\x1d\x03\x1a\x4b\x1e\x8d\x19\x9f\xf4\x79\x55\x0b\xfa\x69\xba\x6f\xa6\xc9\x93\xf9\xd6\x55\x80\xd3\x01\x7d\x85\x6a\x10\x7c\x7f\xa8\xdf\x0f\xaa\xfc\x43\x72\x7c\x96\x38\xbd\x42\xe1\xe5\x9f\xa7\x3c\x30\x65\x1f\xa6\xfc\x52\x3b\xea\xb2\xc4\xd6\xd1\x23\x76\xc7\x59\xcc\xab\xa9\xb1\xfd\x8f\xda\xac\xdd\x9c\x92\x16\x70\x02\xa7\x32\xff\x73\x22\xb3\xb4\x13\xfa\xbf\x5c\x26\xc3\xda\x48\x83\x9b\x91\x14\xb6\x80\x96\xdc\xe7\x5e\xaa\xe3\xaa\x2e\xb5\x69\xdb\x96\xa8\xa1\xaf\xdf\xb6\x1d\xc9\xfd\x3b\x21\x1d\x0b\x1a\xc8\xc8\x12\x21\x68\xe8\x32\x63\x55\x9d\x76\xee\x2f\x0d\xfc\xa7\x27\xc9\x5f\x05\x88\x1e\x47\x9f\x0d\x8d\xa4\x1f\x2e\xd6\x22\x0f\xbf\x1f\xe7\xe8\x25\x18\x7f\x75\x13\x79\xd1\x3c\x74\x23\xcc\xf9\x8e\x17\xe7\xb5\x19\x1e\x33\x9b\x10\xdd\x64\xe8\x0b\xc2\xc6\xe7\xe4\xac\x25\x50\xc6\xf1\x87\x8a\xaf\x7f\xa7\x78\x7e\x36\x94\x81\x67\x1f\xed\xfa\xba\x59\x2c\x04\x03\xb4\xd1\xd5\xc4\x83\x6a\xd2\x3f\x5c\xcd\x7c\x1e\xbf\x0c\xa7\x6b\x41\x33\x11\x0d\xe0\x45\xbd\xa3\x06\xb8\xdf\x77\x5e\xcb\xa7\x8a\xc5\x59\xe9\xce\x56\x12\x8d\x84\xe5\x93\x78\x4e\xb2\x9c\x8b\xdf\x03\x3b\x1e\x1f\xcb\x2a\x16\xcf\xd9\x9e\xa5\x22\xb1\x25\x3d\x57\x16\x06\x74\xcf\x9c\x90\xf4\xd5\x1d\x1f\xc2\x7d\x56\x8b\xfc\x15\x3f\xf2\xfa\x3c\x7f\x21\xf3\x6b\x33\xb4\x7b\xe6\x90\x53\x7b\xcf\x8c\x10\x1f\xda\xca\xe4\xd8\xf7\x78\xc0\x8e\xa1\x10\x7e\xcf\x20\x15\xa2\x6a\x5d\xde\xf1\x22\xfb\x8d\xd3\x0b\xce\x82\xbd\x1b\x18\xfd\x4d\x4b\xf4\x59\xe2\x74\x96\xda\xa1\xb7\x72\xef\x3a\x3d\xe9\x66\x47\x19\xba\x40\xc2\xad\x68\x5c\xab\xbf\x34\x97\x43\x4e\xce\x2c\xc2\x90\x03\xaf\xa5\x19\xf7\x8e\xa0\x06\x85\xa3\x2d\xf5\x4e\x55\xc3\xfd\x55\xa0\x45\xd5\xa6\xd9\x11\x50\x4e\x8f\x09\xf5\x03\x22\x88\xe6\x6c\x0d\x0e\xa7\x6f\xba\x2a\xd0\x6f\x91\x6b\x1b\x56\x48\x64\xf6\x93\x54\x3a\x47\xd2\x85\x1c\x2b\x35\x18\xb8\x2b\x3c\x5f\xef\x1b\xed\x84\x63\xb9\x16\xe9\x55\x56\x5c\xe9\x89\x24\x33\x87\xd3\x5f\xfd\x34\xe8\x5a\x6c\x9a\x5b\x3f\x0d\x6c\x5b\x7c\x10\x4f\x0e\x17\x69\xbf\xdf\x8b\x14\xd4\x01\x88\xcb\x2f\xb5\x9e\x25\xce\x2c\x52\xfe\xc6\xdd\x1c\xef\xd4\x77\x77\xe7\xf5\xba\x2f\xe2\xfe\xe6\x30\xc8\x48\x37\xfb\x6d\x0f\x16\x15\xd3\x24\x40\xa2\xc8\x15\x0c\x1c\x05\x2c\x6b\x13\xdd\x84\x9b\x70\x3e\x27\xf1\x1c\x5d\x3e\xa5\x8a\xbe\x37\x79\xe9\xdd\x18\xd9\xd0\x2b\x23\x5c\xc6\x59\x05\x9c\x46\xb6\x6d\xaa\x4b\x85\x7c\x02\x09\x7d\xea\x4f\xab\x42\x49\x79\xbc\x81\xb4\x9d\xf4\xda\xf3\xd0\x8f\x03\x2d\xde\x85\xc6\xb1\x6f\x77\x3a\xc3\x54\x89\x91\xc0\x9e\xca\x9e\x20\x54\x0a\xb9\x29\x41\x80\x4c\xcf\x2b\x9e\xa8\xd9\xb6\x99\xaa\xa3\x3b\xd3\x1d\xaa\xa6\x2f\x77\x4a\x48\xec\x19\x1d\x09\x93\xe0\xec\xe4\x29\x9d\x6d\xef\x3a\x99\x77\xe7\xaf\x03\x53\x0f\x2e\x64\x60\xba\xf3\x5f\x60\x3f\xf1\x9c\xee\x16\x30\xed\xbc\x2f\x86\xc9\x6b\x3d\xb0\x2f\xed\xc5\xac\xc1\x5c\x74\x4a\xa7\x09\x9b\x66\x0c\xcb\xe4\xf3\x40\x65\x9c\x20\xed\x2e\x13\x24\xa4\x6b\xf1\x81\x4d\x60\x4a\xe0\x54\xbb\x6b\x6f\xb8\xf2\xcf\x96\xd2\x81\x1f\x07\x10\x19\xda\xfb\xae\x9e\x77\xcc\x54\xf2\xe8\xba\x84\x18\xe4\x07\xb0\xa3\x2b\xc8\x7a\x50\xbc\xa5\xd2\xb5\x39\xec\xa2\x9c\x38\x09\x65\x4a\x0c\x12\xb8\x22\x72\x12\xac\x47\xbc\xa7\xfa\x6c\x05\x6e\xd1\x9c\x40\x7a\x4f\x18\x61\x44\xba\x2e\x3c\x1a\x5d\x30\xc4\xa5\xd8\xb6\x67\x42\x58\xb3\x6d\x27\xa6\x8f\xcc\x89\x09\x01\x6e\xdb\x33\x2e\xd3\xb8\x48\x13\xf9\xc9\xc0\x90\x58\xe1\xc0\x81\x9e\x88\xa2\xe2\xc3\x0f\xd0\xbe\x55\x8d\xe5\x40\x93\xa6\x79\x60\x4e\xd8\x34\xd6\x73\x0b\x76\xbd\x4d\x84\xbf\x0b\xdc\x1d\x4a\x7e\xf7\x74\xc6\x9a\x66\x96\xd8\x76\xe8\x1d\xdc\x77\xcc\x39\xc0\x1e\x18\x56\x0f\x15\x8d\x3c\xde\x34\x4e\xe2\x31\xb7\x6c\x9a\x98\x78\x7e\xe0\xa6\xee\x3d\x5a\x84\xdb\x76\xe4\xdc\x43\x25\x73\xc6\xe4\x74\x4b\xdf\x31\xa7\x82\x82\x40\xec\xdc\x82\x98\x58\xf1\xe1\x8e\xde\x0e\x01\xe1\x4e\x08\x96\x39\xbd\xf5\xef\x70\x46\x2b\xbf\xf0\xef\x02\x21\x5b\xde\xab\xa7\x9c\xa0\x5b\x83\x3c\x2e\x12\xbc\xb7\x7c\x10\x0d\xa0\x23\x4f\x35\x59\x5f\x25\xeb\xbb\x95\x6b\x70\xef\xdf\x89\x8a\x36\x1c\x39\x1d\x69\xc1\x76\x0b\x19\x69\x7f\xa7\xb8\x73\x4b\xb9\xf7\x37\x27\x81\x9c\xb8\x7b\x91\x74\xb3\x58\xdb\xb6\x93\xf8\xb7\xa2\x87\xa9\xf8\x11\xdd\x93\x3b\xb4\xc2\x01\xe3\x29\x7d\xa5\x8f\xd3\x4a\xd0\xf5\x13\xb7\x22\xc0\x3d\xd5\x83\x14\x2a\xc8\x88\xab\xdd\x33\x52\xa8\x06\xa6\xdb\x4f\x43\xe4\x08\x48\xe8\xcc\xb8\x14\xf1\x52\x1f\x1e\xf9\xa8\xaf\x17\xb8\x5b\x80\x6e\xda\x34\xc6\x27\x41\x0b\x21\x43\xab\x81\x15\xdc\xd1\xe3\x05\xb1\x18\xb9\xca\x1d\xcc\x56\x04\xf2\x0b\x99\xfe\xe6\x84\x20\xe5\x5d\x95\x71\x4f\x7d\x53\x21\xd2\xef\xf3\x59\x2a\x60\xb7\x69\xa2\x19\xa5\xb7\x82\xe4\x38\x21\x8d\x48\x0f\x69\x77\x2a\xbb\x9b\xab\x87\x3e\x2a\x96\x54\x09\xf0\x36\xd8\x24\x37\xd9\x26\x93\x9e\xce\xd1\x70\xac\x99\x1a\x2b\xd9\x53\xff\xc8\x9c\x9a\x39\x7b\x02\x11\x91\xea\xb1\x93\xca\x2f\x89\xa2\x91\x5b\x4d\xb3\x3c\xcb\x14\xa9\x8a\xc8\x11\x88\xfc\x87\x40\xce\x34\xa7\xf3\x79\x36\x08\xb7\x61\xb6\xcb\x75\xbb\x03\xcd\xd7\x23\x73\xb2\x9b\xb5\x6d\xcb\x6e\xe0\xa3\xa0\x6b\x9d\x9e\x38\x5b\xac\x89\x0e\x2a\xa0\xe8\xac\x75\x25\x0f\x8b\xb2\xc5\x0b\x59\xa5\x67\x3d\xb7\x5c\xcb\x6a\x8d\xb0\x41\xda\xa1\x26\x02\x7e\x93\xd9\xf6\x53\x5f\x65\x26\x10\x0d\x24\x37\x5c\xa6\x76\xaa\xe7\x2e\x15\xc9\x2a\x69\xf7\x9a\x77\xd5\x14\x1a\x7b\xd8\x03\xd8\xfb\xa1\xc3\x48\xa7\x46\x31\x9c\x4f\x6e\x56\x66\xd0\x04\xcd\x66\xdd\xc9\x32\x39\xec\x51\x0d\xbd\x82\x7b\x6a\xad\x2c\xa8\x68\x62\xdb\x7e\x00\x47\xb1\xb3\x6a\x7a\x0b\x0f\x02\xd5\xa0\xad\xaa\x36\xd7\x75\x04\xca\xb9\x23\xf0\x8e\x3e\xce\xa9\x14\x38\x6a\x6f\xed\x0e\x22\x27\x35\xcd\x72\x0d\x4f\xf4\x41\xef\x49\xb1\x2e\x77\x32\x78\x95\x34\x35\x48\xc9\xe6\x7e\x46\xe9\x93\x6d\xab\x40\x53\x39\x7d\xf0\xef\x03\xb2\xb9\x9f\xcf\x25\x5e\xb0\xed\x9c\x9c\xf6\x9d\xa3\x61\x49\x99\xbf\x9f\xcf\x91\x64\x96\x8e\xd8\x78\x3b\x42\x4e\x8a\x5f\xcb\x89\xd2\xc8\x8a\x36\x1e\xe9\x3b\xd2\x46\xa8\xfe\xa4\xb3\x52\x54\x63\xdb\x87\xc5\x02\x12\xdb\xae\x74\x76\xc4\x44\x87\x39\xbd\x87\xc8\xb6\x45\x47\x0e\xc3\xb6\x42\xd9\x56\xe9\x54\x70\xc4\xa6\xfa\x13\xf0\xc3\xcd\x4a\xd9\x70\xdd\x2f\x16\xa4\xf2\xef\x83\xa6\x39\xe2\x5f\x47\xfc\xd0\xaf\xa5\x59\x44\x46\xc8\xe6\x28\x10\xc9\x91\xb4\x1a\x3b\x64\x70\x24\x70\x67\xdb\x02\x29\x1f\xbb\xd5\xb1\xed\x43\x17\xa7\x44\x00\xde\xc0\xc4\xc0\xc9\xfa\x23\x7c\x39\x36\xb8\xa5\x35\x81\xaa\xed\xc8\x21\xea\x03\x89\x9b\xe8\x7c\x3b\x9a\x4a\x9b\xac\x2c\x9f\xe6\xb5\x95\x8d\x02\x46\x2e\xf9\xcc\xe0\xb4\x67\x09\x39\xc9\xb3\xb3\x14\x1d\xfe\x7a\x78\xd2\xe6\x44\x8b\x05\x49\xe8\x13\x73\x42\x3f\x0a\x08\x24\xfe\x43\xe0\x75\x56\x08\x2e\xd7\x4f\x9b\x84\x7e\xe6\x30\x78\x2f\x28\x9c\xa0\x7b\x49\x77\xd0\x4e\x99\x61\xe0\x9c\xf5\x47\xf0\x43\xb7\x5c\xa4\xa3\xa2\xab\x46\x4c\x08\x3a\x15\x64\xc9\xb6\x19\x94\x54\xcc\x66\xea\x30\x5a\x74\xcd\x08\xda\x22\x6d\xa8\x38\x9e\x1b\x08\x16\x4b\xfb\xe8\xe1\x1a\xde\xd2\x52\xf0\x50\xa5\x71\x42\x4a\x40\xd3\xb3\x9b\x17\xb6\x6d\x7d\xfb\xa5\xd8\xdd\xce\x1d\x1e\x30\x10\x25\x5e\x77\x4e\x05\xd2\x27\xc5\x74\xc0\x3a\x88\x1d\xd2\xa1\x99\x5b\x3c\xd9\x46\x34\x83\x46\x7f\xb4\x77\x42\x70\xee\x34\xce\x32\xc5\x04\xa5\xc6\x0c\x89\xe8\x2f\xf1\x57\x01\xcc\x3a\x23\x34\xbe\x29\xf0\xe8\x77\xa0\x9d\x83\x1e\x65\xdc\x6a\x41\x40\x32\xd6\x7a\x9c\x6d\x46\x7f\x5d\x9a\x4e\xe3\xda\xfd\xd0\x5b\xb9\x23\xd2\x9d\x2d\x16\xd8\x4f\x31\xda\x2c\x00\x63\x20\x39\xbd\x1b\xa0\x4b\xc1\xeb\xee\xa9\x1c\x8c\x9f\x23\x65\x4d\xe8\xfe\x83\x63\xd2\x7e\x8f\xb7\x9a\xbc\x4d\xfb\x3f\x2a\x6f\xcf\x5b\x4d\x70\x33\x58\x8b\x41\x26\x9d\x91\x69\xc5\xd0\xb7\x96\x8d\xfd\x4b\x05\xb4\x00\xd7\x27\x32\x0a\xbe\x9c\xa2\x69\x76\x0e\x83\x92\x10\x27\x41\x03\x24\xe0\xf0\x3b\x1e\x98\x04\x78\x0b\xa6\x59\x0e\x7d\xd0\xde\x7c\x16\xd1\x46\x39\xca\xbc\x15\x5d\x2f\x1e\xe0\xdc\xb4\x87\xce\x66\x39\xec\x1d\x02\x43\x8b\xc7\x0b\x2e\x2f\xeb\x0f\x18\x90\x4e\x7b\x35\x4e\x98\x6a\x77\x8c\xfb\xd0\x00\x7f\x57\xf1\x84\x7e\xf4\x27\x69\x7e\x6f\x81\xf5\x27\xa9\x28\xea\x75\x74\x23\x0d\x91\xc8\x2f\x24\xd5\xa6\xb9\x63\x52\x5f\xd4\xa0\x6e\x74\xc7\xb3\x74\x57\x37\x8f\x59\x5c\xef\x2c\xb8\x70\x36\x1d\x79\xd2\xa0\xcb\x1d\x5b\x6e\x81\xd5\x1d\xa1\x0e\xf5\x4d\xde\xda\x7d\x21\xbd\x98\x7a\xdb\xaf\x33\x9b\xe6\xc9\xa1\xa1\x62\xec\x1a\x5d\x00\x8c\xc1\x0c\x0d\xcc\x71\x27\x58\x18\xb0\xcf\xfa\x9d\x71\xcb\xac\xdd\xc0\x55\xc9\x4b\xe3\x6c\x1a\xa5\x98\x9b\x5d\x56\xcc\xf5\x73\xa1\x5d\xcf\xd0\x30\xe9\xd2\xc2\xa9\x90\x5c\xa3\x6e\xf5\x66\xe9\xaa\x67\xdf\x9d\xf5\x09\xe5\xa6\xcd\xd9\x0a\xf8\x61\x80\x9a\x5a\x6f\x34\xe3\xae\x13\x5f\xb4\xa1\x8b\x0d\x1b\xba\xd8\xb4\xa1\x23\x90\xb2\xd6\x61\x64\x53\xe0\x9e\xa7\x35\xc6\x4f\x3c\x54\xb4\xee\xad\xa7\x54\x92\x6f\xb9\x96\x8c\xc7\x78\xa8\x3a\x6d\x50\xa1\x68\x19\xad\x0d\xa2\x06\x85\x0c\x47\x57\x6b\x3b\x34\x0c\xfe\xf4\xeb\xf7\xaf\xbe\x2c\x23\x5a\xcb\x47\x28\x7a\x13\xc8\xba\x7b\x44\xeb\xc4\x07\xdd\x08\x22\x9d\x01\x8e\x83\x77\xf4\xfa\xed\x4b\x0c\x25\xb1\x3d\x3e\xdf\x5e\x7b\x37\x8e\xe7\xbe\xdc\x5e\x6f\xd7\x37\x0d\x79\x76\x0d\x8f\xf4\xfa\xed\xd2\x7f\xeb\xfe\x69\xeb\x6f\x97\x10\x3c\x7f\x76\xdd\x2b\x32\x9e\xf4\xbc\x66\x89\x33\x08\x46\x15\x76\xe7\x2a\xc5\x32\xad\xf8\x61\x60\x30\x22\x18\x66\x7d\xf6\xaf\xe3\xb3\x41\x0c\x68\x52\x19\xb5\x48\x86\xc2\x33\x6b\xd0\x89\x7a\x86\x6c\x7c\x5f\xf8\x3c\x76\x16\x76\xf0\xb1\xb3\x5b\xed\xaa\x94\xcc\xb2\x8a\x8c\xb6\x09\xa9\x99\xd2\x31\x11\x1f\x6a\xb9\x0f\xad\x46\x6e\xe8\x4a\x76\xa1\xd5\xb5\x5c\x88\x9c\x11\xfa\xab\xc0\x38\xd7\x71\x18\xb5\xdc\xa2\xac\x1d\x34\xb4\x21\x16\x01\xa9\xde\xd0\x48\x1c\xcd\x27\x7a\x19\x42\x02\xd5\xd8\x54\x17\xed\x59\x3c\x3f\x0e\x5c\x3f\x70\x87\x59\x1c\x06\x6a\x04\xe1\xd4\x08\x86\x6e\xd2\x18\xf2\xd6\x08\xcf\xea\x9c\xd0\x60\x6f\xca\x72\xcb\x0c\x3d\xab\xd8\x23\x91\x64\x2e\x40\x6f\xd3\xa3\xa7\x7c\x1c\x8e\xd7\x61\x44\x4f\xb9\xa1\x41\x47\x7d\x2b\x5d\x75\x5a\x30\x84\xae\xce\xc3\x80\xfb\x61\x00\x18\x8e\xb8\x57\xcd\x28\xc5\xa0\x59\x48\xce\x82\xc3\x00\xf3\xf7\xca\xe3\x98\x9e\x85\xc4\x5d\x7b\x7a\xcf\x39\x31\x71\x63\x82\x0a\x59\xc5\x7e\x61\x66\xfd\xe6\x0d\xde\xe6\x68\xc7\xe7\x32\x88\x3b\x33\xb0\x89\x09\x1e\xb5\xf6\xa4\x42\x04\x23\x97\x35\x5b\xe3\x7c\x4f\x9f\xe2\x7d\xb0\xe0\x8a\x60\x80\xd3\xa9\x83\x9f\x99\xca\x79\x6e\x87\x65\xdb\x0f\x1d\x47\x23\x4a\xb8\x5d\x2f\xb4\x17\x68\x2b\x8d\x99\xdf\xc3\x6f\x32\xd6\xcc\xf6\xf8\xdc\x79\xe9\x6f\x1f\xb7\xbf\x04\xf3\x1b\xe2\xbf\xbd\x09\x9e\x37\x2a\xfe\xcc\x73\x0c\x37\xf3\x19\xed\xc2\x67\x4f\x33\xce\xc8\x25\x0f\xd6\x7f\x72\x8b\x4a\xcd\x46\x44\xad\x97\x52\x44\x5c\x05\xb6\x6d\xdd\xc8\x67\x2d\x95\x2d\xd6\x41\x1f\x9f\xed\x86\x7e\xec\xf9\x52\xa8\xc5\xd3\xf5\xc0\xfd\x4d\x07\x1f\x81\x59\xd4\x34\xb3\xc8\x17\xd9\x35\x4b\x38\x0b\x9b\x26\x5c\xca\xf0\xda\x9e\x13\x36\xcd\x7b\xa2\xe0\x83\xb8\x67\x31\x8d\xc3\xee\x1b\x6a\x77\x74\x10\x93\x90\x86\x57\x59\x71\xac\x59\x11\x89\x2e\x17\x9e\xd8\xc9\x6e\x08\x66\xdc\x6b\x28\x96\x18\x00\x56\x10\x5e\x2c\x09\xa1\x36\x3b\xc2\xed\x3b\x11\xab\x25\xc7\xd5\x84\x77\xca\x82\x51\xb4\x76\x1e\xc0\x37\x94\x71\x84\xa3\xab\xac\xb8\x0a\xc9\x00\xd7\xaa\x38\xce\xc4\x53\x0f\x52\xd4\x90\xa3\x42\x5b\xb7\x08\x30\xc5\x8c\x08\xdd\x76\x9b\x21\x1f\x3b\xe8\x46\xfe\x8b\x80\x40\x8c\xae\x96\xc6\x71\xa7\x63\xec\x77\xba\xc6\xfd\x27\xe4\x81\x98\x80\x19\xb5\x9b\xe6\x30\xd8\x22\x94\x81\xd9\x9c\xe1\x89\xe4\x0c\x8a\xe9\xea\x64\xf6\x61\x33\xc4\x1d\xc7\x39\x9c\x32\x1b\x7c\xbf\xac\x38\x8b\xdf\x7b\xea\x17\xa1\xdb\x29\x88\xeb\x74\x66\xf3\xbd\xf1\xb2\x1e\x4d\xdf\xcb\xee\x71\x38\x1a\xa6\x9f\x88\x58\x64\x1d\xe6\xd3\x61\x0a\xfd\xb4\x9b\xcf\x8c\x93\x1d\xb1\x11\xe0\x3d\x2d\x84\x68\x2d\x80\xff\x73\xb9\x85\xe4\x24\x1e\x9b\x43\xc5\xdf\x39\x9e\xfb\xf7\xa2\xce\xf2\x06\x1d\x44\xaf\xe1\x0b\x7a\x42\xbb\xab\x8a\x17\x78\x48\x25\x2d\x2f\x8e\xe2\xb9\xe0\x4f\x78\xd0\x24\x8a\xb9\xb3\x55\xbb\xe9\x51\x72\x9c\x55\xd3\x51\x2f\x25\x16\xee\x06\x1c\x69\x33\x05\x86\x87\x08\x44\x86\xf3\xe9\xd7\x40\xab\xd2\x8d\x14\xa5\x46\x40\xbc\x9c\x1d\x9d\x48\x5b\x3b\x29\x91\xb5\x27\x8a\x71\x0b\x47\x79\x4a\x7d\x31\x92\xa8\x1f\x4c\x1c\x69\x8f\xa3\x72\xb0\x19\x1e\xfc\x46\xba\xfe\x5e\x63\x8d\x31\x23\x0d\x42\xb4\x63\xc7\xa9\x98\xf3\x7a\x31\x4c\xd1\xdb\x44\xa0\xd3\xd4\x45\x46\x66\x11\xc4\x82\x6d\xd8\x19\x85\x91\x61\xe9\x7d\x16\x98\x14\xa6\x85\x28\x2f\x8f\xdc\x0c\xd4\x3e\x1c\xb0\xf2\x76\x32\xe9\x62\x22\x56\x24\xa5\x1d\xd6\x6d\x9a\x73\xd2\xe8\x61\x3d\x7d\x64\x75\x04\x37\x77\xd5\xe9\xf3\x71\xdb\xcb\x0d\x12\x07\x9b\xc8\xb6\x23\x31\x65\x9b\xb1\x9f\x52\xe2\xf4\x6e\xd2\x2f\xd7\x6b\xdb\x76\x52\x2f\x95\x66\x2b\xca\x42\x74\xec\x4a\x7d\x81\x93\xc0\x00\xb3\xe4\x94\x74\x27\xc0\x83\xd3\xae\x11\x51\x4a\xfa\xa3\x8f\x8e\x84\x26\xc4\x4d\xd0\x7a\x20\xe6\x4f\x93\x66\x14\xde\x44\x90\x5b\xc5\x48\x15\xe8\x8b\x23\x91\x01\xd1\x81\x6b\x25\xdd\xd3\xc8\x5b\xd0\x06\x57\x61\x6d\x24\x13\xea\xc1\x0c\x25\xa0\x56\xbe\x3a\x0a\x11\x5f\xec\xa0\xcf\xf2\xdc\xd1\x44\xce\x5d\xac\x5b\x60\x71\x3c\x1d\x59\xf9\xec\xc2\x00\x35\xaa\xc1\xe5\x06\x29\xaf\x1d\x02\xb2\x20\x06\x48\x65\x71\xfc\xf9\xf8\x52\x04\xb3\x42\x16\xc7\x8e\x8e\x23\x3c\x8a\xa9\xef\x8e\xde\x35\xc4\x32\x42\x5a\x33\x62\xe9\x97\xb2\x9b\xe3\x2d\xbd\x1e\x6e\xe9\xfe\x88\x5c\x05\x5c\x3d\x4d\x58\x88\x68\xc3\x8c\x73\x97\xcf\x50\xb9\x65\x99\x14\x4b\x79\x06\x29\x3c\x36\x19\x21\x79\x19\x67\x95\xc3\xc0\x3c\x72\x24\x5d\x09\xc4\x78\x17\xc3\x14\x4f\x14\xc5\x28\xc5\xc5\xd8\xd2\x43\x15\x10\xd3\x30\x30\x98\x25\xad\xc4\x91\x97\xf2\x8e\x0d\x83\x55\xdd\x9f\xe5\xf9\x07\x87\x32\xd1\xc4\xef\x15\xb9\xd0\xd2\x1f\x1b\xbf\xd9\x1e\x4e\x80\xa8\xed\x0f\x4e\xdd\xd8\xf4\x59\x14\x57\xd8\xf9\xc2\x7a\xa9\xaf\x8e\x63\x02\x41\xd3\x9c\x5a\x62\x48\xfd\x42\x06\x82\x8e\x3e\x7d\xb0\x1e\x53\x59\x20\x0a\x69\x42\x36\xa9\x99\x50\x5f\x7b\x1e\x48\xef\x2d\x3f\x00\x66\x46\xec\x6b\xdb\x51\x90\x5f\x41\x10\x7c\x16\xf4\x9c\xa6\x71\xf8\x62\x5c\xed\x11\xf6\x47\x9f\x16\x4e\xe1\xc0\x52\xfd\x53\x82\x27\x8c\x11\x32\x39\x67\xa8\x28\xc6\xa3\xc6\x4e\x10\xc4\xd3\x4d\x93\x29\xb9\x11\xa8\xf5\x0b\xb4\xd8\xed\x70\x03\x27\xf0\x79\xaf\x27\xe3\xcb\x8a\xbf\xe3\x15\x5a\x08\xc1\x08\x9d\x70\xa2\x59\xec\xaf\xe8\xf5\xf6\xf5\xfc\x3a\x85\xaf\xe9\xc9\x30\x0d\xf8\x6b\xbf\x3f\xbf\x16\x23\x3d\x75\x4a\x6b\xb5\x9f\x95\x0b\xa8\xf3\x15\xea\x3d\x61\x68\x99\x2b\xf8\x3c\x2a\x88\x15\x84\x6d\xb1\xfc\x82\xe5\x79\xc8\xa2\xbb\xe3\xc0\x67\x8d\xd1\x09\xf4\xfb\x35\x0e\x48\x34\xee\xf6\x14\xb7\x05\xe5\xdb\x38\x08\x3b\x2c\x48\x5a\x46\x67\x6c\x59\x16\x11\xc7\x83\x8f\xdb\xbe\xfe\x5c\x0b\x6f\x6c\xb9\xe7\xfb\xb2\x7a\x6f\xdb\x39\x44\x74\xb6\x82\x94\xf2\xa6\x59\x29\x3f\xbf\x5d\x2f\x38\xce\x56\x9b\x9d\x6d\x27\x37\xe9\x26\x95\x74\x78\xe7\xa7\xfa\xcc\x2a\xf7\x57\x01\xe4\x82\x21\x46\xcb\x41\xf4\xf9\xab\xcb\xc3\x8f\xc5\xd7\x2c\x3f\x72\x72\x0a\xe9\x6c\xad\xc8\x53\x4c\x67\x6b\xd8\xd9\xb6\x93\x79\x59\x27\x3a\xdf\x3a\x99\x56\xf4\x12\x37\xf4\x44\xd7\xdd\x3b\x6d\x60\x88\xf6\x5b\x77\xf4\x34\xa0\x04\x32\x44\xa1\x3e\x1b\xd2\xdd\xdc\x74\x57\x37\x5d\xa5\x0e\x82\x21\x2e\x45\x38\xba\x43\x46\xf2\x60\x2a\x6a\x74\x44\x36\x83\x78\xd1\xb1\xa7\xcf\x28\x6c\xfb\x6e\xb9\x63\x82\xb9\x6a\x9a\x9d\xa6\xb2\x2e\x5a\x78\xea\x8e\xf7\x5c\x02\x8d\x6d\x3b\x75\x22\xd2\x92\xd6\x88\xc9\x0d\xb1\xd7\x4f\xa2\x1b\x22\xc8\x46\x70\x8b\x61\x28\x4d\x2e\x1f\x64\x28\xb6\x89\x8b\x5d\x76\x82\xfe\x4b\x80\xea\x42\x85\x4f\xc8\x6f\x9a\x83\x8c\xa8\x90\xf1\x24\xf7\x1b\xc2\x0e\x22\x22\x58\x0a\xb2\xd3\xaa\xe7\x08\xd6\xb8\x9d\x9c\xe4\x86\x46\xb6\x9d\x2c\x16\x90\xe2\x53\xba\x58\x90\x56\x6e\x83\x16\xc6\x2c\x5c\xc7\x10\xf4\x95\x33\xd8\x21\xb3\x32\x73\x54\x48\x48\xad\x9a\x1f\xdb\x50\xf6\x23\xd1\xf1\xe4\x65\x1b\x6a\x75\x27\x33\x66\x34\x54\xdc\xf1\x30\xf3\xc4\xcd\x37\xb3\x5d\x0b\x79\x69\x52\xf4\x3e\xc2\x99\xae\x23\x6c\x1a\x03\x9a\x54\x95\xa2\xd0\x64\x85\x19\x5a\x0b\xf2\x5f\xb2\x7a\xfa\x4e\x23\x31\xde\xc8\xb6\x67\x19\x1e\x27\xe1\xad\x01\x10\x52\x9f\x41\x28\x11\x97\xa7\x7e\x1d\xe2\x86\x01\xc4\x9e\x3a\xc7\x0b\x89\x8b\xeb\xae\x9a\x17\x4d\x4c\xf4\xf9\x6e\xa9\xdb\x1e\xdf\x50\x64\x94\x9b\xea\xf5\x2c\x6a\x3b\x0c\x74\x67\xde\x04\xf3\x25\x4f\x78\x55\x4d\x1a\xe6\xfa\xbe\x55\xf1\x63\x99\xbf\xe3\x16\x58\x71\x59\x70\x0b\x0c\x64\xe4\x58\x02\x71\x5c\x49\xf4\x60\x11\xd0\x79\x63\x2b\x00\x51\x10\xa3\x53\x82\x95\xb0\x2c\xff\xbd\x72\xb7\x68\x63\x8b\xe5\x8a\xb2\xce\x92\xf7\x96\x20\x86\x65\x5a\xf1\xe3\x71\x54\x56\x17\x0b\x02\x88\xa8\x75\xe0\x45\x8c\x94\x32\xa6\xa7\x63\xcd\xea\xa9\x29\x8b\x5a\x60\xf9\x23\x7b\x7f\x9c\xf8\xc6\x97\x62\x58\xc6\x86\x5c\x8a\xee\x3a\x67\xb3\x5a\xef\x4c\xc2\xa9\x03\x42\x76\xd9\x7a\xd4\xae\x67\xd3\x08\x90\x37\x8d\x66\xd4\x31\x5f\x4a\x87\x32\xb1\xe4\x05\xc5\xcf\x86\xfb\x89\xbf\x0e\x02\xe7\xac\xd9\xd4\xb6\xd3\xe9\x5b\xaa\x36\x4c\x2a\x1c\xfa\xfa\x84\x58\xbb\xcf\x8e\x9c\x78\xdd\xa3\x43\xe4\xa0\xa3\xa5\x5a\x30\x35\x68\xf1\x2e\x16\x42\x30\xda\x72\xea\x51\x12\x11\xcb\x41\xdc\xc8\x4f\xfc\x55\x30\xb7\x04\xe4\x59\x01\xb6\x8b\xb8\x30\xea\x6b\x95\x37\xc4\xa5\xf2\x9a\x8b\xae\x4b\xad\xc0\x1a\x4c\x46\x8e\x24\x7d\x66\xc1\x16\xe1\xe3\x87\x6e\x34\xeb\x80\x94\x41\x4c\xdc\xb8\x6d\x81\x1b\x74\x34\x5e\x1e\x32\xb4\xc3\x14\x8b\x03\xe7\x73\xcc\xfa\x39\x4e\xfc\x17\x01\xec\x30\x7a\xeb\x26\x96\xd3\x4a\x53\xc1\xcb\x0b\x4a\x83\x0f\xe6\x2c\x47\x74\xd7\x42\xe8\xaf\xdf\xb2\xc0\x7f\x11\x68\xc4\x00\xa1\xff\x02\xdf\x05\x62\x20\xc0\x71\x42\x82\x89\x1b\x19\xf8\xe4\x54\x71\x2f\x76\xa7\x77\xec\x30\x3f\x4d\xbb\x1d\x8e\x81\xc2\xf4\x8c\x71\xbc\x05\x47\x5d\x6b\xc6\x81\xe3\x89\xdd\xe3\x8e\x4f\x59\x6a\xaf\x20\x3a\xbb\x3f\x82\xe0\xd5\x7c\x9d\x38\x2b\x64\x03\xde\x34\x1f\x80\x17\xee\x0a\x5a\x8f\x4e\xee\x1e\x73\x0d\xc8\x26\x62\x26\xa7\x39\xd9\x2e\x59\xd0\x74\xc1\xf5\xe0\x88\x23\xf1\x34\xbe\xea\xeb\x66\xed\x9d\x75\xd1\xe5\xe8\xab\x91\x79\xa9\x82\x3b\xc4\x73\xa2\x01\x77\xb1\x48\x9a\x26\xd5\x10\xdb\xa5\xb7\xad\xb4\x9a\xc5\x13\xef\x9b\x35\xca\xd8\x19\x5a\xde\x4a\x2a\xc4\x09\xdc\x0e\x5f\xef\x06\xaf\x1b\xae\x94\xcc\x91\x1f\x06\xa3\xb9\x10\x49\xfd\x74\x98\x6f\x7a\x07\x89\x3e\xdc\x09\x12\x2a\x37\x50\x7a\xbe\x81\x44\x8e\x5b\xc8\x08\x0e\xa0\x8b\x34\x37\x1e\x89\xa8\x03\x52\x63\x73\x28\xd6\xf2\x1b\xbc\x58\x50\xea\xc1\xe8\x24\xdb\x8e\x9f\xc6\xdd\x62\x1a\xb2\x7a\x24\xdf\xdd\xdc\xb5\x06\x2c\xf2\x0b\xcb\x6a\x77\x0d\xbb\x32\x8f\xe5\x87\x01\x63\xe9\xa9\x8a\x45\xae\xf9\xdc\x55\x6f\xce\x6c\x45\x5a\x59\x7c\x90\x1d\xc3\xc6\xcc\x56\xde\x62\x61\x14\x43\x55\x1f\x56\x4d\x9a\xc6\xe9\x5e\x30\x92\xed\x4c\x64\xb7\xed\x41\xfe\x9b\x55\xd3\x38\xdf\x0c\x66\x25\x07\xbf\x08\x94\xee\xa8\xae\xb2\x34\xe5\x2a\xfc\x40\x65\xdb\x8e\xe0\x50\x47\xa9\x8e\x85\xb5\x59\x42\x9e\xcf\xc9\xb2\x4c\x92\x2e\x85\x8c\x44\xf0\x6f\x1d\x72\xca\x55\x78\xdb\x51\xbc\xcb\x2f\x7f\xfc\x5e\xf9\x4d\xbd\x2a\x59\xcc\x63\x0b\xbe\x85\xd9\x9a\x00\x9b\xce\x2e\xa3\x5d\xca\x2c\x7a\x9e\x48\x3b\x5a\x18\x3a\xe1\xfb\xf5\x8d\x18\x2f\x1d\x6c\x2b\x2b\x2a\xf7\x87\x9c\xd7\x78\x64\x9c\xcb\x2a\x5e\x0b\xa2\xe6\x1d\x79\xfd\x26\xdb\xf3\xf2\xa1\x76\x54\xd5\xc4\x75\xf2\x89\x68\x9d\x97\x7b\x7f\x9e\xd7\xe8\x3a\x21\xf0\x4d\x07\x45\x21\x9e\x1c\x8d\x20\x0b\xe1\xf1\x6f\xe8\x6c\x14\xf1\xe3\x71\x84\x00\xb4\x50\xa1\x6c\x86\xa7\x8c\x4f\x29\x8d\xf0\xbc\xa0\xbb\x43\xac\xe7\xaf\xc9\x89\x0b\xe9\x41\xec\xdd\xdd\x55\x56\x5c\x45\x44\x37\x83\xb5\xef\x20\xf2\x77\x01\xcc\x56\xd8\x42\xe7\x77\x3a\xb8\xeb\x4e\x54\x30\xbc\x45\x27\x96\xb7\xfd\xcd\x56\x68\xc6\xea\xa4\x9e\xd3\x9f\x50\xea\x50\xc6\xc4\x75\x6e\x69\x08\xe1\x25\x74\x76\x6b\xe8\xb2\x22\x0c\x7c\xa6\xb4\xf7\x9d\x19\x6d\x88\x4e\x84\x10\x41\xea\xc5\xfa\x82\x4d\x4c\xd9\x81\xfe\x44\x7a\xe3\x40\xee\x31\xf7\xd6\xd3\xfd\x20\x6e\xe6\x85\x18\xae\x40\x20\xb8\xa4\xdd\xc8\x51\x1f\xea\x2f\x59\xcd\xa6\xb6\xfb\x50\xe5\x2a\x03\x91\x98\xef\xb3\xb9\x71\xfe\xd7\x03\xfb\x77\x0e\x39\x29\x8d\x94\x54\xb6\x77\x1e\xa2\x52\x53\xc9\xa2\x9d\x20\xab\xb0\x82\xd3\xc0\xad\x4d\xb7\x7b\x6a\x5b\xc5\xf8\x2f\xd5\xad\x81\xf2\x0c\x5a\x3c\xcd\xbf\x5b\xe2\xcd\x41\x2d\xfe\xd2\x35\x7c\xa7\xc6\x70\xa4\xe6\x68\xe0\x3b\xf3\x6e\xd4\x3b\xfe\xfe\xec\x66\xdb\xae\x9c\xc3\xba\x63\x5d\x19\x7c\x2a\x14\x7d\x8b\x28\xf3\xcd\x1e\x04\xca\x80\xff\x14\x51\xd5\x03\x0c\x79\x1d\x0e\x33\x51\xed\x16\xd0\xc2\xd4\x04\x64\x78\xa6\xda\x05\xc7\x8e\xc9\x59\xf9\x08\x0c\x16\x24\x24\xed\x40\x79\x8a\x13\xe7\x47\x41\xd3\x38\x83\x77\xb4\x58\x8f\x5a\x38\xf2\xfa\xe2\x0d\x6d\x58\xe0\x8e\xbf\x17\x90\x95\x50\xa3\x38\x0f\x2e\x1c\x7c\x27\x7e\x18\xd0\x68\xa3\xa1\x5f\xc0\xba\x71\xe1\x9f\x93\x10\xd2\x75\x75\x50\x1d\x84\xc6\xad\x57\xb1\x3c\x75\x4a\xfc\x78\x78\xef\x57\x32\xbe\x5c\xb6\x37\xab\x34\x2a\x33\x3a\x1d\x0c\x63\x19\xc9\x20\x08\xae\xa0\x91\x2d\xc8\xad\x3b\x3d\xf4\xf3\x62\x4d\x13\x4e\x28\x6f\x42\xdb\xee\xb2\x44\x9e\xa3\x8e\x7a\x53\x2e\xa3\x3b\x41\xbf\xf7\x15\x57\xa5\x3e\x15\xcb\xee\xae\x3e\x0c\x6b\xe5\xea\xe3\xa1\x5a\xf5\xa2\x2f\x19\x79\x91\x1b\x92\x73\xc1\xda\x3c\xf2\x44\x83\x65\x73\xa9\x52\x73\x3a\xa4\x47\x44\x3f\x12\x32\xf8\x26\x18\x55\x34\xe7\xed\xaf\x95\x0c\x89\x87\xf7\xd8\x49\x53\xda\x10\xd5\x5c\x46\x8f\x45\x77\x39\x1d\x0e\x01\xf0\xca\xc6\xd4\x8b\xa9\x1f\x02\x0f\x5c\x27\xa6\x1c\x62\x1a\xcb\x54\x3f\x0e\xdc\x78\xa0\x46\x42\xcb\xc5\xf8\xdc\x72\x51\xc5\xc8\x4d\xfd\xd8\x8f\x82\xa0\x45\x09\x5e\x6c\xcc\x4b\xf7\x60\x9a\xa0\x65\x0c\x6b\xb4\x09\xf1\xf6\x55\x82\xd2\x77\xc4\xaa\xa1\x04\x39\xca\xda\x85\xc1\xff\x40\x6d\x6d\x8b\x1b\xfe\x15\x72\x68\xdf\xc1\xf7\xea\xf7\x07\x75\x82\x7d\x92\xc7\xd7\xcf\xb7\x6d\xb3\xf5\xf5\x73\x40\x9e\x5d\xc3\x8f\xf4\xda\xf1\x3f\x5b\xfc\x4f\x40\xae\xd3\x1e\xef\xfd\x34\x84\x3c\x73\xad\xce\xa2\xca\xcb\x3b\x06\xad\x98\xd5\x6c\x61\xcd\xfb\xc8\x6a\x3f\x82\xb5\x78\xb6\xb6\xc6\x0e\xcc\xd1\xd8\x64\x29\x26\xe7\xa7\xf4\x11\x39\x09\x9c\x14\x51\xab\xae\x1e\x90\xa6\x47\x78\x8d\x58\xc2\xf2\xa3\x7e\x5d\xbb\x96\x20\x44\xf2\x0d\x83\xe3\xcc\xa3\xb9\x25\x5f\xe7\x91\xfb\x83\x0e\x86\xe3\xa9\xd3\xe8\xbf\xbd\xfe\xf1\x07\x54\x32\x19\x21\xd5\xbe\x37\xe0\x5b\x52\xc7\x88\x0e\x03\x8e\x45\xed\xe0\xf4\xed\xc2\xc2\x5f\x7d\xbf\x54\x1f\xf1\x80\xeb\x95\xf1\xd6\x42\x3c\x2c\x33\x20\x93\xdf\x9b\xd4\x3a\xea\xf6\xd4\x97\xe3\x22\xe4\xf4\xbd\x62\xa2\x54\x8c\xb6\x7f\x7d\xa8\xd6\x57\xe3\x5a\xff\x75\xb1\xda\x57\x83\x6a\xc7\xe7\x8d\x67\x8d\x9c\xef\x6f\x41\x83\x53\x9a\xd8\x76\x62\x58\xe4\x0d\x20\x46\x92\x29\x43\x7d\x8c\x4c\xc7\xf7\x88\x77\x12\x69\xe9\x93\x18\x47\x72\xb3\x57\xf2\x0b\x58\x6a\x12\x05\xa8\x1c\x05\x23\x7a\x8a\x3a\x57\x16\x63\x7f\xa6\x7e\x24\xdd\x65\xc4\xc3\xb2\x60\x7b\x0e\x2b\x34\x17\xea\x22\x1e\x49\xd0\x94\x1a\x6f\x13\x47\xc4\x4a\x99\xf4\x29\x21\xf0\x93\xf4\xe2\xf1\xe3\x40\x30\x1d\xaf\x10\x2e\xc6\x5d\x00\xc1\xc5\x6b\x7e\x44\x3d\x9c\x5f\xec\x2a\xcf\xb6\x50\xa2\x36\xf8\x01\x09\x69\x52\x8a\x25\x2d\x71\xff\x36\xbe\x80\xbb\xb7\xb2\x36\xbb\x28\xad\x2f\x12\x03\xa5\x87\xca\x48\x44\xcd\x1f\x30\x13\x31\xf7\xd7\xc1\x0d\xf2\xc4\x1f\xc8\x23\x07\x2e\x3f\x4f\x66\x93\xfb\x62\x72\x50\x92\xcc\xc9\x56\x70\x38\x31\xd9\x18\x03\x8d\x05\xbd\x59\xc8\xc3\xb9\x6e\x31\x70\x21\xfa\x66\x6c\xdb\x9c\x19\xbc\x70\xbc\x55\xc1\x1e\xe1\x5c\xee\x95\x5f\x94\x30\x35\x01\xd1\xa3\x5b\xc3\xcf\xd6\x40\xc1\x7a\xb7\x0c\x12\xe0\x35\xb4\xdf\x3f\xf0\x07\xfe\x61\xe2\xcb\x3c\x27\xa4\xe8\x3f\x95\x3c\x59\x64\x6e\x61\x11\x0b\x62\xfa\xaa\xa7\xb0\x91\x6d\x63\xb4\xd8\xc1\xbd\xd4\x22\x87\xb1\x29\x4d\x4b\x8a\x88\x10\xb7\x0b\xe9\x4d\x00\x2f\x28\xea\xa3\x6d\xc7\xfc\xbc\x5b\xe4\x84\xca\x51\xd1\x07\x19\x64\x94\x16\x4b\xcc\x25\x3b\x10\xf7\xda\x0a\x4e\xa3\xde\xdf\x93\x16\xcb\x7f\x61\xb6\x6f\xca\xf2\xee\xd8\x05\x2e\xe9\x27\xa8\x58\xaa\xd6\x24\x36\xd8\x58\x59\xd1\xa9\x12\x29\xa5\x5c\xea\xd8\xbb\x0a\xe3\xc5\x02\xbd\xd3\x1c\xd1\x11\xaa\x6c\x18\x74\xa8\x50\xb3\x2c\x01\x45\xc1\x12\x3c\xbb\x00\xae\xc5\x89\x14\x5d\xd9\x66\xb1\x6d\x23\x02\x41\xf5\x36\xea\x70\x1c\x81\xb4\xfa\xbe\x4e\x5f\x2a\xab\xa6\x1f\x73\x58\x9b\x0e\xf7\xc9\x85\x88\x10\x0f\x77\x53\x1e\xc1\x49\x2a\xcf\x2f\x6b\x53\xc7\xba\x2c\x03\x35\xfa\x61\xb7\xd4\x51\x20\xaf\xd9\x18\xa3\xca\xa9\x55\x92\xfd\x7c\x31\xba\xce\x74\x66\xd8\xa2\x39\xf2\x1e\x53\x31\x81\x20\x90\xd9\x19\xcc\xbf\x8c\x3c\xbd\xb6\x1a\xdb\x76\xa1\xd8\x91\x75\x14\xa9\xee\x07\x76\xa7\x59\x1a\x37\xd8\x66\x08\x05\x6a\x2f\x80\x5a\x43\x66\xdb\xe6\xca\x89\x2d\x8a\x06\x06\x3d\x68\xf4\x9b\x67\x02\x36\x3f\xbc\x01\x27\x2b\x89\x72\xce\xaa\xff\xfe\x60\x3d\x0a\x26\x25\xc4\x03\x5e\xd5\x7f\xae\x02\x35\x1d\x55\xd6\xc0\x87\xfa\x01\x49\xab\x34\x13\xda\x5d\x88\x6b\xf4\x6d\xb1\x88\x9b\x86\x0f\xb4\x28\x09\xf8\x49\x20\xb6\xc1\xe5\x85\xd3\x78\x93\x51\xd5\x3b\xf3\xae\x16\x85\x14\x12\x3f\x0d\x80\x0d\x80\x15\x91\x44\x24\xe1\x5d\x90\xa5\xf9\x1c\xd4\x1b\x02\xa1\xe1\x32\xba\x73\x08\x70\x53\xbf\xa0\xf4\x5b\xff\x4d\xaf\xfd\xf9\x22\xf0\x04\x77\x17\x3f\xdf\x2e\x1b\xb2\x8d\xe7\x8e\xe7\xfa\xfc\xab\x00\x3f\x6c\xe3\x79\x43\xae\xd5\x4d\x3e\xf0\x33\xf5\xad\x37\xe5\xc1\x02\xeb\xe7\x2c\xdd\xd5\x16\x58\x9f\x97\x75\x5d\xee\x2d\xb0\x5e\xf1\xa4\xb6\x02\x78\x7d\xe9\xc2\xd3\xb0\x69\x18\x58\x45\x59\x70\xa9\x79\x88\x70\x3f\x59\x71\x76\x3c\xe4\xec\xbd\x45\x9a\x66\x66\x98\x0d\x8d\x82\xc4\xe2\xf1\xf9\x1b\xc9\x85\xea\xf0\x03\x0d\x06\x24\x20\xcf\xae\xb3\xfe\x4c\xb1\x53\xd0\xeb\xbb\xc8\x75\x05\x5f\x57\x2c\x45\x3f\x06\x02\x21\x1d\x86\x87\x3f\xbb\xb6\x5c\x7a\x3a\x40\x74\x7e\xa1\xb9\x8e\x3f\x1f\x4d\xc7\x9f\xc7\x0e\x59\xe8\x7c\x31\x0c\xb3\xa7\x62\xe4\x83\xd5\x47\xcb\x1f\xe7\x51\x31\xe9\xe5\x6d\xe9\x66\xf7\x22\x02\x77\x32\xde\xca\x17\x79\x59\x70\x21\xbd\x88\x5f\x34\x9e\x9f\xad\xc8\xe8\xad\x8b\x68\xa6\x43\xb4\x40\x38\xf0\x5e\xd0\x37\x26\xde\x3c\xbd\xbc\xee\x9e\x2d\xb8\x5b\x16\x25\x56\xff\x85\x2c\x45\xd1\x9a\xfc\x42\xcd\x03\xb7\x02\xa5\x97\xfa\xbb\x79\x55\xc7\xe6\x6e\x89\xf1\x72\xb2\xe2\xf3\x87\x30\xcc\xf9\x91\x5a\x65\xa1\x52\xf0\x6a\x60\x2c\xf2\x0f\x7a\xfd\xf6\x8e\xbf\xbf\x86\x5f\xe4\xba\xee\xcb\x87\x23\x6f\x0e\x65\x56\xd4\xbc\x6a\x94\xf5\xd6\x9e\x17\x0f\xa4\x89\xf2\x2c\xba\xbb\x86\x5f\x65\x46\x55\x93\xbc\x81\x16\xff\x96\x0f\x75\x98\x3f\x54\x42\x10\xc1\x6b\x22\xfd\xb7\xcb\xe0\x39\xde\x34\xb9\x74\x96\x73\xd2\x10\xd3\xd0\xfe\x7f\xcc\x98\x90\x5d\xea\x33\xc7\xbc\x98\x4b\xa7\xfe\xcb\x91\x12\x84\x82\xe2\x7c\x18\xe8\x47\x49\x00\x8c\x9c\x5a\xc1\xde\xbf\xe3\x45\x4d\x4f\xf2\x62\x7d\xf7\x74\x6e\x17\x35\x88\x06\x38\xba\x75\x58\xdd\x39\xac\xe9\x3f\xb2\x6b\x15\x39\x45\xcb\x5d\xa7\x6f\x4d\x68\x04\x11\x4d\x74\x0a\x70\xda\x7b\xb6\xe1\x8d\x33\x78\x91\xb4\x23\x1f\xa8\xbe\x50\x9a\x80\x93\xd1\x4a\x76\xee\x48\x9a\xc6\x78\x93\x7e\xfd\x29\xad\x54\x95\x52\x5d\xa7\xdf\xa6\x34\xa6\x0a\x6f\x15\x33\x4a\xff\x8e\x87\xde\xa2\x1e\xad\x03\xe6\x31\x1a\x3b\xd5\xd2\xb2\x5e\x7e\x12\xbb\x1b\xbd\x24\xf4\x1d\x8f\xc6\x49\x83\xe2\x4c\xc4\x86\x44\x4e\xc8\x22\xa6\x1c\x6d\x59\x01\xdc\x8e\x3d\x00\x6f\x17\x0b\xb2\xa3\xff\x94\xc6\xc9\xa1\x7f\x1b\x48\xc3\x8d\x92\xde\xd3\x9d\xbf\x0e\xe0\x40\x9d\x9d\xff\x42\xc5\x8e\x55\xce\x53\x4b\xed\x3d\x45\xa0\xc4\x1b\x97\x75\xdf\xd0\xe5\x84\xe5\x7e\x89\x32\x34\x94\xd4\xe1\x5e\xbe\x14\x1c\x46\xca\x6a\x14\x22\xdc\x7c\x19\x66\x45\x8c\xd2\x69\xd3\x94\x70\xb1\xec\x1d\xed\x09\x38\x86\xbf\x28\xa1\xac\xb2\x14\xeb\xb8\x97\x82\x5b\x0c\x6a\xdd\xdc\x48\xde\x5d\x2e\xd7\x09\xf4\x0a\xba\x7c\x78\xc5\x2a\x1a\x15\x5e\x70\x33\x91\x22\x29\x27\x20\x90\xc6\xf1\xc0\x22\xee\x1e\x94\x67\xd8\xd2\x22\x2d\x24\x04\x9c\x3d\xcd\xfc\x52\xcc\x8f\x7a\xa2\x7e\x00\xfb\x6e\x74\x5f\x94\x0f\x45\x4d\x57\x90\x0b\x2c\xf4\x70\xb0\x6d\xf5\xd0\x7b\x90\x1c\x20\x25\x33\x4a\x67\xeb\xa6\x39\xd7\x5c\xdb\xf6\x84\x36\xbb\x84\x14\x15\xd9\x80\x5a\x71\x31\xd5\xe2\x57\xd7\x78\x27\xd0\x98\x9a\x02\x0d\xaa\xc3\x04\x2a\x67\x84\x10\xe0\xde\x5e\x1b\x43\x8c\xba\x3c\x9f\xc3\x0a\xee\x88\xab\xfc\x92\xef\x90\xed\xc6\x15\x91\xdb\x4e\x0c\x54\xb0\xf5\x93\x4a\xa5\x3f\xb6\xff\x7a\x91\xdb\xb6\x07\xbb\xd1\xb6\xcd\x9d\x24\x78\xe7\xff\x04\x6c\xf1\xba\xce\xff\x3d\xe4\x92\xd3\x87\x00\x37\xfe\x1d\xc0\x95\x20\x80\x2d\xee\x30\x40\xc7\xe0\xd2\x0c\xcb\x79\xdb\x6c\xb7\x4b\x62\xcd\x35\x14\x6d\xb7\x4b\xc7\x73\x97\xcf\xb7\x82\x21\x10\xf2\x89\x23\x9e\x9e\x11\x0b\x19\x7d\xba\x1f\x0e\x2f\x59\x2c\xc8\x1d\xdd\xfb\x49\x00\x33\x2e\xfd\x99\xef\x96\x1a\xfe\xd1\x10\x43\xae\x2c\xa6\xcb\xa5\xdf\xd9\xf6\x6c\x27\xc1\xf8\x6e\xd9\x41\x31\x69\x9a\xd8\xb6\x65\x3e\xc3\xbe\xdc\x7a\xfe\xdc\x92\xb7\xe1\xce\xfa\x74\x84\x6c\x0d\x25\x09\xac\x05\x78\xf5\x65\x46\x60\xb3\x58\x80\x3e\x6b\x12\xa0\x2e\x9f\x7a\xc8\x24\x9b\xd4\xb6\x67\xfb\x5e\xad\x90\x2f\x6b\xce\xaa\xb8\x7c\x2c\x44\x76\xfd\xac\x0b\x1c\xa0\xc3\x98\x6a\x87\x14\xe6\xc9\x94\xc3\xa0\xec\x73\x68\x91\x05\x77\x63\xdb\xa9\x81\xcb\xab\xac\xb8\xca\x88\x5e\xd2\x4e\x42\x28\xe7\x02\x38\x10\x52\x67\x2b\xc1\x63\x0f\x15\x81\x19\x6a\x1f\x64\x8d\xba\x09\x30\x04\x0c\x4b\x42\xa7\x85\xe1\x94\x15\x5e\x1e\x06\x95\x39\xdb\x01\x77\xca\x05\xdf\x8f\x9b\x26\x0f\xe0\x9e\xde\x6a\x3f\x2c\x15\xb5\xcb\x93\x38\xdd\x0d\xa1\x32\xbe\x75\x8b\x86\x19\xba\x37\x03\x76\x5d\x5f\xc6\xab\xa1\x3b\x8a\xd7\xe5\xe6\xf0\xf1\xe0\xde\x13\xdb\xfe\x3f\xa3\xf7\xd9\xaf\x12\x24\xee\xe7\x67\xa4\x45\x8c\xfb\xbe\x97\xfe\x97\x16\xb9\xa1\x2b\xdb\x76\x2a\x7a\x6f\x34\x09\xf7\xb4\xea\xc4\xca\x4a\x6d\x1e\x02\x77\xd4\x28\xea\x5a\xe4\xe5\xca\xb6\xad\xb2\xb0\xe6\xf7\x10\xd2\xd0\xef\x8e\x52\x02\x2f\xc4\xab\xad\x8b\xa5\x5c\xc8\x7b\x38\x53\xcb\x84\xb6\x1d\x0a\x0e\x2d\x3b\xbe\x91\x5d\xa3\xdc\x7b\xe1\x7e\x0c\xc6\x14\xd0\xaa\xc7\xc5\x66\xfa\xbf\x2a\xc1\xbc\x75\xaf\xde\xe4\x0e\xac\x7e\x77\x07\xba\x52\xab\x21\x24\x8c\x87\xbc\xee\xec\xa7\x96\x32\x24\x62\xd3\x38\xfa\x91\xc6\x82\x87\x55\x07\x7f\x9e\x1f\x06\xee\x40\x5b\x00\x7e\x18\x10\x28\xcf\xb0\xca\xbd\xc4\x2a\xbc\x69\x66\xa5\x9e\xff\xa6\xe9\x1e\xbb\x8b\x60\x23\x09\xfc\xd2\x6f\x5a\xec\xfb\x59\xb9\x2c\x4a\xc9\xf0\xd9\x36\xaa\xb0\x7f\xc9\x8a\xb8\x7c\x74\x62\x22\x2d\x19\x33\x5a\x0e\xb0\x54\xd3\xdc\x83\x5a\xf1\x6c\x7e\x2f\xb9\x8f\xd4\x34\xc4\xdf\xa4\x9b\x51\xca\x41\xe2\xfd\x94\xc0\x8e\xa6\x9b\x1d\xa5\xd4\x89\xc7\x5e\x40\x18\x70\x41\xc5\xcc\x31\x6f\x5f\x44\x0b\x41\xac\x4a\xf6\xab\x69\x18\x69\x93\x2e\xea\x82\x93\xd2\x83\xbc\xe6\xc0\xb6\x67\x62\x81\x7f\xaa\xca\x03\x4b\x31\x7e\xe4\xeb\xba\x3c\x1c\x84\x00\x48\xd4\xa5\x9d\xc9\xcd\xda\xcb\xdc\xb2\xc3\xb2\x62\x28\x7b\xea\x48\x7a\x91\xf6\xdb\x10\xb5\xf3\xbe\x2c\x14\x68\x7a\x92\x82\x25\x77\xae\x45\x60\x2f\x70\x95\x0e\xde\x12\x11\xd8\xd3\x3b\xdb\x4e\xfd\xbb\xc0\xf8\x22\x38\x81\xfe\x20\xcf\x49\x31\x14\xbb\x5e\xff\x41\xe9\x2e\x55\x9a\x78\x86\x68\x0a\xcf\x8b\xfa\x4b\x39\x0b\x8e\x11\x24\x45\x8e\xe3\x5e\xac\xb3\x18\xac\xca\xf1\x93\xcc\x2f\x86\x2a\xd6\xfc\x5f\x6a\xfa\x6c\xbb\x7f\x56\xed\x1d\x64\xc0\x55\x0d\x06\x28\xbd\x19\x9d\x8c\x49\xd3\xdc\x8d\xac\x39\x62\xff\x1e\x27\x77\x00\x1a\x18\x10\x2d\x16\x03\x16\x88\x57\x3c\xc8\x73\x61\x38\x43\x01\xf4\x1e\x44\x0d\xce\xd4\x27\xb5\x0b\xba\x2a\x76\xa4\x9f\x8c\x16\x4f\x47\x90\x0d\x1d\x1e\x8f\x74\xa0\x9f\x64\x4f\x8e\x61\x9e\x3b\x30\xce\x3d\x37\xe2\xb9\xd5\xeb\x2c\x3d\x03\x87\x4b\x2d\x43\x21\x06\xea\x72\xf0\xf1\xde\xea\xbe\x9e\x5a\x19\x22\x0b\xdd\xb4\x58\xbf\x2b\xe4\xc6\xc5\x8a\x67\x77\x62\xf5\xbe\x54\x3d\x6f\x9a\xc1\xab\xe9\xcc\xa1\xf6\xe1\x69\xd7\x35\xa7\xb8\xaa\xa3\x99\x0b\x6e\x05\xa3\xdd\x01\x7b\x42\x77\x7e\xa8\x80\x9d\x5d\x02\xf6\x13\x5b\x46\x0f\x95\xd8\x31\xaa\x63\xc9\x92\xe7\x7c\x0f\x91\xb9\x69\x3a\x81\xe4\x88\x77\xb1\xeb\x0a\xbf\xdd\xef\x79\x9c\xb1\x9a\x4f\xd6\xec\xcc\xd8\x00\x31\x0a\x26\xd3\x7c\xd7\x37\x01\xf6\xac\x01\x46\x0b\x56\x4d\xfd\x18\xde\xd2\x54\x4c\x1b\xab\x19\x4d\xf1\x07\x38\x75\x9c\xf1\x74\xa7\x1d\x17\x22\x8f\xc9\x54\xf1\xa6\x49\x75\x9f\x89\x82\x66\x35\xb0\xcc\xd0\x5c\x73\x6c\x50\xed\x27\xae\xad\xa6\xd1\x3c\x6b\xb8\xa7\x40\x5a\x52\x1b\xe3\x74\x48\x6f\x3b\x7c\xb7\x3c\x94\xc7\x5a\xaf\x9b\x6d\x0f\xdf\x07\xeb\x08\xac\x87\x58\x3d\xa7\x97\x4f\x4d\x74\x94\xb1\x70\xc8\xea\xa0\xd9\x87\x24\x00\x78\xdd\xbb\x6d\x67\xe6\x45\xcd\x33\xb6\x94\x41\x40\x9b\xc6\x42\xa9\x5a\x9a\xf3\x63\x60\x0c\x65\x57\x31\xa3\xca\xf9\x98\x66\x03\x87\x06\x74\x0d\x13\x50\xdb\x85\x95\x45\x2b\xa2\x89\x8a\x10\xdb\xc7\x32\xa6\xf4\x6a\xb3\xc3\x40\x98\x73\x92\xd0\xd0\x8f\x82\x81\xd8\x3a\xb7\xae\x2c\xe8\x94\x90\xb1\xcf\xf1\x04\xc6\xe7\x01\x4d\x06\x02\x8f\x57\x38\x5c\xba\xa6\x29\x2f\xac\x4c\x90\x7f\xe5\x9c\xad\x3e\xa9\x9b\x8c\xb2\xee\x26\x23\x02\xb2\xc2\x2e\x44\xcc\x26\xee\x38\x3c\x15\x60\xed\x24\x56\xdd\xcd\xfa\xd9\x8e\xdb\x6e\xe1\x76\x2f\xc3\xe9\xec\xd8\x58\x57\x42\x5b\x12\xef\x48\x4b\x20\x45\x85\xe2\xe1\xe8\x5a\x2c\xaf\xbf\xe3\xef\xaf\x42\xa9\xff\xb8\x8a\x58\x11\xf1\x5c\x4c\xda\x55\x54\x57\xb9\xf8\x34\xd8\x59\x57\x08\x52\x3f\xed\xd8\x91\x5f\xed\x79\xcd\x44\x06\x8c\x82\xc2\x63\x95\x01\xb9\x1a\x91\x2c\xd7\xf6\xaa\xce\xf6\xfc\x75\xcd\xf6\x87\xab\x77\x19\x7f\xbc\x7a\xdc\x65\xd1\xce\xd2\xac\xd0\x95\x45\x20\xc9\x9e\xa4\xce\x5b\xc8\xa7\xfc\xbd\x7a\x56\xdd\x8b\x76\x02\x90\x76\xac\xfa\xa2\x8c\xf9\xd5\x1d\x7f\x2f\xfe\x8b\xe7\x51\x15\x23\x47\x6c\x43\xa1\xa7\xc3\x46\x60\xc3\xb8\x2f\xf0\x49\x47\xb9\x5b\xea\xca\xbd\xfe\xd1\x0d\x97\xaa\x15\x02\xac\x6d\x01\x15\x3e\xc3\x7e\x49\xd8\xbc\x92\x3f\xc7\xab\x28\xcf\x78\x51\xff\xaa\x7e\xff\x79\x55\x26\xc9\x91\xd7\xbf\xaa\xdf\x7f\x5e\x1d\x58\xca\x7f\xc5\xbf\xff\xbc\x3a\x46\x15\xe7\xc5\xaf\xea\xf7\x9f\x57\x75\xa9\xb4\x34\xbf\x3f\x24\xf3\x2c\x32\x54\xfb\x63\x33\x1a\x27\x36\xa5\xc3\x45\x85\x4b\xd5\x33\x0c\xc8\xa9\xf7\xdb\x19\x23\x82\x27\x28\xe7\x77\x42\x47\xcb\xb0\x8c\xdf\x83\xaa\xb3\xaf\x6c\xee\xa0\xa7\xf0\x31\xaa\xca\x3c\x7f\xc5\x93\x1a\x23\x5f\xf1\x41\xc2\x8a\x2c\x64\x2e\x59\xc6\xc8\x65\x26\xe0\x3d\x4e\x38\x2d\x5d\xed\xff\x1c\xd4\xfe\xa6\x3c\x0c\x2a\xc7\xf7\x51\xdd\x7d\x1e\xe3\x7d\x85\xd7\xb6\xe0\x52\x37\x4d\xb7\x73\x93\xa6\xe9\x00\x60\x6d\x27\xde\xda\x7d\x61\x27\xde\xc7\xee\x27\x76\xe2\xbd\x70\x57\x72\xb9\x93\xec\x69\x6c\x73\xc4\x0c\xde\xbb\x0b\x86\x69\xba\xc9\x28\x8c\x02\x09\x65\x5a\xe9\xae\xe1\xda\xe7\xc1\x26\xd5\x86\x3f\x46\x22\x4d\xe9\x2f\x5a\x41\x22\xcf\x50\x0d\x38\xfb\xc7\xf0\x4b\xbf\x2f\x5a\x02\x31\x45\xfb\xcf\xc3\x51\x7b\x15\x96\x87\xa3\xb6\x14\x51\x5f\x88\xdb\x7f\x02\x46\x4d\x59\x21\x11\xe4\x75\x64\xf6\x11\xa2\xf6\x3e\xf6\xc3\x00\x98\x1f\x05\x34\xf1\xa3\xce\x6a\x87\xf5\x0c\xbb\x7e\xa4\x39\x81\x8f\x65\x38\x65\x09\x4f\x06\xde\xee\xf2\x74\x1f\xcd\x68\x4c\xa9\x72\xb1\xf2\xd2\xce\xe3\x11\x12\xe2\xb2\x16\x14\x29\x74\x4f\x79\xc9\x62\xf7\xa4\x19\x74\xbc\xd1\x49\xc6\x34\x3f\x9d\x89\x89\x83\x43\x93\x19\xa5\xff\x72\x88\xf4\x0c\x95\x5a\x5d\xe5\x6e\x9e\xc8\x80\xe6\x78\x25\x79\x7f\xc8\x68\xe8\x20\x2c\xad\xf1\x6d\x21\xcc\x1f\xaa\xdf\x6b\x88\x9a\x0d\x89\x02\xaa\x1d\xf1\xf8\xfb\xcd\x94\x0f\xb5\xd5\x02\x12\xa2\x0f\x34\x64\xe9\xb3\x03\x8b\x4a\x22\xa7\x22\x6b\x49\x8b\x18\x51\x58\xb0\xac\x3a\xac\x8e\x62\xf0\x94\xd2\x5f\x7b\xd9\x8b\x5c\xa3\xfe\x68\xd6\x78\xda\xaf\xaf\xab\x4e\x2f\x1d\x58\xcc\x12\xf2\x79\xc8\x93\xb2\xe2\xf2\xba\x76\xf7\x64\xb2\x05\x43\x83\xf2\xde\xe9\x5e\xb2\x07\xb6\xcd\x90\xb1\xc9\x0a\x96\xab\x4b\xe0\x9d\x51\xca\x52\xb6\x8e\x2a\xfa\xae\x1c\x69\xdb\x16\x8e\xd9\xfe\x21\x1f\x78\x69\x28\xcd\x58\xef\xfb\xa7\x34\x99\x06\x74\x43\x04\x52\xaf\xc9\x20\x3b\xbe\x56\x35\x60\xac\xe7\x41\xab\xee\xa9\x6d\xc9\x26\xf6\x46\xfc\xb9\xc3\xf5\xb5\x8b\xee\x99\x5e\x58\xa9\x13\x38\x01\x3e\x29\x85\xa0\x3b\xfb\x88\xe1\x6a\xa5\x71\x6c\xa7\x70\x39\xb3\x1d\x9d\x34\x14\x16\xb3\x36\x65\x3f\x2c\x06\x3f\x5b\xa3\xc1\xed\x79\x6d\x03\x08\x1d\x04\xac\x90\x99\x3d\x07\x3d\x02\xea\x3e\x0a\xc3\x60\x42\x74\x0c\x06\x94\xb5\x14\x2a\xc3\x84\xf3\xa1\xd2\x2e\x5c\x53\x97\x64\x20\xd8\xf3\x8f\x72\x38\xfd\x22\x0b\xf6\xd4\xfb\x1f\xf7\x99\x42\x51\xb2\x45\x08\xa5\x16\xb9\xb3\x49\x84\x50\x99\x91\x76\xec\x03\x95\x03\xd0\xaf\x4d\x23\x00\xf6\xd1\x91\x8c\xb0\x3c\x2b\xee\x71\x34\x9d\xad\x08\x19\xa8\x48\xa4\x15\x91\x7a\x33\x2d\x4c\xcf\x47\xe8\x3e\x83\x29\x39\x03\x93\x3f\x20\x2d\xb8\xcf\x60\x08\x00\xe7\x4e\x40\xe7\x13\xbf\xb9\x34\xcb\xff\x23\x5d\x38\x86\x35\x9e\xa7\x38\xa4\x85\x11\x53\xff\x9f\x34\x7b\x3e\x0a\xdd\xf0\xa8\xd2\x89\x24\xdd\xf4\xd4\x94\xfc\x27\x7d\xf8\xc0\x94\x9a\x9d\x99\xca\xf6\xa1\x6f\xca\x39\x6f\xa2\xd7\xad\x76\x00\x3a\x21\xc1\x15\xf3\x5d\xb9\x16\x3e\x97\xef\x78\x65\x49\x7e\x2f\xe7\xec\x1d\xd7\xc9\x0f\xb5\x05\xea\xc0\x4f\x65\x57\x6f\xb2\x80\x7a\x51\x45\xf4\x27\xc4\xf4\x63\x5f\xe6\x91\x9c\x1d\xd0\xd3\x80\x48\x84\xa0\x35\x35\x6e\xa8\x78\xf7\x33\xa7\x9d\x08\xa4\x8d\x2b\x48\x8c\x69\x30\xdf\x18\x33\xb7\x13\x3d\x15\x05\xc7\xfb\x75\xb9\x34\x7e\x37\x4f\xae\x75\x0c\x67\xa6\x34\x45\x9d\x0c\x6a\x1e\xde\x4d\xfb\x8e\x81\x2a\x13\x12\x88\xd0\xe2\x7b\x7c\x8e\x2a\x76\xa7\x9c\x62\x49\xbb\x3b\x12\x2b\x29\xac\x49\x0a\xa7\x2c\x48\xcc\x11\x77\x53\xa6\xb0\xb9\x13\x42\x47\x9f\x86\x7a\x11\xb4\x93\xda\x8c\xa7\x38\x0c\xe8\x09\x8f\x8c\xc6\x30\xa9\x0c\x85\x47\xbc\xb0\x9a\xd8\xce\x92\x26\x46\xa3\xe8\xa6\x89\xcf\xcf\x91\x18\xe0\x85\x83\x60\xe6\x05\x87\x0b\xde\x73\x2e\xf0\xb4\x56\xc7\xff\xef\x1b\x5e\xac\x37\xdc\x1b\x54\xcf\x89\x8b\x71\x7a\xcf\xc9\x43\xdf\x1b\xa5\x64\x17\x15\x90\xf6\xcc\x6e\x67\x70\x5f\xc7\xd9\x59\xd3\xd0\xef\xa2\x8f\xd3\x74\x66\x12\x12\xa2\x44\x11\x35\x4d\x08\xda\xbf\xd6\x88\xd3\xcf\xa4\x81\x73\x59\x38\x29\x36\xc1\xfc\x34\x00\x3e\x8c\x46\x94\x25\x2a\x3a\x46\x24\xa5\x15\x4a\x63\xcf\x89\x69\x08\x51\xe7\xb1\x2b\x75\xc8\xd2\x6d\xe3\xdc\xee\x5b\xe4\x8e\x20\xea\xf2\xaa\x57\xa3\x43\x82\x57\x46\x9d\x53\x4c\x9f\x75\xe6\xf0\xb3\x78\x10\x98\xca\x70\x98\xe0\x78\x78\x1d\x43\x3c\xe9\x40\xe5\x48\x3f\x21\x46\x20\x99\xde\x15\x2d\xc4\xf2\x7c\x30\xd1\xe7\x86\xc9\xe8\x88\x5b\xbb\x46\x9c\x99\x09\x49\x90\x65\x71\xac\x95\x60\x31\x44\xca\x36\xb0\x2c\xa6\x58\x20\xd3\x5a\xc8\x58\x48\x01\x76\x65\x92\x5c\xf2\x25\x10\xab\x7b\x81\xac\x74\x58\xa3\xbb\x89\xd9\x44\x25\x50\x38\x63\xe5\x9f\x9c\x8d\xd8\x38\x27\x88\x3b\x0c\x32\xb7\x96\xd6\xdc\xf8\xe4\xf6\x9f\x8c\x30\x6b\x10\x77\xda\x2d\xa8\x75\x8c\xb0\x09\xd8\xc3\x60\xd5\x26\x54\x25\x89\xc3\x21\x04\xe6\xf3\xc9\x00\x57\x78\x45\xfc\x6c\xdd\x34\x13\xb1\x70\x43\x79\x35\x85\x01\x23\xe8\x46\x88\x1a\xb3\x88\x3e\xfb\x9d\x05\x1a\x98\x53\x76\x2b\x74\xc6\xc5\x9f\x85\x96\xb9\x54\x9f\xe6\x3b\xc5\x2a\xa1\xd6\xc8\xa8\x4e\x79\xaa\x5d\x74\xb4\x30\x03\x0a\x9e\x31\xb2\x12\x1e\x66\xab\x8e\xf5\x57\x56\x54\x8c\xd1\xeb\x97\x8e\x37\x63\x15\x67\x4d\x58\x35\x51\x99\x37\x7c\x1f\xf2\xb8\xd9\x55\x4d\xb6\x4f\x1b\x94\x21\x9a\x3c\x2b\xee\x9a\x3d\xaf\x59\x73\x60\x15\xdb\x13\xc7\xf1\xb7\x8f\x6e\x30\x97\xc1\xe0\xc8\xf6\xfa\xe6\x3a\xcd\x20\xc4\xca\xd4\x97\x6b\x88\xc4\x6b\x63\xff\xc9\xdb\x3e\xce\x37\xd7\x10\xcb\xa6\xdc\x63\x54\x65\x87\xba\x39\xd6\xef\x73\x8e\x15\x93\xeb\x0c\x38\xa3\xd7\xca\xee\x67\x7b\x7c\xee\x78\xae\xff\x96\x06\x0d\xdd\x1e\x9f\x6b\x73\xa0\xa5\xc8\x96\x30\x7a\xfd\xf6\x59\xb3\xbd\x76\x3c\xf7\x96\xbd\x63\x0d\x8f\xf6\x8c\xc8\x1a\xaf\x33\x48\xc5\xe7\xba\x7a\xe0\xdb\x6b\x67\xf9\x9c\x5c\xc3\x4e\x24\x6c\x8f\xcf\x5f\xce\x1c\xcf\xdd\xfa\x5f\x7c\xf9\xd9\x9b\xcf\xb6\x7e\xb3\x58\x90\x46\x24\x04\xdb\x40\x3c\xdf\x6c\x8f\xcf\x9f\x5d\xa7\x90\x31\x7a\x92\xb7\x42\xb9\xfe\x1a\xac\x97\x12\x2e\xaf\xf6\x0f\x79\x9d\x1d\x72\x4e\x3f\xd2\x4f\x1f\xdd\x58\x60\xbd\xbc\x96\xdf\x6f\xac\x00\xea\x1d\x67\xb1\x2c\x84\x11\x70\xe5\x77\xf5\x18\x40\x54\xe6\xae\xff\xa2\xfb\xf8\x32\x2a\xf3\xb4\x2a\x1f\x0e\x32\x5b\xf7\x66\x94\xa8\xab\x41\x81\x3a\x2c\xe3\xf7\xaa\x52\x7c\x34\xb3\xc6\xae\xff\xf1\x38\xeb\xcb\xba\x52\xd9\xab\x9b\x89\x32\x9d\xdc\xe7\xaf\xc0\xb2\xc0\xb2\x82\x76\x93\xb1\x65\x79\xa8\xb1\x27\x54\x3e\x67\x65\x01\x19\x5b\x62\x69\x91\x54\x27\x65\x59\x8b\x07\xdd\x63\x7c\x66\x98\x11\xbf\x8b\x59\xc0\x12\x3b\x7c\x8d\x7b\xd3\xa5\x5b\x36\xd4\xc1\x19\xb2\x25\x58\xd8\x2d\x8b\x0c\x24\xd8\xf3\x00\x44\xa1\x19\x94\xc6\xaa\x2b\x8b\xc8\xeb\xce\x95\x7a\xea\xf8\xf9\xfb\x37\x2c\xc5\xb2\x16\xf6\xd8\x22\xfe\x2a\x40\x1b\x10\xc3\x26\x6d\x64\x9d\x37\x36\x93\x53\x05\x89\xcb\x7a\x4b\xaa\xbb\xe1\x2d\x1f\xc8\xe7\x38\x52\x9b\x76\xe9\xb6\xa8\xb9\x75\x6d\xcd\x95\xd4\x64\xd4\x94\xb3\xde\xcd\x3a\x65\x2a\x50\xa1\x54\x3c\x77\xa7\x5b\x9e\xe6\xa4\xfc\x75\xe0\x6a\xb9\xef\xac\x05\xb3\xd6\x3d\x3b\xbb\x01\x0a\xe2\xfe\xb6\x91\x58\x29\xb1\xa5\x45\x3f\xf3\xa3\x00\x2c\x69\x72\xf2\xd5\x3b\x96\x5b\x30\x0b\x9b\x46\x9e\x0a\x85\xe3\x6f\xc4\x88\x97\x5f\xb0\x09\x3d\xbe\x34\x44\xd9\x9c\x5d\xc6\x82\x2a\xb2\xa1\x41\x8a\x93\x18\xf6\xdf\x04\x52\x2a\xfb\x13\x42\x42\xe0\x96\x26\xbd\x71\x8a\xf6\x36\xd2\x36\x02\x69\x6f\xf3\xb5\xe9\x10\xff\xad\x0a\x88\x26\x46\x7a\xeb\xf3\x60\x3c\x58\x93\x80\x86\xc0\x41\xe4\xf1\xa3\x80\xb4\xdf\x0f\x3b\xb5\xa3\xdf\x1b\x9d\xca\xa8\x19\xf6\x66\x47\xe0\x7b\xd5\xc7\x4c\xf0\x4d\xdd\x54\x94\x83\xab\x03\xa6\x61\xf0\x12\x68\xca\x8b\x47\x88\xcb\x96\x18\xc7\x4c\xc7\x5b\xfb\x2c\xcf\xbd\xf3\xa4\x2e\xb7\x3f\xe1\x32\x87\xbe\x6f\x83\x7d\x14\x12\xaf\x8b\xa1\xc4\xd0\x2f\xd4\xb8\xf1\xe5\x30\xba\xef\xe0\xc2\x75\x6c\xfd\xed\x69\x91\x6d\xbf\xd1\x37\xbc\x21\x88\xa2\x3e\x5c\xda\x58\x76\xd7\xe8\xb9\x8e\x51\x40\xdd\x91\x26\x48\x09\xbe\xcb\xd3\x5e\xd3\xe6\x92\x0e\x23\x3b\x13\xc3\xb5\x08\x4d\x36\x2f\xb1\x29\xea\x58\x93\x0d\x0d\x3b\x71\xbd\x3e\x60\x77\x8b\x6e\x9e\xce\xd8\x40\xb4\x69\x86\xc1\xd2\x54\xac\x33\xd3\x17\xb6\x0f\xaa\xec\x30\x22\x0f\x97\x52\x5a\x32\x67\x47\x20\x11\xbf\x8c\xa8\x28\x7f\xc9\xf8\xda\x9d\x03\x73\x12\x3f\x0e\x20\xf5\xe3\x40\x86\x30\xc6\x98\x7c\x58\x45\x42\x93\xa6\x91\xa5\x53\x9a\xe2\xe3\xee\x62\x45\xc5\xa0\x22\xe4\x54\x71\x07\xee\xfa\xab\x74\xb0\x47\x60\x49\xe2\x67\x11\x48\x8d\x8b\x11\xf6\xcc\x49\x61\x96\xd9\x36\xc2\x6a\x97\x87\xc0\xae\x85\xf0\x21\xcb\x63\x6d\x55\x3c\xc1\x49\x6a\x3c\x32\xbc\x59\x2c\xbc\x6c\x96\x9c\xa3\x85\x1e\x5d\x41\xd9\x63\x9d\xf2\x66\xbf\xd9\xcb\x50\x4e\x9c\x32\x7f\x1f\x00\x97\x97\x68\xa2\xb7\xdc\xb9\x23\x37\x27\x44\xc3\x6e\x0e\xdc\xb8\x96\x87\x07\x2e\x27\x1d\xab\x1e\x31\xad\x06\x27\x27\x9c\xcf\xbb\x01\x76\x0f\x2f\x18\x44\xa7\xd4\x09\x15\xbe\xe5\xd2\xd6\x4d\x90\x3c\x72\x7e\x8d\xbe\xa0\x5b\x7e\x1a\x34\x4d\xc6\x3a\xb3\x01\x48\x0c\x3b\xe4\x9d\xbf\x0e\x8c\x9b\xb8\x19\x03\xeb\xe5\xb3\xf5\xcd\xcb\xeb\x67\x2f\x6e\x2c\x32\xdf\xf9\x2f\x02\xb8\xa5\x3b\xc1\x8b\xf5\x16\x74\x89\x58\x5f\x6d\x84\xbc\xe9\xc7\x99\x98\x61\xce\x20\xa1\x77\x26\x85\x4b\xcc\x6b\x58\xa9\x65\x49\x0b\xac\x5c\xc5\xfb\x51\x03\x7d\xc3\x9f\x50\x91\x2e\x26\x64\x73\x37\x2a\x01\xfd\xc5\x1c\x9c\xe6\xdd\x25\x20\xe8\x9a\xb3\x58\xe3\xdc\xeb\x80\x4b\x78\xdf\x04\x9a\x09\x9a\x21\x96\x47\x3b\x8a\x2b\xf0\x1f\xce\x39\x27\x06\x0c\x66\x08\x7a\x09\x81\x88\x90\xd3\xad\xd1\x7c\xe2\xdf\x8a\xe6\x13\xbd\x7e\xfa\x5e\x47\x8b\x74\x61\x3f\x79\x7f\xf2\x2c\x7d\x22\x8a\x33\xdf\x26\xf3\x3e\x20\x79\xe8\x35\x12\xee\x21\xa5\xab\x4d\xa7\x76\x76\x22\x2a\x56\x93\x60\x50\x31\x19\x9d\xdc\x30\xf4\x40\xfc\xc4\x69\xe4\xbf\xea\xb4\x84\xe8\xcb\x13\xd2\x57\x9d\x03\xb4\xba\x5c\x21\xd4\xe4\xa9\xf7\x83\xee\x52\xfc\x38\xf0\x46\xe2\x00\xde\xee\x33\xb4\xac\x13\xfd\x0d\xb5\x65\xdd\xa6\x6f\xa0\x73\x6d\xed\x93\x5a\x95\xf2\xbd\x76\x0a\xf7\xbf\x37\xdd\x5c\xc7\xe2\xfb\xd9\x65\x98\x6a\x12\xc7\xfe\x75\xfd\x97\x5e\x37\xeb\xc9\x78\xf2\x98\x53\xa9\x5f\xe5\x35\xa1\xe4\x4c\x42\x41\x0a\x8f\x39\x7a\x3c\xb9\x9e\x48\xfb\xcb\x38\x89\xe8\x38\xbb\x26\x70\xb2\xde\xcb\x8d\x9d\x79\xfc\x90\x16\x24\x80\x5d\x38\x6f\x59\xc6\xe5\xfe\x7b\x56\x64\x87\xc9\xa8\x65\xb8\x60\xff\xeb\xce\x2a\xee\xec\x96\x69\xd3\x86\xcd\xd0\xa7\x81\x91\xb6\x95\x61\x19\xff\x3f\xd4\xc1\xac\x38\xf2\xaa\xfe\x1c\x0f\x65\x04\x1e\x1f\x84\x61\x14\xdd\x95\xe7\x35\xff\xcb\xde\xca\x83\x43\x23\x08\xf4\x28\x61\xdc\x7c\x27\xb7\xb2\xa4\xbe\x78\x66\xf6\xff\x46\xa3\x83\x10\xc3\xed\x25\xf7\x7c\x33\x52\x2f\xf3\x8a\xfe\xd8\xb1\xdf\x04\xc0\xe9\x6a\xa3\xae\x57\x8a\xd0\xb8\x83\xe0\x95\x58\xa1\xe4\x1d\xa2\x01\xab\xd0\x61\x2a\xa7\x64\xe8\xbe\x18\x0d\x23\x66\x23\x97\xd6\xe1\xd4\x68\x84\x53\x05\x12\xda\xcb\xa2\x26\x99\x36\xeb\x50\x78\x44\xfb\xd4\x0c\x15\x1c\xe7\x01\xf1\xba\xc0\xc6\x68\x34\xa5\x46\x21\xd5\xed\x7e\x18\x10\x0c\x58\x34\x8e\xbf\xec\x8c\x86\xc1\xa4\x39\x3c\x1b\x11\x94\x51\xd3\xe7\x2c\x9b\xe1\x34\xa5\xe3\xdc\xce\xd6\x2e\x53\x11\x53\x28\x0d\x3d\xe6\x4a\xc5\x06\xc6\x2f\x38\x07\x8d\x42\x72\x78\x43\x67\xd8\x5d\xbd\x9f\x0c\xb9\x3a\x81\xe1\xe4\xe6\x50\xda\x10\xb4\x06\x95\x32\x82\xe1\xf4\x36\xf4\xcc\x3e\xbb\xac\xbb\xb3\x32\xec\x68\xfe\x74\xa4\x7a\xdb\x9e\xc5\xc6\xb5\x38\xb3\x8c\xf9\x1d\x8f\xc1\x3e\xc4\x63\x04\xe4\x64\xde\x71\x3e\xe2\x20\x30\x12\x09\x5a\x33\x69\x51\x46\x0d\x27\x92\xc3\x59\x8f\xae\x4f\x1a\xaf\x5c\x28\x57\xce\x74\x9d\x12\x28\x82\xae\x0c\x9f\xff\x36\x54\xdb\x49\xe3\x7a\x89\xe0\xd0\x55\xff\x32\x4a\x56\x1d\x1e\x46\x2e\x3c\x0b\x66\x67\x28\xa1\x7e\x77\x9b\x87\x62\x26\x46\xfb\x1a\x46\xe3\xc1\x4d\xa9\xa2\x97\xa9\x1e\x28\x56\x4f\xe3\x19\xf1\xcd\xd1\x7c\x27\x5a\xe3\xe9\xa5\x34\x3c\x43\x15\x61\x46\x8f\xcd\x9a\x8d\x8e\xa9\xcd\xfe\x76\xf6\xe8\xe8\x62\xad\x7b\x3f\x02\x72\x46\xb9\x52\xf8\xfa\x81\x8e\x8e\x2a\x08\x7c\xc7\x2f\xd3\x15\xe4\x03\x47\xcb\xbd\x3c\x9f\x29\x69\xbe\x58\xc3\x01\x6f\x36\x80\xfb\x61\xf4\xbe\x03\xca\x0b\xf7\x4d\x93\xdf\xac\x27\x82\x9a\x1c\x6c\x7b\x66\x7a\xd3\xd9\x36\x57\xe0\x77\x20\xe4\xb2\x6a\xb1\x0b\x48\xba\x5f\xf2\x7b\x27\x22\x9b\x7b\x31\x5b\xfe\x2a\xa0\x07\xc3\x82\x2f\x82\x78\x29\x76\x99\x43\x08\x81\xd8\x58\x34\xdc\x81\xa2\x5f\x39\xaa\x43\x8b\xe5\x40\x78\x50\x48\xd3\x5f\x05\x23\xac\x36\x53\x31\xfb\xd1\x8c\xc8\xe0\x69\x65\xf0\xf1\x9e\xe9\xed\xfd\x16\x22\x1a\x13\xd0\xe6\xd6\x89\x0a\xe3\x3b\xc4\x8a\x70\x87\x92\x53\x27\x2a\xe5\x37\xb7\x9b\xdb\xf9\x9c\xec\x68\x04\xb7\x33\x4a\x4b\x94\xe7\x35\x06\xd9\xc1\x6c\x85\xc7\x1f\xa9\xc0\xbf\x92\xe3\x4e\x60\x28\x32\xe1\x46\xe9\x66\xc1\xbf\x0d\x60\x07\xb7\x44\x5e\x55\x2b\xcd\xbe\x13\x3f\xe9\xaf\x9d\x18\x0d\x52\xf6\x31\x81\x9c\x11\xb1\xe2\x9b\xb4\xeb\x4f\x22\xaa\xd2\xac\xee\xce\x64\x75\x67\x9d\x0e\x64\x37\xd4\xb4\x0c\x88\x44\x06\x3b\x54\x4e\x2c\x8f\x55\xe4\x15\xcb\x7f\xf1\x77\x2c\xff\x7b\x95\x8b\x3c\xfa\x59\x7e\x14\x5c\x66\x5f\x8b\x68\xaa\xc7\xd7\x1d\x86\xd9\x31\xb0\x2c\x32\x8a\x09\x2b\xdd\xfe\xf1\x40\x4e\xee\xfe\x37\xa5\x6b\xc9\x27\x4b\x73\x37\x22\x49\x3d\x5a\x60\xd2\x5b\xd7\x92\x0c\x85\x4e\xfd\x0c\x09\xbd\x85\xf4\xde\xd2\x88\xe2\xb3\x3c\x77\x2d\x03\x69\x4c\x1c\x7d\x8e\xc2\x38\xb3\x21\x71\xc6\x6b\x0c\x0a\x29\x2e\xf3\x6e\x15\x60\x87\x53\x4d\x65\x08\xad\x88\xee\xf0\xba\xd1\x7e\xa7\xcb\xd5\x17\x0b\x5f\x38\xdc\xdf\x05\xc4\x0f\x03\x27\xea\x8f\x68\x62\x88\x64\xe0\xf6\x01\x31\x33\x82\x33\xc7\x9d\x87\xf1\x3d\x83\x8a\x0d\xe2\x32\x1f\x99\x33\x88\xc4\x54\x38\xd1\x48\xe2\x0c\x89\x46\xa7\x6f\x4a\x47\x9a\xcb\x11\x3c\x84\x4d\xb9\x3e\x5c\xf9\xa2\xdc\x1f\x1e\x6a\x1e\xbf\xae\xdf\xe7\x1c\x43\x72\x5c\xfc\x8a\xf7\x6c\x13\xe2\xc5\x4b\xe5\x6c\xec\x4a\xef\x63\x91\x6c\x38\x20\x6f\xfa\x80\xa4\x88\xdd\x1c\x02\xc9\xf8\x96\x62\x49\x19\x73\x88\x68\xc5\x7c\xd6\x9f\x14\xe0\x95\xd8\xf2\x02\x69\xa2\x9c\x9c\x65\x54\x0a\xf1\xe5\x9e\x51\xe7\x9e\x35\x4d\xe1\x58\x2f\xb3\xa4\x62\x7b\x7e\x85\x7f\xc3\xb2\x8a\x79\x45\x3f\x5a\x7d\x74\x85\xd7\x9b\xe1\x93\xbc\xef\x4c\x3c\x5e\xdf\x58\xe6\x34\x84\x63\x4b\x42\x02\x21\xbd\xc7\x5b\x5b\x47\x41\xc0\x21\x5c\x3e\x56\x59\x2d\x24\x71\x74\xe2\x55\xf1\x72\x74\xf7\xee\x59\x37\x40\x02\x38\x0c\x1a\x11\x88\x5a\xbc\xdb\x8a\xd1\xeb\xb7\x7b\x56\xa5\x59\x71\x0d\xef\xa4\x85\x9b\xf6\x51\x79\xeb\x58\xf3\xff\x9e\x5b\xc4\xf1\x66\x87\x27\xe2\xb3\xc5\x6f\xff\x15\xcc\x9f\x59\x60\x65\x16\x81\x47\x36\xe5\x2f\x3a\xba\xa1\xc5\xf4\xc6\x58\x96\x07\x5e\xf0\x6a\x7c\x89\xcb\x20\x4b\xca\x47\x0b\x19\x82\x8c\x19\xc7\x2e\x7e\x32\xc0\xec\x89\x5d\xd2\x7f\xe1\xa1\x49\xb7\x76\x34\x6a\x9a\x47\x54\x28\x45\xb6\xed\xa4\x14\x21\x5b\x07\x68\x43\x0d\x9b\x13\x92\xa6\x89\xd0\x5f\x46\x64\xb1\xc4\xda\xa6\xc8\xb5\x5e\x56\x9b\xa1\x37\x4b\x21\x9b\x92\x77\x1b\xc0\x3b\x85\xcb\x52\x62\xdb\x0f\x4c\xdf\xa0\x85\xb0\xbb\x5b\x22\x04\x00\xa7\xbb\xe5\x3e\x2b\x7e\xc1\x97\x44\xbc\xb0\x27\xf9\xd2\xa7\x1b\xa9\xba\x1c\x4d\x41\xf4\xfb\x51\xe5\x94\x69\xb1\x59\x86\x83\x51\x2a\x21\x86\x9d\x7b\xea\xa5\x73\xcb\x72\xd3\xf3\x4b\x5d\x55\xec\xb9\xc9\x88\x74\x57\x4c\x5f\x34\x67\x06\x95\x12\x59\xbb\x5b\x1d\x68\x48\x2e\x1c\xe8\xb6\xed\xd8\x61\x1f\x55\x11\x34\x9f\x30\x96\x9d\xf6\xce\x87\xe4\xc2\x07\x0c\x73\x23\x67\x9d\x9c\xd4\xc3\x32\x64\xd1\x5d\x5a\x95\x0f\x45\xfc\x45\x9e\x1d\xa8\xa5\xf6\xcb\x22\x2c\x9f\x2c\x48\x46\x7e\xee\xd3\x45\x2c\xb8\x43\x96\xaa\x42\xb6\x01\x41\x6e\x58\x0f\x46\x1f\x9a\x2a\x0b\x5c\x25\x47\xc7\xe3\x1b\xfe\x54\x53\x4b\xee\x7b\x77\xb5\xc1\x75\x72\x57\x1b\xb9\xe7\xdd\xd5\xa6\x2e\x0f\xee\x6a\x93\xf3\xa4\x76\x17\x7f\xf9\xcb\x5f\xfe\x72\x78\xda\xc8\xcd\xb8\x10\x5f\xd6\x87\xa7\xcd\x41\xdd\xcf\xe8\xb2\xf0\x58\xe6\x0f\x35\xb7\x80\x0f\x44\xeb\xc4\x88\xb1\x99\x3a\xfd\x1c\x74\xad\x2f\x1e\x79\x78\x97\x61\xa7\x17\xc7\xec\xb7\xac\x48\x5d\xd9\x21\x91\xb2\x59\xec\xcb\xdf\x2e\x7c\x9a\x4e\xd5\x28\x35\xcc\xcb\xe8\x6e\xd0\xdb\xff\xda\xa8\x1f\x35\x5e\xec\x3e\x8b\x63\x51\x81\x78\x96\xa3\xff\x64\x7a\x50\xa6\xb6\xd0\xb2\x20\x1e\x29\xcb\x36\x32\xa8\xf0\x04\x1e\x48\x24\x1e\xd8\x84\xd4\x5a\xff\x17\x6e\xd4\x65\x5d\x1e\x20\xa2\xd6\x27\x07\x5c\xa5\x54\x6d\x93\x78\x20\x11\x72\xd2\x9e\x57\x66\x18\xcf\xdd\xc1\xe9\x90\x3d\xf1\x5c\xdf\x8f\x39\xb1\x23\x52\x81\x6f\x5b\x08\xcb\xa7\xd7\x38\x4b\x3f\xf3\x3c\xbb\x10\x3e\xbd\x33\xd0\x10\x65\x22\x21\x15\xc8\xac\xdf\xe3\xf4\x61\x68\x8d\xb1\x74\x10\xa2\xf9\xd0\x1f\x88\x5b\xd1\xe1\xb5\xd1\xc2\xff\x11\x40\x30\x00\xfa\x0c\x12\xcc\x6f\x17\x92\xa7\x60\xc1\x5d\x6d\x3a\x78\xd7\xab\xbf\xb2\x40\xf7\x6e\xdf\x8f\x98\xea\x34\x89\xbe\xac\x95\x80\x82\x41\xca\xfa\xf0\x74\x0e\x0a\x10\xd2\x19\x06\x66\xfb\x3a\x2f\x59\xed\x4c\x80\x44\x24\x41\xc2\x6c\x8b\x9c\x2d\x3f\x24\x23\x0d\x01\x84\x6d\x4b\xda\x16\xdd\xcd\x8e\x8f\xec\x70\x16\x6f\x55\x19\xcc\xa2\x27\x8e\x3a\x55\x4b\x64\x70\xc7\xd4\x4f\x02\x4d\x66\xfc\x24\x80\xfe\x91\x86\x7e\x12\x6c\x38\x8d\xba\x80\x08\x32\x4c\x93\x59\xda\xc8\x2d\x2a\xea\xd8\x12\x19\x9a\xef\x37\x41\xa1\x1d\xc1\x61\x34\x78\xe0\xeb\x78\xb3\x45\xe4\x73\x16\x90\xe5\x9c\x5c\xc3\x67\x17\x68\xf6\xf2\x39\xd1\xa4\xfa\xf3\x71\x16\x7f\xbe\x08\x08\x55\x39\x55\xa6\x2f\x18\x3d\x75\xdb\xd2\xea\xf7\xe5\xbb\xec\x98\x85\x59\x9e\xd5\xef\x5d\x6b\x97\xc5\x31\x2f\x2c\xd0\xcb\x6e\xe1\xba\x5b\x2d\x7c\xc9\xe8\x29\xe7\x75\xcd\xab\xd7\x07\x16\x89\x15\xc7\xc5\x2c\x8b\xfa\x17\x89\xe9\xac\x4f\x56\x2b\xab\x85\xaf\x18\xf5\xad\x5f\x10\x0a\x2d\xb0\x7e\xb4\xc0\xfa\xbe\xfc\xcd\x02\x6b\x7f\xb4\x82\x1e\x8b\x7d\xad\xc8\x51\x96\x38\xa1\x34\x57\xd1\xdc\x85\x0a\x5a\x15\xe2\xd5\xbe\xe5\xdf\x0f\x07\xad\x1d\x98\x6b\xaf\x9c\xb5\x10\x9e\x42\xe0\xf4\x2b\x36\x34\xcd\xe7\xd2\x25\x3f\xa4\x5f\x31\x9f\x07\xf3\x08\xc6\x55\x6b\x73\x9d\x9e\x2c\xfe\x75\xc8\x4f\xd0\xcf\x94\x9e\x22\xec\xaf\x43\xf4\xf0\x16\xf2\x3d\x7b\x72\x56\x10\xfb\xeb\x60\xe1\x44\xe8\x32\x31\x77\x62\xe9\xdc\x7f\x78\xb2\x88\x1b\xf6\x75\x7e\xc3\x4c\x63\x31\xcd\xb7\x27\x34\x42\xcf\x56\x4f\x91\x0a\xcb\xd5\xa4\xc6\x22\xde\x27\xae\x25\x2f\xc3\xc5\xb0\x4d\x6b\x0c\x11\xbe\xda\x7c\x72\x93\x6c\x92\x39\x7d\x41\x2c\x09\xe5\xea\xec\xd1\x49\xe7\x5d\xa8\x9d\x68\xfe\x33\xba\xe8\xaf\xf0\x1a\x96\xd8\x73\xba\x4a\x75\xde\x45\x1f\x96\x47\x6d\x55\x6b\x50\x46\xd7\x3d\x3b\xcf\xaf\x3a\x8a\xd9\xe7\xd6\x2f\xf2\xb6\x5e\x59\x8c\xb8\x66\x2f\xa6\x6a\xee\x13\x67\x67\xbd\xfe\x60\xcd\xdd\x29\x5e\x3f\xa3\xdf\x8e\x56\x49\xe4\xa4\xe6\x8c\xb1\xa5\xf4\x1e\xc2\x9a\x5c\xfd\xf6\x0d\x02\x26\x24\xf4\x51\x1d\x2d\x5a\x3d\x8d\x1b\xc4\x2b\xea\xd0\xbb\x25\xe4\xf3\x04\x19\x8e\xd5\x0d\xe5\x4d\x23\x91\xba\xba\xf7\x8d\x2a\xee\x33\x21\xe0\xac\x6e\x8c\xaf\x78\x5a\xa2\xb7\xb8\xe0\x28\xdf\xf5\x87\x71\x7a\xa7\x6f\x62\x9a\xda\xb6\x73\xb7\x3c\xa3\x25\x0e\x69\x1a\x8e\x6a\xc7\xbe\x02\x4e\x0d\xf4\xc7\x49\xd3\xac\xba\xe0\x82\x73\x0d\x5f\x82\x1b\x9d\x82\x26\x88\x21\x21\x73\x01\x96\xfd\x0c\xfe\x6d\x6c\x09\xa1\x0e\x89\xf0\xfa\xb5\x15\x32\xd0\x6a\x2b\xed\xd4\x4d\x33\x31\x1e\x10\x41\x2c\x3b\x65\xdb\x18\xaa\x4a\x85\xb2\x89\xc1\x2a\xf3\xb8\x13\xb2\x30\x36\xaa\x44\xea\x2a\x0d\x42\x0f\xf3\x37\x4d\x2f\x37\x35\x8d\x33\xca\x45\xbb\xbb\x94\x47\x1f\x6c\xfb\xb5\xf4\x22\x56\x6d\x76\x46\x9d\x66\xb3\x50\x33\x27\xee\x0e\xeb\x09\x91\x71\x5e\x5f\x63\xcc\x50\x1d\x91\x2a\xb2\x6d\xde\x34\xd2\x9e\x62\x54\x9a\x7b\x91\x12\x19\x63\x43\x5e\x24\xea\xa2\xd4\x14\x7d\x1c\x2f\xcc\x44\x68\xdb\xdd\xb0\xce\x7a\x6e\x4d\xa4\x4e\x0c\x3d\xf4\xd4\xfc\x58\xae\xac\x8b\x0c\xaf\x26\xd3\x67\xff\xc7\xa3\x72\x66\x2a\x05\xd6\xad\xdf\xbb\xa7\xf3\x88\xc2\x78\x98\xae\xcc\x17\x10\x44\x2d\x95\xb9\x93\x7f\x55\xe8\x53\x6b\x6d\xb9\x51\xdb\xb6\x2d\x44\xc7\xe3\x0f\x0f\xfb\x90\x57\xee\x29\x2a\xf3\x87\x7d\x81\x7e\xa6\xee\x6c\x05\x49\x96\xe7\x3f\xaa\xb6\xc4\x6b\xce\x9f\xfe\x5a\x95\x8f\xfa\xf9\xf5\xae\xca\x8a\x3b\x7c\xeb\x51\xff\x6c\x05\x79\x56\xf0\x6f\xba\xb7\xb2\xaf\x40\xf2\x09\xf8\x70\xd8\xb1\x02\xef\x6e\x7c\xcc\xe2\xf2\x11\x9f\x7e\xfb\x16\x6f\xc1\x13\x4f\x65\xb9\x47\x0f\xa6\xe8\x88\xa6\xf6\x47\xf7\x64\x25\x02\xfc\x05\x68\x1f\x8f\xb8\x13\xac\x16\x70\x16\x27\x8e\xe6\xa5\xb5\xe6\xc7\x23\xfb\x85\xff\x33\x7a\x67\x5a\x8a\x30\x4e\xf1\xc7\xa1\x81\xb3\xb1\x30\x19\x4a\x44\x81\x9d\xf2\x77\x01\xc6\xdd\x37\xde\xe9\xd7\x0c\xd5\x52\x02\xc1\xe0\x07\xe9\xb4\x16\xe2\x05\x58\xdd\xeb\x2e\x00\x23\xf2\x72\x6a\xdb\x56\xca\x6b\x2b\x2b\xae\x52\x23\xe2\xa5\xc3\x69\xaa\x42\x14\xce\xd6\x10\x13\xe2\x71\x37\xf3\xc3\xc0\x75\x92\x2e\xfa\xad\x11\x10\x97\x26\x88\x78\x3e\x57\x34\x2b\x22\xd2\x60\xd3\xe1\xfe\x3a\x98\xaf\xc9\x73\xee\xbf\x08\xe6\x06\x1e\xd1\xf8\x4e\xc8\xad\x09\xb5\x0a\x04\x00\x8b\x80\x3c\x09\x89\x6c\x3b\x52\x44\x43\x7f\x9a\xa1\x17\x20\x16\x93\xd0\x22\x87\x1f\xcd\x29\x12\xbd\x73\xf1\x49\xc0\xb3\xdc\xeb\x2b\xb4\x51\xeb\x02\x74\xf4\xe2\x93\x85\xf1\xaa\xfc\x30\xa0\x56\x56\xec\x78\x95\xa1\x81\x86\x6d\x5b\xc7\xd1\x7c\x50\x3c\xa5\x4e\x55\x14\x5e\xb1\xc8\x5d\xc9\xa8\x93\x75\x89\x84\xe5\x4b\x8e\x4f\x93\x2b\xfc\x1f\xac\xab\x82\x84\x3f\xb2\xba\xc3\x25\x35\x57\x72\x05\x7d\x7f\xbb\x48\x99\x8a\x94\xc4\x04\xb1\x55\xb5\x67\xb9\x8a\xa2\x89\x3c\xcb\x97\x0c\x33\x7d\x89\xf7\xd6\x48\x14\x19\x35\x4d\xe4\x39\xc9\x90\x2e\x48\x83\xdc\x95\xb4\xcb\xf9\xe1\x61\xcf\xab\x2c\x72\x12\xe2\x25\x4d\xb3\x72\x39\x71\xb9\xa1\xcd\xf4\x2d\x29\x92\x5a\xa0\x28\x67\x70\xa6\x75\x34\x86\x47\xc7\x78\xc6\x34\xa5\x8e\xbc\xdf\x14\x81\x3b\x0f\xf9\x47\x6c\x5b\x3a\x37\x19\x14\xd9\x93\xdc\xb6\xc3\xe0\x0b\xe3\x96\xf0\xae\xba\x6f\xf5\x5c\xb4\xc4\xed\x9e\x3b\x47\xbc\xe3\x44\x3f\xa4\x57\x5b\x6c\xdb\x48\xd9\xf5\x82\x22\x33\x17\x41\xec\x29\x0a\x19\xc3\x1f\x23\xf8\x9c\x00\x27\xee\x4a\x3b\x00\xe8\x59\x18\xc8\x31\xef\x99\x73\xb7\x9c\x10\xe9\x60\xf2\x90\x2f\xec\x47\x7c\xea\x38\xe9\xac\x10\x48\x72\xa1\x19\xea\x27\x06\x3e\xd3\xec\x97\x8c\xbc\x18\x18\x01\xd4\x3a\xbf\x1b\x29\x71\x59\x16\x68\x49\xcb\xb2\x40\x49\x5f\x8a\x73\x3a\x57\x1f\x77\x0b\xc9\xe6\x62\x29\xa5\x8d\x84\x6b\x1e\x7d\x68\x46\x40\x1a\x59\x9d\x5a\x81\x10\xce\x22\x6d\x7b\x91\xe1\x66\xed\xfa\x51\xb0\xf9\x44\x19\x61\x71\x9f\xcd\x7f\xf6\xe3\x40\x54\x9f\xf8\x71\xd0\x34\x89\x1f\x2f\x5e\xe0\xef\xca\x10\x6f\x5a\x78\x60\xfd\xe5\xae\xce\xa8\x67\x62\x6f\xd3\xbf\x32\x32\x36\xdc\x18\xef\xe8\xcb\xe7\x9a\x23\x5d\xa0\x18\x88\x20\xdd\x2a\x92\xbf\x8e\x0f\xaf\x83\x17\x48\x3e\x90\xf7\xa1\xbc\xb8\x22\xf1\x89\x1f\xfa\x69\x10\x74\x00\x22\xde\x24\x0a\xee\x43\xf9\x0f\x4c\x44\x30\xd2\xbd\xa1\x09\x04\x3c\x70\xd0\xe8\xb5\x05\x36\x19\xa2\x98\xb4\x70\xdc\x95\x8f\x13\xca\x83\xbf\x29\xa3\x05\x3c\x5b\xdb\x65\xf1\x94\x82\x41\xe5\x21\x2d\xd4\x65\x9a\xe6\x53\x21\x51\xad\xb0\x2c\x73\xce\x0a\x33\xe4\xb4\x0a\x3a\x2d\x1a\x56\x97\x49\x2d\x45\x03\xfa\x79\x6c\xcd\xf2\x5a\x36\xe2\xc9\x7b\xb3\x89\x2e\xa7\x5f\x65\xd1\x76\x78\xef\xcb\x77\x03\x89\x47\xeb\x43\xf8\xe3\xd5\x77\xac\x77\x1f\xc4\x3b\xd0\x8d\x7c\x6d\xb1\x7c\xf3\xc8\x79\x41\xbf\x63\x60\xe6\xa3\x27\xe3\xaa\x71\xf7\x3b\x06\xa2\xdc\x84\x1f\x0e\x24\xca\x16\x82\xe7\x7c\xaf\x9d\x32\x0f\x55\x79\xa0\x91\xf6\x4c\x38\x66\x05\x5e\xab\x68\x1d\x1f\x71\xaf\x4b\x97\x08\x34\xca\x3e\xd2\x50\xfb\xbc\xb1\xaa\xd6\x96\x24\x8f\xea\x22\x02\xf4\x4e\x96\x95\x14\x31\x8d\xe5\xe3\x03\xde\xe1\xae\xa1\x58\x11\xc4\x28\xf0\x04\x0b\x27\xc8\x61\x0b\xd1\x43\x75\x7e\x00\x2c\xc7\x76\x90\x50\xdf\x75\xb2\xf7\x19\x17\x5c\x49\xca\x6b\x69\x16\xab\xec\x8d\xcc\x32\x7d\x90\x9e\xee\x7b\x0b\xd5\xc3\xc4\x05\x55\x10\xfd\x5e\x63\xe6\x04\x2c\xe3\x87\x0a\xdd\xfc\x94\x6b\x7c\x79\xa4\x82\x22\xca\x59\xf3\x8d\x19\x0c\xb4\x11\xc9\xb8\xe0\x73\x06\x2b\x58\x4f\x7f\xd3\x5e\xf5\x58\xab\x36\x42\x29\x1f\xa9\xa3\x67\x75\xd1\xcf\x3e\x79\x1e\xce\xfb\xb7\x61\x7d\xc7\x9a\x1f\xd4\x99\xbc\x99\xd4\x9f\x4d\xca\x20\x2f\xba\x7e\x7d\xd7\xb5\x6d\x63\x2c\x56\x2f\xea\x02\x88\x5f\x9a\xd4\xee\xbb\xbc\xdc\xa9\x85\x33\x98\x35\x40\xd3\xfc\x06\x66\x7d\xf4\xd4\xf9\x0d\x8c\x08\xa7\x5a\x9b\x71\xe8\x09\xd1\x6b\x5f\x56\x80\x96\xf8\xe2\x5d\x0b\x18\xea\x9e\x36\x23\x4d\xe7\xf4\x9c\x50\xe3\x28\x39\x6e\x99\x0e\x42\x90\x12\x52\x09\x7b\xa8\x4b\xc1\x80\x85\x5e\xe8\xae\x88\x3b\x6c\x66\x4c\x4a\xf1\x9c\xf1\x09\xa7\xb3\xab\xff\x2c\x05\xef\x89\x1f\xf4\xce\x99\xe8\x9e\xc1\x38\xa9\x72\x43\xfe\x48\x25\x92\x1e\x65\x0e\xba\x2f\x58\xf4\xc7\x39\xde\xff\x59\x8f\x7b\x8d\xfc\xfb\x63\xdb\x2d\x8c\x5a\xbf\x2e\xc4\x05\x9d\x48\x7e\xc5\x93\x1a\x3d\x19\x87\xb1\xa1\x64\x8f\x4d\x69\x00\x13\x06\x36\x49\x53\x8d\x6b\x37\x58\x44\x27\x27\x41\xc4\x59\x35\x79\x4f\x67\x0b\x88\x68\x26\xbe\x2d\x3f\x5d\xa0\x1a\x29\x2a\x8f\x0e\x7b\x8e\x8f\x3f\x7d\x4b\xae\x5f\x60\xcd\xc9\x13\x3d\x03\x3a\xe8\x56\x82\x9e\xd4\xfd\x1d\x0c\xbe\x67\xf0\x03\x93\xf1\x72\x25\x09\x68\x04\x76\x6e\x04\x4e\xc6\xfb\x3a\xc6\x4a\x40\xcf\x55\x7a\xc0\x86\x68\x9d\xa1\x3c\xe2\xeb\x15\x87\x3f\x31\x7a\xdd\x07\x9f\x7e\x76\x0d\xff\xcd\xa8\xff\x0f\x16\xc0\xcf\x8c\x9e\xac\xe7\x96\xeb\x5f\x72\xaa\xd2\x76\xb6\x02\x87\xf7\xb1\xe5\x25\xea\xe4\xf4\xc7\x4e\x91\x06\x89\x60\x6b\xb9\xff\x71\x30\x42\x9d\xac\x47\x9d\x90\xd2\xd1\x27\xa9\x57\x9b\xa1\x8c\x33\x8f\x89\x6d\xeb\x0a\x25\xf8\x47\x0a\x7e\x08\x81\x1d\x5d\x43\x46\x5f\x20\xd1\x4f\x6d\x3b\xf5\x3f\x0e\x44\x31\x65\x05\x2d\x5e\x81\x0b\x22\x80\xaa\x8e\x79\xdc\x34\xeb\x4d\x5c\x5e\xed\xe8\xae\x69\xac\xe5\xa7\x16\xa4\xd7\x74\x07\x1a\x2e\x75\xbd\x90\xce\x13\xa2\xd4\x8a\x3b\x21\x99\xed\xf4\xd8\xae\x63\x75\xa9\xf8\xce\xb6\x17\x8b\xac\xbf\xf4\x41\x9d\x33\x4a\x7a\x32\x4f\x9b\x46\xb4\xb5\x82\x48\xd1\x0d\x88\x90\x96\x08\xd9\xcc\x4b\xe7\x03\x19\xcd\x9d\x8b\xbf\x04\xa2\x36\x30\x8e\x3a\x5f\xb3\x9e\xf4\x1b\x17\x95\x99\x41\xe8\x75\x8c\xf1\x96\xc0\x2b\x46\x95\x93\x7f\xaf\xf4\x79\x33\x74\x4c\x91\x9c\x9e\x3a\x97\x62\x52\xb9\x1d\x4a\x75\xa3\xe2\xeb\xe8\x8b\x45\x48\x22\x2a\x18\x3b\xe0\xbe\xd6\x0c\xce\xa3\x80\x72\xbf\xd7\xf1\x45\x01\x65\xc6\x7d\xe8\x0e\x5f\x2a\x71\x9f\x6a\xa5\xbe\x60\xb2\xfa\x6e\xfc\xbd\xd3\xde\x75\x4c\x27\x70\xea\xfc\xcc\x50\x8a\xf2\x03\xa2\x03\xb2\xfc\xcc\x7c\xeb\xb9\x15\x10\xbc\xad\xb6\xb7\x6c\xd8\xa4\xa8\x07\x9d\xcb\xcb\x64\xb8\x9f\x04\x12\xfb\x47\x82\xc7\xea\x14\x6c\x86\x52\xf7\x1f\xd3\x87\xc4\x3a\x32\xac\x34\x38\xda\x53\x0c\xb3\xaa\x25\xbc\xc3\x40\x4f\xf0\x5a\xf0\x89\xf7\xdd\xc5\x0d\x56\xf2\x24\x36\x1a\x06\x09\xc7\xcd\xd2\x34\x68\x4f\x33\xbc\x31\x01\xaf\x7d\x00\x89\xe0\x77\xcb\x87\x02\x3f\xc6\x68\xad\xa2\x5f\xf0\x06\xb7\x9d\x71\x97\x01\x98\x2f\x66\xc8\xf9\xbe\x4c\xd3\x64\x82\xe1\x82\x3e\x65\x3e\x87\x7c\x29\xef\x5e\x35\xc1\x61\x2a\xad\x2f\xb4\x58\x40\x7f\x11\x04\x76\xb5\x33\x0c\xdb\x0d\x2f\x57\xc0\x1b\xd9\xce\xec\x20\xb5\xf4\x98\x15\x57\x61\xd3\x28\x11\x12\xcf\x36\x6c\xdb\x89\x96\xe5\x3b\x5e\x25\x79\xf9\x48\xfd\xb2\x7b\x86\xfe\xf1\x57\xe3\xf9\x9f\x01\xdc\x4e\xc4\x8a\x87\x3b\xda\xe9\xed\x6e\xbd\x6e\xea\x4d\x45\x63\xd3\xd4\x42\x26\xef\x74\x7e\xee\x2d\x28\xa9\x4a\x14\xba\xd3\x6a\x39\x53\xd2\x93\x2a\x24\xd1\xc7\xb2\x57\x3b\x0e\x24\x31\xb4\x29\xd5\x7d\xc3\x7c\xdd\x58\xba\xd3\x90\xa9\xa9\x35\xf2\xf5\xe5\xfd\x55\x60\x8e\xda\xfc\xb2\x36\xbf\xfc\xd3\xfc\xf2\x22\x68\x95\xda\x51\x5d\x24\x86\xca\xe6\x50\xec\xc2\x1f\x3a\xdf\x0c\x54\x71\xa9\x73\x79\xfc\x84\xe8\xcd\x92\x74\x00\x75\x07\xc0\x29\xa5\xce\xc1\x13\xbd\xe6\x96\x6b\x49\x90\xc5\x72\xf2\x59\xde\x4d\x3a\xbb\x37\x62\x90\xdc\xfb\x71\x40\xa2\xb2\xa8\xb3\xe2\x81\x6f\x0e\x74\xb6\x6a\xf7\x7e\x1c\xd0\x7b\xdb\xbe\x47\xb1\xae\x97\x71\x62\x15\xe9\xf6\x56\xdf\x82\x34\x71\x5d\xda\x9e\x10\x63\x41\x1c\x63\x3d\xc7\x0b\x37\x5c\x91\x5b\xe9\xc7\x72\xba\xf7\xf4\x94\x67\xc5\xd5\xbd\x6d\x3b\x07\x7a\xbf\x94\x29\xc4\xbd\x37\xaf\x49\xd1\x5b\x12\x4e\x2d\x81\x04\x03\xc9\xca\x7c\x74\x76\x20\xf0\xff\xe7\xee\x4f\xdb\x1c\xb7\xb1\x43\x71\xfc\x7d\x3e\x85\x44\x3b\x32\x51\x82\x54\x52\xb5\xc7\x99\x50\xcd\xd2\xdf\xeb\x4c\xcf\xf5\x16\x77\x3b\xf1\x44\xd6\xf4\x05\x49\x50\x62\x95\x8a\x94\x49\xd6\x36\x25\xdd\xcf\xfe\x7f\x70\x0e\x56\x2e\xaa\x6a\x4f\x92\x27\xcf\xef\x45\x77\x89\x20\x00\x62\x39\x38\x38\xfb\xd9\x2f\xc5\xdd\xab\x78\x97\x1d\xe6\xef\xb4\x9d\x8a\xc5\x5b\xc5\xca\xd0\xf6\x7b\x24\xd9\xec\x18\xbc\x1a\x03\x00\xd6\x14\xdb\x74\x43\x6c\xfe\xef\x66\x15\xad\xc9\xd1\xda\xc5\x1b\xb2\x09\x7f\x66\xfe\x7e\x29\xd6\x31\x98\xd1\x84\xee\x08\x85\x37\xbf\x1d\x0e\xbe\x28\x0c\x37\x92\xe0\xdd\x8b\xab\x03\x6e\x07\x55\x22\xff\x5a\x3a\x8e\xe4\x70\x50\x27\x10\x12\x04\xcf\x83\x99\xe3\x79\xf7\x1f\x5d\x4e\x88\x30\x9a\x18\x95\x60\x80\x3c\x6d\x41\x58\x0c\x9c\x30\x42\x12\xf8\x3e\x1a\x8e\x39\x45\x8d\x46\x2a\x60\x16\x5f\x86\x82\xad\x27\x34\x96\x79\x21\x99\x18\x7e\xaa\xc2\x20\x8b\x0a\x0d\xf9\x58\x82\x02\x31\x14\x3c\x80\x4c\x4c\xdc\xc7\x1b\xe9\xad\xe1\xa7\xc4\xb4\x4d\xd6\x66\x98\x29\xc1\xd1\x1e\x0e\xbe\x0e\xae\x45\x21\x69\x3f\x97\xd0\x27\x06\x1c\x5a\x97\xcb\x2f\x6d\x5c\x0f\x97\xc7\xbf\x31\x93\xc9\xc3\xce\xf6\xd1\x71\x90\x55\x30\x67\xb8\xfa\x8f\x84\x66\x36\x1e\x16\x47\x91\xa8\x1c\x02\x0b\xed\x65\x13\x7e\xcb\x0e\x07\x71\x43\xd3\x38\xb4\xf4\x83\x57\xb8\x6f\xe2\xae\x1e\x5f\x69\x5e\x68\x82\xb4\xd1\xb9\x29\x11\x84\x41\x1a\xce\x27\x09\x68\x5f\xb2\xf0\x6a\x5a\x0b\x32\xca\x18\x62\x4b\xb9\x84\x2a\x5f\x6d\xd6\xd3\xf2\x36\xf7\x53\x93\xfb\xc3\x4e\xe5\xcb\xe8\xea\x8a\xa6\x54\xec\xc2\xfc\x32\x1d\x8d\xb2\x65\x1c\xf8\x5b\x27\x5f\x89\xa8\xb2\x26\x18\x14\xe9\x2a\xdc\xea\xac\x21\x18\xfe\x90\xc9\x10\x87\xb6\x93\x67\x44\x68\xb1\xaf\xad\xb2\xe1\x8c\x3e\x49\x67\xa2\xaf\x81\x16\x0e\x9e\x8e\x47\x1a\x13\x1d\x2f\xca\xe4\x7f\x0c\x22\x5d\xf8\x03\x72\x6d\x41\x4c\xf5\xd2\x04\x7a\xf1\xd4\x7a\x04\xb1\x5e\x1a\x8a\x33\x0e\x56\x6b\x6a\x11\x98\x4e\xbc\x6b\xa5\x06\x94\x02\x04\x9f\xd1\x2b\xc1\x1c\x56\x20\x19\xc0\x9f\x53\x67\xa0\x40\x68\xc8\x17\x48\xc6\xeb\x75\xd4\x2b\x0f\x8e\x56\x09\xa1\x09\x46\xea\x09\x6c\xcb\x37\xe3\x3b\x1c\x2d\x1b\x5b\x15\x00\x1e\xe4\x4e\x98\x0c\xf0\xc5\x0d\x87\x33\x6d\xa8\xae\xb7\x31\xc6\x6d\x9c\x5b\x4e\xcd\xed\x6d\xa2\xd1\x9a\x04\x5b\x99\xf4\xd8\x29\xa5\xca\x40\xf5\x3a\xbc\xc2\x80\x75\xf0\xad\xff\x60\xfe\x75\xe7\xbc\x89\x4b\x3a\xfd\x1b\xd3\xb4\xd3\x15\x65\x54\xb5\x31\x24\x94\xc9\xb7\x7e\xc3\xf6\xfe\x35\xfd\x19\x22\xcc\x3a\xb6\xd8\xea\x33\xc0\xb7\x8f\x46\xf6\xa3\x0a\xa5\x0e\x4d\xd2\x07\x88\x3f\x55\xfa\x1a\x7c\x32\xaa\x60\x8d\xe5\xd9\x4d\x70\x45\x31\x3f\x90\xec\x01\x1e\x04\xe1\x71\x65\xb2\x3c\xcb\x57\xea\x59\xa6\x61\x96\xa5\xe2\xb7\x9a\xb4\x4a\xf0\x2b\x13\x46\xcb\x52\xf1\x5b\x9f\x77\x59\x86\x4f\xe4\x98\x4f\x3f\xcf\xb3\x1b\x00\x38\xe3\xd9\xfc\x0b\xa3\x4f\xb0\x51\xad\x50\x12\x6e\x32\x6f\xb2\x94\x19\x7e\x90\x5c\x0d\x98\xa0\x22\xb5\x9c\x74\x61\x5b\xea\xce\x20\xf0\x50\xc3\x83\x35\x0e\x05\xd6\xa3\x3f\x01\x8a\x83\xff\x81\x3b\x81\x5f\x3a\x47\x56\x84\xbe\x54\x9d\xb1\x2d\xa3\xe5\xbf\x31\x5d\x91\x91\xe0\xdf\x18\x82\x2f\x93\xa9\xa7\xaa\x3d\xe7\x49\x2b\x82\x1a\x1e\x1a\x36\x1a\x75\xe4\xa5\xb3\x0f\x3e\x23\xc1\x93\x5a\xd1\x20\x3e\x1c\x86\xf1\x68\x14\xa1\x8a\xc1\x5a\x83\xd1\x88\x99\xe3\xcb\x28\x1e\xab\x00\xab\x46\x32\x20\xf4\x37\xe6\x0c\x8d\x46\x91\x95\x61\x5e\x35\x0c\x01\x50\x8a\x34\x5d\xce\x02\xa5\x74\xd2\xa3\x32\xd5\x96\xe6\x67\x60\x7e\x8a\x9b\x02\xb9\x64\x31\xdd\x6a\x69\xfd\x5e\x99\x5a\xeb\xc0\x2a\x37\x9e\xab\x32\x9e\x4e\xa2\xe8\x79\xf9\x03\xd4\x29\x60\xbe\x29\x9f\x91\xa8\x4f\xa6\xc5\x2e\x09\x13\x0d\x68\xd4\xfc\x74\x73\xa4\xd9\x91\xb1\x45\x1b\x32\x1a\xc1\x5f\x23\xaf\x12\x9d\x41\xd7\xad\xbc\x59\xb2\x9c\x1c\x05\x0e\x72\xe4\xe1\x29\x4b\xf8\xbb\xe2\x99\xd0\x36\xd2\x5b\xeb\x2d\x01\xd2\x57\xab\x80\xe9\x4c\xd1\x41\x82\xc2\x10\x17\x20\x00\x3e\xf7\xb5\x46\x39\x3a\x52\x54\xab\x1c\xa9\x7c\x77\x22\x8e\xa0\x4b\xef\x31\xcc\x1c\x07\x8b\x8b\x99\x08\x1a\x59\xe3\xf0\xb2\xfc\x45\x4a\xb7\x5d\x28\xa3\x29\x59\xf8\x5c\x85\x58\x80\x0a\x5e\x9a\xe5\x59\xb5\x05\x2d\x52\x04\xe1\xc8\x20\x3b\xba\x36\x09\x99\xe2\xfb\x70\x43\xf9\xe1\x90\x9a\x2d\x9b\x5b\x89\x15\x37\x52\xf4\x88\x2b\x2b\x2b\xd1\x0d\x69\xe2\x75\xe7\x54\xb4\x3d\xa5\x30\x5a\xda\x42\x91\x29\x98\x9c\x2e\xf2\x63\x3d\x9a\xce\xcc\x5f\x18\x2e\xc7\xce\xfe\x15\x8d\x46\x90\x99\x7d\x2e\xa5\x98\xad\x7c\x65\xdd\x61\x74\x70\x10\x60\xe8\x22\xe5\x6d\xa3\x91\x9b\x27\x0c\x56\x1e\x70\x6c\x05\x31\x23\xb4\x74\x18\x2f\xa4\x0d\xb8\xd0\x8a\xff\x61\xe8\xa3\x51\xe2\x6f\xd0\x5d\x4f\xa5\xab\x80\x70\x11\x9b\x76\xc5\x1f\xb5\xf1\x8a\x69\x84\x57\x9a\xf1\xc5\x9f\x4c\x16\x24\x15\x4d\x04\x4a\x97\xc1\xa5\xd1\x1e\x06\x46\x0a\xaf\x60\xac\xc3\x30\x14\xd4\x1c\x14\x08\xe8\xc2\x4d\x8d\xc1\xc2\x6f\x4e\x53\x95\xec\x83\xd3\x39\x21\x0b\x3f\x12\xd8\x86\xf4\x64\x94\xc3\xbd\xef\x14\xbc\x0d\x55\x20\x6f\x95\x6a\xed\xd4\xaa\xd2\xd8\x5e\x2d\x41\x99\xad\xd4\xca\x7a\x6b\xca\xad\x47\x5c\xe8\xb5\xbb\xd2\xc9\x32\x31\x77\x3f\x50\xae\x0a\x2a\x21\x57\xba\x93\x51\x4f\x6c\x2f\x46\xe4\x85\x95\xc5\xbf\x96\x77\xd1\x50\x00\x88\x59\xd6\x08\x97\x35\xc2\x65\x95\x0e\xae\x62\x35\xa3\xb5\x86\x75\x06\x66\x31\x91\xbd\x9a\xa2\x17\xbd\x92\x11\xac\x24\xca\x76\x66\x8b\xcd\x65\x04\x6e\x8d\xc9\x2a\x5a\x8f\x46\xe2\x7f\x39\x58\xe7\xc1\xc2\x4e\x0a\xe0\xd5\xa4\x74\x1e\x4c\x54\x21\x4b\x76\x93\x22\x43\x49\x91\xd5\x6c\x2a\x92\x55\x72\xc1\x34\x5f\x45\xeb\x85\xfc\x6b\x5f\x47\x8e\x9a\x08\x05\xe2\x87\x43\x97\xee\x2a\xee\x36\x36\xc7\x03\xae\x10\xd9\x3b\xf0\xed\x9b\x11\x8a\x1d\xdb\x0e\x3c\xd5\x2e\x4b\xf8\x57\xc5\x7d\x1e\xbc\x63\x92\x09\x26\x14\x0a\x7f\xde\x43\x11\x8c\x5f\x16\xbd\x43\xad\x9a\x28\x96\xd3\x24\x54\x60\xde\x37\xb9\xb1\xbe\xc1\x3e\x8e\x50\xfe\xc3\x6d\x6d\xbd\x80\x9e\xf0\x85\xec\xc8\xbc\x93\xdd\x1d\x9f\xf7\xf3\x69\xa3\x75\x35\xcb\x48\x21\x69\x98\x1e\x42\x63\xb8\x5a\x2b\x52\x2b\xbe\x6e\xa2\x5d\xf0\x72\xa5\xb1\x06\x5d\x80\x09\x23\x26\x5c\x44\xaf\x63\x0d\x77\xe3\x31\x61\x61\x0c\x31\x8d\x7d\xe9\x16\x81\x07\x37\xd6\x60\x35\x99\xd0\x39\x59\xc4\x5a\x68\x24\xe5\xd3\x90\x2f\xc2\x08\x22\x2d\xca\xaf\x11\x99\x10\x07\xa1\xe8\x14\xf1\x1d\xa5\x6c\x60\x65\xed\x93\xc0\xd4\x10\x5d\xca\x8e\x20\x38\xe4\x1d\xdb\x85\xf3\x57\xd4\xd4\xb6\x67\xfa\x9d\xc0\x2e\xdf\xb1\xb0\xe2\xf5\x1b\x59\xd9\xd7\x4b\xe2\x76\x42\x54\xaf\x62\xd4\x76\x1f\x60\xc4\xa2\x5b\x7f\xc7\x08\xfd\x0e\xbd\x74\x55\x7d\xa0\x1d\xc2\xa7\x6a\x57\xdc\x07\x9f\xcd\x66\x34\x65\x55\x1d\x5c\xcc\x66\x26\x58\xd4\xa7\xb3\x99\xbc\xb3\x13\xbe\x63\x8f\x7d\xf9\x12\x45\x77\x0e\x9d\xc2\xd6\x87\x03\x03\x37\x60\x99\xb3\x95\x5a\x97\x44\x44\xbb\xb8\x1e\x4b\x16\x1c\x51\x06\x79\x0b\xbb\xe6\xa3\xea\x48\x3f\xf7\x96\x5a\xb2\x2f\x0f\x22\x8d\xda\xaf\x30\x9a\x17\x18\xfa\x45\xa7\xcd\xd5\x51\x47\xe7\x11\xb2\x90\xc1\x9a\x4c\x74\x66\x95\xf1\xf0\x87\x3c\xf4\x30\xaf\xc0\x1d\xdb\xdd\x72\x7a\x2d\x28\x73\x0c\xed\xc3\x93\x30\x96\x21\xef\x20\xc9\xa1\xca\x49\x20\x30\x2b\x54\xfb\x4a\x17\xc4\xfa\x25\x3d\x31\x17\xf9\x8d\xd0\xab\x3d\x15\x87\x53\x26\x74\xa4\xd7\x53\xf8\xf1\xef\xea\x7d\xa8\x47\xa4\xd2\x1f\xfe\x95\xd1\xff\x64\xf4\x63\x48\x29\xf2\xb0\x2f\x21\x85\x35\x46\x9a\x5b\x38\xd4\x99\x28\x3f\x69\xae\x90\x43\xd3\x7e\x9b\x00\x13\xc9\xea\x03\xb2\xa5\x9a\x46\x7d\x29\x8b\x5b\xe3\x72\xa5\x24\x46\xba\xbb\x30\xc6\x73\x29\xda\xcc\xa5\xa3\xd1\x05\x28\x51\xdc\x1c\x81\x6e\x4c\xaf\x30\x0c\x7f\x5e\xe6\xc0\x87\x2a\x13\x08\x08\xde\x90\x62\x8a\x18\x1d\x26\xe8\x70\xf0\xa3\x30\x6a\x84\x93\x11\xdc\x3b\x2c\xa8\x36\xa7\xf2\x9d\xd4\x78\xe2\x32\x50\xde\x54\xcb\xff\x64\xc1\x5f\x99\x6d\x43\x15\x2f\x13\x63\x6e\x95\x28\x8d\x69\xe8\xf3\x30\xd1\xb9\x96\xc9\x92\x63\xe2\xff\x34\xcb\x13\xf8\x16\x6a\xa9\xa4\xd1\x2e\x3a\x3a\x81\x95\x94\x6c\x8d\x9d\x56\xba\x53\xdb\x2c\x2f\xd1\x66\x68\xb2\x63\xe6\xa6\xfa\x8c\x68\x3c\xf6\xc4\x01\x91\x16\x3c\xce\xfe\xc0\x68\x7a\xb6\xd9\x91\xd9\x85\x33\x9a\x86\x91\x20\x7d\x55\xf6\x39\x99\xe1\xbb\x91\x55\x5f\x26\x38\x0f\xd3\x15\x1f\x8f\xd7\x44\xac\xa5\xd8\x85\x6f\xb2\x07\xe0\x25\x63\xda\xb3\x94\x10\xd7\x05\x64\x77\xc3\x39\x66\x26\x71\x43\xa8\xc5\x82\x05\x50\x9b\x12\x60\x74\xf0\x86\x1a\x55\xda\x9b\x0e\xed\xe3\x33\x1a\xc9\x43\x85\x79\x9e\xdd\x08\x76\xf2\x28\x9a\x98\x60\x70\xc6\x4c\xc4\xfc\xae\xa4\xab\x32\x51\xb6\x3a\xbd\xe8\xb7\x01\xd6\x59\xff\xc9\x9a\x7a\x5d\x27\xb9\x3d\x46\xb3\x5c\x36\x56\x3f\x26\x41\xe3\x3b\x31\x05\xef\x48\x1d\xdd\xb8\xbd\x5e\x98\x1b\x57\xee\xc3\xf9\xaf\xf7\xe3\xf3\x0d\xe9\x24\x73\x3e\x66\xd2\x18\x50\x83\xd9\x02\x8a\x5c\xf6\xdb\x18\x24\x6a\x26\x58\x50\xc5\xd8\x9a\x62\x03\xae\x69\xfd\x58\xb6\x59\x36\x4e\x0d\xa6\x09\x93\xdd\x13\xca\x95\x3b\xee\x7b\xa9\x14\xc6\x88\x94\x88\x40\x0f\x2a\xe0\xd8\x01\x73\x5f\x40\x4e\x5d\x07\x6f\x09\x90\x79\x06\x6f\xa1\x76\xfe\x34\xde\xfa\xd1\xed\xe6\x34\xde\xb2\x1c\x0b\x57\x06\x68\xe1\x16\x5c\x37\xf1\x97\x7c\x1b\x3c\x79\x69\x51\x7a\x81\xb7\xad\x6f\x76\xdf\x14\xa5\x47\xbd\x78\xc7\xaa\xca\x0b\xf0\xaf\x00\x34\x0f\x73\xb4\x9c\x0a\x92\xd6\x8d\xef\x36\x88\xef\x36\x88\xef\x36\x0a\xdf\xa5\xe1\x1c\xdd\x41\x87\x0e\x26\x03\x45\x43\x64\x9d\x36\xb1\xf3\x11\xe4\x93\x36\x56\x37\x60\xff\x6f\x8c\xc4\xb8\xc1\x29\xdc\xc6\x29\x49\xc8\x1d\x9c\x92\x04\xb0\xad\x71\xc0\x0d\x66\xe3\x06\xb3\x89\xea\x06\xb3\x61\x65\x9c\xb4\x3a\xac\x2c\x42\x5b\xea\x96\xfd\x89\x3e\x6a\x5b\x56\xd9\x47\x8d\x45\x60\x9e\xeb\x91\xc3\xe1\xbd\x32\xd2\x33\x2a\x9a\xc3\x81\x4d\xb7\x25\x4f\x97\x6c\xaa\xbb\x9e\xcc\x8f\x32\x84\xb5\x75\x65\x03\xe6\xb6\x2c\x30\xd4\x4d\xde\x6d\x07\x03\x59\x57\x94\xbd\x85\xa5\x45\x8e\x1c\x33\x0c\xfb\x49\xf7\x08\x63\x00\xf4\xed\xb2\x24\x72\x74\x1e\xf5\x4a\xce\x92\x1f\xf2\xdd\xa3\x47\xbd\x1b\xf6\xf0\x2d\x40\xaa\x00\x17\xbe\xdb\x49\xa7\x25\xf9\xf4\xa3\xd4\x65\x53\xaf\x2c\xee\xdf\xee\x59\x2e\xca\x8b\x9d\xfc\x75\x5b\xf1\xef\xd8\xde\xa3\x1e\x78\x74\x7f\x81\x0e\x16\x54\x39\x58\x7c\x9d\x64\x18\x95\x73\x4d\x9d\x0b\x59\xc1\x04\x46\x74\x72\xe2\x9a\x00\x1f\xa7\xc2\xca\x46\xe1\xf9\xea\xd7\xfa\xd7\xf2\xd7\xfc\xd7\x74\x7d\xbe\x69\xd0\x12\x49\xf2\xa5\x00\xea\x2e\xe3\x2e\x13\x5a\xb2\x6d\xaa\xc9\x46\x23\x46\xb3\x70\x46\xaf\x9a\x01\x5d\x1a\x62\xbb\x13\x01\x31\x04\x43\x22\xad\xfc\xd4\x30\x7c\x66\x31\xab\xd2\x60\x4e\x1f\x3a\x22\x03\x60\x6c\x09\x72\x9b\x20\xd4\x70\x33\xa7\xae\x17\x57\x97\xd9\x22\x43\x01\xb4\x8c\xc5\x9b\xad\x69\x12\x62\xb8\x0b\x4b\xbd\x1c\x9b\x7e\x97\xbe\x37\xf0\xc6\x56\xc1\xd8\x1b\x78\xc4\x84\x85\x89\x28\x98\xa3\x8a\xff\xc8\x53\x6a\x45\x52\x8b\x30\xd5\x5f\x62\xcc\xce\x07\xde\x18\x5b\xbf\x9e\x41\x2a\xf5\x10\x9f\x16\x1b\xc1\x15\x95\xd9\x8d\x9f\x10\x6a\x7d\x08\x31\x81\x3d\x96\x70\xe3\x06\x87\x90\x38\xef\x25\x7b\x04\x56\xd0\x0d\xb4\x79\x38\xfc\x77\xef\x9c\x35\xbe\xff\xd5\x9b\xd7\xb3\x77\xf8\xd8\xb5\x83\x97\xe1\x8c\x24\x61\xa2\x7b\xd2\x6f\xa8\xdc\x51\x08\xd9\x86\x7b\x1a\x78\xde\x07\x6e\x2b\x72\xe8\xcd\x6d\xb5\x0c\x9e\xe4\x66\x2d\xfa\xec\x6c\x23\x3b\x30\x4d\x18\x2f\x23\x94\x48\x9a\x83\x44\xec\x70\x3b\x6e\x99\x24\x41\x5c\x15\x83\x6d\xac\xad\x36\xd7\x1a\xa5\xb3\xb9\x71\x63\x73\x05\x01\x15\x91\x63\xe0\xea\x31\xed\xf1\x29\x80\x45\x0d\x85\xec\x1f\x98\x02\x07\x00\x64\xca\xa3\x30\x5d\x25\x62\x77\xb8\xb8\x42\xf0\xf3\x11\x59\x72\x67\x32\x11\x09\xb8\x99\x6e\x84\xea\x5a\x3f\x16\x4c\x82\x23\xdb\xc1\xe0\xa7\xee\x78\x47\xa3\x6f\xb5\x1d\x26\xf5\xde\xbf\xd7\x2f\xde\xbf\xf7\x9a\x70\xdb\x78\x0e\xdd\xc7\xc3\x81\x21\x09\xe8\x79\x81\x2d\x57\x76\xfb\x24\x00\xeb\x10\x40\x4b\xce\xa7\x3b\x96\x62\x28\x80\x8c\x01\x90\xb5\x83\x65\x29\xe5\x9e\x15\xb2\x6e\x15\xaf\x1d\x93\x99\x81\x37\x56\xc5\xa7\xcf\x83\x86\xf7\x08\xe0\x5c\x65\xb2\x5f\xe8\xe4\xf5\xf2\xf2\x88\xa2\xf0\xfc\xd7\xb2\x79\x69\xdc\xb1\x5d\x1f\x2e\xd2\xe1\xcf\xc1\x41\xaa\x19\xba\x4a\x87\xaf\x6f\x00\x5f\x97\xf0\x54\x82\x0c\x5f\xb4\x62\xf1\x81\xad\x40\xb2\x74\x01\x52\x81\xec\x1d\xdb\xf9\x84\x04\x4c\xb3\x5d\x3c\x04\x97\xb7\x86\xae\x87\x2f\xf9\x58\xbc\x30\x36\x08\xd2\xab\x12\x55\x91\xbc\x2b\x58\xa3\x8a\xa3\xe6\x79\x01\x1b\x7b\xde\x91\x10\x2a\x68\xb3\x3b\xb6\xb3\xcc\x9f\x65\x6e\xce\x66\x71\x77\x8c\xdf\x35\x18\xd1\x4a\x6a\x2d\xb2\xa8\xb5\xc8\x80\x27\xa7\x1e\x70\x25\xe0\xc7\x04\x7d\x21\x93\xa2\x31\xaa\x09\x94\x66\x0f\x86\x77\x8c\x84\x9f\x1c\xc6\xa6\x3d\x0c\x3f\x0e\x23\x00\x6a\x33\x08\xb2\x8c\x03\x3f\x0e\xb9\x14\xa6\x74\xfa\x70\x28\x60\x8b\x22\xea\x79\x2a\x4f\x44\x2c\xd6\x2d\x56\x3e\x2f\x16\x28\x69\x7f\x43\x50\xb7\xf5\x50\x71\x2e\x57\xad\x06\x63\x1b\x39\x0f\xc1\x08\x59\xa2\x64\x19\x4c\x93\x41\x0a\x6b\xa4\xe7\xda\x3d\x3b\x21\x4c\x43\x15\x66\x1d\x53\xb7\xb8\x34\x60\x1a\x4a\x91\xd4\x44\xda\x17\x31\x19\x49\x6a\x76\xc9\xe9\x26\x4c\x97\x62\x00\x01\x64\xd2\x4c\x97\x7c\x3c\x0f\x94\xc4\x5e\x5c\xb4\x97\x7c\xb9\x0d\xd2\x25\x0f\x66\x8b\xad\x7d\xbf\x25\xe2\x72\x1b\xfa\x43\x23\x81\x1a\x8d\x32\x34\x99\xf2\x1d\xe1\xd3\xd2\xc8\x9e\xb4\xe8\xa0\x11\xec\x5c\xbd\xf7\x08\x39\x1c\x9c\x88\x85\xea\x8d\xc3\x26\xdb\x35\xa8\xa7\x42\xcd\x7b\x2a\xc4\x6a\x28\x4e\x1f\x9e\x24\x9a\x1a\x6f\x76\x99\xc0\x32\xd2\x57\xd9\xa6\xe5\x2c\xe5\x08\x17\xcc\x8a\x62\x14\x31\x95\x35\x3a\xb2\xc3\x48\x49\x94\xbf\x99\x4c\x48\x12\xf2\xd5\x66\x4d\xfd\xc4\xd0\xf4\x26\x2c\x6e\x22\x01\x2e\x05\x84\x05\x9a\xaf\xe1\x8c\xd8\xb1\x93\x1a\x9b\x16\x4e\xe6\x84\xa6\xc7\xa3\x43\xbb\x4b\x59\x9c\x11\x12\x36\x68\x6a\xe7\xd0\xae\xdb\xac\xbf\x15\x1d\xd1\x78\xfb\x2c\x75\x0c\x6e\x6b\xbc\x60\xf1\x85\x4b\x08\x38\x56\x27\x7b\x30\x62\x49\x60\x64\xdc\x2f\x42\xc8\x9b\x5e\xd4\xd3\x8a\x71\x2f\x8f\xc1\xd2\x2b\x72\x2f\x50\xd2\x44\x62\x26\xec\x45\xbb\xdb\x72\x00\x09\x7d\x06\x32\xcb\xcf\x40\xa5\xf7\x19\xec\x0a\x96\x0c\x4a\x5e\x65\x7f\xe7\x03\x34\x63\x1f\x60\x1a\xb9\x01\xa4\xa7\x1b\x24\xd1\x0e\x7f\x40\x9a\xa5\xa4\xb8\xcf\xf1\xd7\xed\x1e\xff\x8a\xab\x78\xa0\x33\x33\x0d\x54\x32\xa6\x81\x49\xdc\x34\x30\xc9\x9a\x06\xf1\x96\xe5\x1b\x3e\x90\x79\x1c\xaa\xdb\xe8\x26\xab\x07\xd7\xfc\x11\xfa\xbd\xe6\x8f\xfb\x92\x57\x95\xf8\x71\xbb\x1f\xf0\xb2\x2c\xca\x41\x8c\x69\x5b\x6f\x78\x7e\xeb\x66\xdf\x6c\xab\x34\x5c\xe9\x87\x11\xcf\xb4\xa4\x08\x33\xa4\x92\x40\xc0\x2d\xe3\x2b\xc6\x2a\xd9\x99\x4c\xcf\x11\x91\x56\x5c\xdf\xad\x98\xe0\x89\x14\x22\x66\xc6\x62\xd3\xcd\xa4\xfd\x08\xd2\x83\x43\xda\xa8\x3e\x09\x52\xad\xb3\xc4\x60\x7a\x3b\x1a\x93\x23\xbd\xcd\x5b\x4d\x1a\x0d\xd2\x54\xb7\x20\x26\x97\xe1\xf3\xc9\x68\xb4\x56\x87\xde\xe6\x3d\xad\x74\x9b\x79\x07\x43\xb1\xb4\x3e\xef\x9d\x9d\x79\x72\xf1\x44\x41\x44\x05\x49\x7f\x76\xe6\x89\x29\x48\xfa\x21\x8e\x94\xfa\x87\x26\x82\x94\x58\x9e\x2f\xf2\x29\x78\x94\xfe\xe5\xed\x0f\xdf\x77\x41\xba\x28\xc7\x1a\xbe\xb8\x64\x41\x85\x02\x8f\xbf\x7c\xf7\x6d\x5b\x6b\x4e\x63\x88\x2c\xcf\x0c\x9f\x63\xb4\xe3\xc4\x3a\x3b\x10\xcb\x33\x06\x6f\x85\xaf\x7e\xf8\xee\x47\xd1\x5f\x49\xa3\x30\xc6\xae\xbf\x29\x8b\x9b\xb7\xd0\x1c\x52\x60\xf0\x87\xfa\xfc\xe1\x66\xe7\x11\x19\xa8\x33\x21\x4f\x2a\x23\x8d\xca\x62\x33\x8c\x0e\x87\xa8\x27\xe3\x05\x74\x59\x02\x0c\x6b\x9b\x67\x50\x31\x43\x91\xef\xbd\xc9\xef\xd8\x2e\x4b\x06\xbf\x7c\xf7\x6d\x20\xc8\x3d\x42\x23\x74\xb8\xe0\x51\x78\xfe\xd1\xf4\xec\xe3\x73\x9a\x46\xe1\xb9\xbf\x5a\x8e\xd6\xe4\x7d\xb8\xfa\xdb\x68\x7d\x76\x4e\x37\x51\x78\xfe\x37\x7f\x7a\xb6\x24\xc1\x6a\xf0\x6b\xbd\x3e\xf3\x57\x7f\x13\x3c\xfd\xfa\x8c\x7c\x7c\xbe\xb9\xa1\xdb\x08\x45\x72\x2c\x2a\x6e\xeb\x03\xdb\xef\xc5\xbf\x49\x55\x17\x25\xdb\xf0\xc3\x74\x3c\x01\x60\xae\xb2\x22\x3f\xa4\xd9\x8e\x1f\x4a\x5e\x1d\xee\xb3\x64\xc3\x6b\x12\x7c\x7c\x4e\x33\xd9\xfc\x4f\x5f\xbf\x3b\xfc\xf9\xeb\xcf\xbf\x22\x1f\x9f\xd3\x2b\x51\xf6\xeb\xf9\xaf\xe7\xe7\xf4\x1a\x5e\xaf\x7e\xbd\x9f\x8e\x27\xeb\x71\x40\xfc\x65\x20\x5e\x40\x72\x98\x5f\xcf\x97\x1f\xad\xcf\xfe\x7f\x07\xe2\xe3\xef\x60\x7d\x26\xde\x07\xfe\xaf\xc9\x98\x1c\xc8\x81\x9c\xd3\x5d\x14\x3e\x1d\xe9\x0d\xfc\x9f\x47\xa1\x77\x76\xee\x29\x1b\x7c\xef\xcc\x23\xb4\x88\x42\x36\xdd\x15\x31\x18\xd0\x80\x14\x88\xee\xa3\xf0\x3a\x42\x5b\xe4\xa2\x21\x95\x44\x56\x41\x5b\x64\xfe\x16\x59\xe0\xe3\x2a\xb0\x7a\x32\x63\x45\x54\x8c\xc1\x43\x10\xd5\x62\x70\xf7\x23\x2e\x5f\xd2\xe4\x8e\x63\xa2\x38\x47\x25\x13\xf7\xc6\x60\x27\xbb\x9a\xad\x97\xbe\x60\x1b\x55\xe8\x18\x48\x1b\x41\x51\x08\xce\xc0\xc2\x79\xb5\x26\xda\x96\x2a\x26\x24\x68\xbe\x83\x4b\x36\xb6\x6d\x6d\xcb\xa8\x69\x17\x03\xfe\xb3\x82\xf3\xb8\x89\xec\x48\x5d\x5b\x7c\x9f\x69\x7f\xd8\xd5\x76\x8d\x56\x02\x70\x1d\x30\xf0\x3a\x5f\x39\xea\x73\xd9\xe4\x2a\xdc\x4a\x7b\x9a\x3e\x6b\x93\xab\xc3\x21\x3d\x1c\xf8\xea\x6a\xbd\x4c\x97\x43\x3f\x0b\xaf\x94\xf3\x70\xe0\x47\x90\x3e\x5d\xd0\xe6\x95\x9e\xda\x15\xa1\x1b\xf1\xdf\x70\x2e\x2e\xa4\x4c\xd3\x0c\x76\xe5\xd5\x6c\x4d\x0e\x87\x21\x07\xbb\xb6\xd1\x68\x03\xd0\x60\xe6\x5d\x45\x4d\x92\x22\x9f\xb2\x2b\xf6\xf0\x96\xd7\x75\x96\x6f\xaa\x69\xba\x63\xb5\xb4\xf7\x84\x74\xf8\xda\xae\x37\x22\x86\x94\x5e\xc5\xeb\xd1\xc8\xf7\xf9\x2a\x5e\x2f\x59\x90\x1c\x0e\x7e\x12\x3e\x1d\x09\x59\xc5\x6b\x78\x69\x22\xef\x58\xa1\xba\x86\x33\xca\x68\xe2\x24\x77\xa9\xa3\xde\x54\x18\x28\xa8\xab\x20\x94\x83\x9e\x9c\xa4\x6c\xbc\x33\x01\x18\x99\x98\x6a\x36\xc5\xa5\xb1\x14\x4b\x89\x8c\x3a\x79\x93\xdd\xc8\x30\xd6\x80\x56\x7e\xe2\xd5\xbe\xc8\x2b\xfe\x67\xce\x12\x5e\xfa\x9e\x0c\x24\x3a\x79\x87\x49\x6d\x04\x3c\x26\x44\xdb\xd4\x6c\x05\x41\xb9\x05\xb3\x1a\xf1\x3f\x8a\x56\x13\x41\xcc\xe9\xdd\xe0\x64\x11\x95\x9c\x5d\x1f\xb3\xd4\x17\x63\xc9\xf2\x41\x4c\x52\x18\x16\x5a\xc2\xeb\xce\x62\xd4\xb7\x64\x32\x63\x4f\x5c\xe4\x77\xbc\xac\x79\x59\xad\x80\x95\x1c\x8b\x17\x6b\xf2\x94\x86\x5c\xf6\xb8\x81\x28\x85\x9c\x1c\xd1\xff\x49\x6d\x74\xba\xf4\xd3\x21\x4e\x7c\x34\x32\x03\x49\x09\x8d\x57\xa9\x71\x3c\xd7\xcb\x7b\xdb\x04\x72\x2b\x86\xef\xd3\x91\x5e\xdb\x4b\x2b\x4f\x17\xac\xc3\xf5\x6a\xbe\x26\x26\xb9\x9d\x35\x60\x72\xb5\xda\x34\x05\xa3\xce\x84\x36\xeb\x45\x1a\x5e\xab\x4d\x91\xfb\x95\x8a\xc5\x84\x6c\xb7\xb0\x03\xdf\x64\x7c\x97\x54\xab\x54\xc0\x50\xbc\xea\x28\x5f\x87\x11\x81\xfc\x1e\x90\x4d\x55\x0c\xf1\x1b\x30\x8c\x03\xc9\xbd\x5d\x00\xc9\x11\xd5\x14\x08\x98\x7d\x53\xeb\xf3\x90\x8c\x03\x60\x25\x15\x1b\xa3\xf3\x6c\x78\x67\x9e\x58\x46\xe4\x0d\x52\xd8\x9c\x4d\x78\xb5\xca\x60\x33\xd2\xf5\xe1\x70\xb5\xf2\xce\xe0\x27\x1d\x6e\x88\x95\x96\x47\xc0\x44\xc8\x6d\xca\x69\xbb\x9a\xaf\x65\x14\x0f\xd3\xc5\x56\xec\xa7\xee\x05\x9e\x08\x79\xda\x80\x61\xe2\x72\x03\x29\x7d\x02\xf1\x1f\xe4\xe9\x87\xe4\x41\xa2\x0e\xbd\xd6\x3b\x2a\x7a\x25\x16\x78\x6d\xa0\x26\x41\xef\x38\xb6\xf2\xea\x6d\x59\xdc\x57\xde\x9a\x44\xe1\xc6\x8f\xa4\x3d\x98\xb8\x8f\xf1\x59\x5e\xb2\x3b\x1d\x2a\xb2\xaa\x05\x4d\xe2\x5c\xa3\x14\xfe\x04\x9b\xe5\x2e\xf0\xbe\x2f\x06\xb8\x85\xe2\x2e\x1b\xa4\x65\x71\x23\x80\x72\xec\x0d\xea\x42\xac\xc2\xf1\x78\x74\xfb\xa9\x6e\xc1\x85\xc3\xa3\x62\xe9\x83\xe8\x68\x05\xbb\x61\x71\x9d\xdd\xf1\x60\x46\x77\xac\xaa\xbf\x2b\x92\x2c\xcd\x78\x12\x3c\x1d\x29\xaf\xd9\x46\xfc\xb5\x91\x4d\xf0\x74\x5b\xee\x82\x22\xa2\xa0\xa5\xf4\xfe\xf4\xf5\x3b\x8f\x66\xd5\xb7\x45\xcc\x76\xc1\x36\x92\xf1\x9f\x23\xb1\x16\x14\xe3\x00\x07\xc3\x19\xdd\x97\x85\xf8\x38\x64\xcf\x10\x28\xa5\x7a\xcc\x63\xf1\x43\x62\x0c\xcc\x3d\xcd\xf6\xfb\x5d\x86\x77\xdf\xf9\xc3\xe4\xfe\xfe\x7e\x92\x16\xe5\xcd\xe4\xb6\xdc\xf1\x3c\x2e\x12\x9e\x2c\x20\xcb\x7e\xc5\xeb\xf0\xe7\x77\xdf\x4c\xfe\xe8\x51\x4c\x9d\x51\x05\xe0\x30\x99\x47\x14\x12\x4f\x20\xe1\xb2\xdf\xb1\x2c\xf7\x30\x50\x3b\x96\x88\x9f\x1e\x7d\x10\xcf\xce\x97\x6e\x76\x74\xa0\x69\x1d\x7a\x55\x41\x8c\x36\xab\x82\x28\x91\x35\xae\xd8\x1d\x93\x91\x9a\x8f\x6a\xec\x55\xf0\x24\xfa\x14\xad\xcf\xf1\x73\xf0\xa5\x73\xec\x09\x5a\x9f\x1f\xa9\x7b\x5c\xb0\x89\xa7\x0a\x7f\xf9\xee\x5b\x4f\x8e\x5d\x15\xbd\xe3\x0f\xb5\x1a\x8c\x2a\x13\x24\x21\x7e\x57\x1e\x5c\x31\x6f\x18\x98\x17\x20\xd9\x86\x44\xdb\x00\x66\x2a\x96\x17\x1f\x45\x2f\x5e\x60\xd1\x9b\xb2\x5c\xcc\x37\x30\x54\xe5\x91\x5a\xd7\x08\xee\xb2\xda\xa1\x87\x3a\x18\x0a\x96\x4d\xc2\xc1\x6d\x8f\xfa\x32\x5a\x56\x91\x0f\x97\x95\x7b\x3f\x09\xd6\x2f\xa8\x22\xdf\x2d\xa5\x82\x25\x10\x05\x3f\x6a\x6b\xeb\xdf\x22\x7f\x17\x11\x28\x7c\x57\xb2\xbc\xda\x17\x65\x2d\x0a\x6f\x64\x61\xe3\xb3\x6d\x7b\x6a\x44\x36\xb6\x15\x68\x18\xc1\x8d\xd8\x91\x23\x8c\x5e\x9b\x6b\xf4\x76\x2f\x3d\x2f\x76\xe1\xf5\x54\x4e\xf9\x70\xb8\xa6\x37\xe6\x71\x34\xf2\x77\x56\x96\x85\xdd\xf4\x0a\x72\x63\x91\x65\xee\xef\x74\x2e\x6e\x5a\x38\x4e\x2f\x74\x1f\xe6\xd3\x2f\xd9\x6e\x17\xb1\xf8\xba\xf2\xbd\x22\x8f\xf9\xe0\x86\xdf\x14\xe5\xa3\x47\xe8\x6f\x02\xe9\xd5\xac\xbe\xad\xbe\x2c\x12\x0e\xb1\xf4\x4b\x81\xe2\x2b\xf1\x5f\x1d\xce\xe8\x6d\xe8\xc5\x2c\x8f\xf9\x8e\x27\x1e\xbd\x0b\x9f\x4a\xce\x92\xc7\xb7\x70\x9c\x67\xb4\x75\x3b\x76\xf8\xbe\x67\xa9\x7f\x11\x86\x61\x8d\x57\x19\xb8\xe6\x3e\x1d\xb5\x70\x79\x13\x69\x1f\xb7\x74\x15\xb5\xb3\x02\x84\xd1\xea\x62\x7d\x8c\xc2\x74\xc5\x1a\x6f\x8e\x0e\x3b\x1e\xa1\xd8\x27\x3a\x8a\x31\x7d\xbe\xdb\xb9\xc3\xaa\x3a\x22\x6a\xc0\xa0\x96\x3c\x40\x5b\xaf\x4a\xcc\xe4\xb7\x5b\x5e\xd5\xad\x89\xd8\x39\xcd\xdc\xac\x60\x8a\x9f\x3b\x1c\x7c\x16\x56\x82\x86\xc1\x84\x04\x8c\x96\x2b\x06\x77\x11\x2a\x18\x04\xcb\x5a\x66\x09\xff\x4e\x12\x16\x9d\x1a\x73\x90\x34\x29\xd2\x23\x64\xaa\xad\xd9\x9c\xee\xb5\x05\xe7\xae\x8b\xcb\x9a\x68\xdf\x34\x46\x7e\x13\x4c\xf8\xea\x37\x30\xe9\x5b\x45\x6b\xa4\x2a\x06\x77\xca\x1d\x82\xad\xee\xe4\x9e\x37\xd2\x82\x52\x16\x09\x60\xef\xd0\xdb\x1e\x0e\xb7\x5a\xbc\x33\x1a\xc5\x53\xa8\xe8\x47\x84\x3e\xf8\x33\x95\x82\xfc\x78\x14\xe3\x29\xb4\x77\xd1\x1d\x31\x76\xf2\xfb\x29\x4b\x12\x7a\x37\x95\x17\x40\x78\x87\x2e\x1c\x77\xc8\x87\x85\x77\xe0\xaf\x21\xee\xb1\x72\x17\xfa\x3e\x3b\x1c\xe0\xe7\xe1\x50\x44\x44\x30\x9e\x5a\x8a\xc9\x41\x8a\xa9\x1f\xaf\x22\x0a\x68\x7e\xec\x9d\x9f\x43\x48\x2a\xcc\x11\x38\xbd\xe1\xf5\xb6\x48\x04\xfd\x86\xa2\xc1\x6b\x5d\x82\x55\xe8\xb5\xa1\x5f\x94\x36\xd0\x14\x61\x76\xb9\x7e\x16\xc4\xf3\xd6\x52\xa2\x7d\x3d\x8d\xcb\xa2\xaa\xbe\x2a\x6e\x58\x96\x43\x9c\x7d\xc5\x27\xc1\xf8\x1b\xac\x12\x75\xaa\x87\x43\x7f\xb8\x3d\x1c\x24\x1d\x00\xd3\x10\x24\xe3\x85\x7c\xba\x10\x44\xce\x16\x1d\xe7\xbd\x6d\x5d\xef\x03\x41\x8e\x88\xda\x4b\xef\x8f\x33\x2f\xf0\x3e\xfd\xf4\x95\x47\x08\x78\x7d\x46\xcd\x6a\xd0\x9b\x53\x0f\xbe\x2e\x26\x38\x1a\x5d\x4f\xad\x9b\xd0\x68\xae\x34\x73\xa1\xea\xc9\x15\x09\x01\x3b\x33\xb5\x40\x62\x91\x4b\x96\x40\x1c\x51\xb6\x23\x84\x96\x02\x5f\xd2\x6b\x1a\xd1\x3b\x42\xf1\xa4\xab\x68\x39\x8b\x4c\x25\xa2\x12\x5f\xc5\x9b\x98\x66\x18\x23\x2a\x9f\xe2\x8d\x3f\x1e\x03\xa5\xef\x64\x65\xf5\x00\x23\xd6\xac\xac\xcd\xa6\xe2\x1f\x37\x2a\x28\xbd\x06\x9d\x94\x4c\xc4\x32\xcc\xe4\xcd\x8f\x55\x09\x4d\x42\xd8\x05\xa7\x16\x9c\x32\x39\x3f\xf9\x7e\x1c\xfa\x49\xa4\xe8\xf4\xa5\x37\xf2\x02\x6f\xe9\x91\xb1\x9c\xae\x34\x5a\xc1\x27\xd8\x42\x16\x6f\xb9\x4a\x85\x8b\x10\x9b\x9a\xe6\x46\x47\x99\x46\xd4\xfb\x78\xfe\x3e\xf4\xc6\x71\x34\x1e\x93\x20\x19\x77\x7e\xc6\xd3\x35\x44\xe7\x59\xaa\x48\x1f\xc8\x61\x62\xd3\x42\xab\x64\x3d\x1a\xdd\x4d\x9b\x88\xca\xf7\xde\xa4\x13\x55\x67\xf2\x36\xcb\x63\xee\xd1\x56\x4b\x90\x42\xd6\x6c\x73\xaa\x93\xef\x8b\x9c\x4f\xbe\x13\x60\xee\x99\xda\x84\x50\xdf\x00\x8e\x59\x47\xf1\x64\x11\x4e\x43\x4c\x24\x1c\xd9\x65\xa4\xfb\x4b\x0e\xfb\x44\x9d\x5e\x08\xed\x6a\xf0\x39\x10\x58\x9e\x7d\x66\x81\x8f\xb9\x96\x59\xcb\xaa\x95\xfb\x66\xbd\xec\x7d\x33\x96\x14\xbc\x5b\xbc\xf4\xe8\xc0\x1b\xe7\xd1\xd8\x5b\x0c\x7e\x0b\x67\xd3\xd9\xdc\x0b\x3c\x8f\x04\xa6\x1b\x70\xf5\x02\x8e\xf6\x4a\x60\xd8\xeb\xe9\x16\xaf\x15\xd2\x31\xde\x2b\xaa\x5f\xaf\xae\x30\xdb\xe1\xf5\x14\x13\x43\xbc\xe5\x79\x82\x61\x40\xf5\x23\xea\xcd\x76\xf4\x8e\x5e\x13\x99\x8d\x19\xcf\x90\x3e\x44\x12\xd7\x92\xc5\x6d\xe8\xc1\x4f\x4f\x0f\xe4\x49\xa2\xd3\x60\x2e\x89\xf2\x39\xd5\xbe\x5b\xf3\x23\xb9\x5b\x5d\xad\xfd\x6b\x35\x88\x38\x2c\x05\x11\x23\x4f\x2a\x79\xba\x9b\x9a\xab\x3c\x9c\x43\x8a\xba\xc6\x01\x84\x04\x17\xab\x3b\x7a\xbd\x16\xa0\x09\xf4\xb2\x58\xf5\x1a\x8d\x8b\x2f\x67\xc0\xba\x74\x47\xa7\x50\xa3\xf6\x64\x65\x8f\x1c\xa9\x6e\x49\x30\x9d\x4f\x1d\xce\x69\x3c\xad\x04\xf5\x5f\xd2\x07\xc5\x7b\xdc\x23\x9d\x00\x57\x1a\x01\x76\x65\x70\xbf\x78\xf0\x27\x73\x7a\x4f\x8e\xe8\x06\x0c\x4f\x82\xf7\xd0\xf4\x99\x67\xc5\xa3\x7a\xc0\x30\xad\x5a\x98\x42\x4b\x5a\xd1\x5b\x7a\x4f\x1f\xc2\x68\x71\x31\x0c\x43\x41\x45\xd5\xe1\x05\xdd\x8c\x46\x8e\xb1\xf4\x86\xe8\x1c\xe9\x94\x43\x84\x10\x8f\x3a\x8b\xc4\x2e\x67\xcb\x4f\x83\x19\xbd\x0a\xd9\x65\x78\x31\x9b\x8d\x46\xaf\x66\xb3\x4b\x76\x38\xbc\x9a\x7d\x1a\x86\x21\x03\x0b\xb1\xdb\xb0\x8e\xfc\x6b\x7a\x47\x53\x42\xe8\x6d\x78\x2b\x1e\x6e\xe9\x1d\xbd\x22\xf4\x6a\xe9\x37\x4e\xf8\x7d\x78\xd7\x25\x61\xf8\x96\x55\xb5\x3e\xd3\x1e\xa1\xf7\x5d\xc8\x20\xbc\x27\xb4\xa7\xbd\x38\xbb\xba\x99\x3c\xc8\xe1\x3d\x21\xf4\x02\x07\x7a\x38\x78\x7f\xfe\xfa\xf3\xaf\x20\x6e\x02\xe0\xca\xe5\x43\xe8\xe5\x85\x0a\x22\x1b\xc8\xf9\x60\x69\x7d\xa3\x06\x12\xf8\x0f\xe1\x2d\x50\x0e\x9c\x96\xe1\x2d\xe2\xc7\x2a\xbc\xc5\x4b\x9c\x5e\x85\xc3\x8a\x90\xc0\xaf\xc2\x07\x2a\xae\xf0\xe1\x03\x19\x8d\xfc\x87\xd0\x93\x7c\xe3\xec\x12\x52\x12\x85\x33\x71\x19\x29\x0a\x24\x64\xfa\x27\x04\x31\xf7\xa3\xc3\xe1\x41\xdc\xf9\xf4\x6a\x59\x38\xde\xb1\x3b\xba\x2a\xe9\x03\xbd\x5b\x93\xa0\xb0\xfd\x63\x77\x02\x44\x1f\x68\xb5\x36\x9d\x0a\x6a\xc9\xff\x4d\x10\xb7\x72\x3b\x1d\xe0\xbe\x5a\x22\x78\x4b\x5e\x34\x80\xa7\xaf\x71\x8c\x02\xda\xe9\xd5\xb2\x0c\x44\x77\x7b\x08\x8a\x61\x7d\x64\x0d\x99\x1c\xfd\xc6\x39\xf9\x52\x1e\x39\x7d\x56\x26\x13\x75\xb9\x1d\x0e\x3d\x57\x5b\xb1\xb7\x53\xcf\xdc\x01\xd5\x2a\xb8\xa2\x3e\xf1\x7f\xae\xec\xff\x68\x4c\x3d\xe0\xa5\x08\xb4\x79\x0b\x8c\x60\x9f\x5a\x0c\xdb\xc8\x35\x88\x4c\x82\x1f\x47\x0d\xb7\xe1\xb5\x47\xbd\x7d\x51\xd5\xed\xa0\x90\x4d\x65\x8e\xeb\xbc\xe3\x0a\x61\x41\x63\xcf\x0f\x87\x84\x26\x61\xac\x4f\x12\x91\x6c\x98\x0f\xbc\x1c\x43\x86\x3d\xa2\x0a\xfd\x06\x1c\x45\x01\x31\x55\xa8\x2c\x51\x96\xa0\x2a\xa5\x4f\xa7\x0a\xae\xdd\x27\x0a\x01\x74\xbf\x6a\xae\x8a\xcd\x9f\x6b\x41\xc0\x9c\x2a\x29\x88\xe0\x27\x49\xc3\xdb\xf2\xbe\x64\xfb\xcf\x77\x1d\x26\x15\x8b\xae\x59\x33\xb2\x3c\x6d\x91\x25\xbb\x73\xad\xb1\x08\x39\x12\xcc\xaa\x01\xf7\x18\xe8\x78\x7b\x52\x46\x91\x29\xff\xcd\x9f\x11\x2b\x7d\x8f\xaa\xe6\x5a\x4c\x3a\x69\xff\x64\x15\x42\xa3\x66\x3a\x39\xf4\xfe\x00\x3f\x75\x64\xc0\x18\xa6\xa1\x92\x9a\x13\x4c\x8d\xc8\xc2\x8e\x52\x13\x9f\x58\xe7\x45\xab\x31\x07\x99\x0c\x4a\x27\x66\xfa\x26\xcf\xf9\x33\x6e\x0c\xbd\x96\x4e\x8d\x45\x83\xae\x5a\xcb\xd6\xca\xa1\xa0\xad\x97\xe2\x50\xd3\x1e\x95\x6f\x5c\x95\x96\xb1\xd9\x02\x12\x44\x56\x4a\x37\x39\xe4\x4e\xf3\x06\x67\x88\x8b\x93\x59\xc4\x9a\x1b\x1d\xb9\xa6\x30\x24\xc0\x4f\xdd\xe6\xee\xc7\xdc\x95\xc1\xbd\xec\xc8\x2e\x6a\x99\x09\xa0\x29\x13\xe6\x61\x3f\x1c\x8c\xc5\x9f\x4e\x24\x25\xcd\xaa\x4c\xe6\xdc\xa3\x74\xfa\x55\x46\x1e\xfb\x52\xba\x09\x57\x2a\x5a\x4a\xa7\xa1\xb0\x15\xba\xf5\x75\x38\x1b\x8d\xdc\x80\xea\xaf\x43\xf0\x75\x72\x7a\x83\x14\x02\x3b\xde\xd1\xdd\xb0\xf3\xbb\x62\xf9\x41\x18\x72\x17\x85\xe7\xff\x7c\x31\x3b\xdf\xd0\xfb\x28\x3c\xff\x75\xf5\xeb\xfa\xe3\x73\xfa\x00\x66\x4e\xcb\x5f\xf3\xf3\x0d\x7d\x94\x9a\x30\x54\x51\x4b\x63\xf6\x43\x76\xc3\x36\xa0\x34\xe3\x35\xe8\xcf\xc8\xc7\xe7\x19\xfd\x7b\x74\xca\x0c\xfe\x9a\x3f\x6e\x78\x4e\xce\x33\x43\x27\x7c\xde\x94\x71\xb7\x02\x87\x4a\x14\xe9\x78\x62\x71\xf2\x14\x1f\x0e\xf7\x91\x8a\x6b\xba\x4c\x7c\x46\x39\x09\x44\x6f\x63\x6f\xe5\x8d\xfd\x96\x40\x88\x2f\x23\x41\x4e\x8e\xbd\xb5\x47\x39\x2a\x7c\xad\xcc\xcd\x87\x83\x6a\x30\xd4\x59\x9f\x23\x42\x12\xc0\xc0\x0d\x7f\xdd\x88\xe8\xcf\x70\xe8\x2e\x5a\xf1\x35\xf6\x28\x99\xb4\xb0\xd3\xfe\x03\x73\x86\x35\xc2\x08\x84\x0d\x27\xfd\x65\xe4\x2b\xf3\xa0\x68\xe9\x79\x02\x49\xaf\x94\xe9\xcc\x3a\x44\xe9\xe7\xcf\x3f\xbd\x11\xb7\x5d\x91\x43\x96\x3b\x32\xf6\x42\x6f\xdc\xf1\x26\x22\x47\x27\x9d\x64\x24\x2d\xe7\x6d\xc9\x9b\xe0\xfa\x1c\xfd\x91\xcd\x55\x5a\xe1\x68\x18\x18\xa3\xa3\x9c\x4b\x46\x16\xf8\x71\xc7\xb2\x5c\x3b\xa1\xab\x6d\x72\x82\x0b\xe3\x79\x99\xe6\xec\x86\x53\x63\xa7\xa5\x17\xde\x0a\x8d\xf3\x79\xe4\xc7\x14\x42\xe0\x88\xcd\x35\x51\x0a\xae\x8a\x2c\xf7\xbd\x91\x25\x6c\xb8\x8b\xa8\x37\xf6\x9a\x17\x46\xc5\xcb\x8c\xed\xb2\xbf\x77\x66\x85\x91\x9c\x33\xc6\xb7\x54\x15\x71\x5e\x84\x1c\xa9\x5b\xd4\x87\x1d\x3a\x71\xb8\x74\xbd\x42\xac\xc0\xa5\xe2\xdb\x18\x61\xb1\xa5\x6d\xe4\x23\x6d\x4d\x8f\x44\xc5\x09\xe8\xbc\x12\x00\xf6\x1c\x6c\x97\x83\x79\xe6\x50\x21\x9b\xac\xf2\xbd\xc0\x58\x37\x8d\x46\x7f\x97\xc7\xc0\xb1\xaa\x23\xa3\xd1\xf0\x31\x32\x29\x40\x15\x5e\x92\x59\xe6\x87\x2a\x7b\xbe\xb8\x05\xdd\xb9\xd9\xfe\xc3\xb6\x05\xa1\x1b\x3e\x33\x46\x49\x9f\x81\x90\x18\xf2\xfb\xb3\xbd\x1f\x77\x18\x0a\x3e\x89\x39\x04\x11\x42\x02\x00\x41\x60\x92\x8c\x3e\x44\xd4\xfb\xb5\xfc\x35\x07\x8a\x28\xe8\xa8\x1a\x77\x57\xc5\x4c\x79\x88\x57\x1d\x18\x7e\xd8\x96\xb6\x2f\xa6\xe0\x72\xac\x80\xb8\xbf\x7c\xf7\xed\x9f\xeb\x7a\x2f\xd9\x45\xc9\xef\x30\xf2\x74\x44\x6c\xf8\x45\x14\xce\xe8\x97\xa0\x97\xff\x2a\x0a\x9f\x66\xe0\x64\x3a\xbf\xb8\x78\x15\x5c\xcc\x3e\x3d\xd2\xaf\x9b\x27\x48\x7c\xcd\x27\x0b\x36\x65\x75\xcd\xe2\xed\xd7\x28\x67\x71\x1e\x7d\xaf\xc8\xd1\x8e\xc8\xb3\x4f\x87\xce\x85\x2b\x8e\xc0\x97\x11\xf9\x32\x5a\xb1\x35\x44\xc3\x12\xec\x78\x59\x85\xc3\xe1\xd7\xd1\x68\xe4\xdd\x67\xf5\xf6\xcb\x92\x27\x3c\xaf\x33\xb6\xab\xbc\x2c\x1f\x7c\x1d\x09\x56\xf0\x8a\x3d\x84\x5f\x47\x50\x4d\x2e\x81\x66\xc5\xfc\x5e\xca\x09\xfb\x3e\x1c\x44\xd7\x43\x66\x8b\xc2\x96\x4f\x95\x93\x35\xda\xd6\x3b\x86\x0c\xe7\x49\x37\xe1\x78\xfc\x45\x84\xe9\xc0\x8a\xbd\xb8\x45\x50\x94\xc7\x40\xc6\xc3\x90\x3f\x15\x4f\x15\x2f\x61\x0b\xd9\x74\xcf\xaa\xea\xbe\x28\x13\x42\xa1\x13\xd4\x7b\x18\xb5\x9c\x53\xb8\xe2\xeb\xd0\x2a\x58\xf1\xf5\xc2\x68\x82\x47\xa3\x74\xda\x14\xe2\x76\x95\xf9\xa6\x89\xf8\xa6\x35\xc5\xc3\x21\x5e\x79\xbf\x4c\xe4\xde\xf3\x64\x02\xb9\x1f\x21\xa0\x7e\x57\x79\xe8\xb9\xc0\x22\x23\xcb\x48\xa5\x70\xda\x96\x3a\x70\x1a\x43\xac\x86\xa8\xeb\x52\xb7\xb6\x5e\xa0\x62\x29\xd2\xfa\x12\x02\x5d\x47\x61\x3a\x2d\x00\x44\xe0\x07\xca\x63\xc1\x98\x49\x4a\x1b\x80\x13\x4c\x95\x14\x22\x90\xdc\x1c\x94\x26\x90\x99\x4d\x70\x5c\x34\xb5\x98\x38\x12\x24\xfe\x57\xd1\x4a\x15\xad\x0f\x87\xce\x6a\x6d\x7b\xd5\x74\x6a\xab\x9e\x96\x32\x87\xbb\x53\x78\x94\xba\x6a\x9a\x4e\x3b\x05\xfd\x3e\x01\x33\x53\x3d\xa7\xc8\x27\xd4\xcc\x2b\xf2\xe5\xf0\x09\x8d\x42\x98\xbf\x28\xc2\x79\xca\x1c\xc3\x28\x90\x60\x8e\xa8\x0b\x55\xc5\x18\xe2\x42\xc9\x29\xb6\x32\xf5\x06\x8a\x28\xb6\xc7\x96\xf0\x1c\xd6\x3a\x12\x68\xc2\x8e\xeb\x6e\xe9\x7a\xb4\xe2\x10\x59\x15\xa9\x21\x34\xea\x3d\x3a\x70\x34\x80\x3d\xe5\x3c\xbe\xe9\x2c\x7f\x98\x98\x37\x8e\xa2\x50\x7e\xed\xdc\x5f\x06\xa2\xcf\x83\xa8\x46\xb0\xf0\xbc\xa1\xd8\x03\x0d\x9d\xec\xa2\x8b\xac\x77\xb2\xac\x32\x42\xd9\xd1\xe0\x44\xad\x50\xf3\x35\x2b\xe6\x60\x06\x9d\x6a\x1a\x05\xaa\xe0\xf1\x89\xa2\x55\x74\x4f\x75\x05\xeb\xca\x7f\x5b\x30\x79\x44\x7f\xc3\x20\x9d\xce\x6f\x80\xed\x80\xd5\x8f\xb1\x53\x53\x5a\x69\x07\xeb\x00\x6f\x2b\x38\x0a\xef\x35\xf6\x76\xe9\x11\xbc\x65\x9f\x8c\xae\x18\x75\xbf\x01\x9b\x62\x95\x2f\xf1\x99\x56\x65\x1c\x00\x22\x3a\x92\x69\x91\xfb\x1e\x98\x6e\x4a\xb9\x47\xec\x1c\xc9\x48\x27\x5d\xa6\x31\x9e\x33\x36\x1a\x71\xdf\x3a\x56\x28\x91\xf9\x74\xf6\x29\x5c\x00\xf8\x88\x91\x0a\xb7\x9c\xb9\xc9\xd2\x22\xc1\xe6\x75\x40\x5e\x3c\x1a\xc5\x02\xf2\x94\xe9\xdf\x37\x91\x20\x01\xff\x14\x85\xe7\x7e\x48\x7e\x5d\xfa\xcb\x70\x74\xf8\x98\x1c\x7e\x5d\xa2\x11\xa0\x05\x94\x57\x55\x91\xef\x03\x2f\x96\xfa\x42\x54\xff\xee\x95\xfa\xb0\x1d\xe0\xfc\x9b\x08\x63\x36\x80\xa4\x03\xe2\xfb\x15\x63\xef\x3d\x4a\xb4\x6d\x5a\x62\xc5\xc0\xf8\x89\x75\x02\x88\xf8\x06\x68\x88\xf7\x9e\x13\xf5\xa0\x99\xca\x23\x9a\x42\x25\x15\xf0\xe5\x4f\x92\xca\x88\xc4\xc2\x93\xa5\x77\x5b\xee\xbc\xa0\x85\x54\x22\x29\xbd\x1e\xfa\x8e\x74\x1a\x3d\x9e\xb4\xab\xd1\xb3\x7a\x7f\x41\xef\x98\x0f\x82\x32\x60\x34\xf2\xc4\x5f\x43\x75\x6d\x0f\x07\x0f\xa7\x01\xe9\xc8\x1d\x19\xb3\xcf\xd5\xf0\xd5\x62\x36\x08\x70\xf7\x25\x59\x36\x0a\x7c\xc1\xbe\x3a\x25\x74\xbb\x8c\x56\xdb\x75\x28\xfe\xd3\x74\xca\x9f\x40\xeb\xe0\x8d\xb9\xae\xae\x56\x2b\x6a\xe8\x3a\xd4\xaa\x29\x45\x84\xac\x8e\x34\x3d\x24\x9a\x35\xc6\x39\xf2\x78\xa1\x16\x7f\x1d\xb6\x09\x55\x48\x9e\x8a\x36\x95\x7c\xec\x0d\xee\x59\x35\xc8\x8b\x7a\x20\xc0\x08\x24\x98\x9b\xd5\x6c\x7d\xa4\xee\x92\x84\x28\xc8\x82\x20\x93\x7c\x4d\xc5\x7f\x76\xcf\x1b\x63\xef\x7a\xa4\x49\x47\xe8\x46\x6c\x00\x3c\x10\x4c\xcf\x5d\xdc\xb8\xb1\x58\x02\x50\x6f\xab\xad\xcf\x89\xcc\x7d\x6d\xad\x7d\x4a\x46\xa3\xd4\xdf\x80\xcc\x64\x13\xa6\x26\xb0\xb2\x96\x98\xd9\x28\x1c\x6c\x15\x20\x8f\x65\x53\x58\xf7\x32\xf3\xd7\x4e\xff\x31\x69\x09\x29\x70\x1f\x98\x0b\xec\xd0\x1e\x32\xbc\x53\xa9\xf3\x29\x0f\x87\xf1\x68\xb4\x5a\x9b\xdc\x74\xab\xa8\x11\xfa\x22\x59\xcd\xd7\x64\x1d\x40\xac\x4f\x37\x23\xf9\x8a\x21\x7f\x83\x01\x89\x54\x7a\xf1\xdc\xe7\xc4\xa0\x23\x95\x0e\x7c\xb5\xa6\x89\x2d\x47\x90\xcc\xfa\x9f\xc1\xf1\x23\x9f\x0a\xd4\xb6\xd0\xbf\xba\xd6\xa0\x2b\x38\xd6\x9f\x23\xb5\x06\x7f\x8e\xba\xe3\xfa\x2c\x6c\x7f\x6d\x78\xb7\x0d\x99\xed\x0b\x68\x8e\xd9\x65\x08\x1e\x9d\x4a\x73\xcb\xa4\x09\xda\x96\x10\x0a\xe1\xf8\xe0\x69\x06\x89\x7b\x9a\x4c\xae\x5c\x67\x29\x9c\x0c\xa2\x8e\x80\x78\x10\xd6\x3a\xf4\x7e\xfc\xe1\xed\x3b\x01\xb9\xda\x5c\x5d\x71\xac\x8e\xd8\x91\x5b\x22\x47\x34\xee\x91\xa6\x4d\xa4\x11\x57\x96\x91\xa7\xd4\x40\x34\xdd\x60\x62\xf9\x64\x29\xae\x9b\x24\xbb\x13\x77\x8d\x14\x4e\x59\x10\x26\xb8\x24\x70\xb6\xf1\x13\x94\x24\x69\x8d\xba\x1f\x8f\x46\x2e\xe3\xb4\x41\x36\x38\xa6\xe9\xe1\x60\xd9\xc6\x01\xa5\x15\x51\xb6\x16\x57\x08\x6a\xf9\xb5\xe8\xd7\xa8\x59\xa9\x91\x4b\xd3\x86\x50\xdb\x16\x8f\x3b\x82\x73\x6a\xb4\x44\xed\x6c\x42\x4d\x47\x80\x2e\xe3\xf7\x2e\xc9\x94\x8c\x78\x94\x74\x4b\x7d\x37\x25\xdf\xfb\x3a\x1c\x97\x2d\x3b\xd4\x51\x76\x04\xe2\xc5\x58\xae\x72\xe3\x10\x7a\xdf\x80\xfd\x9f\xce\x49\xed\x66\x25\x36\x42\xa1\xbf\x44\xce\xf7\xb2\xea\x3f\xb2\x3c\x29\xee\x7d\x46\x96\x2c\xf8\xd7\xb0\x99\x50\xcc\xca\x6d\x7d\xcc\xa5\x9c\x0c\x1c\x55\x7e\x80\x9f\xa7\x42\x12\x58\xf6\x3f\x2a\x8f\xa2\xcc\xce\xe9\x11\xba\xc3\x04\xef\x37\xe1\xd3\x71\xe1\x09\x8a\x39\x8b\x65\x90\x6b\x95\x95\x6a\xaa\x6a\x87\x5e\xc9\x77\xac\xce\xee\xb8\x47\xe8\x36\xdc\xc9\x51\xf8\x18\x9c\x4f\x76\x0d\xfa\x06\x9a\x99\x82\x1d\x4f\x6b\x8f\xd0\xab\xd0\x37\xa9\x40\xc5\x07\x0e\x07\x2f\xcd\x1e\x78\x02\x0f\x90\x84\x6f\x9c\xd9\xb7\xe4\x6d\x5d\x78\xe4\x72\x32\xa7\x57\x4b\x3f\x09\x77\x7a\x14\xc0\x9f\x25\x90\x98\x97\x87\xc9\x54\x74\x4f\x02\x7f\x63\x27\xac\x4a\xc9\xe1\x30\x73\x73\x1b\x66\xa2\xa8\x75\x46\x41\x58\x14\xa9\x80\xa2\x31\x1e\x63\x74\x2a\x9b\x42\x24\x35\xff\x46\xfc\xc5\xa7\xc9\x56\xfc\x3f\xde\x98\x2a\xe2\xdb\x50\x47\xfc\x90\xcf\x93\x2d\xfc\x11\xf7\x9a\x77\x5b\x09\xdc\x94\xe5\x83\x68\x19\x4d\xe1\x41\x7d\xea\x86\x04\x3b\x58\xa0\x1b\x4c\xfa\x60\x49\x79\x8a\xe6\x7e\x92\x53\x3e\x95\x86\xb8\xb5\x32\xf0\xb7\x75\x04\x72\xab\xa6\x1a\x5e\x54\xe4\xb8\x88\x28\x97\x4f\xf0\x86\x93\xf2\x7c\xca\xc3\x27\xc8\x27\x4d\x21\x9f\xf4\xec\x48\xd3\x30\x81\x10\x93\xb6\xc6\x00\x18\x64\xe3\x85\x98\xb6\x32\x70\x5b\x49\xce\x21\xcc\x88\xaf\x23\x6e\x6e\x78\xfd\x45\x71\x9b\x27\x59\xbe\xf9\x72\x97\xf1\xbc\xfe\x89\xc7\xf5\x30\x0c\x7f\x46\x27\xcf\xee\xf7\x3e\x11\x84\xec\x5f\x22\x3f\x25\x14\x86\xc7\x61\x43\xe2\xe9\x9e\x6d\xf8\x5f\x71\x5e\x93\x68\x1a\x43\x83\x77\xc5\x1e\x07\xcf\x71\x3f\xb0\xd6\x2f\x8d\x5a\xdf\xf2\xb4\x3e\x92\x80\x1f\xe9\xbe\x23\x39\x73\x96\x6a\x05\x87\x0a\x75\x46\x63\xbd\x46\x49\x63\x8d\x94\x05\xbd\x86\x6a\x99\x42\xc2\x3e\x6e\xcb\x08\x7d\xf8\xba\x26\x17\xf8\x52\x38\x86\x7b\xf5\xa3\x94\xd4\xd3\xc8\x2e\x85\xcb\xd3\xc4\xbd\x11\xe3\xc0\xcb\x80\x80\x81\x3b\xd3\xd5\x08\x85\x33\xa2\x93\xa1\x42\x4d\xcc\xe7\xf5\xae\xd8\xeb\x54\xa8\xa2\x1a\xac\x4f\x47\x3d\xb1\x38\xa6\xa2\x5c\x73\x3c\x0a\xd0\xf7\x44\x4f\x10\x33\x38\xbc\x13\x28\x5d\x74\x09\x0b\x22\x8f\x03\x76\xdf\xac\x2a\xba\x86\xba\xc7\xe3\x91\xda\xf3\xfd\x20\xc9\x64\x6b\xb5\x0e\x87\x37\x91\x52\x37\x81\x04\xd7\x8e\x10\x84\xcb\x04\x56\x48\x0a\xcb\x75\x20\x44\xd0\x49\xd9\x7d\x6a\x39\xa7\xe8\xfc\xe8\x86\xfd\xd3\x59\x60\x02\xcf\x82\x2e\x8f\xea\xac\x31\x58\x2e\x61\xd3\x4e\xa0\x66\x05\x5a\x73\xaa\x84\x61\x18\xb7\x43\x19\xf2\xde\xcc\x64\x11\x26\xa8\xc2\x94\xe6\x7f\x89\xac\xb4\x83\x1a\x33\xa4\xcb\xcd\x72\xb3\x8a\xd7\x81\x20\x5f\x81\xba\xf4\x37\xcb\x8d\xce\x6c\xe3\x27\x4b\x66\x9f\x8d\x20\xa5\xc9\x32\x0d\x98\x7d\xaa\x08\xb4\x0d\x53\x72\x14\xa4\x5d\x2b\x4a\x0f\xe6\xc8\x6e\x04\x70\x14\xd7\x3b\xe0\xfe\xd3\xf9\xff\x20\xe1\x9d\x93\x1e\x9d\x76\xfa\xf4\xc5\x82\x9e\xc2\x6c\x86\x26\xef\x6d\x4c\x30\x00\xbf\xb9\x1b\x56\xd1\x1a\x52\xd2\x06\xb1\x21\xa4\xad\x1d\x93\xa9\x43\x75\x8a\x42\x4c\xe4\x2b\xc3\xdf\xb7\xc3\x26\x62\x23\x9d\x10\x0f\xd2\xca\x7b\x63\xa6\x44\x1c\x41\x44\x3d\x2f\xf0\x8a\xdb\x1a\x8a\xad\xf6\xc0\x4f\xc2\x36\x26\xd6\x36\x02\xdb\x8f\x49\x9a\x9b\x4b\x28\xc8\x72\x2b\x7e\x81\xa6\x67\x21\xd0\x2d\xe4\xb4\x95\x19\x18\x31\x74\xf0\x52\x25\x4d\x09\x54\x62\x5e\xbd\xef\x6d\x08\xb1\xf4\x4e\x6d\x72\x23\x12\xec\x5f\x1f\xb5\xb2\xf2\x10\x4d\x7a\x63\xb6\x06\x9a\x24\xd2\x34\x09\x72\x99\x4d\x8c\xaf\x63\xd4\x47\xd3\xa8\x48\x1e\x81\x9b\x2b\x76\x3b\xd1\x9e\x72\xe7\x49\x55\xc0\xa3\xa6\x2a\xb8\x4f\xe6\xe3\x44\xed\x26\x24\x25\xc0\x63\x2b\x26\xb6\x21\x81\xca\x8e\x80\x91\x08\x36\x00\xa2\xe9\x32\xd1\xc2\x3b\x0d\x9c\xca\xbd\xb3\xca\xfe\xce\x3b\xf8\x4a\x2b\xe6\x82\xbc\x91\x59\x9e\xbc\xe5\xbb\x14\x79\x11\x96\x24\x5f\x08\xe6\xce\x53\x2d\xad\x00\xd2\x3c\xcd\x72\x3e\x1a\xe1\xdf\x29\xbb\x49\xd4\x6f\xdf\x43\x6d\x92\x47\x57\xeb\x8e\x84\x94\xb9\xbc\x7b\xff\x8f\xa0\x18\xaf\xfe\x4d\xd4\xa4\xdf\x8a\xdf\x1f\x9b\x8d\xca\x8b\x2f\x8b\x3c\xdd\x65\xb1\xe5\x20\x6c\x11\xa1\xd3\x8f\x05\x1a\x03\x62\xed\xe3\xf0\xdb\x08\xe3\x00\xcb\xbe\xf4\x1b\xf9\xf8\x7f\x22\x42\xf3\x23\x55\xbc\x47\x88\xd7\xad\x7e\x2d\x7a\xc8\x45\x0d\xb2\x38\x3f\x1b\xfe\xd3\xd9\xe0\x8b\xa2\xa8\xab\xba\x64\xfb\xc1\xdd\xab\xe9\xab\xe9\xbf\x0c\x7c\xb0\xfa\x3c\x3f\xdf\xf0\x3a\x52\xef\x04\x9b\x40\xfe\xe9\x6c\xf0\x65\xb1\x7f\x2c\xc5\xa1\x1a\x5c\xcc\xe6\xf3\xc9\xc5\x6c\xfe\xd9\xe0\xdd\x7d\x56\xd7\xbc\xa4\x83\x37\x79\x3c\xfd\xa7\xb3\xc1\xb7\x59\xcc\xf3\x8a\x27\x83\xdb\x3c\xe1\xe5\xa0\xde\xf2\xc1\x77\x6f\xde\x0d\x76\x58\xfc\x4f\x67\xe7\x82\xa3\x13\xef\xc4\xda\x25\x66\x81\x71\x80\x52\xfe\x99\xf3\xfb\xc1\xd7\xe8\x52\xa9\x07\xf8\x49\x35\xf8\x0b\xbb\x63\x68\xa2\x32\x28\xf9\x6f\xb7\x59\xc9\x2b\xd9\xce\x23\x8b\xb1\x4d\x4d\x79\xb7\x15\x1f\x08\xbe\x31\xae\xbd\x85\x0a\x16\x95\xe6\x52\xf1\x67\x39\xce\xac\x66\x6b\xf5\x34\xf5\xc0\xb0\x2d\x5a\xcd\xd6\xaf\x2f\x46\xa3\x68\x35\x5f\xbf\xfe\xd7\xc3\x61\x1e\x42\x02\xf9\xd1\xe8\x5f\xc5\x8f\xf9\x5a\xbc\xb9\x58\xbf\x9e\x1f\x0e\xa2\xf8\xf2\xd5\xef\x1d\xf2\x40\xf9\xb9\xcc\xa7\xff\x3a\x9d\x0f\x8a\x72\xb0\xcd\x36\x5b\xb1\x92\x11\x78\x70\xdf\xc3\xea\xb1\x5c\xd7\xfb\xd4\x23\x47\x5f\x2e\x13\xed\x9f\xad\x66\x3e\x22\x7d\x87\xea\xa3\xdf\x08\x6d\xa9\x37\x18\x04\xd8\x4f\x98\x69\x1f\xa4\xa0\x32\xaf\xff\x7d\xa3\xe4\xeb\x3c\xf1\xe8\x77\xc5\xdf\xed\x3a\xb5\xfe\x0d\x06\x7e\x3f\xd8\xef\x0a\xa7\xe9\xa0\x68\xd4\xad\x7b\xbb\x39\x9a\xc8\xf7\x3a\x8d\x8d\x76\xfb\x53\xc9\xcd\xe3\xb5\x24\x4c\x9f\x78\x9e\x04\xd1\x2a\x5e\x1f\x4d\x94\x13\xd8\x6e\x7e\x73\xbb\x63\x35\x77\x46\xe1\x1c\x31\x54\x0f\x0e\xe7\x92\x2c\x5e\xc8\xb4\x98\x53\xc1\x6b\x7b\x51\xd5\x98\xb9\x2d\x12\x0d\x87\x33\x79\xb2\x1d\x44\x13\x1f\x0e\xcc\x4f\x88\x36\xd0\x62\xd3\xea\x76\xbf\x2f\xca\x7a\x6a\x26\x38\xe5\x79\x62\x02\x91\x5b\x16\x8f\x5c\xdb\xbe\x53\xe6\x88\xa6\x3a\x3a\x01\x55\x44\xd7\x0b\x4c\x3c\x07\x56\x62\x32\x81\xc3\xb4\x31\x91\xf0\x29\xca\xf2\x04\x84\x0c\x7d\xc3\xd3\x6e\xe7\xa7\x6b\x6d\x21\x84\xa9\x93\xdf\x42\xf0\x31\x7e\x34\xad\x59\xb9\xe1\x35\x28\x79\xd1\xb0\x47\xb1\x10\x53\x6c\xf3\x43\x74\x25\x7f\x95\xdd\xf2\x1b\x40\xe6\x1f\x08\xef\xd1\xe9\x80\x81\xd2\xeb\x42\x9a\xf8\xf0\x30\x06\xf9\xa1\xd8\xe7\x29\xdb\x71\xd0\xd9\xf0\xc3\xa1\x55\x4a\x39\x38\x97\x6b\x0b\xa5\xb6\x48\x78\x34\xe2\xab\x48\x65\x12\x13\xc3\xc6\x2f\x7d\xb2\x12\x3d\x4d\x92\xac\xba\xc9\xaa\x2a\xf4\xb0\xbb\xf5\x27\xd4\x85\x41\xe6\x47\x28\xe5\x87\x90\x0c\x9e\x89\xc0\x54\x54\x9c\x1c\x17\xc9\xf4\xdf\xbf\xfe\xe9\xed\x9b\x1f\xbe\x0f\x3d\xc0\xd0\x1e\x4d\xa6\xef\x7e\xfa\xfc\xfb\xb7\x6f\xde\xbd\xf9\xe1\xfb\xf7\x5f\xfd\xfc\xd3\xe7\xe2\x47\x38\xff\xc3\x8c\x26\x56\xe6\x3f\x68\xef\x7c\x48\xaf\x54\xec\x93\xa7\xcd\x34\xe1\xb5\x58\x20\x03\xab\x1e\x34\x49\xa6\x66\x3d\xb4\x34\xef\x88\x80\xce\x74\x70\x27\x8e\xf1\x5a\x40\x62\x3d\xc1\xcd\xf6\xc8\x22\x85\x40\x94\xea\xdd\xb6\xe4\xa9\x27\x2a\xa7\xa3\x51\xaa\x45\xca\xe7\xd3\x33\x7f\x19\x7e\xb4\xfa\xdb\xaf\xd5\xfa\xec\x63\x72\x4e\x3d\x8f\xe0\x41\xda\x84\xcc\xf7\x3e\x02\x37\xc9\xe5\x6a\x1d\xa4\x64\x81\xc1\xfc\x4a\x80\xe6\xaf\x50\x38\xe2\x1b\x19\x1a\x3a\xa7\xe2\x44\x2b\x81\xc0\xe5\xa8\x45\x0d\x1d\xf2\x21\x64\x53\xa9\xbb\x86\x7a\xd6\xe4\x08\x8d\xa6\x59\x25\xbb\xfd\x11\x3f\xc2\x13\x5f\x30\x59\x1b\x27\x1c\x95\x97\xe5\x5e\xdf\x59\xdb\x98\x30\x56\x5e\xca\x12\xee\x91\xe5\xa6\x07\x7b\xc4\xa4\x13\x1f\xf9\x9d\xdb\x49\x82\xd8\x57\xa2\x52\x8e\x77\x17\x0c\x7b\x61\x7e\x86\x11\x35\x0f\xd3\x2f\x4d\xa2\xdb\x30\xb1\x5f\x74\x11\x17\x16\x6d\xa1\x7b\xe3\x1a\xf3\x24\xda\x5a\x50\x43\xa5\x5e\x36\x38\x1c\x13\xb6\xcf\x04\x9c\xb6\xc0\xed\xbf\xe1\xc0\x26\xd6\x81\x4d\xcc\xd1\x44\xdb\x2d\x80\xae\x2e\x89\x6b\xb4\x80\x3c\x1c\xcd\xea\xf2\x28\xc7\x92\x7c\x16\x87\x59\x27\x3d\x8b\x96\x5c\x06\x45\xf3\x41\x90\x0b\x21\x35\xc1\x52\xdb\x8f\xcc\x99\xb6\x48\xee\x44\xe6\x0c\xfe\x58\xda\xcb\x84\xe2\x20\xbb\x79\x81\x99\x95\xa4\x22\x9e\x7e\xf5\xf5\x37\x9f\xff\xfc\xed\xbb\xb7\x34\x91\xd5\xb2\xea\xdb\x82\x09\xb6\x23\x1c\xce\x8f\x8b\xb8\x75\xd0\x4d\x93\xf0\x69\x87\x35\xdf\x81\x4b\xa5\x7c\x98\x4e\xa7\x9e\xe8\xd8\xec\x82\x1a\x72\xc7\x25\x67\x02\x09\xa9\xe0\x63\x6a\xe0\xb0\xb2\x59\xa5\x43\x5d\x2f\xbd\x3b\xb6\xf3\x94\x00\x3a\x55\xcb\x4e\x16\xd1\x38\xf4\xd0\x8b\x13\x6d\x67\x40\x97\xce\xeb\x77\xe0\x4a\xa8\x56\x5b\x17\x79\x34\x59\xf1\xb5\x4f\x08\xb5\xae\x38\xc8\x4d\xfa\xf0\xe8\xc4\x71\x15\xb5\x64\x87\xab\x68\xbd\xb4\x57\x70\x15\xad\x83\x14\x13\xd7\x5b\x0b\x00\xbb\xe5\x37\x97\x50\xa0\x3f\x1d\x34\x2e\x26\x88\x84\x62\x71\xec\x40\xeb\x1a\x83\x80\x23\x70\x5b\x29\x7b\x22\x7b\x27\x68\xe2\x9c\xfe\x98\xd8\x11\x80\xad\xde\xe6\x84\x1c\x65\x8e\xe0\x19\x71\xb7\x01\xe1\xa8\x15\x45\x5f\x8c\x31\x72\xd7\x5e\x63\x2f\x79\x59\xc8\x96\x1e\x02\x6c\xe5\xad\x3f\x41\xa2\x54\x89\x04\xed\xa4\xa9\xba\x0f\x10\xe9\xab\xdd\x5b\xe8\x00\xca\x31\x0e\x15\x03\x21\x93\xa5\xaf\x9e\xa5\xf1\x14\x64\x32\x64\xa8\x23\x92\x5d\x48\x3b\x73\xcf\x8d\xfd\xe8\xa9\x52\xea\x7e\x56\x2f\xb6\xae\x40\x02\x13\x7e\xa9\xf9\x7d\x88\x9a\xd8\x18\x80\xcc\xf6\x61\xba\x34\xc8\x54\x75\xa9\xc7\xe8\x56\xb4\xc3\x17\x9a\xcf\xd3\xe6\x17\xe8\x73\xdd\x13\xca\x46\xa3\xd8\xba\x02\x21\x96\x91\x27\x33\xc4\x35\xe6\x0b\xb7\x1a\x2b\x33\x36\x81\x90\x46\xa2\xff\xe1\xf3\x1f\x78\xc1\xc0\x8f\x52\x3d\x07\xe8\x18\xb7\x7e\x61\xfd\x56\x98\x1e\x9f\x1c\x54\x1f\x3b\x6f\x9e\xc7\xf5\xb2\xc3\xe4\x39\x64\x2f\x3b\x34\xd8\xde\x81\xcf\xbf\x29\x00\x15\xd4\x8c\x6d\xbf\xac\x30\x76\xac\x09\x40\x73\x3b\x47\x75\xee\x91\x85\x94\xc4\x27\xd4\x24\xc6\xb0\xab\x67\x95\xff\x09\x00\xf2\xca\x8e\xb0\xbf\xa6\x03\xbb\xd0\x84\xf8\xfa\x44\xdc\xd6\x71\x9b\x44\xb0\xf0\x19\x55\x57\xc5\x32\x31\xdb\x0c\x91\xb2\x3c\x12\x24\xf6\xd9\x09\xa4\xe5\xb1\x6c\xa1\x1e\x3d\x82\xf6\xeb\x36\xa5\x24\xdb\x13\x69\x9f\x01\x8f\x1d\xcb\x36\x88\x76\xb7\xe5\xef\x5d\x4e\x24\x0d\xfb\x16\xd2\x05\x24\x1c\x0f\x3d\xff\x1b\xfc\xf0\xb3\x9c\x2c\x3f\x3e\x57\x8a\x79\xb0\xf8\xf8\x6f\xa1\xa6\x7b\x2e\xe7\x98\x95\x10\x28\xcb\xc3\x30\xa0\xdd\x77\xa0\xbc\x54\x68\xe7\xf5\x4d\xe8\xa6\x1d\x98\x38\x5a\x46\x41\x3a\x85\xfc\x2a\x8d\xeb\x5d\x7f\xb0\xe3\x82\x6f\x86\x87\xc4\x7b\xde\x8f\x48\xb0\x59\xf2\xd5\x66\xed\x93\x20\xd5\x79\x3d\xc4\xb5\xbf\x67\xb7\xe0\xa2\x1b\x3f\xc6\x3b\xcc\x61\xdb\xba\xfa\xe3\xfe\xab\xff\xe3\x2c\x4f\xb2\x98\xd5\x45\x59\x75\x62\x69\x3d\xd6\x89\xa9\xe8\x35\xc8\x06\xc9\x01\xc0\x40\x12\xb4\xea\x41\x9b\xdf\x5d\x06\x77\x94\x29\xd1\x49\x4d\x4c\xd1\xc7\x88\x54\xec\x92\xac\xe6\x37\x95\x55\xa0\xb2\xdf\x5f\xf3\xc7\xa8\x60\x65\x22\xd3\x49\xe9\x91\x0a\x90\x96\xf1\xdb\xa6\xce\xea\xaa\xdb\x1b\xaa\xcb\x1a\x54\xb1\x45\x10\x4a\xcd\x93\x18\x5d\x7d\x02\xe6\x30\x1a\x0d\x7d\xaf\xc8\xeb\xe2\x36\xde\x42\xd6\x15\xc8\x8a\xd0\x23\x8f\x24\x5d\xa3\x31\x61\xd8\x4e\x0c\x08\xbe\x25\x87\x63\x5a\x41\xb8\xb6\x13\xad\x60\x9b\x65\xab\x6e\x22\xac\x8b\xdb\xfa\x6c\x36\x73\xc8\x33\xb5\x11\xc1\x1f\xf8\x2b\x0a\x03\x09\xe4\x82\xa0\x5f\xc8\x70\x46\xd5\x6a\x07\xc3\x99\x4b\x2d\xc8\x85\x0c\x1b\xea\xc0\xe1\x39\x3a\x1f\x28\xaf\x83\xf3\x4c\x05\x56\x47\x94\x30\xad\x31\x2c\x19\x21\x4f\xd5\x7d\x06\xc6\xbf\xd3\xfb\x6d\x16\x6f\xc9\x53\xcc\x2a\x3e\x78\xf5\x2f\x48\xec\x08\xe4\xe8\xcb\x20\x2e\x0b\x7c\xf3\xaf\xf8\x26\xe7\x0f\xb5\x7e\xa3\xd2\xcf\xe0\x99\x3f\xb2\x16\x52\x3d\xba\xa3\x86\x75\xeb\x92\x57\x46\x2a\x98\xa9\x84\x5f\x7d\x79\x9b\x43\xe6\x66\xc9\x71\x5e\xba\x47\xc1\x6a\x33\xb4\xfa\xd4\x64\x9b\x02\x7f\x3b\x65\x8f\xb3\xbb\x62\x8a\x92\x48\xeb\xec\x97\x28\x81\x8b\x3d\xb5\x0d\xaf\xdf\xd4\xfc\x06\x63\x4d\xf6\x19\x1d\xc8\x53\xc5\x8c\xbb\x0d\x18\xb9\x94\x3c\xf7\xbd\xa9\x78\xa7\xe9\x25\xa8\x88\x5a\x6f\x9f\x1d\x0e\xf6\x29\x25\x9d\x1f\xfe\xa6\x28\xbf\xca\x4a\x0e\x9f\xed\x72\xc1\xc0\x23\x66\x8f\xd2\x87\xb4\xb0\x9e\xd8\x32\x0f\xf2\x7a\x41\x66\x93\xc3\xc1\xcb\x91\x5c\x16\x44\x8e\xa2\xb5\x70\x38\x48\x4f\x4e\xe6\x10\x6f\x4a\xad\xae\x5a\x1d\x01\xb2\x26\x38\x28\xf2\xa0\xba\xf3\xe5\x64\x1e\xcc\x69\x1a\xfa\xf1\x98\x93\x7f\x6e\xf7\xb9\x68\xaf\xd2\x94\xff\xe6\xa7\x2d\x1a\xb9\x23\x53\x1e\xba\x39\x75\xcc\xcf\x41\x6d\x9d\x78\x55\x7c\x48\xd3\xaf\x40\x34\x0f\x7d\x76\xd9\x35\xe5\xc3\x81\xbd\x9e\xb9\x31\xd0\x25\x66\x5d\x36\x31\x0f\xf7\x3d\xf1\xca\x45\x1f\xb6\x4d\xae\xb8\x4a\x18\x39\x92\x20\x56\xea\xf9\xe6\xfd\x11\xe8\x0f\x70\x9f\x5d\xc6\x4b\xdc\x92\x00\x97\x93\x36\x16\x89\x91\xc6\x2a\x41\x67\x2f\x3a\x65\xb3\x26\xa5\x29\xd7\x05\x4e\xc0\x00\x4e\xb3\x8e\x59\x38\x1a\xf5\x48\x0e\x1b\xb4\xea\x73\x42\x4c\x6a\x30\xa8\x0f\xea\x63\xf7\x54\x3e\x7b\xcc\xdd\xb9\x8a\x91\x36\xf2\x25\x0f\xed\xdd\x69\xed\x18\xf7\x71\x31\x9b\x6b\x56\xf2\xbb\x0f\xed\x07\x97\xa7\xc1\x4c\x8b\x57\x0d\x8e\x1f\xcf\xc2\xf3\x00\x28\xd8\x66\x79\xd4\x3b\x0e\x35\x78\x7e\x49\xbb\x34\xbd\x24\x74\x1b\xaa\xe3\x1a\x2d\x51\xad\x1a\x78\x25\xea\x30\x33\x14\x4f\x83\xb7\x42\x07\x8f\xd1\x01\xca\xe1\x70\xbe\xc0\xe8\x7f\x29\xc4\xf4\x32\x72\x30\x98\x96\x0b\xd1\x4f\x60\xf4\xc3\x93\x77\x70\xb1\x04\x57\x34\x51\x23\x0d\xb6\x18\x89\xba\x1b\x30\xae\x09\x1d\x5e\x77\x0a\xd1\xb4\x61\x85\x19\xcf\x8c\x6e\xe4\xd5\x2e\x4f\x48\x8b\x60\xd2\x4c\x6e\xeb\xc5\x8b\xd8\x53\x98\xef\x4e\x92\xa4\x4e\x73\x8d\x99\xc9\xaa\x8d\x5a\x52\xb2\x26\x8b\xdd\x68\xb4\xeb\x60\x68\x81\xf4\xbb\x71\xd7\xee\x83\x96\xce\xe4\x26\xea\x38\x6e\x7d\xbc\x23\xec\x90\xe0\xd8\x53\x3b\x0a\x3e\x4d\xc1\x9f\xd7\xf8\x53\x52\x2b\x48\xfe\x96\xd0\xd4\x79\xe2\xcf\xeb\x30\x52\x67\x19\x57\x11\xdd\xae\xa5\x9f\xda\xc0\x23\xa4\x63\x35\xa8\x1b\xa6\x7f\xa5\x5e\xb8\x0d\x69\x66\xc1\x20\xed\x0e\xe8\x90\xb5\x61\xe9\x86\x1c\xe9\x0c\xbc\x4c\xbb\x84\xa4\x9d\x54\x18\x21\x81\xcf\x7b\x44\x15\x69\xd7\xf8\x1b\x07\xa4\xc9\x9a\xeb\x91\x10\x0d\xaa\x12\x89\xab\x88\x44\x16\x9b\xae\x40\x60\xe1\x3c\x29\x56\x5d\x3d\x77\x31\xeb\xfa\xdd\xf3\xec\xba\xee\x56\x32\xec\x4d\x85\x93\x31\x02\xb4\x64\xf3\xcc\xef\x94\xce\x83\x81\x81\x2b\x9b\x87\x94\xc2\x5d\x82\xf9\xb1\x16\xcc\x37\x50\x8e\x61\xed\x94\x41\x8a\xcd\xdf\xa5\x8a\xa9\xe3\xf2\x07\xa1\x5b\x57\x55\x00\xa0\x3d\xa9\x0b\x8f\x2c\xb6\xa3\x91\xbf\x31\x97\x05\xca\xa1\x40\x38\x90\xd2\x0d\xa1\xdb\xd1\x28\xed\x62\x29\xc5\x8d\xbb\x45\x49\x4f\x93\x3e\x5d\xf4\x8a\x33\xf4\x92\x1b\x0e\xdc\x5b\x99\x01\xad\x3d\xca\x3f\xac\xc5\xa4\x2e\xa0\x11\x65\xfe\x3d\x18\x59\x18\x47\x0d\xe7\x8c\x31\x25\xd8\x2b\xc5\x5d\x62\x96\x6f\xfd\x49\xdb\x3d\xdb\xd1\x4f\x29\x41\x49\x4c\x63\xb5\x96\xbf\x4f\x23\xa6\x9c\x76\xa3\x1e\x98\x88\xf5\x1b\x0d\x13\xf1\x49\x98\xd0\x38\xcd\x4f\xac\x80\xae\xf1\x3f\xa0\x7d\x8b\x8b\xdd\x8e\xed\x2b\xde\x92\x17\x24\x46\x5e\x10\x9f\x96\x17\x2c\x86\xe0\x41\x87\x42\x91\xd1\xe8\xbc\xda\x16\xf7\x87\x6d\x96\x70\x25\x05\x01\xeb\x53\x25\x98\x15\xb0\xe6\xaa\xfa\xf4\x10\x1c\x6d\x1f\x4a\x10\x7a\xf4\x7d\x5a\x34\x90\xbc\x50\x34\xd0\xa5\x15\xb0\x67\xa8\x2e\x44\x89\x87\x42\xd6\x14\x0a\xeb\x41\xae\x57\x62\xaf\x42\xef\xa3\x4f\xc6\xd1\x34\x4b\xc6\x9f\x78\x6b\xda\x5b\xd5\xda\x70\xa7\xc5\x27\xf2\x7b\xe6\x46\x72\x65\x0b\x86\x85\x17\xfc\x8d\x24\x8d\xf1\x41\x13\xe8\xca\x80\x51\x66\xd1\x4d\x92\xcf\xcb\x8c\x7d\x9e\x27\x5f\xca\xcf\x4b\x0c\xec\xaa\x1b\x9c\x59\x36\xf8\x32\xb5\x81\x38\x32\xa9\x8e\x79\xb9\x12\xf4\x15\x28\x41\x0d\x5b\x8e\x3d\x00\xc7\x6d\x2b\xab\x92\xec\x06\x03\x66\xb7\x45\xf4\x7d\x77\x32\x9a\x8f\x59\x1e\xd1\xb2\x44\xdb\x99\xb9\x5f\x10\xf0\xd7\x49\x83\x3a\xab\xad\xf8\xae\x8e\xef\x65\xb9\x42\xb1\x11\x55\x04\x27\x2e\xbe\xa2\x1b\xf0\xc9\xe6\x3a\xf7\x2c\x07\x0c\x69\x33\xa2\x39\x1d\x28\xe0\x16\x40\x2c\x19\x23\xc7\x6f\xc3\x8f\x42\xde\x79\x18\x41\xfd\xea\x0c\x98\x10\x6d\xdc\xa6\x49\xa2\x6d\x71\x3f\x75\xda\xf5\xd3\x8b\x29\xa1\xc3\xb4\x87\x5e\x74\x87\x14\x23\xf6\xe3\x54\xa5\x55\x8e\x0e\x87\xae\x41\xa2\xe5\x97\xd2\x1f\xc3\x57\xf5\xe6\xfa\x64\xe1\x0e\xc3\xa1\x14\xcc\x78\x2d\x4a\xc1\x5e\xa9\xd5\x66\xed\xcf\x88\x2d\xf5\x47\x8f\x34\x10\xfb\xcf\x1a\x87\xb5\xbb\xef\xc4\x7b\xae\xbd\x7b\xf8\x90\x64\xdf\x3a\x4e\xdf\xcf\xce\x00\x06\xdb\x9e\x03\x1f\x08\x10\x12\x93\xf0\xbc\xce\x8f\xcd\x7a\xc8\x1f\xd8\xd1\xdc\xdd\xd2\x23\xc6\xe2\xef\xa0\x62\x15\xef\xe1\x24\xfe\x86\x48\xe5\xe0\x8b\x79\xc3\x77\x10\x5c\x4f\xdb\x05\xd2\x8d\xa2\x15\x27\x82\xbc\xe8\x60\xb8\x5b\x24\xab\x12\xe8\x6c\x95\x6c\xef\x03\xd4\xe8\x62\xfe\xce\x37\x56\xb3\xf5\x2a\x5b\x83\x33\xa3\x73\x60\xb7\x0e\xaf\xd7\x7f\x60\x5f\x70\x5e\xcd\xd9\xd8\x2a\x4e\xeb\x05\x67\x23\x22\x74\xd8\x6d\x90\xe0\x48\x7d\x7a\xa1\x7b\x15\x37\x67\x1a\xaf\x7d\x42\x0c\xcf\x80\x76\xb1\xbd\x3a\x3c\x07\x9a\x3a\xa1\x19\x00\xaa\x07\x9e\xe7\xcd\xf3\xd0\x86\xc7\xfe\xc3\x30\x3f\x71\x18\x78\xeb\x30\x9c\x86\xe2\x0f\x39\x21\x9e\xa5\xd1\xc1\x58\x32\x4d\xa8\x3f\xc1\xbe\x61\xd2\xdf\xd6\x8a\xcf\xfa\x8c\xc7\x14\x14\xf3\xdf\x01\xc5\x01\xb7\x4e\x57\x13\x72\xdb\xca\x66\x51\x6d\x75\x0a\x50\x97\x88\x55\x65\x0a\x7a\x41\xc9\x38\x3d\xea\x8b\xbd\x8b\x2d\xf1\x3b\x88\x03\xe9\x55\xd6\x4f\xae\x40\xb9\xa4\x1d\xbc\x4f\xc6\x1d\x5d\x20\x45\x22\x63\xaf\x34\x8d\x04\xac\xf4\x08\x82\xea\x5c\x9c\xa6\x36\x22\x9f\x13\xca\x95\x72\x5e\x85\x2d\x72\x66\xd8\xd7\xb6\x53\xda\xca\x1a\xab\x87\x31\x32\xda\x70\x1c\x0b\x06\xc6\x51\xcb\x19\xd0\xa7\xc3\xb8\x07\xfa\x63\xd7\xca\x47\x35\x59\x38\x4f\x9a\x75\x94\xcf\x5d\xe6\x3e\xfa\xdd\x0b\xd8\x4a\xd5\xed\xb3\x46\x3f\xba\xd3\x1e\xd5\xa5\xbd\xcb\x96\xee\xd2\xda\x2e\xbc\x11\x7a\x38\xd2\xa4\xc5\xc5\x2d\x90\xbc\x80\x3d\xdc\x84\x69\x27\x5d\xb2\x0d\x37\x4b\xa5\x3e\x0e\x14\xab\xb9\x88\x15\xf3\xb8\xfd\xfd\x7c\x52\x37\x97\xb4\x88\x3b\xf8\x24\x1a\x87\xf1\x68\x74\xfe\xd1\xea\xf3\xc9\x7f\xb2\xc9\xdf\xd7\xe7\x26\x2f\x76\x37\xff\xe4\x1a\xbb\x25\xa2\x35\xf3\x63\x3b\x45\x85\x8a\x8f\xb4\x4c\x82\x48\xab\x15\x6c\x16\x2b\x06\x5f\xfc\x57\x90\x33\x13\x54\x3d\x87\x83\xcf\x1c\xaf\x5b\xe6\xa7\xdd\x8c\xa5\xad\xaa\x8d\xfc\x44\x70\x5a\x0d\xd1\x15\x0a\x18\xac\xbc\x8c\x5e\xb1\xe7\x39\x1a\x6e\x8c\x46\xd2\x3a\x51\x7c\xb9\x06\x77\xc5\x1e\xed\x54\xdc\xd4\x4e\x8d\x46\xcc\xb8\x88\xf1\xd5\x6c\x4d\xb5\xb6\xff\x70\xf0\xb9\xc6\xc0\x71\xfb\xda\x4c\xca\x62\x9f\x14\xf7\xb9\x07\x7c\x58\xdc\x67\xad\x97\x74\x1f\x2c\x2f\x65\xbb\xaa\x2d\xb5\x92\xb3\xb2\x84\xdb\xe6\xab\xf2\x06\x70\xbe\x4b\x04\xe3\x6d\xed\x42\xf2\x61\x8c\x6e\x62\x33\xba\xba\x63\xb2\x48\x1c\xf6\xd3\x7c\x31\x01\xf6\x73\x73\xca\xd8\x34\x69\x1b\x9b\xf2\xd0\xd3\x7d\x4c\x22\x16\x5f\x8b\x07\x8f\xa6\x61\xe3\xb0\xea\xef\xac\x3f\x11\xa7\xab\xd7\x0a\xd5\x5d\x05\x8b\x19\x23\xc7\xc5\xa6\xc5\x8b\x6d\x4e\xdc\x46\x2d\x4c\x20\x08\x2b\x0e\xe6\x19\x3a\x81\x1d\x1d\x58\x71\xa0\x14\x83\xa1\x31\x40\x13\x1e\x21\xfa\xac\x4f\xe8\x70\x43\x9e\x5e\xac\x55\x1e\x8d\x86\xa9\x65\x42\x91\xb3\xbb\x88\x95\x93\x9c\x39\x3a\x11\xbf\xcf\x50\x3e\xc9\xee\x5c\x89\x68\x7b\xad\x89\x0c\x9a\xf8\x79\x0a\xb9\xc2\x7c\x4b\x0d\xad\xec\x7a\x89\xa4\xee\x3b\xcf\x1d\xc8\xd9\x14\x4c\x26\x6d\xf6\xca\xec\xc6\x96\xa0\x7d\x4b\x07\xa1\x88\x60\xb9\xe0\x2d\x43\x95\x9e\x13\x52\x97\xb7\x28\x2b\x75\x6e\xad\xbe\x03\xa2\xf9\x02\x67\x2c\x47\x93\x58\xd4\x01\x84\x96\x56\x1b\xbd\xf9\xcf\xfd\x57\x7f\x3c\x7c\x3a\x3b\x5c\xfc\xcb\xe1\xd5\x05\x51\x48\x53\x2a\xae\x47\xa3\x3e\xa5\x77\x0b\xad\x34\xb0\x1a\x00\x45\xdb\x0e\x28\x9e\x56\x75\xb1\xff\xb1\x2c\xf6\x6c\xc3\xf0\x7c\xd2\x61\xf2\x2c\xf4\x49\x14\xb9\x09\xdb\xd8\x10\xe0\x77\x33\x1a\x5d\xfc\xcb\xd0\x20\x61\x78\xd6\x48\x59\xb1\x44\x56\x91\xe0\x6d\x81\x44\x4a\xdb\x46\x44\x34\xb1\x2d\xb0\x05\xa8\x28\x40\xf1\x06\xbb\x2c\xc8\x8b\xda\xd7\x63\x25\xca\x28\x69\xc0\x3c\x9a\x85\x5c\x29\x46\x34\x3c\x42\x4a\xbd\xf1\x16\x86\x99\x39\x46\x83\x57\x61\x26\x75\xd0\x1a\xff\x2e\x5e\xfd\xd1\x1a\xe1\xd5\xe5\x6c\x34\xba\x9a\x4c\xe8\xa7\x33\xbb\xf4\x75\xa6\x15\xa7\xa3\xd1\xd5\x78\x4c\xff\xdf\xd5\xe1\xe0\x5f\x85\x33\x42\xb3\x29\xff\xcd\xbf\x6a\xcf\x48\x30\x57\x72\x0a\x40\x6e\xa8\xd1\x2d\x9c\x27\x45\xb7\xa8\x67\x87\xa6\xd9\x34\xde\x3d\x4f\xd3\xe8\x6e\xb7\xcf\xd1\x34\xba\x53\xcb\x96\xf9\xf9\x2a\x66\x91\x07\x69\x51\xde\xb8\xb1\x81\x58\x1b\xce\x8e\xcf\x77\x99\x76\x60\x4e\xd2\xb4\xbe\x79\xb6\x9d\xac\xfb\x92\x86\x4d\x40\xe9\xec\xe7\x43\x49\xa7\x66\x2a\xc2\xae\xfb\xd0\x75\x1d\xd0\xd7\xde\x4d\x91\xb0\x9d\x27\x0e\x5a\x8f\x95\x18\x7f\x46\xea\x9b\x3a\x52\x21\xec\x8e\xa6\xb6\x0d\xd8\xa6\xf3\x12\x5d\xa6\xab\x68\xed\x27\x24\xd8\x80\xa4\x6e\x34\x4a\xe1\xaf\x9f\x9c\x34\xf7\x6a\xd8\x67\x7d\x1c\x15\xc9\x63\x68\xdd\x1a\xe2\xb9\xa1\x85\xb7\x2d\xc3\x92\x8c\xed\x8a\x4d\xb7\xf2\x18\x86\x3e\xc1\x1a\xda\x70\x44\x5d\x2e\xb6\xc1\x57\xf5\x56\x60\x61\x5b\x4c\x5b\x66\x9b\x2c\x67\xbb\x2f\x8a\xe4\xf1\x47\xe6\x98\x8b\x81\xc4\x25\x62\x25\x68\x0b\x15\xa7\x9c\x6d\xf2\xa2\xe4\x5f\xc8\xae\xbf\x14\xe0\xa9\x55\x61\x8a\x25\x13\xf4\x52\xcd\x9b\xc2\x0e\x67\xa8\x2a\xba\x37\x81\xb8\x2d\x7e\x47\x73\xda\x61\xeb\xdd\x23\x71\x12\x5d\xa0\xe3\x89\x84\x88\xe3\x07\x1b\x64\xbd\x02\x83\xac\x2f\x3e\xff\xf2\xff\x7c\xf5\xd3\x0f\x3f\xbe\xef\xf3\x90\xb1\x6d\xb6\xd4\xf2\x36\x8c\xb3\xa8\x80\x84\x96\x91\x56\x93\xae\x69\x58\x03\xc9\x7d\x41\x29\xb9\xa0\x5f\xb5\xe1\xc7\x16\xe2\x7d\x34\x2c\x0b\x1c\xe9\x70\xa4\xee\x32\x00\x58\xde\xbe\xfb\x25\x5c\x37\xe8\x86\xe8\xd8\x14\x9f\xa9\xf5\xe4\xc4\x01\x16\x71\x46\x7a\x48\x67\x07\xa6\x86\x12\x42\xc0\xbc\xf6\xad\x02\x1e\xa5\xa7\xaf\x20\x3c\xb8\x5b\x06\x27\xc0\x22\x8b\x10\x34\xf0\xa2\x94\x89\xbf\xab\x98\xed\xb5\xae\x1f\x53\xd2\xfa\x4d\x4b\x15\x83\x27\xa5\xaf\x94\x35\xe9\xa6\x13\x15\x16\xaf\x3f\x71\x8d\xf8\xc4\x8a\x2b\x43\x44\xfb\xb0\x19\x33\x40\xc4\x84\xad\xee\x6d\x3f\x84\x86\x1c\x52\x66\xc4\x3d\xd5\xa8\x61\xa2\x9b\x55\xbe\xe9\x44\xf0\x4d\x49\xf7\x69\x9b\x81\x2a\x0f\x07\xaa\x80\xb0\x03\x63\x76\xda\x0c\x24\x5d\x32\x1d\xf4\x3c\x5a\x58\xef\xb4\x51\x9a\x72\x95\xb2\xde\x61\x60\xa0\x77\x85\x18\x2c\xa2\x2c\xeb\x25\xc0\x2b\xd1\x91\x03\xf6\x3e\x84\x8f\x60\xc9\xd5\x6d\x55\x7f\x05\x6b\xea\x43\xd8\xa7\xc4\x96\xa8\x3a\x56\x09\x49\x97\x74\x11\x5c\xa8\x92\x29\xcf\xd3\xa2\x8c\xf9\x37\x60\xa5\xac\x78\xfd\x36\x81\xd9\x0f\xf0\x7c\x99\x58\x7b\xfb\x8c\x81\x43\xd2\x81\x6a\x14\x41\x6c\x54\x12\x1f\x66\x72\x10\xbc\xb4\xd3\xc6\x91\x77\xe5\xcb\x11\x86\x73\x6c\x13\xac\x1d\xc2\x63\x75\x49\xf6\x8a\x8d\xed\x43\x3c\x1a\xf5\x08\x91\x8d\xa7\x8b\x3c\xec\xf3\x93\x07\xd4\x21\x9e\xd2\x54\x4e\x31\xcb\xfb\x07\xd4\xf2\x98\xc3\x76\x3d\xe7\x5a\xbe\xed\x3d\x63\xcd\x63\xac\x2b\x77\x9f\xe3\x3e\xf7\xbc\x3e\x21\xa8\x74\xd5\xfb\x10\xed\x83\xc6\x33\xdf\x89\x2f\x9e\x14\xe2\xf6\x40\x8e\xdb\x83\xdf\x84\x10\xfb\x6c\x84\x8e\x09\xc1\xb3\x5b\x61\x9c\x10\xec\xe2\xf6\x05\xcc\xc8\x93\xea\x0b\x43\x43\xc2\xc1\x52\x26\xa9\xe6\x3c\xf7\xbf\x14\x6b\xa8\x2d\x90\x0d\x76\xe9\xb9\xd7\x15\x3f\xa0\x6e\x73\x77\xbe\x00\x79\x2d\xf1\xbe\x06\x64\x87\xa4\x50\xf7\x73\x6b\xcb\x0c\xc9\xdb\xc6\xd3\x5d\xf3\x17\x4c\x19\x53\xcc\x8d\x75\x5b\xab\x31\x06\xee\xcd\xd9\xf8\x9c\x58\xfe\xde\xef\x35\xe6\x87\x67\xa9\x6f\x7e\x4b\xd7\x70\x04\x2b\x77\x8c\x1d\x87\x08\x9e\xd2\x3f\xef\x13\x56\xab\x6b\x2e\xb0\xda\x8b\x51\x35\x3b\xe8\x40\x3f\x00\x76\xdd\x1a\xef\x06\x21\x81\x4b\xd2\x7f\x41\x31\x79\xf3\x3b\x67\xde\xb9\xfc\x19\xba\x06\x7e\x0e\xf7\xc6\x0d\xa6\x51\x50\x85\x36\x19\xc1\x3a\xa0\xc6\x08\xe2\xd4\x54\x5a\x2b\x2b\x3e\xab\x2e\xd5\xb6\xb6\x54\x0d\x59\x9d\x7f\xf5\x6c\xc4\xa4\x1d\xf4\xb5\xfb\x89\xa8\xd5\x79\x93\x4a\x7b\x06\xb5\xe0\xdf\xc0\xf3\xb4\xc6\xaf\x1b\xb0\xd5\x87\x8c\x5e\xbd\x0b\x8f\x71\xa3\x37\xd4\x23\xfb\x00\x71\x15\x6e\x8d\x6a\x39\xf0\xc6\x9c\x18\x2a\xc0\x50\x72\x1f\x42\x97\x75\x9d\x2d\x87\x20\x6e\x13\x3e\xa0\x31\xf3\x4f\x30\x21\x18\x00\x46\xa3\x17\x0c\xe7\x7b\x5b\x0a\x4a\x06\x49\x80\xd1\xc8\x37\x91\x9d\x3a\x97\x71\xd9\x44\x65\x53\xf4\x88\xb2\xb0\x2f\xc4\x74\x56\xc4\x62\xda\x84\x91\x26\x35\xd3\x80\xa0\x06\x45\x33\x54\x71\x28\x17\xe9\xb2\x51\xb3\xfb\x36\x89\x7a\xaf\x8c\x53\xac\x0b\x09\x22\x5f\xba\x24\x6a\xbd\xb4\x0b\x4e\x1f\x1b\x38\xea\x84\x79\x5b\x7d\x85\xb6\x12\x0e\x9d\xe4\x9e\x28\x1f\x4d\x3f\x7c\x72\x5c\xfc\x03\xd7\xea\x33\x2b\xb1\xf9\x9d\x2b\xb1\x51\x2b\x21\x87\xe8\xa2\x39\x0b\x51\xb6\xf0\x82\x4b\xc4\xba\x0d\xed\x77\xcf\x18\x05\x41\xc8\x18\x40\x61\xa8\x51\xbf\xec\x93\x39\xcb\xf8\x73\x58\xab\x81\x61\xe3\xaa\xd2\x41\xa7\x20\xb8\x18\x6e\xaa\x38\x85\x6f\xaa\x1f\xee\x78\x99\xee\x8a\x7b\x30\x38\x90\x5e\x05\x2e\x33\x1f\x78\x1e\x95\xad\x7f\x82\x68\x57\x7d\xad\x87\x7d\xcd\xdb\x38\xd5\x45\xd7\xfd\x36\x28\xad\xa1\x37\xc7\xd2\xee\xdc\xe5\x28\xdb\xeb\x8b\x37\xd9\x14\x62\x6f\xc1\x00\xd1\xdc\x44\xd9\x53\xf4\x2e\x70\x4f\x94\xbd\x05\x0b\xa3\x29\x58\xce\x4f\x20\x58\x15\x8b\x2a\x1f\x43\xd5\x91\x63\xf7\x42\x85\x8e\x0c\x47\x6e\x1c\x26\xad\x61\x9d\xc2\x14\x28\xbb\xe1\xac\xba\x2d\xb9\x75\x9f\xb5\xbd\xf1\xfb\x27\x0d\x91\x2b\xdf\xe4\xb5\x85\x80\x61\x6d\x3d\xb9\x98\x13\xb4\xfd\x87\x38\x97\xf3\x99\xe4\xf5\x9b\xd2\x1e\x77\xdc\x32\x96\xa7\xb5\x19\x90\x76\xae\x0f\x38\x4e\x7f\x98\xb2\x71\xc7\xcc\xbb\xc0\xa6\x73\x92\xcf\x74\xde\x35\x9b\x46\xdf\xcd\xe5\x6d\x2f\xe1\xc9\x0b\x70\xc1\xa6\xb1\x40\x4b\xdf\xb3\x1b\x2e\xe5\x06\x13\x3d\x97\x89\xec\xdc\x73\x04\x19\x2a\x9d\x93\x0e\x19\x65\xdd\x04\x13\x66\x83\x85\xeb\xe4\x04\x61\xce\x66\x6b\x85\x6b\x21\x98\x3a\x23\x34\x72\x0c\xc6\x61\x04\x0b\xf3\x53\x99\x8a\xc3\x43\x97\x9d\x38\xbe\x78\x5e\xf2\x8d\xbd\x3d\xeb\xd2\x8d\xdd\xf5\xea\xf1\xb5\x60\xa5\xcb\x9f\xbb\xd6\x4e\xbe\x8d\xd0\x2e\x4c\x6b\x5f\x1b\x9a\x7d\x30\xe7\x3b\x69\x5a\xee\xaa\xf7\x25\xa9\x67\xf4\xfa\x46\x22\x8c\xe2\xc4\x60\x78\xfe\x91\x54\x17\x71\x32\x1a\x71\xcb\xda\x5c\xf9\x12\x93\x05\xaa\x76\x98\x07\x4a\xf8\x16\x73\x9d\x4a\x8f\x2e\x57\xac\xe6\xca\xf1\xbb\x05\x65\xb2\x65\x93\x32\x75\x85\x0d\x90\xd0\x46\x3b\x8d\x8f\x46\x6d\x7f\x73\x10\xfb\x18\x23\x77\x99\xec\xec\x7f\xce\x31\xbb\x2e\x8a\x5d\x9d\xed\x4f\x84\x4d\x19\xf2\xd1\xe8\x3c\xe1\x55\x5d\x16\x8f\xae\xfd\xf4\xe1\xe0\xbb\x4e\xd7\xaa\xb3\x0e\x9f\xeb\x5e\x8b\xe9\x0e\xf1\x3a\x18\xda\xa0\x86\xf9\x71\xcf\xdb\xf6\xc7\x56\x09\xcf\x41\x09\x66\x95\xc8\x14\xa0\x56\x09\x38\xdd\x62\x0c\x14\xcb\x03\x5a\x49\xe3\x6d\xcf\xe9\x66\xa5\x2c\xcf\x6a\xdf\xd3\xb3\x82\xb8\xb9\x2f\x16\x3f\x37\x65\xcb\x18\x78\x3a\x2b\xf2\x60\x38\xa3\x70\x08\xc4\xf7\x03\x8c\x99\x89\xe9\xc4\x8a\x32\x18\xce\x69\xcd\x6f\xf6\x82\x08\x0a\x3e\x79\x9d\x64\x77\x03\xc0\x58\xa1\x1e\xc5\xa0\x2c\xc4\xd9\x54\x8f\x97\x1d\x75\x26\xac\x2c\x8b\x7b\xef\xf2\xf5\x79\x92\xdd\x75\x56\xc0\x90\x96\xaa\x02\xfc\xff\x09\x95\xa0\x29\xbd\x94\x07\x32\x80\x40\x9d\xd5\x3b\x2e\x2e\xf4\x84\xef\xd8\x63\x30\xc3\xec\xff\xc3\x39\x95\x16\x1c\x1c\xc6\x7c\x97\xf1\x7b\xc8\x63\xff\xa4\x67\x82\xa9\xe4\x14\x1d\x10\xcc\x1a\xce\xc2\x62\x6d\xc3\x66\xb8\x4a\xc5\xd2\xa8\x6d\x55\xf2\x67\xcc\xba\xdd\xd2\xa3\xc4\x0d\x33\x7a\x78\xd8\x70\x95\xde\xdf\x57\xee\x88\x1f\xab\xe1\xb9\x1c\x82\x2a\x1d\x8d\x98\xcf\xec\x10\xd1\x9d\xb5\xc8\xb2\xb3\xd8\x4a\xc6\xe7\x0c\x4f\xb2\x17\xad\xea\x6a\x7d\xa4\x20\xa1\xf5\x15\x17\x16\x9f\x00\x57\x8b\x15\x86\x4d\x11\x3f\x60\x5f\x82\xe1\xfc\x48\x9b\x34\x68\x96\x57\x35\xcb\x63\x08\x4e\xa9\xaf\x41\x73\x89\x34\x5d\x7a\xd5\x48\xda\xe1\x0a\xff\xaf\x7a\xf5\x7f\x07\x58\x79\x70\x73\x5b\xd5\x83\x88\x0f\x20\x7e\x5c\x9a\xf1\x64\x70\xbf\xe5\xf9\x40\x6c\x22\x24\x18\xcb\xf2\xcd\xc0\x1b\xeb\xbd\x1a\x7b\x83\x22\x87\x48\x8f\x92\x8e\x53\xe3\x19\x20\x8a\x19\xca\x74\x43\x96\xef\xa4\x36\xf7\x97\xd6\xa2\x26\x20\x23\x28\x08\xa5\x23\x71\x3a\x99\x2c\x94\x9b\x11\x5f\xa5\x6b\x41\x15\x6a\x3b\xa4\x0d\xe9\x63\x56\xad\xa1\xd1\xce\x35\x70\x85\x2b\x78\xe1\x48\x86\x50\x67\xd2\xf3\x6e\x58\x7e\xcb\x76\xde\x30\xdc\xe0\x10\xb6\xa1\x8e\x70\xb0\x59\x5a\x01\x09\xbc\x40\x49\xde\x3c\x9a\xb5\xeb\x40\xf8\x01\x55\xa7\xb8\xad\xbd\x96\xc9\xb3\xbf\x1d\x7b\x1f\x3a\x66\xf8\xb2\xab\xf0\xb0\x3a\xcc\x3e\xbc\x43\x18\xa6\x12\xd3\x1d\x3b\x1b\xe0\x89\x78\xdf\xe5\xc5\x62\xd7\xa7\x4f\x1a\xb5\xc8\x25\x34\xf8\x4e\x70\x06\x78\x52\xd2\xec\xe1\x9d\xc0\x35\x4d\x92\x79\xc3\xd5\x7d\x5d\x75\x90\x3b\x06\xc3\xb6\x5a\x49\x2c\xd0\xe9\x2a\xdd\x1e\xab\xf5\x99\x96\x36\x4a\x12\x15\x91\xb8\xa9\x01\x0d\x8e\x46\xed\x20\x22\xea\x8d\x2f\x7f\x85\x4f\xa0\x35\x94\x4f\x54\xdc\x9c\xea\x41\x5c\xf9\x1d\x93\xc4\xf8\x8a\xad\x61\x2b\x9e\xe7\xe9\x68\xf9\xc1\x9b\xb1\x3a\x74\xa7\xda\x8b\xd1\x88\x21\x15\xe0\x94\xda\x91\x8e\x13\xf2\x14\xaf\xd8\x7a\x18\x26\x62\xc8\x2b\xb6\x0e\x13\xd2\x1e\x17\x40\x55\x47\xe8\xb0\x68\x60\x21\x1b\xf8\x86\x85\x68\x96\x51\xc0\xfc\xc8\x95\xd3\x10\x43\x25\x58\x70\xa8\x07\x8f\xe6\x94\x02\x05\x35\x3b\x6b\x76\x64\xed\x96\xb3\x5e\x3e\x21\xf4\x85\x5f\xa5\x31\x21\xd4\x99\x81\xd4\xb0\x80\x97\x89\x44\xbd\x2b\x7d\x86\xc3\x10\x03\xe6\x2c\x25\xcd\xa6\xc2\x78\xac\xc1\xc3\x3e\x9e\xd6\xd9\xde\x27\x0d\x83\xe0\xc3\xc1\x83\x96\xb1\x45\x7d\xa0\xb0\xcb\x2e\x09\xa1\x6e\xe0\x3b\x59\xc2\x63\x9d\xc5\x9c\xb6\xea\xd2\x58\x9f\x41\x09\x6d\x8d\x02\x50\xd6\xa9\x0f\x29\x52\xa8\xdb\xe1\xb6\x3d\x40\xd1\x1b\x2a\xfb\x8e\xcd\x0f\x41\x39\x21\x81\xaa\xd0\x38\xa3\x59\xf5\x06\x57\xed\x5d\x79\xeb\x48\x59\x9c\x44\x80\xf6\xcd\x46\xb4\x0c\x54\x2e\x37\x53\xa1\x5b\x87\x33\x13\xb2\xd5\xf9\x08\x20\xa4\xff\x4e\x58\xc4\x6c\xa9\xff\x3b\xe1\x50\xdc\x13\x27\x00\x71\x4e\xe8\x30\x76\xb7\x41\x1b\xff\x0d\x5e\x06\x5e\xe2\x0b\xcf\xc3\x97\x40\x63\x2f\x84\x2f\x1c\x72\x13\xc0\x94\x6e\xa5\xab\x63\x00\x30\x53\xa1\xdf\x77\xae\xe1\x54\xa3\xd8\xb7\xe6\x6e\x4a\x4d\x89\xca\xb3\xe7\xab\x40\x42\x92\xbc\xec\x33\x3e\x89\x88\x66\xd2\xb5\x99\x72\x4b\xde\xe7\xe4\xc4\x68\x45\x44\x6f\x56\x97\x21\xfb\xba\x59\xc9\x61\xa2\x44\xc6\x86\x1e\xa2\xa9\x4c\x99\x2a\x90\x8b\x8a\x0b\xb1\xe1\xf5\xcf\x6f\xbe\xf2\xad\x49\xa2\x38\x86\xd7\x7a\x86\x34\x95\xcc\x77\x06\x02\xd5\xa6\x6f\x8f\xb1\xf9\x4c\x78\x15\x97\x59\xc4\x93\xe8\xd1\x54\x54\x5b\xa2\x79\x95\xd1\xc8\xf6\x9a\x97\x46\x05\x92\xf6\x69\x87\x44\x77\xfa\xd0\x2c\xce\xb2\xbb\xd8\x22\x9c\x21\x1e\x45\x6b\xc9\x82\xee\x76\x34\x0b\xcf\x7f\xad\x96\xec\xb6\x2e\x96\xbf\x56\xcb\xf3\x8c\x5e\x85\xd2\x24\x74\x4b\x16\x57\xa3\x91\xbf\x0d\xb7\x5a\xce\x90\x51\x0f\xb0\x31\x26\xa8\x49\x4d\x88\x5b\x90\x59\xda\x99\x3c\x68\x92\x55\x7b\xc1\xe2\x78\xd1\xae\x88\xaf\xbd\x23\xb1\xa3\x27\x74\x1f\xe0\xba\x1d\x46\x48\xb3\x45\xcb\xb4\xa1\x50\x69\xd5\xc0\x30\x63\xc6\x44\xd8\xe5\x1f\xfa\x7c\xf1\xb0\x05\xda\x46\x39\x00\x2f\xb6\xe5\xda\xf8\xe1\x9a\xc4\x39\xbb\xb0\x15\x27\xe2\xc6\x2e\x92\xe2\xe8\x2c\xf5\xaf\xf0\x68\xe5\xe1\x96\x16\xed\x9e\x5c\x5e\x8a\x2c\xb6\xa1\x17\x15\x75\x5d\xdc\x78\x61\xb8\x1d\x8d\xae\xa7\xf8\x34\xbe\xb9\x2c\xe4\xcf\x25\xac\x3b\xf2\xb8\xb2\x4e\x5d\xec\x27\x37\xaf\x0b\xf1\x77\xa9\x9a\xab\xa0\x26\xb2\x0a\x3c\x8c\x77\x97\xc5\x14\x1c\x6a\x75\xe4\x13\xf8\x23\xeb\x40\x7a\x91\xdd\xeb\x02\x7e\x2c\x65\xfb\x60\x4b\xdd\x08\x16\xb9\xb3\x87\x20\x67\xd8\xeb\x79\x7d\xc9\x76\x31\x68\x1b\x12\x99\x1c\x67\x4b\xaf\xe9\x8e\xde\x28\x67\xa2\xfd\x7e\xf7\xf8\xa3\x82\x3a\x7f\x4f\xb7\xb8\xc4\xbf\xb5\x85\x8e\xdc\xc2\x71\x0b\x7e\xca\x7f\xd2\x1b\x73\xdc\x2d\xca\x5b\x72\x09\x89\x30\x19\x7a\xbd\xb2\x3b\xee\xf3\x67\xd4\x2d\x75\xb6\x6f\xab\x5a\xd2\x1e\xed\xca\x6f\x1f\x66\x9a\xf0\x5b\x2b\xbc\x97\xbb\x20\x0d\x6b\x48\xa3\x08\x95\x28\x8b\x87\x49\x13\xe8\x52\xbb\x48\xfa\x1e\x6e\x8c\xd0\x3b\x41\xa9\x30\xa6\xb8\x98\xe0\x81\x9d\xcf\x08\xdd\xf6\x55\x91\x39\xa6\xe6\x33\xb2\xc8\xaa\xef\xd9\xf7\xfe\x86\x8c\x46\xfe\x06\xec\x90\xe1\x79\x4b\x00\x1b\xcc\xd0\x0b\x6b\x3f\x0e\x37\x34\x92\x79\x6f\xb6\x94\xb5\xb3\x23\x89\xe1\x51\xc3\x19\x40\xee\x26\x27\x25\x53\x62\xd0\x06\xa8\x11\xca\xe2\x16\x72\xb9\xd6\xc5\x5e\xe6\xbf\x71\x8a\x51\xbf\x20\xd6\x31\x22\x54\x5a\x4d\x25\x4d\xbd\x5b\xd6\x5e\xa8\xab\xf6\x42\x2d\xe4\x19\x8a\x47\xa3\xab\x61\x98\x02\x9b\xa1\x73\x54\x8d\xd3\xc9\x55\xf3\xfc\xff\xbb\x3c\xa6\xa8\xc0\xe1\xc9\x57\x7c\x57\x33\x3f\xa6\x11\xcd\xe8\x15\x59\xe0\x09\x5a\xaa\xe5\xc0\xc7\x40\x2e\x13\x9c\x52\x19\x3b\xe7\xbc\x2e\xf6\x07\x3c\xa7\xda\x9f\x89\xde\x84\xbb\xe5\xc5\x99\x3c\x85\x7c\x9c\x05\xe2\x41\x9c\xec\x74\x7c\x45\xf3\x70\xb7\xf4\xac\xe9\x78\x81\x67\xcf\xc4\x5b\x24\x2a\x5d\x51\xa4\x6d\x8c\x00\x59\x7f\x5e\x96\xc5\xbd\x7f\x43\xc5\xe4\x57\xf9\x9a\xee\x5a\x9a\x05\x53\xad\x95\x16\x10\xcf\x2c\xf4\x80\xc8\x3d\xd6\x78\x03\x24\x6c\x7f\x98\x9d\xf9\xf3\x09\x3b\x8f\xc8\xd8\xfb\x67\x4f\x57\x41\xfc\x04\x15\xc5\x4d\xd1\xd2\xd6\xc8\x8b\xb5\x47\x01\x88\xa0\x1e\xe9\x45\x97\x0c\xec\x82\x29\xcb\x58\x57\xe0\x26\x63\x0d\xa9\x8b\x60\x5b\xdf\xec\x96\x18\x53\x19\x73\xf3\x7a\x6b\xb1\x24\xcc\x55\xd0\x8a\x63\x0d\xd4\x73\xb1\x1f\xe0\x3e\x0c\xc4\x70\x07\x52\x17\xf4\x8c\x4d\x99\xe5\xa4\x84\x24\xff\xd0\xc1\x56\x10\x51\x45\x5e\x89\xd4\x60\x2e\x81\x82\x1a\x66\x5c\x9f\x77\x93\x0e\x9d\xfe\xb2\x06\xcb\x49\x8d\xac\x43\xd7\xa8\x68\x4c\x75\xb6\x47\x3b\xef\x86\x75\x5b\x27\x15\xd7\xc2\xa9\x1b\x42\x87\x9b\x53\x3e\x2f\x83\xf4\xc5\x21\xdb\xd3\x97\x63\xd1\xe4\x03\x4d\x03\x95\x2c\xa1\x4b\x08\xed\x6e\x9d\x92\x7f\x3c\xa3\x6b\x5e\xf8\xca\xd7\x14\x24\xb3\x40\xdc\xb4\xd2\x61\xda\x0a\x18\xa5\x51\x9b\xc8\xfa\x44\xa6\x70\xef\x79\x4f\xdb\xdd\x6b\x67\x21\x59\xa3\x75\x4e\x0c\x89\xdd\x97\x61\xc7\x9c\x8d\x96\xdc\x43\x91\x17\xae\x29\x64\x18\x35\x2c\xad\x16\x92\xd9\xc3\x1c\x6c\xde\x17\x3f\x7c\xf5\x57\x74\x3d\x44\xef\x1f\x08\x1f\xd3\xa7\xfc\xc5\x50\xe2\x1c\xc9\x09\xc8\x35\x67\x8b\x7f\x38\x7d\x82\x17\x01\x97\x1a\x62\xcc\x1d\x47\x31\x8a\x47\xc0\x25\x2d\x33\x81\xbc\x73\x47\xa2\x6c\x54\xa5\x64\xf3\xed\xbf\xff\x49\x3b\x94\xc5\x36\x2f\xd7\x7a\x4f\x37\x61\xb2\x74\x93\xc7\x05\xe9\x52\x8c\x2d\x88\x4c\x9e\xb7\x6d\x28\x13\x8c\x05\xc9\xb2\x57\xcb\xad\x6d\x71\x0f\x87\x86\xba\x57\xa7\x20\x8b\x2c\x83\x5d\x72\xa4\x99\xf8\x36\x4e\xd3\x98\x89\xc1\xb3\xf8\x26\xce\xd4\xbc\xc0\x02\x9f\x1c\x03\x48\x28\xab\x55\x8b\xf6\xa2\x6d\x69\x06\x79\x97\x1a\xdb\xd9\xa4\xaa\x1a\x88\xda\xb8\x86\x18\xda\x91\x2d\x4d\x8e\xb9\x71\x24\x3f\x6e\xa7\x93\x1b\x47\x38\xd4\xf3\x8b\x49\x7c\x7e\x71\x54\xf4\xa4\xdd\x6e\x92\x9c\x6c\x20\x49\xc7\xce\x2f\x9d\x5f\x4c\x92\xf3\x0b\x27\x7d\x5d\x7c\x0c\x5e\x54\x51\x7d\xe7\xd8\x5a\x86\xce\xeb\xb7\x63\x2d\x10\x33\x36\x52\x0a\x6a\x8b\x1e\x43\x6c\xcb\x2d\xe0\x12\xf8\x7a\xf4\x18\xdd\x2a\x07\xa9\x82\x39\x1c\x66\x16\x23\xd9\x4b\xd5\x67\xa9\x7f\x0e\xe7\xe0\x20\x86\x23\xef\x7c\x46\x94\xc8\x1b\x57\x3b\x9d\xa8\x54\x72\x34\xd3\x34\x88\x2a\x1a\x27\x8b\xed\xeb\x0d\x50\xf8\x70\x66\xc2\x0d\x26\xd2\x0c\xb2\xcb\x0d\x26\xd3\x94\x0b\x2a\x8e\xa2\xa9\xa0\x8b\x27\x19\xda\xf3\x48\x27\x35\xb9\x27\x29\xbd\x96\x3f\xc7\xe9\x38\x5e\x5c\xbd\xde\x20\x09\x83\xa7\x35\xc4\xa7\xc9\x55\x70\x7d\xb9\xc1\x73\x0c\xbd\x5b\xef\xc6\x1b\xdc\xac\xc9\xb5\xf2\x50\x1c\xf0\xd6\xbe\x75\xa3\xe0\x66\x8c\x7e\x25\x88\x95\x4b\xad\x0f\x88\xeb\x2c\xde\xc4\xbc\x87\x83\xdf\xc1\x34\xc7\x53\x78\xbd\x94\x7f\x91\x2d\x86\xbc\xeb\x81\x2c\x6a\x1f\xb2\x9f\xdf\x7c\x15\xba\x26\xbc\x03\x36\x0e\xff\xdf\xff\xf3\xe7\xfc\xb3\x33\x24\x41\x59\x9e\x14\x37\x3e\x21\x32\x31\xa2\x46\x13\x1b\xae\xb0\xc8\x17\x8f\x6f\x12\xb1\xb1\x7a\xf4\x0d\xf7\x96\x6c\xdf\x19\xfd\x44\xdc\xdb\x3a\x2e\xa8\xa8\xd4\x88\xff\xa0\xd4\x97\x84\xce\x87\xa1\xe1\x53\xa4\xe3\x61\x4b\xd9\x64\xeb\x8c\xfe\xaf\x6a\xeb\xea\x9d\xe2\x22\xaf\xb2\xaa\x1e\x14\xe9\x80\x3f\xb0\xb8\xde\x3d\x0e\xe6\x82\x1c\x9a\xec\xf8\x1d\xdf\x0d\xe4\x9e\x0c\x3d\x57\x14\x2e\xbe\xda\x60\x60\x5c\xc2\xb1\x11\xbc\x17\xdf\x5a\xbf\xe5\x05\x84\xe2\xdd\x06\x3d\x87\x1a\xd6\x96\xb9\x36\x8b\xda\x61\x37\x2c\x65\xa6\x5b\x5d\x7a\x70\xf6\xd7\x9f\x77\xf9\x1b\x7d\x2d\xdf\xf6\xb6\xb2\x9f\x4e\x3b\x2c\x39\xc1\x83\x17\x98\x23\xfc\xa5\xc2\x52\xfa\x3f\x2e\x29\x25\x34\x5a\x1a\xb1\xe8\x34\x46\x0b\xd1\x46\x01\x6d\x09\x41\x97\x31\x2a\x31\xfc\x58\x9c\x27\xe4\xab\x63\x82\x67\xab\x2d\xb7\x5f\x9a\x2a\x81\x69\xd8\xd8\x37\xb4\x85\xe8\x31\x9b\x76\x84\xad\xb5\x65\x91\xa0\x08\xc1\x2c\xe1\x0d\xc3\x69\xc7\x96\x7c\xea\x8d\x19\x2e\xb1\x24\x5f\xbf\x32\xeb\x21\x5f\x50\x26\x0f\x21\xfe\x35\x04\x3c\x3e\x23\x81\xc9\x14\x44\xab\x27\xad\x03\x57\x05\xb6\x05\xc4\xd1\x4d\x38\x21\xc1\x7c\x61\x3f\x28\xe3\x24\xf9\xd8\x65\x9e\xa4\x5e\x3d\x6f\xa0\xa4\xfa\x94\x26\x4a\xff\x63\xe6\x2e\xfb\x62\x0f\x22\xf3\xff\x12\x73\x17\xd5\xd9\x7f\x99\xb9\x0b\xda\x9b\xe8\x6e\xd1\xde\x04\xc3\x6c\x99\x35\x6b\xab\xeb\x7f\xc4\x06\x26\x9d\xa0\xda\x87\xab\xca\x23\xa7\x93\x08\xd9\x24\x5d\xdf\xee\x1a\xdf\xdb\x27\xcb\x6c\x45\x19\xed\x29\xcd\xae\x0a\x69\x20\x73\xa4\x7a\x5e\x8f\x1d\x8b\x9a\xdd\x29\x3b\x16\xd7\x7e\x65\xfb\xaa\xd1\x56\xde\xa7\x97\xaf\xcf\xb7\xaf\x2e\x3b\xfa\xd6\x9e\xa8\xae\x81\xcb\x91\xd8\xc7\xf8\x65\x53\xd7\xd5\x89\x6b\xbd\xea\xc0\xfe\x3f\xae\xad\xfe\xbd\x82\x06\x4b\x21\xac\x35\x00\x46\xf8\xe0\x2e\xd7\x4b\x85\x0f\x8d\xc6\xc6\xab\xd7\x84\x4c\x36\x52\x74\x08\xa4\xd4\xd5\x71\xf3\x00\xc4\xfa\x5b\x28\x14\x37\x1f\x8d\xbb\x25\x1e\x9d\xe2\x8e\x81\x64\xe2\xbb\x27\x08\x9f\xf6\xc9\xe1\xd0\x5f\xa1\x43\xb7\xf4\x21\xcc\xab\x89\xdf\xad\x97\xbb\xcd\x00\x9d\xdc\x48\x4d\x41\x46\xdd\x14\xa4\x4d\x3f\xea\xa5\xef\x26\x1c\xa3\xa9\xac\xb0\xd4\xbf\x64\xa2\x79\x20\x1e\x75\x61\xd3\xae\xfd\x1f\xa7\x82\x14\xf5\x63\x5f\x19\x72\xa9\x17\xf6\x83\xba\x32\xe4\x63\xd7\x95\xa1\x5e\x3d\x7f\x65\xa8\x3e\x7f\xdf\x95\x11\x9b\xe4\x6c\xa7\x7c\xf4\x91\x81\xf9\x5a\x5b\x98\x31\x3f\x46\x27\x5e\xa7\xf2\x12\xf9\xe4\xa0\x69\x7f\x66\xa3\x94\xa8\x9d\xdc\x4d\x19\xbd\x84\x0d\x82\x59\x7a\xd5\x79\x1e\x19\x7b\x83\x69\xce\xee\x06\xbb\x6c\x70\x39\x60\xca\x24\x1a\xf8\xe8\x2a\x5c\x49\x1d\x19\xd6\x37\xcf\x18\xcb\x1a\xc9\xa7\x96\xaf\x3f\xca\x5c\x75\x4c\x3c\x67\x7e\x60\x20\x85\x25\xd3\x48\x35\xa8\xf6\x8f\xcd\x24\x2a\x65\x11\xf3\xaa\x72\x6c\x8b\x4a\x9e\x96\xbc\xda\x2a\xe9\x96\xac\xd2\x0a\x4d\xf5\x3b\x6f\x69\x33\x92\xce\x7b\x3a\x1e\x8d\xe2\x46\xba\x1f\x6b\xec\x78\x19\x47\x27\x2e\xe3\x58\x5c\xc6\x31\x46\xeb\x8d\x5a\xb7\x63\x64\xd9\x6c\xe2\xd2\x07\xf3\x99\xd8\x4f\xe7\x94\xbf\xb5\x57\xb7\xef\x30\x39\xab\xdd\x74\x12\x39\x1c\x74\xda\x6c\x03\x97\xcd\x4a\xf4\x19\x11\x10\xd6\x22\xee\xf0\xe4\xe6\xb4\x15\xe6\x35\xe6\xd2\x50\xf9\xb6\x69\x12\xce\x16\x2f\x02\x31\x07\x96\x14\x0a\xb4\x97\x00\x88\x4f\x9d\x5d\xbc\x7b\xf6\x18\x5a\xda\x24\xdd\xd7\x49\x07\x5d\xa0\xb4\xc4\x56\x4e\x4c\x01\xc0\x3d\xce\x39\x22\xd3\x1b\xd6\xf2\x92\x8f\xec\xa8\x66\x12\x40\x8c\x0d\x7a\xd4\xb4\x56\x3f\xff\xdb\x47\x53\xcb\x86\x9c\xf9\xc6\x26\x09\x72\x94\xaa\x48\x50\x69\xcb\x96\x7b\xb5\x4a\x01\x88\x40\x74\x91\x50\xbe\x5e\x1f\x0e\x48\x4a\x4f\xab\xa2\xb4\x4c\x21\x80\xbe\x53\x98\x6c\x35\x5b\x4f\x04\x73\x7f\x6c\xc7\x64\x53\x42\xc0\x6a\xba\xbf\xad\xd0\x72\x4b\xac\x1a\x55\xd1\x04\xec\xf2\xf9\x1a\x5c\x20\x23\x27\xc7\x05\x9c\xc1\x7e\xd9\x45\xef\x32\xbb\xc1\x17\x71\x10\x16\x65\xd1\xd8\xe7\x24\xec\xa8\x3e\x8e\x27\x5d\x1f\x51\x72\x44\xe5\x1c\x29\x27\xa8\x8d\x1b\x70\x5e\x4a\x2a\x65\x23\x32\x2d\xf8\xb7\x41\x6f\x18\xc6\x52\xc6\x65\xf0\x4f\x74\x19\x2a\xf3\x89\xc1\x66\x18\xfa\x2c\x4c\x57\xa9\x8e\x4f\xb4\x56\x26\x1f\xd0\x37\xab\xb9\xcf\x40\xc2\xb5\x19\x8d\xa2\xd7\x5c\x2c\xaf\x7d\x60\x7b\x30\x29\xb0\x74\x3e\x5a\xac\x32\x63\x8b\xca\x26\x93\x05\xd9\x0c\xc3\x74\xc5\xd6\xa3\x51\x74\x19\x72\xf8\xe1\xeb\x94\xf5\x7c\xc5\xc6\xf3\xf5\xe1\x20\xbe\x24\x7e\xb5\xc6\x22\x5a\x36\x76\x51\xbd\x73\xb8\xf4\xf6\xe0\x22\x77\x64\x56\x34\x58\x75\x38\xc6\x9f\xb8\x61\xbe\x3f\x19\x47\x10\x14\x5c\x86\xda\xb4\xaa\x61\xe0\x70\xf9\xfe\x13\x0a\xc9\xfa\x88\x0c\x17\x51\xf9\xde\x2e\xf3\xba\x32\x3f\x2c\x12\x15\x51\xa2\x19\x42\xc8\x84\x4f\xf3\x93\x30\x31\x21\xd6\x76\x99\x15\xed\xae\x2b\x55\xa4\x1d\xf6\x4a\xad\x83\x7b\x31\x35\x16\x0b\xa6\xef\x3a\xc6\x37\x10\x84\x9c\xc5\xcf\x79\x9d\xed\xba\x6e\x5d\xfa\x5c\x06\x11\x87\xc6\xd1\x03\x59\xb8\x8f\x8a\x98\xd1\x05\x0e\xa5\x13\x35\x5f\x3e\x4f\xeb\x98\x9e\x8d\x0f\x4f\x33\xa9\x80\xb3\x32\x76\x28\xa8\x8e\x54\x03\xa2\x27\x15\x6e\xb9\x2f\xcf\x80\x46\x9c\x2a\x6c\x66\x44\xa3\x7f\x2c\xcf\xc0\xef\x75\x49\x61\x11\xe6\xdd\x6e\x94\x39\x6c\xf6\x4b\xa2\xf0\xc7\xed\x43\x64\xc7\x60\xfa\x50\x57\x8e\xe7\xcc\xd1\xec\x0f\xd0\x38\x8c\x0c\xe4\xdf\xee\x64\xcc\x36\xfb\x9c\x10\x8f\x40\x0e\x06\xf7\x8a\x82\xe4\x5b\x90\x97\xa3\x71\x55\x25\x61\xd2\x97\x9a\x43\x87\x11\xa5\x43\x1d\x24\x14\x8f\x6d\x47\x62\x20\xa9\x71\x88\xdd\x04\x3a\xc1\x8e\x55\xf5\x80\xc9\x74\x0b\x0d\x85\x2c\x2c\x7e\x33\xf2\x0a\xdc\x61\xb6\xfa\x56\x19\xe1\x75\xd5\xe6\x50\x5b\xcc\x8d\xdb\x91\xdf\xa3\x17\x68\x73\x21\x4c\x63\x7f\x0c\xec\xad\x1d\xfe\x57\xe1\xd5\xc8\x46\x3a\x1e\xd1\x79\x14\x74\x85\x2d\xdd\xea\x78\x38\xf6\x81\x31\xa3\x7b\x12\x1b\x1d\x58\x3a\x6d\x98\x56\xe7\x1a\x44\xcd\x46\xda\xda\xa7\xa3\x0d\xae\xc4\x11\x42\x36\xc7\xa7\xf1\x3e\x4d\x28\xb7\x14\xf8\x29\xa4\x68\xef\x44\x52\x72\x2f\x2f\x07\x2e\x84\x0d\x2e\x07\xcf\x60\x37\xe4\xdf\x3b\x43\x35\x8b\xb1\x0b\x54\xd1\x1b\x9f\x3b\xea\x40\xe1\xbf\xa3\xa7\x19\xa1\xdb\x25\xe8\x3b\x1c\x13\x98\xa8\x61\x2a\x23\xf8\xd9\x96\x90\xc0\x13\xa3\x78\xf6\x16\x8a\x5e\x7e\x05\xfd\xee\xf5\x98\x41\x04\x24\x95\x16\x7f\x13\x26\x66\x4b\x74\xaa\xa1\x6d\xc8\x7b\xb3\xac\x6d\xf4\x68\xdb\xe9\xe3\x0f\x87\xe1\xd0\xea\x0f\x0b\x95\x3e\x65\x61\x5a\x6e\x7b\xf3\xcc\xa7\x1f\x66\xb4\x90\x42\x46\xfd\x96\xf1\x84\x2b\x21\x66\xd1\x42\xfd\xd0\x92\x61\x16\x75\x4a\x85\x59\xf4\x12\x89\x30\x8b\x4e\xa4\x34\xea\x70\xdd\x94\x4e\x93\xea\x0a\xc1\xc0\xe5\xe4\x44\xc6\x1f\x31\x8e\x5e\x5f\x57\xdc\xde\x56\xce\x9f\x93\x6d\xf6\x19\x5c\xa7\xf4\x7f\x30\xc9\x3d\x4b\xd3\xec\xe1\xc5\x39\xee\xb1\xf6\x87\x88\xa7\x4f\x65\xb5\x7f\x61\x0a\xfb\x8f\x25\xc5\xd9\x54\x0b\x4a\xf5\x8e\x2b\x73\x80\x11\x5a\x2b\xec\xe6\x61\xdd\xf2\xf8\x5a\xe9\xa9\x55\x78\x25\x67\x7b\x5e\xde\xfc\x3f\xb2\x7a\x0b\x17\xd5\xb7\x45\xb1\xef\x74\x93\xb2\xe2\x32\x42\xb7\x8e\x57\xe9\x6d\xbe\xcf\xec\x10\x8b\xfb\x2c\xcf\xb5\x85\x83\xc5\x29\xd8\x9f\xf4\x7b\x28\x8c\x9f\xbe\x7e\xfb\xf5\xbb\xd0\x83\x8f\x0c\xe0\xff\x49\x5d\xec\xe5\x2f\x69\x14\xe1\x78\x8e\x4a\x29\xc4\x8c\xe2\x1a\x06\x48\x09\xb6\x04\x8f\x8d\x34\xff\x0d\x13\x03\x7b\x6f\x6c\xe6\x4f\xf1\x62\xb6\x16\x0a\x2d\x51\x36\x6e\x1b\xc5\xcb\x89\x5b\x5c\xcc\x18\x98\x31\x69\x89\x61\xaf\x9a\x36\x55\x78\xad\xde\x83\x93\x9e\xb6\xf6\xe8\xaa\x2b\xfb\x5b\x0e\x7d\x3e\x36\xeb\xfd\x3a\x4c\xc1\xb2\x72\x34\xd2\x86\xc2\xa2\xc2\xe6\x75\xc8\x26\x89\x55\x2a\x0d\xd2\xd1\xca\xc7\xee\x9e\x66\xe1\x76\xc9\x03\xe8\x85\x5e\x85\xdb\xe5\x26\x88\x16\xee\x27\x47\x23\xfe\x3a\x54\x76\x80\x58\x96\x8c\x46\xd9\xf8\xea\x52\x7c\xc4\x7c\xa3\x6d\xb6\x64\x43\x80\xab\x27\x6f\x41\x88\xc3\x64\xda\x2f\x4e\x64\xa3\x91\x70\xe2\xdc\x52\x78\xf8\x17\xb6\x38\xb9\x63\x43\xa3\x9e\x0d\x5d\xf4\x8d\x42\xda\x74\xb0\x8e\x08\x1d\x9d\xc7\xc7\x9e\xae\xe5\xfb\xf1\xdc\xd9\xa5\xf3\xae\x18\x20\x6d\xf3\x2f\xb3\x86\x7a\x0a\xae\xec\xc5\x21\xb8\x4d\xcc\x99\x93\x02\x0a\x40\xa3\x02\x0c\xd2\x30\x91\x06\x5d\x74\x13\x6a\x09\x9c\x7d\x69\xe8\x8e\x9a\x32\x62\xfd\x86\x2c\x14\xf6\xd5\x26\x77\xc9\x68\xe4\xa7\x21\x0f\x13\x42\x3b\x44\xf5\x1c\x8c\xce\x60\x00\x0d\x2b\xff\xce\xea\x29\x74\xa6\xc6\xd9\x6c\x21\xc1\x5d\x8b\x67\xc4\xb1\xf7\x37\x34\xa2\x9c\xa6\xc6\x60\x52\x1e\x81\x61\xb8\x25\x4f\x08\xd7\xe6\x60\x35\xc3\xf5\x80\x51\x35\x58\xc9\x7a\xda\x28\x59\x42\xdb\xd8\xdf\x2e\xbd\x89\x37\xde\x06\x9e\x47\xe8\x95\x26\xf7\xb3\xb1\x37\x35\xf7\x51\xbf\x99\xe6\x15\xa1\x57\x27\x23\x93\x3b\x08\x77\x6b\x63\x5b\xcb\xb9\x60\xa9\x26\x6b\x9f\x3a\x9f\x04\x6d\x7f\xfe\x67\xce\x50\x66\xec\x55\x33\xcd\x4b\xc9\xa9\x52\x4f\x0e\xc3\x23\xce\xe4\x8e\x8e\x97\x43\xe7\xd9\x02\xfb\xad\xcd\x24\x9a\xa4\x82\xb2\xb7\xe9\x24\xe8\x63\x61\x7e\x2a\x5a\x09\x6f\xae\x0e\x6a\x09\x5f\x3c\x4f\x2f\x61\x6f\xfd\xe2\x81\x13\x82\x00\x9c\xd7\xb3\xf9\x06\x4d\xa2\x02\xa2\x4d\xb6\x43\xf5\xe3\x70\x78\x3a\x52\x89\x2f\x65\xd1\x17\xb0\x4a\x10\xe3\x54\x5a\xd6\xe3\xba\x35\x2a\x90\x46\xb3\x77\xc5\xde\x6e\x53\x17\x7b\xfb\x95\xa6\xf3\xc4\x55\xe6\x88\x21\x0c\x41\xb5\xe5\x0f\xef\x8a\x9f\x36\x91\xcf\x95\xaa\xed\xfc\x6f\x1f\x2d\xfd\x15\x9b\xa4\xbf\x26\x6b\xd2\xfe\xf1\xf1\x79\xb6\xe0\xa1\x09\x46\xc2\xcc\x5a\x71\xca\x68\x4d\x33\xb3\xd6\x63\x36\xae\xc7\xf5\x38\x1b\x67\x47\x3c\x1a\xb5\xd3\xfb\xd3\xc5\x91\xf4\xfd\xfe\xf8\x3c\x9b\xf2\x07\x1e\x5b\x32\xe5\x7a\xf9\x54\x06\x26\xcc\xcf\x6a\xbe\xa6\xf3\xcf\x08\xdd\xd8\x65\x17\x58\x16\xd9\x65\xaf\xa0\x0c\xcd\x31\x2d\x1d\xcb\x8e\xdd\xec\x71\xc8\x7a\xc0\x88\xd2\xb2\xdc\xd7\xb8\x8d\x53\x46\x68\x6d\xa9\x66\xb2\xea\x4d\xfe\x79\x59\xb2\x47\x78\x65\xa0\x0a\x62\xc1\xff\x90\xfa\x9c\x5c\x4e\xe6\xe8\x2c\xf3\x97\xb7\xa1\xbd\x34\xb8\xbe\xb5\x89\xb2\xf3\x9b\xd8\x8a\xb7\x52\xdc\xe6\x7b\x1f\x79\x63\x3e\xf6\x04\xdf\xb9\x67\x65\x9d\xc5\x3b\x5e\x4d\xae\xaa\x49\xcc\xf2\x3b\x56\x4d\xf8\xce\x93\x9c\xba\xe8\xf7\x09\x4b\x83\x27\xbe\x0b\x6a\x7a\x1f\xd4\x0e\xff\xb7\xd5\xcf\x28\xfa\x3d\x52\xdd\x63\xf0\x84\xbe\xd7\xc1\xd3\x1d\xdb\xdd\xf2\xe0\xd3\xd9\x8c\x26\x5c\xb0\x35\x8f\xc1\x13\x1a\x3e\x05\xc3\x19\x85\x97\xef\x59\xc9\x59\xf0\xc7\x19\x44\x9f\x28\x76\x85\x6e\xe4\x7d\x94\xa6\xa9\x77\xa4\xd5\x96\xed\x79\x20\x19\xf7\x38\x2b\xe3\x1d\xf7\x68\x55\x97\xc5\x35\x0f\xa4\xbd\xec\x4c\xb6\x14\x4d\x66\xb3\xd9\xcc\x3b\xd2\x7d\xb1\x7b\xdc\x14\x79\xf0\x94\x47\xef\xab\x2c\xe1\x55\xf0\x87\x23\xcd\x6e\xd8\x86\x07\x4f\x55\x19\x07\x9e\x47\xb1\xed\x7c\x36\x53\xd6\xb5\x73\x18\x44\xb1\x67\x31\x0c\x14\x87\x31\xa7\x68\xa8\x17\x0c\xe7\x94\xe5\xd9\x8d\x99\xc0\x9c\x56\x7b\xce\x93\xe0\x42\x35\x79\x7f\x93\xe5\xc1\x8c\x56\x8f\x79\x1c\x40\x6a\x85\x2a\xfb\x3b\x57\xfd\x5c\xcc\x9e\xef\x68\x06\x2d\x5a\xdd\xec\xb2\x9c\xbf\xdf\x65\xf9\x35\x4f\xec\xe5\x4b\x32\xb4\x64\x86\x29\x98\x05\x48\x3d\x3d\x85\xb9\x9a\xe3\x91\x0a\x94\x6b\x37\x56\x43\x37\x99\x8f\xbd\xbc\xc8\xb9\x67\x0d\xb2\xaa\x4b\x06\xeb\x32\x9c\xd3\xe2\xb6\x7e\x7f\x53\x24\x3c\x40\x27\xd4\xa8\xb8\x15\x1f\x16\x33\xa9\xeb\x92\xc5\xb5\x3d\x99\xb2\x10\xf7\xdd\x2f\xc1\x2b\xfe\x4a\xfe\xfe\xab\xf8\x7d\x3c\x52\x26\x40\x3a\x58\xad\x8f\x14\x32\xc8\x02\xdf\x0d\x4b\x9d\xf0\x9a\xc7\xf5\x7b\x31\x0a\x84\x39\x8f\xc2\x35\x54\x05\x4f\x45\x8e\x71\x35\xac\xb1\xe3\x40\x36\x25\x8b\xbc\x23\x2d\x72\x0c\xc0\xd1\x7a\xbf\xbf\xad\xb6\xde\x91\x62\x8c\x4d\x08\x0d\x2e\xca\xab\xe0\x49\x34\x0c\x9e\x9c\xd5\x73\x56\x58\xaf\xde\xf1\x48\xa3\xdb\x48\x74\x6a\x6a\x5f\xcc\x70\x93\x82\x3f\xce\x68\x72\x5b\x62\xb8\x98\xe9\xa7\xe2\x3b\xfb\xdb\x5d\xd5\xac\xea\x54\x11\x23\x0a\x9e\xf4\x29\x79\x9f\x47\x01\x34\xc4\xbd\x71\xca\x2f\x8e\x62\xb8\xb7\xa2\xbf\xa3\xa8\x52\x67\x39\x7b\x8f\xab\x04\x11\x46\xf2\xe0\x49\x2d\x61\xf0\xa4\x67\x76\xa4\x77\x3c\x4f\x8a\xb2\x82\x56\xf5\xcd\x5e\xfc\x95\xd4\x82\x3a\xd5\x0b\x36\x1a\xfd\x00\xa4\xd1\x34\xe1\x7c\xff\x35\x32\x8a\x99\xc0\x40\xd9\xb4\xbe\xd9\x4f\x8b\xe8\x2a\x7c\x02\x38\x44\xc0\xcd\x0c\xa6\x98\x42\xb8\x52\x28\x46\x48\x15\x70\xfc\x1e\x41\xa9\x55\x4d\xbc\x9b\xc2\x3b\x00\xbe\x8e\x6a\xa2\x58\xd6\xb0\x36\xe0\xbd\x5e\x40\xbb\xae\x55\x61\xaa\x2a\x38\xad\x10\xd2\xfb\x9a\xdc\xa3\x4f\x65\x91\xf0\xf7\x62\xfb\xed\x6f\x38\xa0\x38\x85\x95\x9c\x8a\x3a\xe6\x33\xd0\x0c\x21\xe1\xb9\x86\x58\xab\xbb\x29\x80\xcd\xc9\x66\xa2\x06\x36\x91\xd0\xf4\xdc\xe7\x64\x35\xfd\xbd\x23\xcd\x04\xe5\x81\xf0\xf2\xc6\x89\xdf\x43\x9e\xb2\xa9\x03\x47\xa3\x91\x8a\xfc\xc2\xef\xb2\x98\xff\x98\x3d\xf0\xdd\x4f\x02\x58\x2f\xe7\x4b\x3f\x9b\xe2\x39\x9c\xee\x1f\x00\x80\xc3\x9e\xba\x12\x66\xb0\xe3\x70\x38\x23\x41\xbb\xe9\xbc\x51\x69\x2e\x20\x4d\xd6\xb9\x0f\xf5\x4f\xbe\xb3\xaf\x97\xb3\x66\x37\xa6\xcd\xb6\xa3\x0d\x5e\x41\x5d\x8d\xba\x80\x37\xd4\x90\x3e\x35\x80\xfe\x82\xc6\x06\xa4\x9b\x3d\x98\x83\x70\xba\x1b\x03\xf2\x56\x0f\xe6\x78\x9c\x6e\xdc\x75\x06\xac\x6e\xba\xce\x50\x57\x87\xcf\x02\xbc\x33\xb4\xe6\x89\x79\x69\x8f\x8d\x93\xd0\xec\xb3\x71\x9c\x5e\x3e\x71\x38\xc9\x3d\xb3\xbe\xef\x83\x9c\x67\xce\x5c\xcf\xe0\xc4\xab\x97\xf6\xd6\x3c\x89\xcd\x2e\x9b\x07\xba\xd5\xaf\x3c\xba\x58\xd8\x3e\xba\xb2\x72\x5c\x3f\x38\xd0\xaf\xac\xd9\x1e\x6a\xdf\xbb\x48\x3c\xe2\xf4\xf2\xb6\x11\xca\xda\x6e\xa8\x96\x51\x9d\x43\x6a\xbf\x44\x7a\xc8\xbc\xdd\xd2\x6c\x34\x6a\xce\x1b\x2f\x68\x19\x31\x5b\x23\x13\x96\x24\x28\x85\xc8\xaa\x9a\xe7\xbc\x54\x41\xae\x1d\x66\xe7\xf9\xe3\xff\x92\xd3\xee\xa0\x95\xd1\xc8\x20\x9e\xfb\xb3\xf0\x04\xf6\x68\xbf\x24\xf4\x1f\x59\x99\xe6\xe9\x46\x3a\xe4\x70\xf0\x61\x27\xf4\xcb\xaf\x6f\xf6\xf5\xa3\x4f\xa8\x5b\xfa\x25\xc4\xc0\x6c\x15\x7f\x55\xb2\x7b\x55\x28\xaf\xf4\xa9\x24\xa0\x3f\xbf\xad\x8b\x1f\x55\x3d\x9f\xbc\xa8\xd2\xd1\x85\x8b\x1f\x59\x96\xf7\x82\xd7\x34\xcd\x76\x3b\xf0\xca\x9b\xd1\x19\xed\x5a\x86\xad\xdb\xdb\x97\x4d\x15\xbf\xd3\x1b\x18\x00\xbc\xac\x3b\x35\xfb\xd0\x65\xf5\x8c\xb0\xa9\x64\x49\x76\x5b\x85\x7e\x0b\x2f\x23\xcd\xba\x74\xdc\x6a\x82\x39\x39\xeb\x21\x5d\xba\xf1\x3a\xee\x9b\xf2\x9b\x01\xac\x5e\xd5\xac\xbe\xad\x74\xda\x85\xbb\x2a\x3c\x71\x25\x9c\x0b\x42\xb2\xe7\xfd\x63\x1e\xab\xc4\x2d\x77\x32\x08\xde\x5d\xd5\x70\x03\x92\x92\xed\x87\xb0\x5e\xd6\xd3\x87\xc0\x79\x79\x66\x2d\x1c\xd4\x7a\x84\x5a\x8f\x7d\xb5\xa4\x94\xe6\xe1\xd2\xb4\x9b\x5c\x9c\x59\x8b\xb8\x54\xdf\x82\x3f\x13\xeb\x0d\xc6\xf4\x78\x78\xed\x54\x57\xab\xa2\x1a\x8c\xad\x77\x72\xdc\x8f\xe6\x5b\xdb\xae\x6f\x3d\x62\xd3\xc7\xf6\xb7\x1e\xbb\xbf\xa5\x1a\xb8\xdf\x6a\x1d\x37\x64\x45\x04\x7a\xb2\x8e\x01\xc8\x2d\x7f\xb8\xe3\xe5\x8e\xed\x65\xe4\x3f\x39\x4c\xe0\x93\xc2\xa7\x63\x3b\x39\x95\x84\x0e\x02\x2a\x70\xf8\x69\xfb\x8a\x02\x37\x8e\xcc\x75\x15\xca\xf7\x2b\x58\xfd\x74\x57\x14\xa5\xdf\xdc\x08\x33\x4a\xf8\x22\x36\x50\x2a\xc1\xf5\xc2\x8c\x65\x5a\x6e\xa2\x50\xcb\x47\x2a\x19\x27\x1b\x2d\x94\x86\xea\x4b\xd3\x72\x34\x6a\x16\x6d\xda\x45\x91\x5a\x3b\xd3\xf3\x53\x19\xe8\x3e\xe8\x46\xff\xde\xd0\x48\xff\x8e\x8e\x84\x36\x7b\xda\xb6\x3b\xaf\xda\x45\x3b\xf7\x7b\xdb\x6a\x17\x3e\x6d\x75\xbf\x5b\x5a\xe9\xdf\x15\xdd\xe9\xdf\xbb\x23\x46\xec\xf3\x70\xb9\xbc\x50\xf5\xb7\x6c\x8f\xdd\x5a\xe1\x8b\x3f\x34\x1d\xe7\xc6\x33\xba\x79\xb6\x46\xf4\x5c\x8d\x63\xd0\x52\xb6\xc9\xf1\x38\xb3\x93\x69\x77\xbb\x36\xcd\x2a\x47\x10\x52\x71\x9f\x91\x91\x74\x11\x96\x2c\x7c\x11\xce\x52\x75\xdb\x68\x4b\xbd\xe9\xc0\x5c\x4a\x18\xd1\x44\x5e\x45\xd8\xdb\x41\x37\xfe\x72\xab\xd8\x28\x4c\x46\x9e\xb9\x2b\x9a\x28\x6c\x81\xb1\x69\x9e\x8e\x8b\xea\x3e\xab\xe3\xad\xdf\x3a\xae\x5a\xde\x40\x9e\x62\x56\x71\xa9\xd5\x09\x9f\x1e\x82\x19\x7d\x0c\x26\xf3\xe3\x22\x2a\x39\xbb\x5e\xa8\x97\x32\xf4\x34\x56\x99\xfe\x41\xd4\x99\xfe\xc1\xa9\x64\x57\x98\x8b\xf7\x33\xe7\xb5\x74\x0d\x6f\x75\xd3\xe8\x45\xa9\xaf\xf4\x50\xe6\x5d\xbd\x60\xb8\x0a\xa8\x33\x81\x5e\xdc\x5a\xf6\x6b\x31\x94\x59\x6b\x36\xad\x0e\xac\xd9\x24\x28\x81\x37\x43\x98\x1d\x8f\x6d\xde\x41\x0a\x65\x96\x72\x2b\x1e\xc2\x7c\xfa\x20\x77\xf8\x31\xcc\xa7\x8f\x6d\x0c\x89\xdb\xa3\xc0\xe3\x4e\xe2\xf0\xbb\x07\x77\xf3\x74\x1f\xf2\x6f\x73\x6b\x03\xfb\x83\x63\xe7\xa5\x98\x89\x35\x82\xd6\x4b\xd5\xf7\xc3\xfb\x4c\x7d\x5b\xd5\xd7\x25\x8f\x00\x3a\xa5\x7b\xc1\x6e\xd9\x1e\xc3\x54\x80\x7e\xb2\x89\xb0\x4b\xa0\x0c\xca\x1e\x2c\x1d\x87\x65\x3f\x7e\x2e\x1b\x98\x18\xbe\x14\xc6\x47\x44\xbe\x56\x51\x09\x5f\x06\xb9\xa1\x52\x8c\xc2\x0b\xfc\x46\xd1\x1e\x2e\xf6\x97\xdd\x6c\x42\x10\x33\x16\x53\x68\x3b\xad\xca\x98\x02\xad\xa9\x4b\xd0\xfb\x5d\x3d\x6d\xa5\x08\x55\xb5\x9e\x42\x65\x9d\xec\x4d\x15\x84\x73\x42\xbd\xea\x4e\xa0\x2a\x24\x7e\xb3\x9b\xcd\x7b\x4c\xed\xac\x70\x33\x96\x57\xc5\x6d\x19\xf3\xf7\xd5\xdd\x06\x08\x63\xfb\x6a\x04\x8a\xf3\xed\xdd\xe6\xcd\x8d\x4c\x58\x2c\xe9\xe8\xfd\x6d\xb5\x85\x08\xf3\xe6\x9b\x98\xe2\x4f\x70\xec\xe4\xd8\x20\xd2\x6c\x6f\xcb\x92\x39\xb6\x76\x5a\x8a\xcd\x9b\xc4\xa0\xa8\xf9\x46\xcc\xd7\x2f\x29\x13\x64\x07\x65\x82\x20\xa0\x17\x67\xf0\xef\x9c\x99\x99\xa2\x95\x83\xf4\xd8\xcc\x52\x5f\x4d\x8f\x49\x4a\x40\xf2\x68\x04\xa5\xdd\x8d\x52\x0c\x08\xeb\xbe\x72\x3b\x51\x48\xd2\xea\xa5\x6a\x15\x9b\x6e\xac\x77\xa2\x1f\x66\x6e\x01\x82\x18\xcf\x2b\x37\x11\xf3\xbd\xb1\xf5\x66\x5a\x8e\x3d\xea\x96\x6c\x5a\x25\x11\x94\x54\x63\x8f\x78\xe6\x6b\x79\xe8\x6d\xab\x9d\xdd\xdd\xb6\xda\x4d\xb7\x4e\x63\x51\x52\x8d\xbd\x7f\x6e\x14\xed\xb0\x08\x3b\xd4\x38\xb8\x41\xdd\xbf\xad\x1f\x77\x3c\xcc\xa9\xf3\x22\xe2\x9b\x2c\xff\x91\xd5\xe8\xa5\x2a\x81\x1c\xf0\x96\x94\xb9\x07\x4e\x75\x56\xc6\x3e\x9b\x3e\x88\x0d\xa4\x35\x9d\xd1\x0b\xc4\x17\x3f\xbe\xa1\xc3\x39\xb1\xb1\x1e\x4f\x36\xcd\xb6\xe2\x06\xf0\x3b\xf6\xdf\x69\x57\x97\x19\xcb\x37\xf8\x5d\x9b\xc1\x29\xd9\xfd\x5b\x31\x38\x67\x5a\x06\x9a\xc6\xf5\xf9\x7c\xfa\xd9\x67\xd0\xe5\x2b\x7a\xe1\x74\x29\x75\x02\x1f\xd0\xe3\xb9\xdf\xc6\x47\xb2\x97\xa9\x52\x2c\x9c\xbf\x12\x18\x0e\x26\x72\x3e\xfd\x97\xcf\xe8\xc5\xf4\xb3\xcf\xce\x5e\xd8\xd2\xa5\x67\xbb\x6b\x51\x77\x3d\xab\x9a\x95\x2f\x9f\xc1\xc5\x0b\x47\xf2\xa9\x9a\xc1\x7c\xfa\x87\x0b\xb1\x78\x67\x62\x1a\xff\x85\x93\x70\x77\x02\x11\x6a\x20\x70\x6b\x17\x36\x23\x78\x17\x20\x3a\x28\xa2\x2b\x73\x34\x4a\xab\xa6\x78\x51\xa2\x49\x5f\x83\xe5\x2c\x2a\x2e\x21\xb9\x3d\x32\xd4\x1c\x21\xfa\xbd\x9c\xd9\x62\x03\xd1\x16\xdf\xe2\x01\xe9\x6d\x0b\xc7\xcd\x3d\x3d\xbb\x2c\xe7\xff\x21\x05\x07\xa7\xbe\x48\x3b\xbe\x86\x4c\x7c\xe3\x8c\xfa\x4d\xae\x58\x8a\x0a\xba\x42\xb7\xf2\x70\xb6\xe0\xaf\xed\x0f\xa3\xfa\x0d\xe9\xc6\x05\x1f\x8f\x89\xfd\x12\xb4\x30\xe8\xa9\x92\xf3\xfb\x81\xf3\x15\xbf\xc5\xb9\x74\x92\x84\x92\xcc\x6d\x0e\xb1\x9d\xc3\xa7\x7f\x88\x38\x0a\xe9\xa8\x21\x86\x28\x15\xc3\xad\x3a\x2b\xbe\xc6\xc4\xd8\x9d\x72\x16\xa5\xef\xec\x16\xb2\x9e\x5f\x2c\xd8\xf4\x61\x1c\x32\x41\xee\x20\x7e\x10\xbf\x1f\xcf\xea\x63\xa3\xcb\x6e\x82\x7a\x1e\x5a\x97\x02\x12\xd4\x4b\x5f\x97\x5c\x76\x52\xd4\x8a\x75\x68\x36\x04\xc1\xb7\x2e\x84\x71\x14\x24\x30\xd5\x5e\xf7\xd3\xe7\x96\x7e\xb1\xb3\xe3\x99\xd5\xf1\x04\x3b\x36\x05\xaf\x67\x76\x9b\x70\x46\xc8\x73\x02\x10\x98\xb5\x25\x00\x11\x53\xc6\x4b\xf4\xb2\x2d\x00\x31\xd3\x75\x45\x26\xe0\x33\x0c\x8d\x60\xaa\x15\x4c\x15\x0b\x5e\xf7\x89\x51\xa4\xf2\xb3\xdd\xdb\xcc\xf4\x36\xc1\xde\xf4\xb3\x9c\x9e\x94\x0d\x89\xd9\x79\x28\x12\x00\xac\xd2\x00\x0a\xa5\xc6\x94\x17\xfe\xd3\xc3\x7b\x08\xe8\xa3\x9a\xd3\x87\xf7\xc0\x27\x04\x96\xb8\xe5\xf1\x7d\x5d\xec\x4d\x8d\xc7\xf7\xc8\x0a\x98\x2a\xdb\xa3\x4d\x27\xa8\x2e\x27\x27\xfa\x1c\x5b\xbd\x89\xce\x27\xa7\x7a\xd7\x95\x35\x4f\x25\xf0\xba\xde\x10\xd3\xab\xd8\xa5\x87\xb0\x9a\xe2\x00\x04\xac\x87\x7d\xd2\x21\x12\x88\x53\xd1\x58\x41\x6c\x0b\x63\x3d\xdd\x18\x6e\x8a\xf6\x00\xb6\x62\x00\x8f\x61\x35\x85\x49\x89\xdb\xa7\xaf\x8b\x7b\xf1\xfd\xc7\xe6\xf7\xb1\xa9\x34\xfc\x3a\xd9\xba\xcd\xe2\xe8\x7d\x7d\x92\xdc\x1a\x00\x80\x33\x4b\x7b\xa5\x04\x3a\x08\x27\xe2\xff\xc0\x5e\x4c\x39\x10\xf5\x0e\x26\xda\xd1\x7e\x2b\xda\x3f\x42\x9d\xc7\xc0\x5e\x0b\xd5\x5e\xbe\x23\x80\x63\xb4\x25\x07\x2a\xac\x5b\xea\x03\x29\x46\x97\x6a\x6e\x50\x16\x10\x29\xcd\x32\xaa\x19\x25\xcb\xf5\x19\xa1\x76\x97\x48\xa7\xbe\xac\xd3\xc3\xe1\x43\x1a\xa2\x01\x2f\x34\x74\x87\x83\x2d\xfb\x06\x24\xd5\x1c\xbf\x63\x44\xcf\xb7\xec\x1d\x92\x6c\x6a\x8f\xa9\x4f\x7b\xa4\x84\xf3\x2d\x08\x92\x76\x0c\xea\x56\x51\xf7\x56\x1e\xf2\xf1\x7c\x91\xf7\xdf\x5c\xb9\xba\xb9\xca\x8e\x9b\x2b\x5f\x2f\x4e\x0f\x44\x4e\x43\x4d\x58\xd4\xb8\x36\x72\x7b\x46\xcb\x0e\x58\x77\x47\xda\xec\x41\xbe\x7d\xae\x13\x47\x6a\xaa\x1b\x63\xa9\xdb\xf6\xd8\xe4\xfc\x40\x39\xf1\x0f\xc9\xfb\x3b\xa9\x06\xdf\xce\x37\xf2\x0f\x11\x0b\x0c\x08\x62\xbf\xc5\xb2\xa2\x0a\xc6\x1d\x79\xa3\x79\x08\xb6\x29\x4e\xa3\x9f\xda\xae\xda\x31\xcb\x63\xbe\xfb\x89\xff\x76\xcb\xab\xfa\xf3\x3c\xbb\xf9\xa6\x64\x37\x1c\xd9\x6c\x90\x3c\xeb\x32\x42\x4f\xd5\x15\xc3\xb4\xaa\x36\x39\xf7\x10\xd9\x55\xea\x50\xbc\x6e\x61\x5c\xdc\xe6\x35\xd4\x9d\x75\xcd\x55\xa9\x90\x2c\x45\x4d\x53\xab\x24\xd8\x89\x5a\x13\x9b\xdd\x80\xd8\x69\x60\xc6\x01\x71\x3e\xd0\x2a\xe4\x80\x02\x1f\x69\x8e\x18\xbb\xfa\xad\xac\xfd\xfa\xac\x1e\x57\x67\x15\x9a\xa2\xbb\xf7\x7d\x97\x16\xbb\xeb\x08\xd9\xf5\x14\x6d\x93\x9f\xfb\xf3\xf3\x67\x2a\x91\xde\x0a\xea\x6b\x62\x50\xe5\xe5\x4c\xc9\x89\xfa\xaa\x03\xf9\xfb\xbe\xdc\x44\xa2\x80\x2f\x7a\xf9\x04\xc5\xfb\xc7\x92\xe3\x8f\x25\x9f\x1f\x4b\xee\xbe\x14\xcc\xf8\x4b\xd8\x85\xb6\xc1\x4a\x2f\x87\xee\xbc\x10\x27\xfa\x5d\xe1\xf3\xe9\x03\xe5\xd3\x47\xd2\xfe\xd4\xbb\x42\xf1\xea\xa4\x9b\x01\xa1\x7d\x0c\x94\x3e\xfd\xbd\x08\xe6\x7f\x0e\x38\xea\x73\x7f\xce\x5f\x9d\xf5\x22\x44\x69\x77\x46\x68\x1c\x56\x2f\xaa\xfa\x57\xb2\xe0\xd3\xbb\x87\x49\x58\x52\x3e\xbd\x7b\x9c\x80\x81\xef\xdd\xc3\x38\x2c\xc5\xdf\xc7\x71\x18\xb7\x66\xdf\xc0\x90\x27\x27\x9f\xe9\xc9\x57\x8d\xc9\x67\x67\x19\xa1\x79\xc8\x15\x81\xac\x65\x54\xf9\x65\x58\x41\x50\x44\x41\x83\x88\xff\x61\x60\xf0\xf3\x91\x1a\xca\x84\xda\x44\x06\xb5\x6e\x42\xc1\xd4\xf5\x0d\xce\x91\xf2\x85\xc3\x99\x46\xb7\xb5\x40\xb7\x97\xf5\xa2\xfe\x6f\xe2\x13\xe9\xd3\x43\xc0\x96\x6c\xba\x2f\xaa\xf7\x27\xf4\x9a\x8f\xba\x52\xbf\x5a\xf3\x48\x08\xad\xc3\x90\x4f\xe6\xc0\xb8\xf7\x68\xde\x7b\x74\xe9\xce\xf4\xe7\xee\xc2\xa1\x11\x5e\xd7\xd2\x75\xdc\x12\x90\xf9\x2a\xe6\xfe\x8c\xf2\x8e\xab\xf5\xd4\x28\x9c\x6f\xba\x94\x94\xf3\x49\x2d\x42\x65\xe0\xf2\xea\x8a\x25\x43\x5d\x40\xb9\x2b\xf7\xd4\x10\x65\x2c\x89\x6b\x9f\xd1\x9a\x56\x34\xa7\x31\x88\xcb\xd9\x30\xac\x09\xf0\xbe\x62\x39\x94\x15\x8e\xb4\x52\x7c\xcf\x73\x4c\xf1\xa6\x04\xa5\x95\x92\x73\xe7\x93\xfd\x99\x9f\x4f\x98\x40\xae\xa7\xac\x7d\x64\x47\x74\x17\xb2\x49\xb1\x48\x42\x36\xde\x51\x0f\xec\x3f\x20\x30\xba\xdf\x1c\x70\x42\xa8\x27\x27\xa3\x6b\x34\x66\x9b\x90\xa3\xce\xa2\x5c\x0a\x6c\xf1\x02\x6b\xa3\xc6\x24\xc4\x1c\xee\xc2\xca\xf0\x69\x77\x61\x0e\xf2\xe0\x61\xc8\x94\x33\xdf\x07\xce\x70\xf1\x5f\x34\xab\x53\xdd\xe0\x04\x9e\xef\x4b\xd6\x43\x91\xc6\x49\xb2\x5b\xd3\x8c\xbf\x8f\x85\x30\xfa\xef\x87\x49\x7b\x95\x6e\x2b\x8e\x67\x1c\x50\xdb\xe3\x89\x1a\x8f\xb4\xb4\x30\x62\x75\x56\x8d\xf3\xb3\x5c\xe0\xed\xf9\xa4\x7c\x66\xf9\xed\x2b\xfc\x43\x80\x21\xbe\x0c\x67\xa3\x11\xa6\x77\x13\xc7\x14\xa4\x03\x6e\x63\x14\x36\x40\xed\x67\x2d\xc6\x86\x3d\x82\x10\xf2\x92\xc6\x97\x3d\x6d\xe5\x59\xd3\xd7\xc2\xb3\x1d\x9d\xc5\x8b\x02\xe6\xd5\x02\x9d\xc2\x8a\xcf\xbb\xd3\x3d\x76\x6c\x49\xd3\xfa\xd4\x7c\x7e\xb2\x83\xee\x67\xcb\x56\xdf\x41\xb3\x64\xd6\x05\x7a\x4e\xdf\x12\x62\x87\xfd\x12\xb3\x67\x57\x4e\x09\xdd\xfa\x7b\x78\xc2\xa3\xfd\x92\x5e\xce\xe2\xc5\xdd\xa5\x7e\x1a\x8d\xee\x9e\x81\x25\x5d\xb1\xe3\xf4\x59\x4b\x7d\x67\x70\xf3\xa4\x53\xbc\x08\x03\x3d\xbd\x0d\x8a\x8c\x15\x43\x7c\x6d\x0f\xf1\xf2\x1f\x19\xa2\xc4\xa1\xcc\x27\x0b\x3b\xc7\x61\xdf\x29\x18\x8d\x98\x95\xbc\xfe\x19\x96\xfc\xf7\x60\x15\x9b\x99\x7f\x6a\xde\x47\xf0\x32\xcb\x37\xcf\xe3\x1b\xa8\xf9\xfe\x79\xac\x63\xea\xf5\xe0\x9e\x7d\xe8\x03\x95\xf3\x15\xab\x39\xc1\x78\x82\x37\xdc\x27\x27\xbb\xab\xb3\x1b\x4e\xce\xe7\xfc\xd5\x62\x7f\xf9\xa2\x4b\x03\x88\x96\x9e\x7b\x17\x84\x9c\xfb\xcb\x8b\xb3\xdf\xd5\x93\x5a\xb1\x70\xa8\xac\xad\x3b\x3f\x31\x27\xc7\xce\x66\xa3\x91\x5f\x3f\x8f\xb7\x7a\xac\xa9\x9b\x54\x88\x7e\x96\x37\x3f\xa1\xcf\xf4\xad\xa8\x99\x7e\x0a\xb2\x09\xd1\xa6\xc0\x5c\x8e\x46\x47\xdd\x29\x15\xea\xa7\xaf\x50\x94\x80\x49\xe7\x6b\x96\x5f\xf8\x09\xdd\xcb\x48\x28\x77\x0f\xe1\x2d\x2a\x36\xe3\xa2\xf2\x19\x41\x72\x5c\x16\x55\x59\x2e\x8a\x5e\x22\x6e\x96\x9c\xc1\xd3\x43\xc0\xa7\x0f\x63\x20\xec\x1f\x03\x3e\x7d\x14\x3f\x1f\x8f\x8b\x1a\x0a\xdb\x72\x4a\xc3\x07\x04\xf5\xf4\x61\xc2\x6d\x39\xa3\x79\x47\x68\x0d\x3d\xb5\xe5\x94\x86\x79\x08\xea\xe9\x63\xab\xbd\x7c\x47\x8e\xbf\x8f\x72\xf8\x20\x59\xdf\x0b\xef\x5e\xc3\x41\x9d\xa2\x2f\xaa\x67\xe9\x8b\x2e\x76\x93\x96\x62\x03\xea\xf3\x9c\x3e\x06\xd5\x79\x7e\xa4\x71\x0f\x42\x6d\xda\x72\xd3\x22\x04\x27\xa1\x10\xbd\xf9\xe6\xe7\xf1\x99\x3f\x99\x23\x10\xec\x8b\x7b\x3f\x3f\x8f\xe9\x05\x19\xcf\xc9\x59\x7c\x56\xd0\x19\xfd\xc3\x8c\xd0\x3b\xb5\xd9\xe5\xf4\xe1\x6c\x27\x77\xbb\x9c\x3e\x9e\xed\x8e\x8b\x17\x40\xcc\xd2\xbf\xb3\x36\xfc\x72\x36\x1a\xdd\x59\x30\xf2\xda\xc0\x08\xec\xe4\x43\x78\x37\x7d\x20\xf4\xce\xda\x63\x6c\xf2\xd8\xd1\x04\xd2\x6b\x4c\x1f\xc3\xbb\xe9\x23\x21\x81\x6a\x4d\x55\xd1\x3f\x80\xf4\x3f\x4c\x84\xab\x91\xbe\x32\x8d\x4f\xb3\x3c\xab\xb6\x60\x38\x6d\x17\x83\x8c\x6b\x3c\xa6\x1d\xa5\x61\x5b\x00\x68\x22\x9b\x75\x75\x2e\x90\x2c\x69\xf6\xe4\x5c\x36\x71\xa8\xb7\xf5\x65\xc0\x71\xfe\x19\x7d\x25\xae\x8f\x67\xef\xa7\x09\x9f\x3e\xd0\xe4\xd9\x7a\x62\x0b\x1f\xe9\x4d\xb8\x3f\xdb\x8f\x93\xb3\x84\xde\x86\x93\xf8\xfc\xe6\x6c\xbe\x88\x2f\xc3\x1b\xeb\x5a\x9e\x29\xa5\x79\x73\x1e\x0a\x37\x70\x30\xb8\x42\x8c\xc5\xc1\xd4\xca\x65\x3f\x6d\xbd\x82\xcb\xef\xbe\x18\x19\xbc\xf8\x48\xb3\x67\x8f\x74\xfd\xec\x91\xb6\x85\x28\xec\x8c\x8d\xeb\x33\x4c\x85\x51\xf5\x91\x6e\x8e\x4f\x8b\x4a\x9b\x77\xa2\x6a\x97\xc0\xb1\x42\x81\xe3\x87\xb4\xe9\x63\x20\x9d\xe1\x80\xe8\x4b\x89\x20\xfb\xe5\x9e\x1f\x2a\x82\x2c\xa5\x08\xb2\x94\x22\xc8\x52\x8a\x20\xf3\xff\x0d\x22\xc8\x53\xdb\x7f\x62\xe3\x7f\x87\xc8\xf2\xe8\xca\xb8\x11\x7a\x95\x47\x8a\x13\x7f\xd3\x43\xb7\x95\x0e\xf0\xd5\x2e\xa9\xcb\xd6\x61\xd8\x49\x6f\xb8\x96\x53\x1e\xdf\xd9\xce\x2a\xf4\x65\xc7\xe8\x70\x78\x11\xa6\x25\x80\xd2\x9a\xdf\xeb\xf0\xb7\x31\x67\x92\x3e\x77\xaa\x77\xa1\x9c\x09\x51\x87\x34\x86\xa4\x4c\xbf\xc0\x71\xc4\xdf\x7f\x35\x52\x13\x51\x01\x1d\x70\x7e\x39\x1c\xdc\xba\x58\xfc\x57\x53\xfc\xd7\xc5\x89\xed\x0e\xd9\xa9\x0d\x0f\x3b\x3c\x7b\x7a\xbb\xea\x74\xf5\xe9\xed\xba\xdb\xf7\xa7\x0b\x6f\x85\xd6\x3a\x1e\xdb\x95\xfa\x57\x1e\xf9\x2a\x77\xe9\x4f\xac\x04\xc4\xc7\x38\xb5\x18\x9d\x15\x9c\x31\xe2\x17\x8f\xa4\x63\x94\x3d\x57\xf6\x4b\x26\x23\xb3\x05\xb8\x31\x5f\x9e\xbd\xe4\x7a\xae\xb7\x53\x87\xdc\xba\xfa\x4e\xb4\x7e\x3c\xd9\x5a\x70\x62\x61\x17\xff\xf6\xb2\x25\x21\xda\x52\xf2\x79\xba\x05\x2d\x0c\xc0\xb9\xbc\xed\xcc\x8c\xdd\x2d\xfb\x24\xf2\x3d\x24\x85\xa8\x33\xb5\xdd\xbf\xbb\xe7\x4a\x82\x79\x1b\x4d\xf5\xf4\xf0\xdf\x36\x84\x17\x36\xbf\x9c\x3b\x1a\xfa\xdf\x35\x04\xc7\x62\x10\x05\xf4\xd2\xf0\xb1\x53\x66\xdf\x4b\xaf\xc1\xce\xf4\x76\x2c\x25\x15\x41\x0f\x3b\x3d\x73\x07\x81\x04\x6e\xd0\x4d\x79\x85\xc3\x59\x27\x95\xda\x2c\x55\xb4\xe8\x9c\x76\xe7\xe5\xee\xeb\x7d\x7e\xa4\xa8\xd5\x3a\x49\x97\x4a\x86\x9f\x1c\xb5\xd7\xdf\x29\xe7\xc0\xb0\x79\xc0\x5b\x46\x84\xb2\x95\x63\x6f\xc7\xc3\x96\xc7\xe4\x59\xdb\x4d\x12\x44\x23\x0d\x54\xce\xcf\xc3\x8b\x96\xef\xab\x0a\x98\xc5\xcf\xfa\x8c\x18\xcf\x4f\x0c\xcc\x84\x11\xa1\x75\x2f\x43\x30\x61\x8b\xd9\x65\xdd\x7f\x2c\x90\xfd\x8f\x2a\xbf\x26\xe4\x14\x90\xd5\xa4\x49\x5d\xd8\xbe\x66\x0d\x95\x9b\xad\x61\xab\xfb\x0d\x1a\x6a\x65\xd0\x50\x75\x18\x34\xd4\x6b\x10\x6c\x3d\x4c\xaa\xe9\x03\x2d\x81\x4a\xae\xa6\x8f\x34\xb6\xa8\xe1\xfc\x2c\x1f\x97\x67\x25\x59\xc4\xaf\x8d\xf0\xd8\x72\xa7\x13\x2c\x1e\x5b\xb2\x53\x4e\x85\x82\xfb\x13\x55\x4e\x78\x14\xf6\x3b\xd8\x71\xd2\x5e\x14\xcb\xcb\xc0\x61\x2e\x94\xdd\x86\x6b\xea\x40\xeb\xf0\xfc\x23\x7f\x35\x9b\xfc\xeb\xe7\x93\x6f\xd6\x4f\xaf\xe8\x67\x47\x72\xbe\xc9\x68\x15\x32\x1d\x02\xa8\xa6\x56\xe0\xbc\x9a\x66\x14\x45\xf5\xbc\xd7\x16\x9f\xb7\x6c\xf1\x79\xcb\x16\x9f\xb7\x6c\xf1\xb5\x64\xa9\xcf\x26\x9f\xb7\x6c\xf2\x79\xdb\x26\x9f\xb7\x6d\xf2\xdd\x8e\x55\x8c\xbb\x23\xa1\x39\x04\x7f\xfc\x62\x57\x44\xfe\xaa\x5a\x53\x19\xe9\x06\x4c\xa4\xcf\xab\xbb\xcd\xf8\xe1\x66\xb7\x88\xb7\xac\xac\x78\x1d\xde\xd6\xe9\xe4\x8f\x82\x20\x29\x55\x44\x86\x9f\x7f\xfa\xf6\x70\x90\xbf\xef\x79\x74\x9d\xd5\x56\x09\x8d\xc3\x52\xee\x05\xc6\xfc\xf8\xf9\xa7\x6f\xfd\x9c\xd0\x02\x3e\x09\xae\x18\x8b\xa2\xe3\xf2\x6f\x85\xa7\xe2\xca\x14\x3b\x2c\x28\x77\x7c\x43\x66\xb4\x9c\x96\xfc\xae\xb8\xb6\xbe\x10\x93\xa6\x7d\xca\x78\x7c\x24\xb4\x98\x56\x65\x1c\xc6\x2d\xac\x04\x99\x9c\x9c\x40\x46\xca\xc6\xe6\x73\x95\xe5\xbe\xdf\x6a\x46\xc5\x13\xf3\x09\xdd\xff\xe5\xed\x57\xc5\x0d\x66\xcb\xa2\xdd\x16\xf2\x61\x33\x8c\x14\xad\x68\xae\xb8\xb0\xea\x2c\x07\xdb\x81\x9c\x16\xe1\xfc\x8f\xb3\x33\x3f\x9e\x5c\x90\xf3\x98\xee\x42\xe9\xdb\x30\x91\x7f\xcf\x8a\xf3\xf9\x1f\x67\x0b\x3e\xad\x18\x7c\x98\x3b\x8c\x11\xc7\xc0\xb1\x3b\x88\x65\x4f\x6b\x51\x20\xd9\xa3\x19\x9d\x19\x2b\xa7\xbb\x70\xb6\x28\x2f\xef\x16\x77\xe3\x31\xe1\x9a\x47\xa2\x33\xb7\x03\x59\x80\x06\x0b\xfe\x8e\x2c\xb8\x34\x0f\x17\x85\xbc\xaa\x8b\x92\xfb\x4d\x3c\xcf\x1f\xf6\x45\x59\x3b\x27\x8f\x3c\x49\x10\x29\xf6\x3c\xf7\x6d\x54\x5d\x17\x5f\xb1\x9a\x89\x5d\x93\x20\xb7\xcf\x37\x1e\xa1\xde\xfb\x68\xc7\xf2\x6b\xaf\xd9\xb7\xd8\xf6\xe6\x99\xd6\x92\x9c\xec\x66\xf3\x9e\x97\x65\x51\x2a\x2b\x25\xcf\x1b\x76\x18\xc4\x6b\x97\x28\x62\x1c\x00\x34\x6a\x10\x80\xf9\xcb\x77\xdf\xfe\xb9\xae\xf7\xd2\x6a\x6a\xc1\x70\xd4\xde\x9f\xbe\x7e\xe7\x75\x58\xf4\x9b\xee\x28\x9b\x16\x79\xc9\x59\xf2\x28\x08\x64\x1e\x6f\x59\xbe\x71\x65\x1b\x9f\x86\x80\x51\x58\xf2\x28\x93\x2b\xfb\x17\xb3\x19\x98\x3b\x4b\x4b\xe7\x96\x11\x16\x77\xb3\xdf\x89\x45\xdf\x17\x79\xc5\x3b\xf0\xe1\x17\x3c\x2d\x4a\x8e\x9a\x7e\x12\xf8\x71\x91\x57\xc5\x8e\x4f\x77\xc5\xc6\xf7\x20\x11\xd8\x60\xff\x97\xb7\x83\x09\x9e\xbb\x41\x5e\xd4\x83\xb4\xb8\xcd\x13\x8f\xd0\xe6\xf2\x0d\x67\x84\x1c\x29\x9b\x56\x10\xed\xd8\xe8\x97\x6a\xeb\xdc\xd6\x2f\x39\xb7\xae\xed\x58\xfd\xcc\xa0\x8f\xe2\x38\x89\x33\x7a\x62\x91\x51\xfa\xd4\x3b\xb7\xef\x8b\x81\xae\xda\x39\xb1\x8e\x93\xe9\x30\xe4\xca\xd7\xae\xdb\x11\x70\xd9\xe9\x2f\xb2\x6c\x60\x1b\xd7\x4c\xdd\xa6\x23\x96\x7e\x8f\x71\xc8\x09\x42\xde\x41\x38\x61\xd9\x69\xca\x67\x4f\x87\x04\x2f\x37\xfa\x23\x41\x63\x85\x54\xdc\x88\x0f\xfe\x28\x09\x5c\x0f\x40\xb9\xe7\xff\x5f\x9e\xf0\xff\xb6\xa9\x75\x11\x86\xe6\x78\xfd\xc3\x50\xae\x7c\x3c\xc3\x16\xf9\xa4\xe0\xdf\xb1\x3d\x6d\xcf\x0e\xde\x0b\xc4\xd4\x37\xa5\x76\x27\xad\x13\xac\xb6\x4b\x4d\x12\x12\x22\x36\x83\x90\x20\x06\x54\x1b\xf4\x82\xaa\x8d\x95\xcb\x5a\xe1\x6f\x9c\xa0\x56\xae\x59\x6b\xbb\xe4\x6d\xf6\x77\xee\x96\x40\xc4\x93\x67\x82\xaf\x9c\x0e\xa1\xd2\x6b\x51\xee\x8a\x68\x4d\x50\x80\x93\xf5\x9b\x13\x06\x2b\x5c\x67\xc6\x46\xab\x82\xa0\xd2\x71\xef\x81\xa7\xdc\xd2\x77\x81\xe4\x14\xea\x9e\x56\xb7\x51\x55\x97\x1d\x4e\x7d\xa6\x8a\xe4\x96\x5e\x91\xce\x6b\xbf\xf1\x31\xc5\x2a\xf5\x5f\x28\x27\x05\xb1\x7d\xb6\xc8\x8b\x56\x7c\xba\x3e\xbe\x6a\x90\xe5\x03\x46\xd8\xaa\x5e\x8f\x46\xe2\x7f\x3b\xed\x63\xbb\x24\x0c\x43\xec\x79\xe9\xf3\x55\xbd\x0e\xc5\x7f\x10\x25\x95\x95\x1b\x08\x96\x59\x41\x20\x53\xce\xe1\x35\x15\xcd\x09\x09\xa0\xaa\xf8\xbd\x30\x29\xa9\x25\x31\xd5\x3c\x61\x1d\x91\x60\xdb\x35\x0d\x3d\xdb\xa0\xdb\x7f\x3a\x59\xe7\xa6\xf8\xfb\xe9\x0a\xc5\x33\xed\xab\x9e\xf7\x36\x79\x24\xeb\x5a\x62\x09\x4e\xe7\xfc\xd5\xf9\x67\x33\x72\x3c\xfa\x44\xcd\xbb\x1b\x81\xf4\xcf\xbe\x8b\x98\x6f\x4c\xfe\xcb\x66\x97\xdd\x4b\xf0\x92\x6a\xc5\x8b\xfa\xaa\x4e\xd6\xb2\x73\x04\x5b\x13\x97\x1c\xc6\x6a\xad\x0b\xd4\x41\x6a\x85\x61\x55\x41\x45\x86\x76\x84\x69\x16\x72\xca\x43\x4f\xb7\x9a\x5c\x55\x1e\xa1\xfc\x70\xf0\x5b\xa5\x8b\x46\x1c\xd7\x46\x56\x70\x81\x99\xdd\x26\x56\x0c\x57\x5a\x85\x76\x83\xea\x8b\x47\x88\xad\xfc\x3d\x60\x79\x54\x9a\xa9\x00\x08\xe2\x28\x2d\xd4\xd3\xe5\x6c\x41\x74\x7c\xe6\x6d\xb6\x4b\x7c\xc8\x6f\x26\xa3\x7a\xe8\xa1\x20\x43\x29\x3b\xf7\x55\xc4\x4e\xb2\xc8\xa7\xb1\xfa\x4c\x98\xd1\x7c\x5a\xd5\x8f\x3b\xe9\xf7\x1a\x7a\xf3\xd9\xec\x9f\x3d\x5d\x28\xe3\x68\x61\xa9\x0c\xfd\xd0\x3f\xd5\x29\x66\x24\xc5\x21\xe5\x64\x81\xe1\x89\xcb\xd1\x08\xf7\xc3\xd8\x28\xef\xff\xf2\x16\x56\x9f\x1c\x3b\x36\x08\x90\x58\x2b\xb8\x14\x46\xc7\xec\xe0\x3a\x32\x9b\xeb\x00\x97\xa2\x53\xbc\x05\x03\x36\xe8\x53\x71\x37\x1b\xf6\x42\xf0\x37\xc0\x5f\x64\x8e\x36\xb4\x0a\xff\xf2\xf6\x87\xef\xa7\x10\x3c\xd8\x67\x3d\xfc\x05\x59\xb4\x67\xe0\x73\x5a\x11\x5a\x8f\x46\xb5\xd2\x02\xf7\xd2\xe1\xee\x64\x06\xf8\xf9\x60\xe0\x8d\xf5\x50\x68\x6f\xdb\x6f\xb2\x1d\xf4\x9c\x66\x1b\x9b\x4b\x11\xc8\x5c\x32\x24\x0b\x7b\x50\x9f\xd8\x50\xf8\x09\x7d\x32\x50\xe9\x05\x4f\x1e\x52\xdf\xe2\x17\x10\xe0\x5e\xf0\xc7\x19\xf5\xe4\x1d\x2b\x4a\x91\x3a\xf3\x82\xba\xbc\xe5\xd4\x33\xb2\x3c\x4f\xc6\x04\xf6\xe0\xc2\xb4\xda\x43\x8c\x5b\x0c\x0d\xec\xc1\xed\x25\xde\x89\x33\xe6\x99\xf0\xc0\x1e\x6a\x09\xc5\x1b\x00\x3f\x2f\x98\xe9\x8e\xbc\x8f\x66\x33\x19\x24\xd8\x44\x04\x78\xf2\x94\xa7\xba\x17\xfc\xe1\xff\xcf\xd7\xd5\xee\x44\x08\x03\xc1\x57\x31\x93\xf8\xc3\xa4\x62\x8b\x40\xb4\x3e\x0c\xe1\x4b\x8e\x28\x68\xb8\xf3\x87\x31\xbc\xbb\xd9\x76\xb7\x14\xf0\xfc\x77\xe9\xd2\xdd\x76\xa6\x70\x3d\xb6\x3b\xb7\x28\x29\x50\xff\x01\xfd\x8c\xb1\x18\xc6\xfe\xa1\x1f\x2e\xa7\xaf\x3a\xa1\x9d\x99\x12\xaf\x46\x6b\x05\xbf\x96\xc1\xf2\xc1\xe1\x3c\xd2\x3a\x62\x9d\xe4\x4a\xc4\x8e\xec\x6b\xf5\x7e\xee\x14\xaa\x69\x18\xe3\xe9\x73\xb3\xab\x5a\x86\x35\xc1\x4d\x39\x0e\x13\x79\x30\x0a\xe7\xef\xa9\xe1\x0b\x29\x8e\x3b\x5b\xb5\x06\x89\x42\x78\x28\xff\x8f\x90\x69\xef\xe1\xba\xff\x68\xd3\x72\xe4\x49\xf2\xd8\xb0\x26\x8f\xa1\x65\x6a\x22\x14\x74\x92\xad\x70\x2d\x0a\xfe\x35\xfe\xde\x1f\x8f\xaa\x20\xcf\x2c\x10\x04\x51\x24\xde\x43\x27\x12\x38\xa1\x41\x0e\xcd\x80\xd5\x89\xc1\x65\x1e\x7f\xcc\x9e\x6b\x44\x60\x0b\x62\x8e\xcb\x40\x60\x4d\x4a\xdc\x11\xef\xf1\x9b\x75\xea\x1f\xd2\xc0\x58\xa5\x89\xe1\xf7\x31\x64\xe6\x54\xee\x71\x42\x3c\x1e\xc9\x18\xd0\xba\xf0\x59\xa4\xab\x97\xb2\x5e\xb1\xe8\x25\x3a\xe3\xe2\xad\x2e\x94\x2b\x1f\xa5\x11\x05\xe8\x33\x9a\xc3\x8e\xa6\x80\xbb\xa1\xe9\x48\x72\xe3\xd0\xcb\x87\xa0\x45\x20\xf9\x02\xd8\x34\x62\xed\x29\x50\xf2\xe8\x86\xc4\x79\x8f\xd8\x0f\x41\xa6\x38\x11\x16\xdd\xf3\xe5\x54\xc3\x66\xae\x93\x50\xbd\xb5\xa5\x0e\xe8\x8d\x14\xad\x20\xe1\x9f\x39\x65\xdb\x8d\x1f\xd4\xef\x34\xb4\x5d\xd9\x54\x73\x1b\xe8\xab\xab\xe6\xad\x9f\xe9\x71\x54\x86\x25\x57\x17\xe6\x39\xcd\xb0\x31\xf2\xed\x8b\x6d\x6b\xf8\x5f\x53\x8b\x5c\xdf\xde\xe4\xf4\x85\x14\xdb\xe7\xee\xb3\xab\x2e\x6e\xdd\xdd\xf3\xe7\x8d\xdd\x83\x86\xc6\x31\xbe\x2c\x77\x2f\xbf\x01\x00\x00\xff\xff\x0d\xcc\x2b\xb4\x02\x39\x02\x00") + +func assetsLoginDistAllMinJsBytes() ([]byte, error) { + return bindataRead( + _assetsLoginDistAllMinJs, + "assets/login/dist/all.min.js", + ) +} + +func assetsLoginDistAllMinJs() (*asset, error) { + bytes, err := assetsLoginDistAllMinJsBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/login/dist/all.min.js", size: 145666, mode: os.FileMode(420), modTime: time.Unix(1571033118, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _assetsLoginDistRespondMinJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x57\x6d\x73\xdb\xb8\xf1\x7f\x7f\x9f\x82\xe2\x3f\x7f\x06\xb0\x20\x50\x72\xf2\xa6\xe4\xc1\xba\xc4\x71\x7a\xd7\x49\xee\xae\x67\x4f\x7b\x53\x49\x99\x01\xc1\xa5\x44\x89\x24\x68\x02\xd4\x83\x45\x7e\xf7\x0e\x48\xea\xc9\x8e\xa7\xed\x1b\x09\x58\x2c\x16\xfb\xf8\xdb\xa5\x7b\xd5\xb3\xfe\x00\x95\xcb\x2c\xa4\x4b\x65\xad\x47\xf4\x3d\xbd\xf6\xac\x34\xce\xdc\x94\x6f\x07\x9b\x38\xd4\x0b\x2b\x85\x30\xe6\xd6\x63\x09\xc5\xce\xca\x65\xb2\x8b\xe2\x24\xb1\xae\xac\x5b\x99\xef\x8a\x78\xbe\xd0\xd6\xf5\x70\xf4\xce\xba\x17\x52\x6b\xeb\x6f\xb0\x48\x7e\xb0\xae\xac\x2f\xb1\x80\x4c\x41\x68\x95\x59\x08\x85\xb5\xd0\x3a\x57\x9e\xeb\xce\x63\xbd\x28\x03\x2a\x64\xea\x2a\xc3\xbf\x84\x45\xe2\x76\x0a\xb8\x41\x22\x03\x37\xe5\x4a\x43\xe1\x7e\xf9\xe5\xf6\xee\xd7\xfb\xbb\xc1\xd7\x5f\x1e\x8c\x38\xeb\xca\xfd\xe1\x87\x5e\x54\x66\x42\xc7\x32\x43\x1c\xef\xed\x52\x81\xa5\x74\x11\x0b\x6d\xfb\x9c\xa6\x5c\x8b\xc5\x57\xa3\x28\x3b\xdf\x54\xd5\xf9\x9d\x35\x2f\xac\x80\x08\xc6\x69\x28\x45\x99\x42\xa6\xef\x12\x30\x7f\x24\x64\x82\x46\x71\xa1\x0e\x84\xdb\x45\x9c\x84\x55\xd5\x11\x9b\x1d\x01\xc6\xa9\x28\x80\x6b\xe8\x98\x90\x1d\xc8\x70\x67\x63\x12\xbd\x3c\x09\xe3\xb5\x8d\xfd\x02\x74\x59\x64\x56\x44\xe3\x90\xd9\xe9\xe3\x40\x83\xd2\x83\x91\x4d\x22\xaa\xf4\x2e\x01\x2a\x94\x7a\x80\xad\x66\x76\x2e\x55\x6c\xb4\xf4\x78\xa0\x64\x52\x6a\xf0\xb5\xcc\xbd\xc1\x68\x38\x84\xd4\x26\xd0\xb1\x07\x5c\xac\xe6\x85\x2c\xb3\x90\xd9\x99\xcc\xc0\x9c\xf0\x3c\x87\x2c\x6c\x34\x44\x11\x26\xe7\xe6\x9e\x5e\xcf\x32\x28\x7e\x7e\xf8\xfa\x85\xbd\x75\xd4\x62\xe7\xff\xd8\x88\x6b\xe3\xca\xec\xb7\x7d\xde\x7f\x6b\xdf\x58\xff\x77\x54\xd0\xda\x5b\x4d\xe4\x3d\xeb\xfd\x75\xbe\xf5\xad\xfa\x47\xb7\xb9\x71\xf3\x96\x08\x1a\x67\x0a\x0a\xfd\x11\x22\x59\x00\x02\x12\x62\x12\xb0\xf7\xd7\x8c\xb1\x88\xca\x28\x52\xa0\xff\x69\xae\x12\x41\x0b\x48\xe5\x1a\x5a\xcd\x00\x93\x7d\x13\x15\x50\x5e\x40\x9a\x87\x3d\x5e\xd7\x35\x3a\x85\x02\xd7\x48\x2f\x62\x75\x69\xc2\x45\x94\x0f\x07\x56\x80\xf0\xbe\x44\xbd\x21\xae\x4d\x44\x05\xdb\xd7\x3e\xa7\x45\x9b\x45\x4c\x10\x41\xcb\x3c\xe4\x1a\xd8\x51\x12\xde\xd7\xbe\x61\x0d\xd9\x64\x46\x2e\xe8\x4d\x4a\xb0\xde\xc8\xd7\xc5\x6e\x1f\xb0\x0c\x36\x16\xa7\x7f\x7e\xfd\xf2\xb3\xd6\xf9\x1f\xf0\x58\x82\xd2\xb5\x30\x8a\x23\x81\x8f\xe7\x1f\x84\x8e\xd7\xf0\xe7\x6f\xc1\x12\x84\x46\xf6\xd7\x58\x14\x52\xc9\x48\x37\x17\x1f\x1e\x7e\xb7\x71\x7d\xf0\xfd\xe9\xa5\x8e\x12\xd4\x35\x32\x19\x73\xb2\x92\x04\xad\x1a\x82\x01\xc2\xbe\x70\x1c\x24\xa8\xcc\x21\x43\xf6\x5f\xef\x1e\x6c\xc2\x49\x6f\x88\x89\xa0\x32\x2b\x80\x87\x3b\xa5\xb9\x06\xb1\xe0\xd9\xfc\xc2\x8e\xf7\x3d\xc6\x8c\xcb\x79\xb8\xbb\x37\x1c\x55\x75\x3d\x1c\x36\x34\x73\xa1\x54\x8e\xf3\x6e\xf8\xfe\x6c\x5f\x55\x01\x12\x9d\xcf\x14\x98\x24\xc4\x35\x79\x2e\xc4\x71\x04\x55\x90\x85\x28\x2b\x93\x04\xe3\xda\x8f\x23\x24\x28\x5f\xf2\x2d\x8b\x88\xa0\x8f\x25\x94\xc0\xc2\x26\xd4\x73\xd8\xb2\x7d\x1b\x57\xf7\xa7\xe6\x7f\xf2\x6d\xba\x9f\xf5\xa7\x7b\x64\x16\xd3\x7a\x76\x35\xdd\x4f\xbe\x4d\xeb\xe9\x7e\x76\x35\xad\x71\xdf\x9d\xc7\x64\x05\xbb\xa8\xe0\x29\x28\xcf\xfd\x09\x8d\xbd\xe9\x00\x8d\x3d\x59\xa5\xf2\xa9\xda\x40\xb0\x8a\x35\x9e\x0e\xf0\xf8\xc8\x74\x14\x38\xf6\xbe\x2f\xd2\xac\xcd\xca\x88\x2e\x8b\x44\x79\x2e\x2a\x8b\x64\x8a\xf0\xe4\xad\x3d\x1b\x1b\x3d\xdc\x29\x7e\x6b\xcf\x26\xdf\xbc\xe6\xbf\xdf\x1d\x4c\x31\x76\xe7\x24\x8a\xb3\xf0\xde\x64\xb9\x3a\x58\x60\x5d\xa1\xf6\x4d\x6c\xac\x98\xde\x4f\xd5\xac\x3f\xc6\x6f\x5c\x22\xb3\x64\xe7\xb9\xc8\xfc\x4d\x55\x1f\x8f\xd1\x84\x0f\x9e\x3e\x0c\xfe\x65\x38\xd5\xd8\x25\x69\x9c\x6d\x3c\x77\x8a\x26\x53\x35\xbb\x4a\xe3\x6c\xda\x02\xe9\x54\x5d\x79\x0d\xa9\x3d\x98\x0c\x07\x7f\x99\xd2\x59\x1f\xa3\x7c\x5b\x41\x8a\x1b\xe2\x14\xbb\x24\xe5\xdb\xb3\xeb\x7c\xfb\xbf\x5d\xaf\x89\xa0\x8d\xfa\x7f\x2f\xa1\x88\x41\xdd\x97\x79\x2e\x0b\x0d\xe1\x05\x30\x3a\x8e\x89\x69\x8f\x5d\x10\x91\x6d\x4c\xb2\x78\x92\xd8\xd8\x71\x5e\x3b\xa1\x5d\x21\x93\xde\x2b\x0f\xb5\xe9\x3c\x27\x0b\x12\x93\xe5\x19\xd8\x92\x15\x5b\xbe\x40\xde\xc4\x94\x64\x6a\x7e\x32\xf3\x23\xd9\xbe\x26\x39\x7b\x37\x24\x8f\x6c\x49\xe7\x70\xe0\x53\x1f\x77\x0f\x7c\xfe\x2b\x4f\x01\xd9\x0b\xe0\xa1\x8d\x27\xc3\x59\x55\xad\x48\xf1\x2a\x5b\xc0\x15\x34\x6c\x44\xb1\xc7\x57\x78\x92\x38\x5b\xd9\x98\xe8\xe7\x90\xc0\x49\xc0\x96\xdf\xc5\x74\x22\xd8\x92\x1a\xdc\x27\x21\x5b\x75\xb8\x1c\xc9\x4c\xdf\xc7\x4f\x40\x80\x89\xa6\x66\x2e\xa9\x91\xc1\x97\x43\xf9\xff\x67\xe4\x37\xf7\x06\x2a\x7e\x02\x6f\x04\xa9\xdf\x02\xf1\xc8\xb4\x01\x51\x55\x48\xb0\xe8\xa5\x62\x5d\x1b\x12\xaf\xb5\x09\x4c\x9e\x6b\xca\xec\xd1\x70\xf8\xff\x36\x79\xae\xeb\x89\x7e\xde\x57\x02\x4c\x22\xc7\x59\x5d\x42\xbf\x20\xab\xb3\xee\x88\x09\x67\xc1\x45\x03\x88\xc6\xab\x8b\x16\x20\xb0\x77\xd9\x13\x82\xef\xa8\x15\x12\x68\xf0\xef\x19\x19\x8c\xf8\x98\xe5\xbc\x50\xf0\x39\x91\x5c\x23\x8e\x6b\x52\x9e\xa2\x76\x84\x50\x5b\x24\x31\x64\xad\x0a\xb6\x09\xd1\x44\x18\xc4\xb7\x6f\xef\xef\x47\xb7\x32\xcd\xb9\xb6\x19\x33\x1e\x6c\xd6\x5f\x65\x08\x8e\x13\x56\x55\x1b\xd2\x89\x98\x55\x55\x48\x22\x93\x83\x92\xa9\x89\xa2\x09\x64\x73\xbd\x18\x8c\x66\xa4\x60\xc8\x80\xff\x27\xae\x01\x9b\x64\x7a\x88\x53\x03\xd7\x71\x84\x02\xc7\x99\x3b\x4e\x7e\x53\x0c\xe6\xb8\x8b\x32\xa7\x22\x01\x5e\x18\x1e\x59\x6a\xb4\xc0\x64\xc1\x38\x55\xed\x2d\x43\x29\x49\x8e\xc9\x5a\xc6\xa1\x35\xf4\xe7\xac\xf0\x23\x59\x20\x63\xc0\xda\x8a\x33\x2b\xc1\x71\x84\x12\xba\xe0\xea\xb7\x4d\xf6\x7b\x21\x73\x28\xf4\x0e\xad\x71\x6b\xe3\x86\x25\x93\xf5\x8c\x6c\xd9\x86\x1a\x9c\x21\x3b\xb3\xe0\xdb\x0d\x79\x62\xa6\xa4\x19\x63\x5b\xf2\xe1\xb0\xdc\x91\x8f\xcc\x86\xd4\xf6\xb7\x8e\x83\xb6\xe7\x0e\xdc\xe2\x2b\xb4\xa5\x71\x16\xc2\xf6\xb7\x08\x7d\xc4\x37\x83\xd1\x38\xae\x2a\x8d\xb0\x37\xc2\x98\xec\x1c\x07\xed\xce\xf9\x77\xf8\x0a\xed\x5e\xe7\xdf\x18\x75\x9b\x11\xd1\x71\xd0\x93\xe3\x7c\xa8\xaa\x1e\x7a\xaa\x2a\xb8\x61\x5b\x6c\xd6\x1f\xaa\x6a\x77\xc3\x00\xe3\xaa\x42\xd1\x64\xd3\x22\xc7\xec\x62\xc3\x26\x33\x4c\x4e\x5b\x9a\x97\x6a\x81\xd2\xc9\x86\x16\x65\x02\x6a\x86\x71\x7d\x70\xd3\xad\x71\x53\x86\xb3\xe7\x3e\xba\xc5\x8e\x93\x4d\x6e\x67\xed\x2f\xcd\x79\x01\x99\xfe\x55\x86\xc0\x18\x7b\x74\x9c\xc7\x8b\xfc\x33\x2c\xd8\xcf\xba\x18\xb3\xe1\x31\x08\x9f\x8c\xf4\xc8\x04\x21\x7a\xfe\xc0\xa7\x2e\x08\x77\x2f\x8b\xb0\x49\x59\x1b\x93\xcf\x2c\x9a\x7c\x9a\xd1\xa5\x8c\x33\x64\x4f\x33\x1b\xfb\x77\x54\xef\x72\x60\xb6\x86\xad\x76\x85\x52\x36\xb9\x6b\x4d\x64\x9f\xc8\xe3\x65\x51\xdd\x11\x49\x33\xd8\xea\xfb\x38\x48\xe2\x6c\x8e\xc9\x5d\x5b\x0a\xf7\x0b\x00\x3d\x3e\xdf\x1c\x11\xe4\xb3\x77\x77\x51\xac\x07\xc5\xcc\xa1\x31\x1d\x7d\xc6\x98\x64\xad\x33\xef\x70\x5d\x93\xf5\xc5\xec\x41\x3a\xb8\x36\xd3\x6d\x01\x79\xc2\x05\xa0\xae\xa1\xd3\x63\xeb\x25\xf6\x01\xf6\x8f\x67\x8d\x01\x66\x90\x01\xc7\x81\xce\x87\x55\x35\xf4\x03\x16\x50\x55\x06\x66\x66\xcb\xe6\x68\x48\x02\x9a\x70\xa5\x7f\xe9\xf2\xc6\x76\x6d\x8c\x9b\x51\x6c\xce\xbe\x33\xaa\xbe\x54\xc1\xf4\x71\x62\xbf\x19\xd9\xfd\xa0\x6f\xbf\xb9\x7e\xf3\xce\xc6\x35\x59\xb0\x5e\xe4\x38\xa1\x1f\x74\xef\x3a\x0e\x0a\xfa\xcc\xc8\x26\x66\x1d\xb1\x11\x3e\x06\x33\x36\x81\xbd\x89\xfd\xb8\xdf\x6f\x0d\x5d\x92\x15\xc9\x88\xf4\x17\x63\xb4\x64\x21\x49\x5b\xcf\xcc\x11\xc7\x18\x7b\x68\xc9\x60\x12\xcf\x9e\xd9\x7a\x1a\x0d\xb0\xe3\xfc\x01\xf3\xbb\x6d\x4e\xdf\x8c\x0e\x57\x0f\x84\x6b\xc7\x99\x9f\x36\xd8\x78\x9d\x2d\xa9\xca\x93\x58\x23\x9b\xd8\x98\x48\x76\x48\xb6\xa3\x76\x39\x1b\xfa\xf2\x26\xf7\xf3\x7e\x1f\xaf\x58\x36\xc9\x67\x24\x69\xa5\x76\xc3\xd4\xea\x20\x00\x35\xad\xec\x99\x62\xa6\x29\x9f\xa9\x74\x5d\x55\xb6\x69\xd1\xa4\x29\x18\x2f\x3d\xc2\x17\x39\x14\xa7\xb7\x3a\x96\xb0\x91\x78\x33\x18\xb5\xc3\xca\xea\x79\x74\xe3\x6c\x83\x1d\xe7\xac\xfe\x8f\x66\xe3\x3e\x3a\x7f\xcf\xc6\xed\xbc\xf2\x42\x02\xdf\xfe\xd7\x12\x6a\x5c\x97\x08\xd7\x64\x73\xde\x86\xe3\x08\x85\x9d\x01\x87\x31\x3d\xa4\x6a\x11\x47\x1a\x61\x3f\x42\x01\x5d\x14\x10\x9d\x3e\x16\x04\xde\xaf\x91\x20\x1d\x39\x38\xe4\xa7\x9c\xb4\x94\x19\xeb\x0d\xc9\x05\x04\x9f\x3d\xb5\x31\x8f\x0f\x8d\x1a\x35\xd9\x9e\xeb\x70\x08\x53\xc0\x86\x7e\xf0\xe3\xa1\x1d\xf8\xc1\x21\x97\x04\x53\x93\xc0\xf4\x17\xd1\x69\xc3\xba\x91\x88\xcc\x9b\x41\x3a\x71\x9c\x16\x17\x94\x29\x59\xd3\x79\x1a\x2a\xd5\xf2\x8b\xdc\x40\x71\xcb\x95\xe9\x21\xd0\x34\x90\x9e\x9c\xc0\xec\xd4\xfc\x9a\x22\x3f\x0e\x13\x6d\xc9\x17\x7c\x73\xdb\x56\xfd\x18\xad\xd1\x2b\x47\x04\x48\x64\xec\x06\x63\x32\xf6\x50\xcf\xfd\x76\x98\x4f\xbd\xd9\xd5\xd4\x9d\xba\xd8\xa5\xe6\x3b\x0f\x01\x76\x9c\x5e\x51\x55\x70\xac\xba\x53\x6e\x9b\x82\xef\x32\xcf\x6d\x32\x8f\x99\x51\x31\x91\x82\x1b\xd7\xd0\x85\x54\x1a\x3b\x0e\xb2\x5d\xd7\x58\x05\x17\x25\x7f\x6d\x4e\xe0\x9c\x3d\x2f\xa4\x96\x42\x26\x7d\xc0\x24\xec\xf2\xdb\x38\xcc\x83\xee\x5b\x30\xaa\x31\xc6\xb5\x89\x83\xbf\x45\xf8\xf4\x05\xb7\x25\xa2\x99\xdc\xd2\x7f\xf0\xa4\x04\xa6\x09\xa7\x3c\x0c\xef\xd6\x90\xe9\x2f\xb1\xd2\x90\x41\x31\x7e\x49\x42\x76\x01\x66\x80\xb2\x49\x40\x7a\x23\xec\x71\xca\xb5\xe6\x62\xd1\x30\x99\xc1\xf6\x6c\x6b\x26\xdb\x23\x37\xae\xbb\x8f\x50\xff\xdf\x01\x00\x00\xff\xff\xed\x16\x16\x7b\x19\x11\x00\x00") + +func assetsLoginDistRespondMinJsBytes() ([]byte, error) { + return bindataRead( + _assetsLoginDistRespondMinJs, + "assets/login/dist/respond.min.js", + ) +} + +func assetsLoginDistRespondMinJs() (*asset, error) { + bytes, err := assetsLoginDistRespondMinJsBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "assets/login/dist/respond.min.js", size: 4377, mode: os.FileMode(420), modTime: time.Unix(1570872928, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.ReplaceAll(name, "\\", "/") + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.ReplaceAll(name, "\\", "/") + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "assets/login/dist/all.min.css": assetsLoginDistAllMinCss, + "assets/login/dist/all.min.js": assetsLoginDistAllMinJs, + "assets/login/dist/respond.min.js": assetsLoginDistRespondMinJs, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if name != "" { + cannonicalName := strings.ReplaceAll(name, "\\", "/") + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "assets": &bintree{nil, map[string]*bintree{ + "login": &bintree{nil, map[string]*bintree{ + "dist": &bintree{nil, map[string]*bintree{ + "all.min.css": &bintree{assetsLoginDistAllMinCss, map[string]*bintree{}}, + "all.min.js": &bintree{assetsLoginDistAllMinJs, map[string]*bintree{}}, + "respond.min.js": &bintree{assetsLoginDistRespondMinJs, map[string]*bintree{}}, + }}, + }}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.ReplaceAll(name, "\\", "/") + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/template/installation/assets/installation/dist/all.min.css b/template/installation/assets/installation/dist/all.min.css new file mode 100644 index 0000000000000000000000000000000000000000..41a22174b6967514eccf14b887fc21c03fb30513 --- /dev/null +++ b/template/installation/assets/installation/dist/all.min.css @@ -0,0 +1,5 @@ +@font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OX-hpKKSTj5PW.woff2)format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OVuhpKKSTj5PW.woff2)format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OXuhpKKSTj5PW.woff2)format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OUehpKKSTj5PW.woff2)format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OXehpKKSTj5PW.woff2)format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+1EA0-1EF9,U+20AB}@font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OXOhpKKSTj5PW.woff2)format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN_r8OUuhpKKSTjw.woff2)format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:open sans;font-style:normal;font-weight:400;src:local('Open Sans Regular'),local('OpenSans-Regular'),url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFWJ0bf8pkAp6a.woff2)format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:open sans;font-style:normal;font-weight:400;src:local('Open Sans Regular'),local('OpenSans-Regular'),url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFUZ0bf8pkAp6a.woff2)format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:open sans;font-style:normal;font-weight:400;src:local('Open Sans Regular'),local('OpenSans-Regular'),url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFWZ0bf8pkAp6a.woff2)format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:open sans;font-style:normal;font-weight:400;src:local('Open Sans Regular'),local('OpenSans-Regular'),url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFVp0bf8pkAp6a.woff2)format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:open sans;font-style:normal;font-weight:400;src:local('Open Sans Regular'),local('OpenSans-Regular'),url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFWp0bf8pkAp6a.woff2)format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+1EA0-1EF9,U+20AB}@font-face{font-family:open sans;font-style:normal;font-weight:400;src:local('Open Sans Regular'),local('OpenSans-Regular'),url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFW50bf8pkAp6a.woff2)format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:open sans;font-style:normal;font-weight:400;src:local('Open Sans Regular'),local('OpenSans-Regular'),url(https://fonts.gstatic.com/s/opensans/v17/mem8YaGs126MiZpBA-UFVZ0bf8pkAg.woff2)format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:open sans;font-style:normal;font-weight:700;src:local('Open Sans Bold'),local('OpenSans-Bold'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN7rgOX-hpKKSTj5PW.woff2)format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:open sans;font-style:normal;font-weight:700;src:local('Open Sans Bold'),local('OpenSans-Bold'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN7rgOVuhpKKSTj5PW.woff2)format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:open sans;font-style:normal;font-weight:700;src:local('Open Sans Bold'),local('OpenSans-Bold'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN7rgOXuhpKKSTj5PW.woff2)format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:open sans;font-style:normal;font-weight:700;src:local('Open Sans Bold'),local('OpenSans-Bold'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN7rgOUehpKKSTj5PW.woff2)format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:open sans;font-style:normal;font-weight:700;src:local('Open Sans Bold'),local('OpenSans-Bold'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN7rgOXehpKKSTj5PW.woff2)format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+1EA0-1EF9,U+20AB}@font-face{font-family:open sans;font-style:normal;font-weight:700;src:local('Open Sans Bold'),local('OpenSans-Bold'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN7rgOXOhpKKSTj5PW.woff2)format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:open sans;font-style:normal;font-weight:700;src:local('Open Sans Bold'),local('OpenSans-Bold'),url(https://fonts.gstatic.com/s/opensans/v17/mem5YaGs126MiZpBA-UN7rgOUuhpKKSTjw.woff2)format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}/*!* Bootstrap v3.3.7 (http://getbootstrap.com) +* Copyright 2011-2016 Twitter, Inc. +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)*//*!normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css*/html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*!Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css*/@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:glyphicons halflings;src:url(http://yanshi.sucaihuo.com/modals/26/2674/demo/fonts/glyphicons-halflings-regular.eot);src:url(http://yanshi.sucaihuo.com/modals/26/2674/demo/fonts/glyphicons-halflings-regular.eot?)format('embedded-opentype'),url(http://yanshi.sucaihuo.com/modals/26/2674/demo/fonts/glyphicons-halflings-regular.woff2)format('woff2'),url(http://yanshi.sucaihuo.com/modals/26/2674/demo/fonts/glyphicons-halflings-regular.woff)format('woff'),url(http://yanshi.sucaihuo.com/modals/26/2674/demo/fonts/glyphicons-halflings-regular.ttf)format('truetype'),url(http://yanshi.sucaihuo.com/modals/26/2674/demo/fonts/glyphicons-halflings-regular.svg)format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:glyphicons halflings;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:helvetica neue,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,courier new,monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media(min-width:768px){.container{width:750px}}@media(min-width:992px){.container{width:970px}}@media(min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media(min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media(min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media(min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media(min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media(min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media(min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media(min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media(min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media(min-width:768px){.navbar{border-radius:4px}}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media(max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media(min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media(min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media(min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media(min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:1e4px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media(min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media(min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:helvetica neue,Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:helvetica neue,Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1e3px;perspective:1e3px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:transparent;filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:transparent;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media(max-width:767px){.visible-xs-block{display:block!important}}@media(max-width:767px){.visible-xs-inline{display:inline!important}}@media(max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media(min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media(min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media(min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media(min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media(min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media(min-width:1200px){.visible-lg-block{display:block!important}}@media(min-width:1200px){.visible-lg-inline{display:inline!important}}@media(min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media(max-width:767px){.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}@charset "UTF-8";/*!Animate.css - http://daneden.me/animate +Licensed under the MIT license - http://opensource.org/licenses/MIT +Copyright (c) 2015 Daniel Eden*/.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated-fast{-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}.animated.bounceIn,.animated.bounceOut{-webkit-animation-duration:.75s;animation-duration:.75s}.animated.flipOutX,.animated.flipOutY{-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounce{from,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}40%,43%{-webkit-animation-timing-function:cubic-bezier(0.755,0.050,0.855,0.060);animation-timing-function:cubic-bezier(0.755,0.050,0.855,0.060);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-animation-timing-function:cubic-bezier(0.755,0.050,0.855,0.060);animation-timing-function:cubic-bezier(0.755,0.050,0.855,0.060);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{from,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}40%,43%{-webkit-animation-timing-function:cubic-bezier(0.755,0.050,0.855,0.060);animation-timing-function:cubic-bezier(0.755,0.050,0.855,0.060);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-animation-timing-function:cubic-bezier(0.755,0.050,0.855,0.060);animation-timing-function:cubic-bezier(0.755,0.050,0.855,0.060);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{from,50%,to{opacity:1}25%,75%{opacity:0}}@keyframes flash{from,50%,to{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes pulse{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,0.75,1);transform:scale3d(1.25,0.75,1)}40%{-webkit-transform:scale3d(0.75,1.25,1);transform:scale3d(0.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,0.85,1);transform:scale3d(1.15,0.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes rubberBand{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,0.75,1);transform:scale3d(1.25,0.75,1)}40%{-webkit-transform:scale3d(0.75,1.25,1);transform:scale3d(0.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,0.85,1);transform:scale3d(1.15,0.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{from,to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{from,to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}to{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}@keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}to{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9)rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9)rotate3d(0,0,1,-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,-3deg)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes tada{from{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9)rotate3d(0,0,1,-3deg);transform:scale3d(.9,.9,.9)rotate3d(0,0,1,-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,3deg);transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,-3deg);transform:scale3d(1.1,1.1,1.1)rotate3d(0,0,1,-3deg)}to{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{from{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0)rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0)rotate3d(0,0,1,-5deg)}30%{-webkit-transform:translate3d(20%,0,0)rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0)rotate3d(0,0,1,3deg)}45%{-webkit-transform:translate3d(-15%,0,0)rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0)rotate3d(0,0,1,-3deg)}60%{-webkit-transform:translate3d(10%,0,0)rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0)rotate3d(0,0,1,2deg)}75%{-webkit-transform:translate3d(-5%,0,0)rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0)rotate3d(0,0,1,-1deg)}to{-webkit-transform:none;transform:none}}@keyframes wobble{from{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0)rotate3d(0,0,1,-5deg);transform:translate3d(-25%,0,0)rotate3d(0,0,1,-5deg)}30%{-webkit-transform:translate3d(20%,0,0)rotate3d(0,0,1,3deg);transform:translate3d(20%,0,0)rotate3d(0,0,1,3deg)}45%{-webkit-transform:translate3d(-15%,0,0)rotate3d(0,0,1,-3deg);transform:translate3d(-15%,0,0)rotate3d(0,0,1,-3deg)}60%{-webkit-transform:translate3d(10%,0,0)rotate3d(0,0,1,2deg);transform:translate3d(10%,0,0)rotate3d(0,0,1,2deg)}75%{-webkit-transform:translate3d(-5%,0,0)rotate3d(0,0,1,-1deg);transform:translate3d(-5%,0,0)rotate3d(0,0,1,-1deg)}to{-webkit-transform:none;transform:none}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes jello{from,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg)skewY(-12.5deg);transform:skewX(-12.5deg)skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg)skewY(6.25deg);transform:skewX(6.25deg)skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg)skewY(-3.125deg);transform:skewX(-3.125deg)skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg)skewY(1.5625deg);transform:skewX(1.5625deg)skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-0.78125deg)skewY(-0.78125deg);transform:skewX(-0.78125deg)skewY(-0.78125deg)}77.7%{-webkit-transform:skewX(0.390625deg)skewY(0.390625deg);transform:skewX(0.390625deg)skewY(0.390625deg)}88.8%{-webkit-transform:skewX(-0.1953125deg)skewY(-0.1953125deg);transform:skewX(-0.1953125deg)skewY(-0.1953125deg)}}@keyframes jello{from,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg)skewY(-12.5deg);transform:skewX(-12.5deg)skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg)skewY(6.25deg);transform:skewX(6.25deg)skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg)skewY(-3.125deg);transform:skewX(-3.125deg)skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg)skewY(1.5625deg);transform:skewX(1.5625deg)skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-0.78125deg)skewY(-0.78125deg);transform:skewX(-0.78125deg)skewY(-0.78125deg)}77.7%{-webkit-transform:skewX(0.390625deg)skewY(0.390625deg);transform:skewX(0.390625deg)skewY(0.390625deg)}88.8%{-webkit-transform:skewX(-0.1953125deg)skewY(-0.1953125deg);transform:skewX(-0.1953125deg)skewY(-0.1953125deg)}}.jello{-webkit-animation-name:jello;animation-name:jello;-webkit-transform-origin:center;transform-origin:center}@-webkit-keyframes bounceIn{from,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes bounceIn{from,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceInDown{from,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInDown{from,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{from,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInLeft{from,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{from,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}from{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInRight{from,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}from{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{from,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}from{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes bounceInUp{from,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000);animation-timing-function:cubic-bezier(0.215,0.610,0.355,1.000)}from{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{from{opacity:0;-ms-transform:scale(0.95);-webkit-transform:scale(0.95);transform:scale(0.95)}to{opacity:1;-ms-transform:scale(1.0);-webkit-transform:scale(1.0);transform:scale(1.0)}}@keyframes fadeIn{from{opacity:0;-ms-transform:scale(0.95);-webkit-transform:scale(0.95);transform:scale(0.95)}to{opacity:1;-ms-transform:scale(1.0);-webkit-transform:scale(1.0);transform:scale(1.0)}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-50px,0);transform:translate3d(0,-50px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-50px,0);transform:translate3d(0,-50px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{from{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDownBig{from{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{from{opacity:0;-webkit-transform:translate3d(-50px,0,0);transform:translate3d(-50px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeft{from{opacity:0;-webkit-transform:translate3d(-50px,0,0);transform:translate3d(-50px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{from{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeftBig{from{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{from{opacity:0;-webkit-transform:translate3d(50px,0,0);transform:translate3d(50px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRight{from{opacity:0;-webkit-transform:translate3d(50px,0,0);transform:translate3d(50px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{from{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRightBig{from{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{from{opacity:0;visibility:hidden;-webkit-transform:translate3d(0,40px,0);transform:translate3d(0,40px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUp{from{opacity:0;visibility:hidden;-webkit-transform:translate3d(0,40px,0);transform:translate3d(0,40px,0)}to{visibility:visible;opacity:1;-webkit-transform:none;transform:none}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpMenu{from{opacity:0;visibility:hidden;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:1;visibility:visible;-webkit-transform:none;transform:none}}@keyframes fadeInUpMenu{from{opacity:0;visibility:hidden;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{visibility:visible;opacity:1;-webkit-transform:none;transform:none}}.fadeInUpMenu{-webkit-animation-name:fadeInUpMenu;animation-name:fadeInUpMenu}@-webkit-keyframes fadeInUpBig{from{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUpBig{from{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{from{opacity:1}to{opacity:0}}@keyframes fadeOut{from{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,40px,0);transform:translate3d(0,40px,0)}}@keyframes fadeOutDown{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,40px,0);transform:translate3d(0,40px,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{from{-webkit-transform:perspective(400px)rotate3d(0,1,0,-360deg);transform:perspective(400px)rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-190deg);transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{-webkit-transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-170deg);transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px)scale3d(.95,.95,.95);transform:perspective(400px)scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}@keyframes flip{from{-webkit-transform:perspective(400px)rotate3d(0,1,0,-360deg);transform:perspective(400px)rotate3d(0,1,0,-360deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-190deg);transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-190deg);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}50%{-webkit-transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-170deg);transform:perspective(400px)translate3d(0,0,150px)rotate3d(0,1,0,-170deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px)scale3d(.95,.95,.95);transform:perspective(400px)scale3d(.95,.95,.95);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip}@-webkit-keyframes flipInX{from{-webkit-transform:perspective(400px)rotate3d(1,0,0,90deg);transform:perspective(400px)rotate3d(1,0,0,90deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-20deg);transform:perspective(400px)rotate3d(1,0,0,-20deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}60%{-webkit-transform:perspective(400px)rotate3d(1,0,0,10deg);transform:perspective(400px)rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-5deg);transform:perspective(400px)rotate3d(1,0,0,-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{from{-webkit-transform:perspective(400px)rotate3d(1,0,0,90deg);transform:perspective(400px)rotate3d(1,0,0,90deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-20deg);transform:perspective(400px)rotate3d(1,0,0,-20deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}60%{-webkit-transform:perspective(400px)rotate3d(1,0,0,10deg);transform:perspective(400px)rotate3d(1,0,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-5deg);transform:perspective(400px)rotate3d(1,0,0,-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX}@-webkit-keyframes flipInY{from{-webkit-transform:perspective(400px)rotate3d(0,1,0,90deg);transform:perspective(400px)rotate3d(0,1,0,90deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-20deg);transform:perspective(400px)rotate3d(0,1,0,-20deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}60%{-webkit-transform:perspective(400px)rotate3d(0,1,0,10deg);transform:perspective(400px)rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-5deg);transform:perspective(400px)rotate3d(0,1,0,-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{from{-webkit-transform:perspective(400px)rotate3d(0,1,0,90deg);transform:perspective(400px)rotate3d(0,1,0,90deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-20deg);transform:perspective(400px)rotate3d(0,1,0,-20deg);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}60%{-webkit-transform:perspective(400px)rotate3d(0,1,0,10deg);transform:perspective(400px)rotate3d(0,1,0,10deg);opacity:1}80%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-5deg);transform:perspective(400px)rotate3d(0,1,0,-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY}@-webkit-keyframes flipOutX{from{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-20deg);transform:perspective(400px)rotate3d(1,0,0,-20deg);opacity:1}to{-webkit-transform:perspective(400px)rotate3d(1,0,0,90deg);transform:perspective(400px)rotate3d(1,0,0,90deg);opacity:0}}@keyframes flipOutX{from{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px)rotate3d(1,0,0,-20deg);transform:perspective(400px)rotate3d(1,0,0,-20deg);opacity:1}to{-webkit-transform:perspective(400px)rotate3d(1,0,0,90deg);transform:perspective(400px)rotate3d(1,0,0,90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;-webkit-backface-visibility:visible!important;backface-visibility:visible!important}@-webkit-keyframes flipOutY{from{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-15deg);transform:perspective(400px)rotate3d(0,1,0,-15deg);opacity:1}to{-webkit-transform:perspective(400px)rotate3d(0,1,0,90deg);transform:perspective(400px)rotate3d(0,1,0,90deg);opacity:0}}@keyframes flipOutY{from{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px)rotate3d(0,1,0,-15deg);transform:perspective(400px)rotate3d(0,1,0,-15deg);opacity:1}to{-webkit-transform:perspective(400px)rotate3d(0,1,0,90deg);transform:perspective(400px)rotate3d(0,1,0,90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY}@-webkit-keyframes lightSpeedIn{from{-webkit-transform:translate3d(100%,0,0)skewX(-30deg);transform:translate3d(100%,0,0)skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg);opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1}to{-webkit-transform:none;transform:none;opacity:1}}@keyframes lightSpeedIn{from{-webkit-transform:translate3d(100%,0,0)skewX(-30deg);transform:translate3d(100%,0,0)skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg);opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg);opacity:1}to{-webkit-transform:none;transform:none;opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{from{opacity:1}to{-webkit-transform:translate3d(100%,0,0)skewX(30deg);transform:translate3d(100%,0,0)skewX(30deg);opacity:0}}@keyframes lightSpeedOut{from{opacity:1}to{-webkit-transform:translate3d(100%,0,0)skewX(30deg);transform:translate3d(100%,0,0)skewX(30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{from{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0}to{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateIn{from{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,-200deg);transform:rotate3d(0,0,1,-200deg);opacity:0}to{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{from{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}to{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownLeft{from{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}to{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{from{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}to{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownRight{from{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}to{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{from{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}to{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpLeft{from{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}to{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{from{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0}to{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpRight{from{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-90deg);transform:rotate3d(0,0,1,-90deg);opacity:0}to{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{from{-webkit-transform-origin:center;transform-origin:center;opacity:1}to{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0}}@keyframes rotateOut{from{-webkit-transform-origin:center;transform-origin:center;opacity:1}to{-webkit-transform-origin:center;transform-origin:center;-webkit-transform:rotate3d(0,0,1,200deg);transform:rotate3d(0,0,1,200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{from{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}to{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}}@keyframes rotateOutDownLeft{from{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}to{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,45deg);transform:rotate3d(0,0,1,45deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{from{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}to{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}@keyframes rotateOutDownRight{from{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}to{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{from{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}to{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}@keyframes rotateOutUpLeft{from{-webkit-transform-origin:left bottom;transform-origin:left bottom;opacity:1}to{-webkit-transform-origin:left bottom;transform-origin:left bottom;-webkit-transform:rotate3d(0,0,1,-45deg);transform:rotate3d(0,0,1,-45deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{from{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}to{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0}}@keyframes rotateOutUpRight{from{-webkit-transform-origin:right bottom;transform-origin:right bottom;opacity:1}to{-webkit-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate3d(0,0,1,90deg);transform:rotate3d(0,0,1,90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}@keyframes hinge{0%{-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate3d(0,0,1,80deg);transform:rotate3d(0,0,1,80deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}40%,80%{-webkit-transform:rotate3d(0,0,1,60deg);transform:rotate3d(0,0,1,60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}.hinge{-webkit-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{from{opacity:0;-webkit-transform:translate3d(-100%,0,0)rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0)rotate3d(0,0,1,-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes rollIn{from{opacity:0;-webkit-transform:translate3d(-100%,0,0)rotate3d(0,0,1,-120deg);transform:translate3d(-100%,0,0)rotate3d(0,0,1,-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0)rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0)rotate3d(0,0,1,120deg)}}@keyframes rollOut{from{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0)rotate3d(0,0,1,120deg);transform:translate3d(100%,0,0)rotate3d(0,0,1,120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{from{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1)translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,60px,0);transform:scale3d(.475,.475,.475)translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}@keyframes zoomInDown{from{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1)translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,60px,0);transform:scale3d(.475,.475,.475)translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{from{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1)translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(10px,0,0);transform:scale3d(.475,.475,.475)translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}@keyframes zoomInLeft{from{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1)translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(10px,0,0);transform:scale3d(.475,.475,.475)translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{from{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(1000px,0,0);transform:scale3d(.1,.1,.1)translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(-10px,0,0);transform:scale3d(.475,.475,.475)translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}@keyframes zoomInRight{from{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(1000px,0,0);transform:scale3d(.1,.1,.1)translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(-10px,0,0);transform:scale3d(.475,.475,.475)translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{from{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,1000px,0);transform:scale3d(.1,.1,.1)translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}@keyframes zoomInUp{from{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,1000px,0);transform:scale3d(.1,.1,.1)translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}@keyframes zoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,2000px,0);transform:scale3d(.1,.1,.1)translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);transform:scale3d(.475,.475,.475)translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,2000px,0);transform:scale3d(.1,.1,.1)translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(42px,0,0);transform:scale3d(.475,.475,.475)translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1)translate3d(-2000px,0,0);transform:scale(.1)translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(42px,0,0);transform:scale3d(.475,.475,.475)translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1)translate3d(-2000px,0,0);transform:scale(.1)translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(-42px,0,0);transform:scale3d(.475,.475,.475)translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1)translate3d(2000px,0,0);transform:scale(.1)translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(-42px,0,0);transform:scale3d(.475,.475,.475)translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1)translate3d(2000px,0,0);transform:scale(.1)translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,60px,0);transform:scale3d(.475,.475,.475)translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1)translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475)translate3d(0,60px,0);transform:scale3d(.475,.475,.475)translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190);animation-timing-function:cubic-bezier(0.550,0.055,0.675,0.190)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1)translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1)translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(0.175,0.885,0.320,1);animation-timing-function:cubic-bezier(0.175,0.885,0.320,1)}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp}@-webkit-keyframes slideInDown{from{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInDown{from{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{from{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInLeft{from{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{from{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInRight{from{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{from{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInUp{from{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{from{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{from{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{from{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{from{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{from{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{from{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{from{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{from{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp}body{font-family:open sans,Arial,sans-serif;line-height:1.5;font-size:16px;color:#848484;background-color:#f0f0f0}body.style-2{background-color:#fff;background-size:cover;background-position:-30%;background-repeat:no-repeat;background-image:url(../images/bg_2.jpg);height:100%}body.style-3{background:#fff url(../images/geometry2.png)repeat}a{color:#3cc;-moz-transition:all .3s ease;-o-transition:all .3s ease;-webkit-transition:all .3s ease;-ms-transition:all .3s ease;transition:all .3s ease}a:hover{color:#29a3a3}.menu{padding:0;margin:30px 0 0}.menu li{list-style:none;margin-bottom:10px;display:-moz-inline-stack;display:inline-block;zoom:1;*display:inline}.menu li a{padding:5px}.menu li.active a{color:#b3b3b3}.fh5co-form{padding:30px;margin-top:4em;-webkit-box-shadow:-4px 7px 46px 2px rgba(0,0,0,.1);-moz-box-shadow:-4px 7px 46px 2px rgba(0,0,0,.1);-o-box-shadow:-4px 7px 46px 2px rgba(0,0,0,.1);box-shadow:-4px 7px 46px 2px rgba(0,0,0,.1);background:#fff}.style-2 .fh5co-form{-webkit-box-shadow:-4px 7px 46px 2px rgba(0,0,0,.1);-moz-box-shadow:-4px 7px 46px 2px rgba(0,0,0,.1);-o-box-shadow:-4px 7px 46px 2px rgba(0,0,0,.1);box-shadow:-4px 7px 46px 2px rgba(0,0,0,.1)}@media screen and (max-width:768px){.fh5co-form{padding:15px}}.fh5co-form h2{text-transform:uppercase;letter-spacing:2px;font-size:20px;margin:0 0 30px;color:#000}.fh5co-form .form-group{margin-bottom:30px}.fh5co-form .form-group p{font-size:14px;color:#9f9f9f;font-weight:300}.fh5co-form .form-group p a{color:#000}.fh5co-form label{font-weight:300;font-size:14px;font-weight:300}.fh5co-form .form-control{font-size:16px;font-weight:300;height:50px;padding-left:0;padding-right:0;border:none;border-bottom:1px solid rgba(0,0,0,.1);-webkit-box-shadow:none;-moz-box-shadow:none;-o-box-shadow:none;box-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;-ms-border-radius:0;border-radius:0;-moz-transition:all .3s ease;-o-transition:all .3s ease;-webkit-transition:all .3s ease;-ms-transition:all .3s ease;transition:all .3s ease}.fh5co-form .form-control::-webkit-input-placeholder{color:rgba(0,0,0,.3);text-transform:uppercase}.fh5co-form .form-control::-moz-placeholder{color:rgba(0,0,0,.3);text-transform:uppercase}.fh5co-form .form-control:-ms-input-placeholder{color:rgba(0,0,0,.3);text-transform:uppercase}.copyrights{text-indent:-9999px;height:0;line-height:0;font-size:0;overflow:hidden}.fh5co-form .form-control:-moz-placeholder{color:rgba(0,0,0,.3);text-transform:uppercase}.fh5co-form .form-control:focus,.fh5co-form .form-control:active{border-bottom:1px solid rgba(0,0,0,.4)}.btn-primary{height:50px;padding-right:20px;padding-left:20px;border:none;background:#3cc;color:#fff;-webkit-box-shadow:-2px 10px 20px -1px rgba(51,204,204,.4);-moz-box-shadow:-2px 10px 20px -1px rgba(51,204,204,.4);-o-box-shadow:-2px 10px 20px -1px rgba(51,204,204,.4);box-shadow:-2px 10px 20px -1px rgba(51,204,204,.4)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active{color:#fff;background:#47d1d1!important;outline:none}input,textarea{color:#000}.placeholder{color:#aaa}.js .animate-box{opacity:0}#particles-js{position:absolute;width:100%;height:100%;background-size:cover;top:0;left:0;z-index:-1}body{color:#fff!important;background-color:#212121!important} \ No newline at end of file diff --git a/template/installation/assets/installation/dist/all.min.js b/template/installation/assets/installation/dist/all.min.js new file mode 100644 index 0000000000000000000000000000000000000000..96b969b388aed5f5f7efec01275fb95429a158cd --- /dev/null +++ b/template/installation/assets/installation/dist/all.min.js @@ -0,0 +1,5 @@ +/*!jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license*/!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("