From 7bd7be78a4818f1b29bad1100b8debc9f39f9484 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Sep 2022 07:50:20 +0000 Subject: [PATCH 01/63] build(deps): bump django from 3.2.14 to 3.2.15 in /requirements Bumps [django](https://github.com/django/django) from 3.2.14 to 3.2.15. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/3.2.14...3.2.15) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 863759fad..8942c183f 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -62,7 +62,7 @@ jsonfield2==4.0.0.post0 geoip2==4.5.0 ipip-ipdb==1.6.1 # Django environment -Django==3.2.14 +Django==3.2.15 django-bootstrap3==14.2.0 django-filter==2.4.0 django-formtools==2.2 From ca965aca9efe1d09603569d02ae2e25ac66b293e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 23:07:48 +0000 Subject: [PATCH 02/63] build(deps): bump flower from 1.0.0 to 1.2.0 in /requirements Bumps [flower](https://github.com/mher/flower) from 1.0.0 to 1.2.0. - [Release notes](https://github.com/mher/flower/releases) - [Commits](https://github.com/mher/flower/compare/v1.0.0...v1.2.0) --- updated-dependencies: - dependency-name: flower dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 863759fad..e108731b3 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -91,7 +91,7 @@ eventlet==0.33.1 greenlet==1.1.2 gunicorn==20.1.0 celery==5.2.7 -flower==1.0.0 +flower==1.2.0 django-celery-beat==2.3.0 kombu==5.2.4 # Auth From 61ff3db0f1d4b18b98e540e6f94d240c660f5786 Mon Sep 17 00:00:00 2001 From: Quentin Machu Date: Thu, 15 Sep 2022 17:06:06 -0400 Subject: [PATCH 03/63] fix: address issue #8287 with blank SAML's RelayState --- apps/authentication/backends/saml2/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/authentication/backends/saml2/views.py b/apps/authentication/backends/saml2/views.py index 9bc3ddc97..e0fa97590 100644 --- a/apps/authentication/backends/saml2/views.py +++ b/apps/authentication/backends/saml2/views.py @@ -271,7 +271,10 @@ class Saml2AuthCallbackView(View, PrepareRequestMixin): auth.login(self.request, user) logger.debug(log_prompt.format('Redirect')) - next_url = saml_instance.redirect_to(post_data.get('RelayState', '/')) + redir = post_data.get('RelayState') + if not redir or len(redir) == 0: + redir = "/" + next_url = saml_instance.redirect_to(redir) return HttpResponseRedirect(next_url) @csrf_exempt From c337bbff8fcfd69877a37b794f2a6920caee6549 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:02:47 +0800 Subject: [PATCH 04/63] perf: remove old warning msg --- data/caution.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/caution.txt b/data/caution.txt index 4e85670a1..3feabb2e4 100644 --- a/data/caution.txt +++ b/data/caution.txt @@ -1,2 +1 @@ - 你想偷看啥 !!! - What are you trying to peek at !!! \ No newline at end of file + What are you trying to peek at !!! From a4a671afd4f1d611b0cfc40aea838f609cf991e4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:04:16 +0800 Subject: [PATCH 05/63] docs: redirect to doc site --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index e4d922911..e29c64391 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,3 +3,4 @@ ## 访问在线文档 [访问](https://docs.jumpserver.org) + From 3a196f08149ba93b452b8319cedf12e2bb96cf27 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:05:04 +0800 Subject: [PATCH 06/63] chore: keep log dir on git --- logs/.gitkeep | 1 + 1 file changed, 1 insertion(+) diff --git a/logs/.gitkeep b/logs/.gitkeep index e69de29bb..8d1c8b69c 100644 --- a/logs/.gitkeep +++ b/logs/.gitkeep @@ -0,0 +1 @@ + From edfda5825cbb2c20a7986a1387ec2127bd9ea0c9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:05:47 +0800 Subject: [PATCH 07/63] chore: keep dir on git --- logs/.gitkeep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logs/.gitkeep b/logs/.gitkeep index 8d1c8b69c..1a4baf536 100644 --- a/logs/.gitkeep +++ b/logs/.gitkeep @@ -1 +1 @@ - + From 3dcfd0035aeefe6ddb4ddcb76a6e4fd0fd21aaee Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:06:46 +0800 Subject: [PATCH 08/63] chore: add code of conduct --- CODE_OF_CONDUCT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 18c914718..04b8c547f 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -126,3 +126,4 @@ enforcement ladder](https://github.com/mozilla/diversity). For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. + From 2bd889e50537b61b5c1e075370e12eab706f6ea1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:07:23 +0800 Subject: [PATCH 09/63] chore: add english readme --- README_EN.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README_EN.md b/README_EN.md index bb2b15097..7f302195e 100644 --- a/README_EN.md +++ b/README_EN.md @@ -92,4 +92,3 @@ Licensed under The GNU General Public License version 3 (GPLv3) (the "License") https://www.gnu.org/licenses/gpl-3.0.htmll 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. - From 8e7226d9dc4101dfab35661f2f4b513ec233f6e9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:09:28 +0800 Subject: [PATCH 10/63] pref: change run_server script --- Vagrantfile | 56 --------------------------------------------------- run_server.py | 3 +-- 2 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 Vagrantfile diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 98e82ca5a..000000000 --- a/Vagrantfile +++ /dev/null @@ -1,56 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -Vagrant.configure("2") do |config| - # The most common configuration options are documented and commented below. - # For a complete reference, please see the online documentation at - # https://docs.vagrantup.com. - - # Every Vagrant development environment requires a box. You can search for - # boxes at https://vagrantcloud.com/search. - config.vm.box_check_update = false - config.vm.box = "centos/7" - config.vm.hostname = "jumpserver" - config.vm.network "private_network", ip: "172.17.8.101" - config.vm.provider "virtualbox" do |vb| - vb.memory = "4096" - vb.cpus = 2 - vb.name = "jumpserver" - end - - config.vm.synced_folder ".", "/vagrant", type: "rsync", - rsync__verbose: true, - rsync__exclude: ['.git*', 'node_modules*','*.log','*.box','Vagrantfile'] - - config.vm.provision "shell", inline: <<-SHELL -## 设置yum的阿里云源 -sudo curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo -sudo sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo -sudo curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo -sudo yum makecache - -## 安装依赖包 -sudo yum install -y python36 python36-devel python36-pip \ - libtiff-devel libjpeg-devel libzip-devel freetype-devel \ - lcms2-devel libwebp-devel tcl-devel tk-devel sshpass \ - openldap-devel mariadb-devel mysql-devel libffi-devel \ - openssh-clients telnet openldap-clients gcc - -## 配置pip阿里云源 -mkdir /home/vagrant/.pip -cat << EOF | sudo tee -a /home/vagrant/.pip/pip.conf -[global] -timeout = 6000 -index-url = https://mirrors.aliyun.com/pypi/simple/ - -[install] -use-mirrors = true -mirrors = https://mirrors.aliyun.com/pypi/simple/ -trusted-host=mirrors.aliyun.com -EOF - -python3.6 -m venv /home/vagrant/venv -source /home/vagrant/venv/bin/activate -echo 'source /home/vagrant/venv/bin/activate' >> /home/vagrant/.bash_profile - SHELL -end diff --git a/run_server.py b/run_server.py index c7ec7bccb..b7cce251b 100644 --- a/run_server.py +++ b/run_server.py @@ -6,6 +6,5 @@ import subprocess if __name__ == '__main__': - subprocess.call('python3 jms start all', shell=True, - stdin=sys.stdin, stdout=sys.stdout) + subprocess.call('python3 jms start all', shell=True, stdin=sys.stdin, stdout=sys.stdout) From 9329a1563c9f1525a5f4323b82f69435a2844ddb Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:17:38 +0800 Subject: [PATCH 11/63] chore: keep dir git --- tmp/.gitkeep | 1 + 1 file changed, 1 insertion(+) diff --git a/tmp/.gitkeep b/tmp/.gitkeep index e69de29bb..8d1c8b69c 100644 --- a/tmp/.gitkeep +++ b/tmp/.gitkeep @@ -0,0 +1 @@ + From 556d29360ed87e1d81661a20ad9c6a34ba24ccc1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:18:59 +0800 Subject: [PATCH 12/63] pref: add debug tool bar --- utils/redis.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/redis.conf b/utils/redis.conf index 0621db3bf..c28fbc6fa 100644 --- a/utils/redis.conf +++ b/utils/redis.conf @@ -1,4 +1,5 @@ daemonize yes port 6379 logfile "/opt/jumpserver/logs/redis.log" -dir /opt/jumpserver/ \ No newline at end of file +dir /opt/jumpserver/ + From 3e33c74b643ad503bf31541823b7cdf32faacf7a Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:20:03 +0800 Subject: [PATCH 13/63] perf: add .git for ignore --- .dockerignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 0353b6cd4..a504fb4ec 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,4 +7,5 @@ django.db celerybeat.pid ### Vagrant ### .vagrant/ -apps/xpack/.git \ No newline at end of file +apps/xpack/.git + From bba4c15d6d6e1717ea2dc49958e0c4a4f8032c92 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:20:48 +0800 Subject: [PATCH 14/63] perf: add ipdb to git lfs --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 24d9e1cd5..51d79b9d1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ *.mmdb filter=lfs diff=lfs merge=lfs -text *.mo filter=lfs diff=lfs merge=lfs -text *.ipdb filter=lfs diff=lfs merge=lfs -text + From d4e0a51a08bccac8164f145565abfb3fd2cbae9f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:21:42 +0800 Subject: [PATCH 15/63] perf: set data dir to ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ec0378141..e1e5f32d7 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ release/* releashe /apps/script.py data/* + From a1c09591d39a687cdb5bfc9290d2d7db9e31be81 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:22:31 +0800 Subject: [PATCH 16/63] chore: change contributing content --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f28982f9..62b3a9fca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,3 +23,4 @@ When reporting issues, always include: Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can replace those parts with "REDACTED" or other strings like "****". + From 1e3154d9b6d199e9f2a336639e84500000a4ebcd Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:23:24 +0800 Subject: [PATCH 17/63] pref: add openssh client to dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 7e6eac422..cd53b4a58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -93,3 +93,4 @@ ENV LANG=zh_CN.UTF-8 EXPOSE 8070 EXPOSE 8080 ENTRYPOINT ["./entrypoint.sh"] + From fe2f54fcf671aaf92cac9dd99cb981164321f803 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:24:25 +0800 Subject: [PATCH 18/63] chore: upgrade GPL to v3 --- LICENSE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index e72bfddab..53d1f3d01 100644 --- a/LICENSE +++ b/LICENSE @@ -671,4 +671,5 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. \ No newline at end of file +. + From a33a45243467d3cdcc0831acfef4dfc2c3d06987 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:25:07 +0800 Subject: [PATCH 19/63] chore: add english version secrity info --- SECURITY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SECURITY.md b/SECURITY.md index 2328ff7b6..2f2ce1d50 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -18,3 +18,4 @@ All security bugs should be reported to the contact as below: - ibuler@fit2cloud.com - support@fit2cloud.com - 400-052-0755 + From 6e5cea49ae6935f8642b5715c46e78b3ba0d816b Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:26:05 +0800 Subject: [PATCH 20/63] perf: remove unused config --- config_example.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config_example.yml b/config_example.yml index f8f458a80..a21edb01b 100644 --- a/config_example.yml +++ b/config_example.yml @@ -80,8 +80,6 @@ REDIS_PORT: 6379 # 启用定时任务 # PERIOD_TASK_ENABLED: True # -# Windows 登录跳过手动输入密码 -# WINDOWS_SKIP_ALL_MANUAL_PASSWORD: False # 是否开启 Luna 水印 # SECURITY_WATERMARK_ENABLED: False @@ -97,3 +95,4 @@ REDIS_PORT: 6379 # 仅允许已存在的用户登录,不允许第三方认证后,自动创建用户 # ONLY_ALLOW_EXIST_USER_AUTH: False + From 995d8cadb9ec609a30f48eae61f68f9f54e6904d Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:27:09 +0800 Subject: [PATCH 21/63] fix: warning after reboot --- entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/entrypoint.sh b/entrypoint.sh index 58ed0b104..a752ccc8c 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -22,3 +22,4 @@ elif [[ "$action" == "sleep" ]];then else python jms "${action}" "${service}" fi + From 912ff3df24b8d980be3437d65d8e48916c9e6f92 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:28:01 +0800 Subject: [PATCH 22/63] perf: download ipdb if not found (maybe without lfs) --- jms | 1 + 1 file changed, 1 insertion(+) diff --git a/jms b/jms index 5d5b5c433..bc230b268 100755 --- a/jms +++ b/jms @@ -189,3 +189,4 @@ if __name__ == '__main__': collect_static() else: start_services() + From 5ae49295e957f9cea16db24b9bb11e7792d07a69 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:32:24 +0800 Subject: [PATCH 23/63] chore: remove lgtm action --- .github/release-config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/release-config.yml b/.github/release-config.yml index 1a75bdf6f..6dca58946 100644 --- a/.github/release-config.yml +++ b/.github/release-config.yml @@ -41,4 +41,5 @@ version-resolver: default: patch template: | ## 版本变化 What’s Changed - $CHANGES \ No newline at end of file + $CHANGES + From ee1ec6aeee1c7dd434c379f191ce4416411d7dc8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 19 Sep 2022 20:56:48 +0800 Subject: [PATCH 24/63] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20celery=20?= =?UTF-8?q?=E4=B8=A2=E5=A4=B1=E5=BF=83=E8=B7=B3=E4=B8=8D=E4=BC=9A=E9=87=8D?= =?UTF-8?q?=E8=BF=9E=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/services/services/celery_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/common/management/commands/services/services/celery_base.py b/apps/common/management/commands/services/services/celery_base.py index 435e4ebe2..92fa99cba 100644 --- a/apps/common/management/commands/services/services/celery_base.py +++ b/apps/common/management/commands/services/services/celery_base.py @@ -30,7 +30,9 @@ class CeleryBaseService(BaseService): '-l', 'INFO', '-c', str(self.num), '-Q', self.queue, - '-n', f'{self.queue}@{server_hostname}' + '--heartbeat-interval', '10', + '-n', f'{self.queue}@{server_hostname}', + '--without-mingle', ] return cmd From 404fadd899ad05e6e09a1fffebfdbc79b11d6b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Tue, 20 Sep 2022 11:41:27 +0800 Subject: [PATCH 25/63] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20redis=20?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=90=8E=20celery=20=E6=97=A7=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E4=B8=8D=E6=89=A7=E8=A1=8C=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/libs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index b3cc0dfde..a6cb88a0d 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -131,6 +131,7 @@ CELERY_TASK_EAGER_PROPAGATES = True CELERY_WORKER_REDIRECT_STDOUTS = True CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO" CELERY_TASK_SOFT_TIME_LIMIT = 3600 +CELERY_WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS = True if REDIS_USE_SSL: CELERY_BROKER_USE_SSL = CELERY_REDIS_BACKEND_USE_SSL = { From 567b62516a6c00ffc116b5e5dea375e1af037a44 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 21 Sep 2022 15:19:40 +0800 Subject: [PATCH 26/63] fix: reset ssh url problem --- apps/users/notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/users/notifications.py b/apps/users/notifications.py index 645e206cd..cbc6e1976 100644 --- a/apps/users/notifications.py +++ b/apps/users/notifications.py @@ -191,7 +191,7 @@ class UserExpirationReminderMsg(UserMessage): class ResetSSHKeyMsg(UserMessage): def get_html_msg(self) -> dict: subject = _('Reset SSH Key') - update_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=SSHUpdate') + update_url = urljoin(settings.SITE_URL, '/ui/#/profile/setting/?activeTab=SSHUpdate') context = { 'name': self.user.name, 'url': update_url, From a0c61ab8cb426e67b705d2a648064e44da51a34b Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 21 Sep 2022 20:56:40 +0800 Subject: [PATCH 27/63] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20DB=20Listen?= =?UTF-8?q?=20Port=20=E6=98=A0=E5=B0=84=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/application.py | 30 +++++++- apps/applications/apps.py | 4 ++ apps/applications/models/application.py | 2 +- apps/applications/signal_handlers.py | 34 +++++++++ apps/applications/urls/api_urls.py | 1 + apps/applications/utils/__init__.py | 1 + apps/applications/utils/db_port_mapper.py | 88 +++++++++++++++++++++++ apps/jumpserver/conf.py | 3 + apps/jumpserver/settings/custom.py | 4 ++ apps/terminal/models/endpoint.py | 2 + 10 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 apps/applications/signal_handlers.py create mode 100644 apps/applications/utils/db_port_mapper.py diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index 71315cdcf..3d9913b24 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -1,15 +1,19 @@ # coding: utf-8 # from orgs.mixins.api import OrgBulkModelViewSet +from rest_framework import generics, status from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet + from common.tree import TreeNodeSerializer from common.mixins.api import SuggestionMixin +from ..utils import db_port_manager from .. import serializers from ..models import Application -__all__ = ['ApplicationViewSet'] +__all__ = ['ApplicationViewSet', 'DBListenPortViewSet'] class ApplicationViewSet(SuggestionMixin, OrgBulkModelViewSet): @@ -37,3 +41,27 @@ class ApplicationViewSet(SuggestionMixin, OrgBulkModelViewSet): tree_nodes = Application.create_tree_nodes(queryset, show_count=show_count) serializer = self.get_serializer(tree_nodes, many=True) return Response(serializer.data) + + +class DBListenPortViewSet(GenericViewSet): + rbac_perms = { + 'GET': 'applications.view_application', + 'list': 'applications.view_application', + 'db_info': 'applications.view_application', + } + + http_method_names = ['get', 'post'] + + def list(self, request, *args, **kwargs): + ports = db_port_manager.get_already_use_ports() + return Response(data=ports, status=status.HTTP_200_OK) + + @action(methods=['post'], detail=False, url_path='db-info') + def db_info(self, request, *args, **kwargs): + port = request.data.get("port") + db, msg = db_port_manager.get_db_by_port(port) + if db is None: + data = {'error': msg} + return Response(data=data, status=status.HTTP_404_NOT_FOUND) + serializer = serializers.AppSerializer(instance=db) + return Response(data=serializer.data, status=status.HTTP_201_CREATED) diff --git a/apps/applications/apps.py b/apps/applications/apps.py index 1662edcf0..a025a0517 100644 --- a/apps/applications/apps.py +++ b/apps/applications/apps.py @@ -7,3 +7,7 @@ from django.apps import AppConfig class ApplicationsConfig(AppConfig): name = 'applications' verbose_name = _('Applications') + + def ready(self): + from . import signal_handlers + super().ready() diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index cc98abaf8..91507c2cb 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -12,7 +12,6 @@ from common.utils import is_uuid from assets.models import Asset, SystemUser from ..const import OracleVersion -from ..utils import KubernetesTree from .. import const @@ -175,6 +174,7 @@ class ApplicationTreeNodeMixin: return pid def as_tree_node(self, pid, k8s_as_tree=False): + from ..utils import KubernetesTree if self.type == const.AppType.k8s and k8s_as_tree: node = KubernetesTree(pid).as_tree_node(self) else: diff --git a/apps/applications/signal_handlers.py b/apps/applications/signal_handlers.py new file mode 100644 index 000000000..92486fe1e --- /dev/null +++ b/apps/applications/signal_handlers.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +from django.db.models.signals import post_save, post_delete + +from common.signals import django_ready +from django.dispatch import receiver +from common.utils import get_logger +from .models import Application +from .utils import db_port_manager + + +logger = get_logger(__file__) + + +@receiver(django_ready) +def init_db_port_mapper(sender, **kwargs): + logger.info('Init db port mapper') + db_port_manager.init() + + +@receiver(post_save, sender=Application) +def on_db_app_created(sender, instance: Application, created, **kwargs): + if not instance.category_db: + return + if not created: + return + db_port_manager.add(instance) + + +@receiver(post_delete, sender=Application) +def on_db_app_delete(sender, instance, **kwargs): + if not instance.category_db: + return + db_port_manager.pop(instance) diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py index 4fdf006b0..813c047a2 100644 --- a/apps/applications/urls/api_urls.py +++ b/apps/applications/urls/api_urls.py @@ -13,6 +13,7 @@ router.register(r'applications', api.ApplicationViewSet, 'application') router.register(r'accounts', api.ApplicationAccountViewSet, 'application-account') router.register(r'system-users-apps-relations', api.SystemUserAppRelationViewSet, 'system-users-apps-relation') router.register(r'account-secrets', api.ApplicationAccountSecretViewSet, 'application-account-secret') +router.register(r'db-listen-ports', api.DBListenPortViewSet, 'db-listen-ports') urlpatterns = [ diff --git a/apps/applications/utils/__init__.py b/apps/applications/utils/__init__.py index 5efec40b2..de3a7cd10 100644 --- a/apps/applications/utils/__init__.py +++ b/apps/applications/utils/__init__.py @@ -2,3 +2,4 @@ # from .kubernetes_util import * +from .db_port_mapper import * diff --git a/apps/applications/utils/db_port_mapper.py b/apps/applications/utils/db_port_mapper.py new file mode 100644 index 000000000..321751148 --- /dev/null +++ b/apps/applications/utils/db_port_mapper.py @@ -0,0 +1,88 @@ +from common.decorator import Singleton +from django.core.cache import cache +from django.conf import settings +from applications.const import AppCategory +from applications.models import Application +from common.utils import get_logger +from common.utils import get_object_or_none + + +logger = get_logger(__file__) + + +@Singleton +class DBPortManager(object): + """ 管理端口-数据库ID的映射, Magnus 要使用 """ + + CACHE_KEY = 'PORT_DB_MAPPER' + + def __init__(self): + self.port_start = settings.MAGNUS_DB_PORTS_START + self.port_limit = settings.MAGNUS_DB_PORTS_LIMIT_COUNT + self.port_end = self.port_start + self.port_limit + 1 + # 可以使用的端口列表 + self.all_usable_ports = [i for i in range(self.port_start, self.port_end)] + + def init(self): + db_ids = Application.objects.filter(category=AppCategory.db).values_list('id', flat=True) + db_ids = [str(i) for i in db_ids] + mapper = dict(zip(self.all_usable_ports, list(db_ids))) + self.set_mapper(mapper) + + def get_db_by_port(self, port): + mapper = self.get_mapper() + db_id = mapper.get(port, None) + if db_id: + db = get_object_or_none(Application, id=db_id) + if not db: + msg = 'Database not exists, database id: {}'.format(db_id) + else: + msg = '' + else: + db = None + msg = 'Port not in port-db mapper, port: {}'.format(port) + return db, msg + + def add(self, db: Application): + mapper = self.get_mapper() + usable_port = self.get_next_usable_port() + if not usable_port: + return False + mapper.update({usable_port: str(db.id)}) + self.set_mapper(mapper) + return True + + def pop(self, db: Application): + mapper = self.get_mapper() + to_delete_port = None + for port, db_id in mapper.items(): + if db_id == str(db.id): + to_delete_port = port + break + mapper.pop(to_delete_port, None) + self.set_mapper(mapper) + + def get_next_usable_port(self): + already_use_ports = self.get_already_use_ports() + usable_ports = list(set(self.all_usable_ports) - set(already_use_ports)) + if len(usable_ports) > 1: + return usable_ports[0] + else: + already_use_ports = self.get_already_use_ports() + msg = 'No port is usable, All usable port count: {}, Already use port count: {}'.format( + len(self.all_usable_ports), len(already_use_ports) + ) + logger.warning(msg) + + def get_already_use_ports(self): + mapper = self.get_mapper() + return list(mapper.keys()) + + def get_mapper(self): + return cache.get(self.CACHE_KEY, {}) + + def set_mapper(self, value): + cache.set(self.CACHE_KEY, value, timeout=None) + + +db_port_manager = DBPortManager() diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index f31f4250a..7edc28610 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -484,6 +484,9 @@ class Config(dict): 'SERVER_REPLAY_STORAGE': {}, 'SECURITY_DATA_CRYPTO_ALGO': None, 'GMSSL_ENABLED': False, + # Magnus 组件需要监听的端口范围 + 'MAGNUS_DB_PORTS_START': 30000, + 'MAGNUS_DB_PORTS_LIMIT_COUNT': 1000, # 记录清理清理 'LOGIN_LOG_KEEP_DAYS': 200, diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 1fdb530cb..caa712ab3 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -177,3 +177,7 @@ HELP_SUPPORT_URL = CONFIG.HELP_SUPPORT_URL SESSION_RSA_PRIVATE_KEY_NAME = 'jms_private_key' SESSION_RSA_PUBLIC_KEY_NAME = 'jms_public_key' + +# Magnus DB Port +MAGNUS_DB_PORTS_START = CONFIG.MAGNUS_DB_PORTS_START +MAGNUS_DB_PORTS_LIMIT_COUNT = CONFIG.MAGNUS_DB_PORTS_LIMIT_COUNT diff --git a/apps/terminal/models/endpoint.py b/apps/terminal/models/endpoint.py index beefdeb69..305443821 100644 --- a/apps/terminal/models/endpoint.py +++ b/apps/terminal/models/endpoint.py @@ -14,6 +14,8 @@ class Endpoint(JMSModel): http_port = PortField(default=80, verbose_name=_('HTTP Port')) ssh_port = PortField(default=2222, verbose_name=_('SSH Port')) rdp_port = PortField(default=3389, verbose_name=_('RDP Port')) + + # Todo: Delete mysql_port = PortField(default=33060, verbose_name=_('MySQL Port')) mariadb_port = PortField(default=33061, verbose_name=_('MariaDB Port')) postgresql_port = PortField(default=54320, verbose_name=_('PostgreSQL Port')) From c9afd947145068945b77c29b8f33b90fda1b85d7 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 22 Sep 2022 14:48:20 +0800 Subject: [PATCH 28/63] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20DB=20Listen?= =?UTF-8?q?=20Port=20=E6=98=A0=E5=B0=84=E8=A7=84=E5=88=99=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/utils/db_port_mapper.py | 57 +++++++++++++---------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/apps/applications/utils/db_port_mapper.py b/apps/applications/utils/db_port_mapper.py index 321751148..0b6bdd93a 100644 --- a/apps/applications/utils/db_port_mapper.py +++ b/apps/applications/utils/db_port_mapper.py @@ -29,6 +29,27 @@ class DBPortManager(object): mapper = dict(zip(self.all_usable_ports, list(db_ids))) self.set_mapper(mapper) + def add(self, db: Application): + mapper = self.get_mapper() + usable_port = self.get_next_usable_port() + if not usable_port: + return False + mapper.update({usable_port: str(db.id)}) + self.set_mapper(mapper) + return True + + def pop(self, db: Application): + mapper = self.get_mapper() + to_delete_port = self.get_port_by_db(db) + mapper.pop(to_delete_port, None) + self.set_mapper(mapper) + + def get_port_by_db(self, db): + mapper = self.get_mapper() + for port, db_id in mapper.items(): + if db_id == str(db.id): + return port + def get_db_by_port(self, port): mapper = self.get_mapper() db_id = mapper.get(port, None) @@ -43,36 +64,17 @@ class DBPortManager(object): msg = 'Port not in port-db mapper, port: {}'.format(port) return db, msg - def add(self, db: Application): - mapper = self.get_mapper() - usable_port = self.get_next_usable_port() - if not usable_port: - return False - mapper.update({usable_port: str(db.id)}) - self.set_mapper(mapper) - return True - - def pop(self, db: Application): - mapper = self.get_mapper() - to_delete_port = None - for port, db_id in mapper.items(): - if db_id == str(db.id): - to_delete_port = port - break - mapper.pop(to_delete_port, None) - self.set_mapper(mapper) - def get_next_usable_port(self): already_use_ports = self.get_already_use_ports() usable_ports = list(set(self.all_usable_ports) - set(already_use_ports)) if len(usable_ports) > 1: return usable_ports[0] - else: - already_use_ports = self.get_already_use_ports() - msg = 'No port is usable, All usable port count: {}, Already use port count: {}'.format( - len(self.all_usable_ports), len(already_use_ports) - ) - logger.warning(msg) + + already_use_ports = self.get_already_use_ports() + msg = 'No port is usable, All usable port count: {}, Already use port count: {}'.format( + len(self.all_usable_ports), len(already_use_ports) + ) + logger.warning(msg) def get_already_use_ports(self): mapper = self.get_mapper() @@ -82,6 +84,11 @@ class DBPortManager(object): return cache.get(self.CACHE_KEY, {}) def set_mapper(self, value): + """ + value: { + port: db_id + } + """ cache.set(self.CACHE_KEY, value, timeout=None) From b8ec60dea13296891663e90584f5915b5ad851e3 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 22 Sep 2022 14:51:19 +0800 Subject: [PATCH 29/63] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20DB=20Listen?= =?UTF-8?q?=20Port=20=E6=98=A0=E5=B0=84=E8=A7=84=E5=88=99=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/application.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index 3d9913b24..8aa9c0550 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -1,7 +1,7 @@ # coding: utf-8 # from orgs.mixins.api import OrgBulkModelViewSet -from rest_framework import generics, status +from rest_framework import status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet @@ -60,8 +60,11 @@ class DBListenPortViewSet(GenericViewSet): def db_info(self, request, *args, **kwargs): port = request.data.get("port") db, msg = db_port_manager.get_db_by_port(port) - if db is None: + if db: + serializer = serializers.AppSerializer(instance=db) + data = serializer.data + _status = status.HTTP_200_OK + else: data = {'error': msg} - return Response(data=data, status=status.HTTP_404_NOT_FOUND) - serializer = serializers.AppSerializer(instance=db) - return Response(data=serializer.data, status=status.HTTP_201_CREATED) + _status = status.HTTP_404_NOT_FOUND + return Response(data=data, status=_status) From 57e12256e7a1a931cf16bcd5f1d9501c6899eca5 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 22 Sep 2022 15:52:47 +0800 Subject: [PATCH 30/63] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=20Endpoint=20?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=20Manugs=20DB=20listen=20port=20=E7=9A=84?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/const.py | 6 --- apps/applications/models/application.py | 10 ----- .../attrs/application_type/oracle.py | 6 --- apps/applications/utils/db_port_mapper.py | 3 ++ apps/authentication/api/connection_token.py | 5 ++- apps/terminal/api/endpoint.py | 14 +++---- apps/terminal/models/endpoint.py | 42 ++++++++++--------- apps/terminal/serializers/endpoint.py | 29 +++++++------ 8 files changed, 48 insertions(+), 67 deletions(-) diff --git a/apps/applications/const.py b/apps/applications/const.py index 4e0d2fe50..313477c25 100644 --- a/apps/applications/const.py +++ b/apps/applications/const.py @@ -83,9 +83,3 @@ class AppType(models.TextChoices): if AppCategory.is_xpack(category): return True return tp in ['oracle', 'postgresql', 'sqlserver'] - - -class OracleVersion(models.TextChoices): - version_11g = '11g', '11g' - version_12c = '12c', '12c' - version_other = 'other', _('Other') diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index 91507c2cb..3661188fe 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -10,7 +10,6 @@ from common.mixins import CommonModelMixin from common.tree import TreeNode from common.utils import is_uuid from assets.models import Asset, SystemUser -from ..const import OracleVersion from .. import const @@ -304,15 +303,6 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): target_ip = self.attrs.get('host') return target_ip - def get_target_protocol_for_oracle(self): - """ Oracle 类型需要单独处理,因为要携带版本号 """ - if not self.is_type(self.APP_TYPE.oracle): - return - version = self.attrs.get('version', OracleVersion.version_12c) - if version == OracleVersion.version_other: - return - return 'oracle_%s' % version - class ApplicationUser(SystemUser): class Meta: diff --git a/apps/applications/serializers/attrs/application_type/oracle.py b/apps/applications/serializers/attrs/application_type/oracle.py index fdc8016d2..c87c4904d 100644 --- a/apps/applications/serializers/attrs/application_type/oracle.py +++ b/apps/applications/serializers/attrs/application_type/oracle.py @@ -2,15 +2,9 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from ..application_category import DBSerializer -from applications.const import OracleVersion __all__ = ['OracleSerializer'] class OracleSerializer(DBSerializer): - version = serializers.ChoiceField( - choices=OracleVersion.choices, default=OracleVersion.version_12c, - allow_null=True, label=_('Version'), - help_text=_('Magnus currently supports only 11g and 12c connections') - ) port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True) diff --git a/apps/applications/utils/db_port_mapper.py b/apps/applications/utils/db_port_mapper.py index 0b6bdd93a..19fc7ce12 100644 --- a/apps/applications/utils/db_port_mapper.py +++ b/apps/applications/utils/db_port_mapper.py @@ -49,6 +49,9 @@ class DBPortManager(object): for port, db_id in mapper.items(): if db_id == str(db.id): return port + logger.warning( + 'Not matched db port, db_id: {}, mapper length: {}'.format(db.id, len(mapper)) + ) def get_db_by_port(self, port): mapper = self.get_mapper() diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 2b4ce7e2b..e52f76577 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -62,12 +62,15 @@ class ConnectionTokenMixin: def get_smart_endpoint(self, protocol, asset=None, application=None): if asset: + target_instance = asset target_ip = asset.get_target_ip() elif application: + target_instance = application target_ip = application.get_target_ip() else: + target_instance = None target_ip = '' - endpoint = EndpointRule.match_endpoint(target_ip, protocol, self.request) + endpoint = EndpointRule.match_endpoint(target_instance, target_ip, protocol, self.request) return endpoint @staticmethod diff --git a/apps/terminal/api/endpoint.py b/apps/terminal/api/endpoint.py index ca745d412..37de98576 100644 --- a/apps/terminal/api/endpoint.py +++ b/apps/terminal/api/endpoint.py @@ -47,11 +47,12 @@ class SmartEndpointViewMixin: return Endpoint.match_by_instance_label(self.target_instance, self.target_protocol) def match_endpoint_by_target_ip(self): - # 用来方便测试 - target_ip = self.request.GET.get('target_ip', '') + target_ip = self.request.GET.get('target_ip', '') # 支持target_ip参数,用来方便测试 if not target_ip and callable(getattr(self.target_instance, 'get_target_ip', None)): target_ip = self.target_instance.get_target_ip() - endpoint = EndpointRule.match_endpoint(target_ip, self.target_protocol, self.request) + endpoint = EndpointRule.match_endpoint( + self.target_instance, target_ip, self.target_protocol, self.request + ) return endpoint def get_target_instance(self): @@ -83,12 +84,7 @@ class SmartEndpointViewMixin: return instance def get_target_protocol(self): - protocol = None - if isinstance(self.target_instance, Application) and self.target_instance.is_type(Application.APP_TYPE.oracle): - protocol = self.target_instance.get_target_protocol_for_oracle() - if not protocol: - protocol = self.request.GET.get('protocol') - return protocol + return self.request.GET.get('protocol') class EndpointViewSet(SmartEndpointViewMixin, JMSBulkModelViewSet): diff --git a/apps/terminal/models/endpoint.py b/apps/terminal/models/endpoint.py index 305443821..98cc6a328 100644 --- a/apps/terminal/models/endpoint.py +++ b/apps/terminal/models/endpoint.py @@ -1,27 +1,23 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator +from applications.models import Application +from applications.utils import db_port_manager from common.db.models import JMSModel from common.db.fields import PortField from common.utils.ip import contains_ip +from common.exceptions import JMSException class Endpoint(JMSModel): name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) host = models.CharField(max_length=256, blank=True, verbose_name=_('Host')) - # disabled value=0 + # value=0 表示 disabled https_port = PortField(default=443, verbose_name=_('HTTPS Port')) http_port = PortField(default=80, verbose_name=_('HTTP Port')) ssh_port = PortField(default=2222, verbose_name=_('SSH Port')) rdp_port = PortField(default=3389, verbose_name=_('RDP Port')) - # Todo: Delete - mysql_port = PortField(default=33060, verbose_name=_('MySQL Port')) - mariadb_port = PortField(default=33061, verbose_name=_('MariaDB Port')) - postgresql_port = PortField(default=54320, verbose_name=_('PostgreSQL Port')) - redis_port = PortField(default=63790, verbose_name=_('Redis Port')) - oracle_11g_port = PortField(default=15211, verbose_name=_('Oracle 11g Port')) - oracle_12c_port = PortField(default=15212, verbose_name=_('Oracle 12c Port')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) default_id = '00000000-0000-0000-0000-000000000001' @@ -33,12 +29,18 @@ class Endpoint(JMSModel): def __str__(self): return self.name - def get_port(self, protocol): - return getattr(self, f'{protocol}_port', 0) - - def get_oracle_port(self, version): - protocol = f'oracle_{version}' - return self.get_port(protocol) + def get_port(self, target_instance, protocol): + if protocol in ['https', 'http', 'ssh', 'rdp']: + port = getattr(self, f'{protocol}_port', 0) + elif isinstance(target_instance, Application) and target_instance.category_db: + port = db_port_manager.get_port_by_db(target_instance) + if port is None: + error = 'No application port is matched, application id: {}' \ + ''.format(target_instance.id) + raise JMSException(error) + else: + port = 0 + return port def is_default(self): return str(self.id) == self.default_id @@ -48,10 +50,10 @@ class Endpoint(JMSModel): return return super().delete(using, keep_parents) - def is_valid_for(self, protocol): + def is_valid_for(self, target_instance, protocol): if self.is_default(): return True - if self.host and self.get_port(protocol) != 0: + if self.host and self.get_port(target_instance, protocol) != 0: return True return False @@ -105,19 +107,19 @@ class EndpointRule(JMSModel): return f'{self.name}({self.priority})' @classmethod - def match(cls, target_ip, protocol): + def match(cls, target_instance, target_ip, protocol): for endpoint_rule in cls.objects.all().prefetch_related('endpoint'): if not contains_ip(target_ip, endpoint_rule.ip_group): continue if not endpoint_rule.endpoint: continue - if not endpoint_rule.endpoint.is_valid_for(protocol): + if not endpoint_rule.endpoint.is_valid_for(target_instance, protocol): continue return endpoint_rule @classmethod - def match_endpoint(cls, target_ip, protocol, request=None): - endpoint_rule = cls.match(target_ip, protocol) + def match_endpoint(cls, target_instance, target_ip, protocol, request=None): + endpoint_rule = cls.match(target_instance, target_ip, protocol) if endpoint_rule: endpoint = endpoint_rule.endpoint else: diff --git a/apps/terminal/serializers/endpoint.py b/apps/terminal/serializers/endpoint.py index 3d8e858ac..d32d21a13 100644 --- a/apps/terminal/serializers/endpoint.py +++ b/apps/terminal/serializers/endpoint.py @@ -2,25 +2,23 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import BulkModelSerializer from acls.serializers.rules import ip_group_child_validator, ip_group_help_text +from django.conf import settings from ..models import Endpoint, EndpointRule __all__ = ['EndpointSerializer', 'EndpointRuleSerializer'] class EndpointSerializer(BulkModelSerializer): - # 解决 luna 处理繁琐的问题,oracle_port 返回匹配到的端口 - oracle_port = serializers.SerializerMethodField(label=_('Oracle port')) + # 解决 luna 处理繁琐的问题, 返回 magnus 监听的当前 db 的 port + magnus_listen_db_port = serializers.SerializerMethodField(label=_('Magnus listen db port')) + magnus_listen_port_range = serializers.SerializerMethodField(label=_('Magnus Listen port range')) class Meta: model = Endpoint fields_mini = ['id', 'name'] fields_small = [ 'host', - 'https_port', 'http_port', 'ssh_port', - 'rdp_port', 'mysql_port', 'mariadb_port', - 'postgresql_port', 'redis_port', - 'oracle_11g_port', 'oracle_12c_port', - 'oracle_port', + 'https_port', 'http_port', 'ssh_port', 'rdp_port', ] fields = fields_mini + fields_small + [ 'comment', 'date_created', 'date_updated', 'created_by' @@ -30,19 +28,20 @@ class EndpointSerializer(BulkModelSerializer): 'http_port': {'default': 80}, 'ssh_port': {'default': 2222}, 'rdp_port': {'default': 3389}, - 'mysql_port': {'default': 33060}, - 'mariadb_port': {'default': 33061}, - 'postgresql_port': {'default': 54320}, - 'redis_port': {'default': 63790}, - 'oracle_11g_port': {'default': 15211}, - 'oracle_12c_port': {'default': 15212}, } - def get_oracle_port(self, obj: Endpoint): + def get_magnus_listen_db_port(self, obj: Endpoint): view = self.context.get('view') if not view or view.action not in ['smart']: return 0 - return obj.get_port(view.target_protocol) + return obj.get_port(view.target_instance, view.target_protocol) + + @staticmethod + def get_magnus_listen_port_range(obj: Endpoint): + port_start = settings.MAGNUS_DB_PORTS_START + port_limit = settings.MAGNUS_DB_PORTS_LIMIT_COUNT + port_end = port_start + port_limit + 1 + return f'{port_start} - {port_end}' class EndpointRuleSerializer(BulkModelSerializer): From 497a52a509f8b1cae82fd8648d237d44f0b8769f Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 22 Sep 2022 17:23:17 +0800 Subject: [PATCH 31/63] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=20DBPortManage?= =?UTF-8?q?r=20=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/signal_handlers.py | 4 +++- apps/applications/utils/db_port_mapper.py | 21 ++++++++++++++------- apps/terminal/serializers/endpoint.py | 18 +++++++++--------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/apps/applications/signal_handlers.py b/apps/applications/signal_handlers.py index 92486fe1e..0d92a9f96 100644 --- a/apps/applications/signal_handlers.py +++ b/apps/applications/signal_handlers.py @@ -6,7 +6,9 @@ from common.signals import django_ready from django.dispatch import receiver from common.utils import get_logger from .models import Application -from .utils import db_port_manager +from .utils import db_port_manager, DBPortManager + +db_port_manager: DBPortManager logger = get_logger(__file__) diff --git a/apps/applications/utils/db_port_mapper.py b/apps/applications/utils/db_port_mapper.py index 19fc7ce12..13e185b2e 100644 --- a/apps/applications/utils/db_port_mapper.py +++ b/apps/applications/utils/db_port_mapper.py @@ -5,6 +5,7 @@ from applications.const import AppCategory from applications.models import Application from common.utils import get_logger from common.utils import get_object_or_none +from orgs.utils import tmp_to_root_org logger = get_logger(__file__) @@ -19,9 +20,13 @@ class DBPortManager(object): def __init__(self): self.port_start = settings.MAGNUS_DB_PORTS_START self.port_limit = settings.MAGNUS_DB_PORTS_LIMIT_COUNT - self.port_end = self.port_start + self.port_limit + 1 + self.port_end = self.port_start + self.port_limit # 可以使用的端口列表 - self.all_usable_ports = [i for i in range(self.port_start, self.port_end)] + self.all_usable_ports = [i for i in range(self.port_start, self.port_end+1)] + + @property + def magnus_listen_port_range(self): + return f'{self.port_start}-{self.port_end}' def init(self): db_ids = Application.objects.filter(category=AppCategory.db).values_list('id', flat=True) @@ -57,7 +62,8 @@ class DBPortManager(object): mapper = self.get_mapper() db_id = mapper.get(port, None) if db_id: - db = get_object_or_none(Application, id=db_id) + with tmp_to_root_org(): + db = get_object_or_none(Application, id=db_id) if not db: msg = 'Database not exists, database id: {}'.format(db_id) else: @@ -69,11 +75,12 @@ class DBPortManager(object): def get_next_usable_port(self): already_use_ports = self.get_already_use_ports() - usable_ports = list(set(self.all_usable_ports) - set(already_use_ports)) + usable_ports = sorted(list(set(self.all_usable_ports) - set(already_use_ports))) if len(usable_ports) > 1: - return usable_ports[0] + port = usable_ports[0] + logger.debug('Get next usable port: {}'.format(port)) + return port - already_use_ports = self.get_already_use_ports() msg = 'No port is usable, All usable port count: {}, Already use port count: {}'.format( len(self.all_usable_ports), len(already_use_ports) ) @@ -81,7 +88,7 @@ class DBPortManager(object): def get_already_use_ports(self): mapper = self.get_mapper() - return list(mapper.keys()) + return sorted(list(mapper.keys())) def get_mapper(self): return cache.get(self.CACHE_KEY, {}) diff --git a/apps/terminal/serializers/endpoint.py b/apps/terminal/serializers/endpoint.py index d32d21a13..75eb60352 100644 --- a/apps/terminal/serializers/endpoint.py +++ b/apps/terminal/serializers/endpoint.py @@ -2,7 +2,7 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import BulkModelSerializer from acls.serializers.rules import ip_group_child_validator, ip_group_help_text -from django.conf import settings +from applications.utils import db_port_manager from ..models import Endpoint, EndpointRule __all__ = ['EndpointSerializer', 'EndpointRuleSerializer'] @@ -11,7 +11,13 @@ __all__ = ['EndpointSerializer', 'EndpointRuleSerializer'] class EndpointSerializer(BulkModelSerializer): # 解决 luna 处理繁琐的问题, 返回 magnus 监听的当前 db 的 port magnus_listen_db_port = serializers.SerializerMethodField(label=_('Magnus listen db port')) - magnus_listen_port_range = serializers.SerializerMethodField(label=_('Magnus Listen port range')) + magnus_listen_port_range = serializers.CharField( + max_length=128, default=db_port_manager.magnus_listen_port_range, read_only=True, + label=_('Magnus Listen port range'), + help_text=_( + 'The range of ports that Magnus listens on is modified in the configuration file' + ) + ) class Meta: model = Endpoint @@ -19,6 +25,7 @@ class EndpointSerializer(BulkModelSerializer): fields_small = [ 'host', 'https_port', 'http_port', 'ssh_port', 'rdp_port', + 'magnus_listen_db_port', 'magnus_listen_port_range', ] fields = fields_mini + fields_small + [ 'comment', 'date_created', 'date_updated', 'created_by' @@ -36,13 +43,6 @@ class EndpointSerializer(BulkModelSerializer): return 0 return obj.get_port(view.target_instance, view.target_protocol) - @staticmethod - def get_magnus_listen_port_range(obj: Endpoint): - port_start = settings.MAGNUS_DB_PORTS_START - port_limit = settings.MAGNUS_DB_PORTS_LIMIT_COUNT - port_end = port_start + port_limit + 1 - return f'{port_start} - {port_end}' - class EndpointRuleSerializer(BulkModelSerializer): _ip_group_help_text = '{}
{}'.format( From 7a6ed91f629c04c0bbed5bf3fbf0761abbd009d6 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 22 Sep 2022 17:29:14 +0800 Subject: [PATCH 32/63] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 133 ++++++++++++++------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 132 +++++++++++++------------- 4 files changed, 139 insertions(+), 134 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index e43defdca..447ad1294 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d5dcc300fa64f04b513b670af0d9717fcdb108b54ddf264971b355cc72178de -size 132193 +oid sha256:ac618645a3defe4a3cd217dac34b95e6edd10ec30fc2273cd13f8da1c00f9fcf +size 132101 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index ea517add0..11caa1be4 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-15 15:52+0800\n" +"POT-Creation-Date: 2022-09-22 17:23+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -23,13 +23,13 @@ msgid "Acls" msgstr "Acls" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 -#: applications/models/application.py:220 assets/models/asset.py:138 +#: applications/models/application.py:219 assets/models/asset.py:138 #: assets/models/base.py:175 assets/models/cluster.py:18 #: assets/models/cmd_filter.py:27 assets/models/domain.py:23 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:86 +#: terminal/models/endpoint.py:13 terminal/models/endpoint.py:90 #: terminal/models/storage.py:26 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:669 @@ -38,12 +38,12 @@ msgid "Name" msgstr "名前" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:89 +#: assets/models/user.py:251 terminal/models/endpoint.py:93 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:90 +#: assets/models/user.py:251 terminal/models/endpoint.py:94 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" @@ -53,7 +53,7 @@ msgstr "1-100、低い値は最初に一致します" msgid "Active" msgstr "アクティブ" -#: acls/models/base.py:32 applications/models/application.py:233 +#: acls/models/base.py:32 applications/models/application.py:232 #: assets/models/asset.py:143 assets/models/asset.py:231 #: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 @@ -61,7 +61,7 @@ msgstr "アクティブ" #: assets/models/domain.py:65 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 -#: terminal/models/endpoint.py:23 terminal/models/endpoint.py:96 +#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:100 #: terminal/models/storage.py:29 terminal/models/terminal.py:114 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: users/models/group.py:16 users/models/user.py:706 @@ -185,7 +185,7 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:53 msgid "IP" msgstr "IP" @@ -241,7 +241,7 @@ msgstr "" msgid "Time Period" msgstr "期間" -#: applications/apps.py:9 applications/models/application.py:64 +#: applications/apps.py:9 applications/models/application.py:62 msgid "Applications" msgstr "アプリケーション" @@ -260,11 +260,7 @@ msgstr "リモートアプリ" msgid "Custom" msgstr "カスタム" -#: applications/const.py:91 rbac/tree.py:29 -msgid "Other" -msgstr "その他" - -#: applications/models/account.py:12 applications/models/application.py:237 +#: applications/models/account.py:12 applications/models/application.py:236 #: assets/models/backup.py:32 assets/models/cmd_filter.py:45 #: authentication/models.py:67 authentication/models.py:95 #: perms/models/application_permission.py:28 @@ -282,9 +278,8 @@ msgstr "アプリケーション" msgid "System user" msgstr "システムユーザー" -#: applications/models/account.py:17 -#: applications/serializers/attrs/application_type/oracle.py:13 -#: assets/models/authbook.py:21 settings/serializers/auth/cas.py:18 +#: applications/models/account.py:17 assets/models/authbook.py:21 +#: settings/serializers/auth/cas.py:18 msgid "Version" msgstr "バージョン" @@ -300,7 +295,7 @@ msgstr "アプリケーションアカウントの秘密を表示できます" msgid "Can change application account secret" msgstr "アプリケーションアカウントの秘密を変更できます" -#: applications/models/application.py:222 +#: applications/models/application.py:221 #: applications/serializers/application.py:101 assets/models/label.py:21 #: perms/models/application_permission.py:21 #: perms/serializers/application/user_permission.py:33 @@ -309,7 +304,7 @@ msgstr "アプリケーションアカウントの秘密を変更できます" msgid "Category" msgstr "カテゴリ" -#: applications/models/application.py:225 +#: applications/models/application.py:224 #: applications/serializers/application.py:103 assets/models/backup.py:49 #: assets/models/cmd_filter.py:82 assets/models/user.py:250 #: authentication/models.py:70 perms/models/application_permission.py:24 @@ -323,21 +318,21 @@ msgstr "カテゴリ" msgid "Type" msgstr "タイプ" -#: applications/models/application.py:229 assets/models/asset.py:217 +#: applications/models/application.py:228 assets/models/asset.py:217 #: assets/models/domain.py:29 assets/models/domain.py:64 msgid "Domain" msgstr "ドメイン" -#: applications/models/application.py:231 xpack/plugins/cloud/models.py:33 +#: applications/models/application.py:230 xpack/plugins/cloud/models.py:33 #: xpack/plugins/cloud/serializers/account.py:63 msgid "Attrs" msgstr "ツールバーの" -#: applications/models/application.py:241 +#: applications/models/application.py:240 msgid "Can match application" msgstr "アプリケーションを一致させることができます" -#: applications/models/application.py:320 +#: applications/models/application.py:310 msgid "Application user" msgstr "アプリケーションユーザー" @@ -393,7 +388,7 @@ msgstr "クラスター" #: applications/serializers/attrs/application_category/db.py:11 #: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:11 +#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:14 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "ホスト" @@ -402,7 +397,7 @@ msgstr "ホスト" #: applications/serializers/attrs/application_type/mongodb.py:10 #: applications/serializers/attrs/application_type/mysql.py:10 #: applications/serializers/attrs/application_type/mysql_workbench.py:22 -#: applications/serializers/attrs/application_type/oracle.py:16 +#: applications/serializers/attrs/application_type/oracle.py:10 #: applications/serializers/attrs/application_type/pgsql.py:10 #: applications/serializers/attrs/application_type/redis.py:10 #: applications/serializers/attrs/application_type/sqlserver.py:10 @@ -497,10 +492,6 @@ msgstr "Mysql workbench のユーザー名" msgid "Mysql workbench password" msgstr "Mysql workbench パスワード" -#: applications/serializers/attrs/application_type/oracle.py:14 -msgid "Magnus currently supports only 11g and 12c connections" -msgstr "現在、Magnusは11gおよび12cバージョンへの接続のみをサポートしています" - #: applications/serializers/attrs/application_type/vmware_client.py:26 msgid "Vmware username" msgstr "Vmware ユーザー名" @@ -3457,6 +3448,10 @@ msgstr "監査ビュー" msgid "System setting" msgstr "システム設定" +#: rbac/tree.py:29 +msgid "Other" +msgstr "その他" + #: rbac/tree.py:37 msgid "Accounts" msgstr "アカウント" @@ -4956,58 +4951,34 @@ msgstr "ストレージが無効です" msgid "Command record" msgstr "コマンドレコード" -#: terminal/models/endpoint.py:13 +#: terminal/models/endpoint.py:16 msgid "HTTPS Port" msgstr "HTTPS ポート" -#: terminal/models/endpoint.py:14 terminal/models/terminal.py:107 +#: terminal/models/endpoint.py:17 terminal/models/terminal.py:107 msgid "HTTP Port" msgstr "HTTP ポート" -#: terminal/models/endpoint.py:15 terminal/models/terminal.py:106 +#: terminal/models/endpoint.py:18 terminal/models/terminal.py:106 msgid "SSH Port" msgstr "SSH ポート" -#: terminal/models/endpoint.py:16 +#: terminal/models/endpoint.py:19 msgid "RDP Port" msgstr "RDP ポート" -#: terminal/models/endpoint.py:17 -msgid "MySQL Port" -msgstr "MySQL ポート" - -#: terminal/models/endpoint.py:18 -msgid "MariaDB Port" -msgstr "MariaDB ポート" - -#: terminal/models/endpoint.py:19 -msgid "PostgreSQL Port" -msgstr "PostgreSQL ポート" - -#: terminal/models/endpoint.py:20 -msgid "Redis Port" -msgstr "Redis ポート" - -#: terminal/models/endpoint.py:21 -msgid "Oracle 11g Port" -msgstr "Oracle 11g ポート" - -#: terminal/models/endpoint.py:22 -msgid "Oracle 12c Port" -msgstr "Oracle 12c ポート" - -#: terminal/models/endpoint.py:28 terminal/models/endpoint.py:94 -#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38 +#: terminal/models/endpoint.py:26 terminal/models/endpoint.py:98 +#: terminal/serializers/endpoint.py:56 terminal/serializers/storage.py:38 #: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 #: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 msgid "Endpoint" msgstr "エンドポイント" -#: terminal/models/endpoint.py:87 +#: terminal/models/endpoint.py:91 msgid "IP group" msgstr "IP グループ" -#: terminal/models/endpoint.py:99 +#: terminal/models/endpoint.py:103 msgid "Endpoint rule" msgstr "エンドポイントルール" @@ -5188,11 +5159,21 @@ msgstr "レベル" msgid "Batch danger command alert" msgstr "一括危険コマンド警告" -#: terminal/serializers/endpoint.py:12 -msgid "Oracle port" -msgstr "" +#: terminal/serializers/endpoint.py:13 +msgid "Magnus listen db port" +msgstr "Magnus がリッスンするデータベース ポート" -#: terminal/serializers/endpoint.py:51 +#: terminal/serializers/endpoint.py:16 +msgid "Magnus Listen port range" +msgstr "Magnus がリッスンするポート範囲" + +#: terminal/serializers/endpoint.py:18 +msgid "" +"The range of ports that Magnus listens on is modified in the configuration " +"file" +msgstr "Magnus がリッスンするポート範囲を構成ファイルで変更してください" + +#: terminal/serializers/endpoint.py:50 msgid "" "If asset IP addresses under different endpoints conflict, use asset labels" msgstr "" @@ -7015,3 +6996,25 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "コミュニティ版" + +#~ msgid "Magnus currently supports only 11g and 12c connections" +#~ msgstr "" +#~ "現在、Magnusは11gおよび12cバージョンへの接続のみをサポートしています" + +#~ msgid "MySQL Port" +#~ msgstr "MySQL ポート" + +#~ msgid "MariaDB Port" +#~ msgstr "MariaDB ポート" + +#~ msgid "PostgreSQL Port" +#~ msgstr "PostgreSQL ポート" + +#~ msgid "Redis Port" +#~ msgstr "Redis ポート" + +#~ msgid "Oracle 11g Port" +#~ msgstr "Oracle 11g ポート" + +#~ msgid "Oracle 12c Port" +#~ msgstr "Oracle 12c ポート" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index dd47ce08a..99fd77b14 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:618dce0f2521591410fb16b3d0afa536333d2e4f317d75d1955fe413dc0e64cb -size 108949 +oid sha256:fc9d0fe73352de035ee77c477e143d6209a18ba533e232f0a39f8c7440379f04 +size 108810 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 946eaa1f0..dcb0f325f 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-15 15:52+0800\n" +"POT-Creation-Date: 2022-09-22 17:23+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -22,13 +22,13 @@ msgid "Acls" msgstr "访问控制" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 -#: applications/models/application.py:220 assets/models/asset.py:138 +#: applications/models/application.py:219 assets/models/asset.py:138 #: assets/models/base.py:175 assets/models/cluster.py:18 #: assets/models/cmd_filter.py:27 assets/models/domain.py:23 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:86 +#: terminal/models/endpoint.py:13 terminal/models/endpoint.py:90 #: terminal/models/storage.py:26 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:669 @@ -37,12 +37,12 @@ msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:89 +#: assets/models/user.py:251 terminal/models/endpoint.py:93 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:90 +#: assets/models/user.py:251 terminal/models/endpoint.py:94 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -52,7 +52,7 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)" msgid "Active" msgstr "激活中" -#: acls/models/base.py:32 applications/models/application.py:233 +#: acls/models/base.py:32 applications/models/application.py:232 #: assets/models/asset.py:143 assets/models/asset.py:231 #: assets/models/backup.py:54 assets/models/base.py:180 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 @@ -60,7 +60,7 @@ msgstr "激活中" #: assets/models/domain.py:65 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 -#: terminal/models/endpoint.py:23 terminal/models/endpoint.py:96 +#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:100 #: terminal/models/storage.py:29 terminal/models/terminal.py:114 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: users/models/group.py:16 users/models/user.py:706 @@ -183,7 +183,7 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:53 msgid "IP" msgstr "IP" @@ -236,7 +236,7 @@ msgstr "" msgid "Time Period" msgstr "时段" -#: applications/apps.py:9 applications/models/application.py:64 +#: applications/apps.py:9 applications/models/application.py:62 msgid "Applications" msgstr "应用管理" @@ -255,11 +255,7 @@ msgstr "远程应用" msgid "Custom" msgstr "自定义" -#: applications/const.py:91 rbac/tree.py:29 -msgid "Other" -msgstr "其它" - -#: applications/models/account.py:12 applications/models/application.py:237 +#: applications/models/account.py:12 applications/models/application.py:236 #: assets/models/backup.py:32 assets/models/cmd_filter.py:45 #: authentication/models.py:67 authentication/models.py:95 #: perms/models/application_permission.py:28 @@ -277,9 +273,8 @@ msgstr "应用程序" msgid "System user" msgstr "系统用户" -#: applications/models/account.py:17 -#: applications/serializers/attrs/application_type/oracle.py:13 -#: assets/models/authbook.py:21 settings/serializers/auth/cas.py:18 +#: applications/models/account.py:17 assets/models/authbook.py:21 +#: settings/serializers/auth/cas.py:18 msgid "Version" msgstr "版本" @@ -295,7 +290,7 @@ msgstr "可以查看应用账号密码" msgid "Can change application account secret" msgstr "可以查看应用账号密码" -#: applications/models/application.py:222 +#: applications/models/application.py:221 #: applications/serializers/application.py:101 assets/models/label.py:21 #: perms/models/application_permission.py:21 #: perms/serializers/application/user_permission.py:33 @@ -304,7 +299,7 @@ msgstr "可以查看应用账号密码" msgid "Category" msgstr "类别" -#: applications/models/application.py:225 +#: applications/models/application.py:224 #: applications/serializers/application.py:103 assets/models/backup.py:49 #: assets/models/cmd_filter.py:82 assets/models/user.py:250 #: authentication/models.py:70 perms/models/application_permission.py:24 @@ -318,21 +313,21 @@ msgstr "类别" msgid "Type" msgstr "类型" -#: applications/models/application.py:229 assets/models/asset.py:217 +#: applications/models/application.py:228 assets/models/asset.py:217 #: assets/models/domain.py:29 assets/models/domain.py:64 msgid "Domain" msgstr "网域" -#: applications/models/application.py:231 xpack/plugins/cloud/models.py:33 +#: applications/models/application.py:230 xpack/plugins/cloud/models.py:33 #: xpack/plugins/cloud/serializers/account.py:63 msgid "Attrs" msgstr "属性" -#: applications/models/application.py:241 +#: applications/models/application.py:240 msgid "Can match application" msgstr "匹配应用" -#: applications/models/application.py:320 +#: applications/models/application.py:310 msgid "Application user" msgstr "应用用户" @@ -388,7 +383,7 @@ msgstr "集群" #: applications/serializers/attrs/application_category/db.py:11 #: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:11 +#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:14 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "主机" @@ -397,7 +392,7 @@ msgstr "主机" #: applications/serializers/attrs/application_type/mongodb.py:10 #: applications/serializers/attrs/application_type/mysql.py:10 #: applications/serializers/attrs/application_type/mysql_workbench.py:22 -#: applications/serializers/attrs/application_type/oracle.py:16 +#: applications/serializers/attrs/application_type/oracle.py:10 #: applications/serializers/attrs/application_type/pgsql.py:10 #: applications/serializers/attrs/application_type/redis.py:10 #: applications/serializers/attrs/application_type/sqlserver.py:10 @@ -492,10 +487,6 @@ msgstr "Mysql 工作台 用户名" msgid "Mysql workbench password" msgstr "Mysql 工作台 密码" -#: applications/serializers/attrs/application_type/oracle.py:14 -msgid "Magnus currently supports only 11g and 12c connections" -msgstr "目前 Magnus 只支持连接 11g、12c 版本" - #: applications/serializers/attrs/application_type/vmware_client.py:26 msgid "Vmware username" msgstr "Vmware 用户名" @@ -3414,6 +3405,10 @@ msgstr "审计台" msgid "System setting" msgstr "系统设置" +#: rbac/tree.py:29 +msgid "Other" +msgstr "其它" + #: rbac/tree.py:37 msgid "Accounts" msgstr "账号管理" @@ -4879,58 +4874,34 @@ msgstr "存储无效" msgid "Command record" msgstr "命令记录" -#: terminal/models/endpoint.py:13 +#: terminal/models/endpoint.py:16 msgid "HTTPS Port" msgstr "HTTPS 端口" -#: terminal/models/endpoint.py:14 terminal/models/terminal.py:107 +#: terminal/models/endpoint.py:17 terminal/models/terminal.py:107 msgid "HTTP Port" msgstr "HTTP 端口" -#: terminal/models/endpoint.py:15 terminal/models/terminal.py:106 +#: terminal/models/endpoint.py:18 terminal/models/terminal.py:106 msgid "SSH Port" msgstr "SSH 端口" -#: terminal/models/endpoint.py:16 +#: terminal/models/endpoint.py:19 msgid "RDP Port" msgstr "RDP 端口" -#: terminal/models/endpoint.py:17 -msgid "MySQL Port" -msgstr "MySQL 端口" - -#: terminal/models/endpoint.py:18 -msgid "MariaDB Port" -msgstr "MariaDB 端口" - -#: terminal/models/endpoint.py:19 -msgid "PostgreSQL Port" -msgstr "PostgreSQL 端口" - -#: terminal/models/endpoint.py:20 -msgid "Redis Port" -msgstr "Redis 端口" - -#: terminal/models/endpoint.py:21 -msgid "Oracle 11g Port" -msgstr "Oracle 11g 端口" - -#: terminal/models/endpoint.py:22 -msgid "Oracle 12c Port" -msgstr "Oracle 12c 端口" - -#: terminal/models/endpoint.py:28 terminal/models/endpoint.py:94 -#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38 +#: terminal/models/endpoint.py:26 terminal/models/endpoint.py:98 +#: terminal/serializers/endpoint.py:56 terminal/serializers/storage.py:38 #: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 #: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 msgid "Endpoint" msgstr "端点" -#: terminal/models/endpoint.py:87 +#: terminal/models/endpoint.py:91 msgid "IP group" msgstr "IP 组" -#: terminal/models/endpoint.py:99 +#: terminal/models/endpoint.py:103 msgid "Endpoint rule" msgstr "端点规则" @@ -5111,11 +5082,21 @@ msgstr "级别" msgid "Batch danger command alert" msgstr "批量危险命令告警" -#: terminal/serializers/endpoint.py:12 -msgid "Oracle port" -msgstr "" +#: terminal/serializers/endpoint.py:13 +msgid "Magnus listen db port" +msgstr "Magnus 监听的数据库端口" -#: terminal/serializers/endpoint.py:51 +#: terminal/serializers/endpoint.py:16 +msgid "Magnus Listen port range" +msgstr "Magnus 监听的端口范围" + +#: terminal/serializers/endpoint.py:18 +msgid "" +"The range of ports that Magnus listens on is modified in the configuration " +"file" +msgstr "请在配置文件中修改 Magnus 监听的端口范围" + +#: terminal/serializers/endpoint.py:50 msgid "" "If asset IP addresses under different endpoints conflict, use asset labels" msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现" @@ -6916,3 +6897,24 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" + +#~ msgid "Magnus currently supports only 11g and 12c connections" +#~ msgstr "目前 Magnus 只支持连接 11g、12c 版本" + +#~ msgid "MySQL Port" +#~ msgstr "MySQL 端口" + +#~ msgid "MariaDB Port" +#~ msgstr "MariaDB 端口" + +#~ msgid "PostgreSQL Port" +#~ msgstr "PostgreSQL 端口" + +#~ msgid "Redis Port" +#~ msgstr "Redis 端口" + +#~ msgid "Oracle 11g Port" +#~ msgstr "Oracle 11g 端口" + +#~ msgid "Oracle 12c Port" +#~ msgstr "Oracle 12c 端口" From c1c70849e9c2ba755e41bbd707fa1b040a062e6e Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 22 Sep 2022 18:47:16 +0800 Subject: [PATCH 33/63] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=20DBPortMapper?= =?UTF-8?q?=20=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86=E9=97=AE=E9=A2=98;=20DB?= =?UTF-8?q?ListenPort=20API=20=E8=BF=81=E7=A7=BB=E8=87=B3=20terminal=20app?= =?UTF-8?q?=20=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/application.py | 32 +---- apps/applications/signal_handlers.py | 36 +----- apps/applications/urls/api_urls.py | 1 - apps/terminal/api/__init__.py | 1 + apps/terminal/api/db_listen_port.py | 36 ++++++ apps/terminal/models/endpoint.py | 8 +- apps/terminal/serializers/endpoint.py | 3 +- apps/terminal/signal_handlers.py | 34 +++++ apps/terminal/urls/api_urls.py | 1 + apps/terminal/utils/__init__.py | 4 + apps/terminal/utils/common.py | 59 +++++++++ .../{utils.py => utils/components.py} | 122 +----------------- .../utils/db_port_mapper.py | 61 +++++---- apps/terminal/utils/session_replay.py | 75 +++++++++++ 14 files changed, 249 insertions(+), 224 deletions(-) create mode 100644 apps/terminal/api/db_listen_port.py create mode 100644 apps/terminal/utils/__init__.py create mode 100644 apps/terminal/utils/common.py rename apps/terminal/{utils.py => utils/components.py} (55%) rename apps/{applications => terminal}/utils/db_port_mapper.py (57%) create mode 100644 apps/terminal/utils/session_replay.py diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index 8aa9c0550..4d04e894f 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -1,19 +1,16 @@ # coding: utf-8 # from orgs.mixins.api import OrgBulkModelViewSet -from rest_framework import status from rest_framework.decorators import action from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet from common.tree import TreeNodeSerializer from common.mixins.api import SuggestionMixin -from ..utils import db_port_manager from .. import serializers from ..models import Application -__all__ = ['ApplicationViewSet', 'DBListenPortViewSet'] +__all__ = ['ApplicationViewSet'] class ApplicationViewSet(SuggestionMixin, OrgBulkModelViewSet): @@ -41,30 +38,3 @@ class ApplicationViewSet(SuggestionMixin, OrgBulkModelViewSet): tree_nodes = Application.create_tree_nodes(queryset, show_count=show_count) serializer = self.get_serializer(tree_nodes, many=True) return Response(serializer.data) - - -class DBListenPortViewSet(GenericViewSet): - rbac_perms = { - 'GET': 'applications.view_application', - 'list': 'applications.view_application', - 'db_info': 'applications.view_application', - } - - http_method_names = ['get', 'post'] - - def list(self, request, *args, **kwargs): - ports = db_port_manager.get_already_use_ports() - return Response(data=ports, status=status.HTTP_200_OK) - - @action(methods=['post'], detail=False, url_path='db-info') - def db_info(self, request, *args, **kwargs): - port = request.data.get("port") - db, msg = db_port_manager.get_db_by_port(port) - if db: - serializer = serializers.AppSerializer(instance=db) - data = serializer.data - _status = status.HTTP_200_OK - else: - data = {'error': msg} - _status = status.HTTP_404_NOT_FOUND - return Response(data=data, status=_status) diff --git a/apps/applications/signal_handlers.py b/apps/applications/signal_handlers.py index 0d92a9f96..4aa11c79b 100644 --- a/apps/applications/signal_handlers.py +++ b/apps/applications/signal_handlers.py @@ -1,36 +1,2 @@ # -*- coding: utf-8 -*- -# -from django.db.models.signals import post_save, post_delete - -from common.signals import django_ready -from django.dispatch import receiver -from common.utils import get_logger -from .models import Application -from .utils import db_port_manager, DBPortManager - -db_port_manager: DBPortManager - - -logger = get_logger(__file__) - - -@receiver(django_ready) -def init_db_port_mapper(sender, **kwargs): - logger.info('Init db port mapper') - db_port_manager.init() - - -@receiver(post_save, sender=Application) -def on_db_app_created(sender, instance: Application, created, **kwargs): - if not instance.category_db: - return - if not created: - return - db_port_manager.add(instance) - - -@receiver(post_delete, sender=Application) -def on_db_app_delete(sender, instance, **kwargs): - if not instance.category_db: - return - db_port_manager.pop(instance) +# \ No newline at end of file diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py index 813c047a2..4fdf006b0 100644 --- a/apps/applications/urls/api_urls.py +++ b/apps/applications/urls/api_urls.py @@ -13,7 +13,6 @@ router.register(r'applications', api.ApplicationViewSet, 'application') router.register(r'accounts', api.ApplicationAccountViewSet, 'application-account') router.register(r'system-users-apps-relations', api.SystemUserAppRelationViewSet, 'system-users-apps-relation') router.register(r'account-secrets', api.ApplicationAccountSecretViewSet, 'application-account-secret') -router.register(r'db-listen-ports', api.DBListenPortViewSet, 'db-listen-ports') urlpatterns = [ diff --git a/apps/terminal/api/__init__.py b/apps/terminal/api/__init__.py index 16021a5ed..4cdabc9dd 100644 --- a/apps/terminal/api/__init__.py +++ b/apps/terminal/api/__init__.py @@ -8,3 +8,4 @@ from .storage import * from .status import * from .sharing import * from .endpoint import * +from .db_listen_port import * diff --git a/apps/terminal/api/db_listen_port.py b/apps/terminal/api/db_listen_port.py new file mode 100644 index 000000000..4170a33d4 --- /dev/null +++ b/apps/terminal/api/db_listen_port.py @@ -0,0 +1,36 @@ +# coding: utf-8 +# +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet + +from ..utils import db_port_manager, DBPortManager +from applications import serializers + + +db_port_manager: DBPortManager + + +__all__ = ['DBListenPortViewSet'] + + +class DBListenPortViewSet(GenericViewSet): + rbac_perms = { + 'GET': 'applications.view_application', + 'list': 'applications.view_application', + 'db_info': 'applications.view_application', + } + + http_method_names = ['get', 'post'] + + def list(self, request, *args, **kwargs): + ports = db_port_manager.get_already_use_ports() + return Response(data=ports, status=status.HTTP_200_OK) + + @action(methods=['get'], detail=False, url_path='db-info') + def db_info(self, request, *args, **kwargs): + port = request.query_params.get("port") + db = db_port_manager.get_db_by_port(port) + serializer = serializers.AppSerializer(instance=db) + return Response(data=serializer.data, status=status.HTTP_200_OK) diff --git a/apps/terminal/models/endpoint.py b/apps/terminal/models/endpoint.py index 98cc6a328..9d65fa271 100644 --- a/apps/terminal/models/endpoint.py +++ b/apps/terminal/models/endpoint.py @@ -2,12 +2,14 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator from applications.models import Application -from applications.utils import db_port_manager +from ..utils import db_port_manager, DBPortManager from common.db.models import JMSModel from common.db.fields import PortField from common.utils.ip import contains_ip from common.exceptions import JMSException +db_port_manager: DBPortManager + class Endpoint(JMSModel): name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) @@ -34,10 +36,6 @@ class Endpoint(JMSModel): port = getattr(self, f'{protocol}_port', 0) elif isinstance(target_instance, Application) and target_instance.category_db: port = db_port_manager.get_port_by_db(target_instance) - if port is None: - error = 'No application port is matched, application id: {}' \ - ''.format(target_instance.id) - raise JMSException(error) else: port = 0 return port diff --git a/apps/terminal/serializers/endpoint.py b/apps/terminal/serializers/endpoint.py index 75eb60352..ce45ffa3d 100644 --- a/apps/terminal/serializers/endpoint.py +++ b/apps/terminal/serializers/endpoint.py @@ -2,9 +2,10 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import BulkModelSerializer from acls.serializers.rules import ip_group_child_validator, ip_group_help_text -from applications.utils import db_port_manager +from ..utils import db_port_manager from ..models import Endpoint, EndpointRule + __all__ = ['EndpointSerializer', 'EndpointRuleSerializer'] diff --git a/apps/terminal/signal_handlers.py b/apps/terminal/signal_handlers.py index ec51c5a2b..0d92a9f96 100644 --- a/apps/terminal/signal_handlers.py +++ b/apps/terminal/signal_handlers.py @@ -1,2 +1,36 @@ # -*- coding: utf-8 -*- # +from django.db.models.signals import post_save, post_delete + +from common.signals import django_ready +from django.dispatch import receiver +from common.utils import get_logger +from .models import Application +from .utils import db_port_manager, DBPortManager + +db_port_manager: DBPortManager + + +logger = get_logger(__file__) + + +@receiver(django_ready) +def init_db_port_mapper(sender, **kwargs): + logger.info('Init db port mapper') + db_port_manager.init() + + +@receiver(post_save, sender=Application) +def on_db_app_created(sender, instance: Application, created, **kwargs): + if not instance.category_db: + return + if not created: + return + db_port_manager.add(instance) + + +@receiver(post_delete, sender=Application) +def on_db_app_delete(sender, instance, **kwargs): + if not instance.category_db: + return + db_port_manager.pop(instance) diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 3f0445350..8adcb8f52 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -24,6 +24,7 @@ router.register(r'session-sharings', api.SessionSharingViewSet, 'session-sharing router.register(r'session-join-records', api.SessionJoinRecordsViewSet, 'session-sharing-record') router.register(r'endpoints', api.EndpointViewSet, 'endpoint') router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule') +router.register(r'db-listen-ports', api.DBListenPortViewSet, 'db-listen-ports') urlpatterns = [ path('my-sessions/', api.MySessionAPIView.as_view(), name='my-session'), diff --git a/apps/terminal/utils/__init__.py b/apps/terminal/utils/__init__.py new file mode 100644 index 000000000..b55827a8d --- /dev/null +++ b/apps/terminal/utils/__init__.py @@ -0,0 +1,4 @@ +from .components import * +from .common import * +from .session_replay import * +from .db_port_mapper import * diff --git a/apps/terminal/utils/common.py b/apps/terminal/utils/common.py new file mode 100644 index 000000000..26fd303b2 --- /dev/null +++ b/apps/terminal/utils/common.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# + +from common.utils import get_logger +from .. import const +from tickets.models import TicketSession + + +logger = get_logger(__name__) + + +class ComputeStatUtil: + # system status + @staticmethod + def _common_compute_system_status(value, thresholds): + if thresholds[0] <= value <= thresholds[1]: + return const.ComponentStatusChoices.normal.value + elif thresholds[1] < value <= thresholds[2]: + return const.ComponentStatusChoices.high.value + else: + return const.ComponentStatusChoices.critical.value + + @classmethod + def _compute_system_stat_status(cls, stat): + system_stat_thresholds_mapper = { + 'cpu_load': [0, 5, 20], + 'memory_used': [0, 85, 95], + 'disk_used': [0, 80, 99] + } + system_status = {} + for stat_key, thresholds in system_stat_thresholds_mapper.items(): + stat_value = getattr(stat, stat_key) + if stat_value is None: + msg = 'stat: {}, stat_key: {}, stat_value: {}' + logger.debug(msg.format(stat, stat_key, stat_value)) + stat_value = 0 + status = cls._common_compute_system_status(stat_value, thresholds) + system_status[stat_key] = status + return system_status + + @classmethod + def compute_component_status(cls, stat): + if not stat: + return const.ComponentStatusChoices.offline + system_status_values = cls._compute_system_stat_status(stat).values() + if const.ComponentStatusChoices.critical in system_status_values: + return const.ComponentStatusChoices.critical + elif const.ComponentStatusChoices.high in system_status_values: + return const.ComponentStatusChoices.high + else: + return const.ComponentStatusChoices.normal + + +def is_session_approver(session_id, user_id): + ticket = TicketSession.get_ticket_by_session_id(session_id) + if not ticket: + return False + ok = ticket.has_all_assignee(user_id) + return ok diff --git a/apps/terminal/utils.py b/apps/terminal/utils/components.py similarity index 55% rename from apps/terminal/utils.py rename to apps/terminal/utils/components.py index abdfbd738..0610b0b79 100644 --- a/apps/terminal/utils.py +++ b/apps/terminal/utils/components.py @@ -1,124 +1,13 @@ # -*- coding: utf-8 -*- # -import os -from itertools import groupby, chain - -from django.conf import settings -from django.core.files.storage import default_storage - -import jms_storage +from itertools import groupby from common.utils import get_logger -from . import const -from .models import ReplayStorage -from tickets.models import TicketSession, TicketStep, TicketAssignee -from tickets.const import StepState logger = get_logger(__name__) -def find_session_replay_local(session): - # 存在外部存储上,所有可能的路径名 - session_paths = session.get_all_possible_relative_path() - - # 存在本地存储上,所有可能的路径名 - local_paths = session.get_all_possible_local_path() - - for _local_path in chain(session_paths, local_paths): - if default_storage.exists(_local_path): - url = default_storage.url(_local_path) - return _local_path, url - return None, None - - -def download_session_replay(session): - replay_storages = ReplayStorage.objects.all() - configs = { - storage.name: storage.config - for storage in replay_storages - if not storage.type_null_or_server - } - if settings.SERVER_REPLAY_STORAGE: - configs['SERVER_REPLAY_STORAGE'] = settings.SERVER_REPLAY_STORAGE - if not configs: - msg = "Not found replay file, and not remote storage set" - return None, msg - storage = jms_storage.get_multi_object_storage(configs) - - # 获取外部存储路径名 - session_path = session.find_ok_relative_path_in_storage(storage) - if not session_path: - msg = "Not found session replay file" - return None, msg - - # 通过外部存储路径名后缀,构造真实的本地存储路径 - local_path = session.get_local_path_by_relative_path(session_path) - - # 保存到storage的路径 - target_path = os.path.join(default_storage.base_location, local_path) - target_dir = os.path.dirname(target_path) - if not os.path.isdir(target_dir): - os.makedirs(target_dir, exist_ok=True) - - ok, err = storage.download(session_path, target_path) - if not ok: - msg = "Failed download replay file: {}".format(err) - logger.error(msg) - return None, msg - url = default_storage.url(local_path) - return local_path, url - - -def get_session_replay_url(session): - local_path, url = find_session_replay_local(session) - if local_path is None: - local_path, url = download_session_replay(session) - return local_path, url - - -class ComputeStatUtil: - # system status - @staticmethod - def _common_compute_system_status(value, thresholds): - if thresholds[0] <= value <= thresholds[1]: - return const.ComponentStatusChoices.normal.value - elif thresholds[1] < value <= thresholds[2]: - return const.ComponentStatusChoices.high.value - else: - return const.ComponentStatusChoices.critical.value - - @classmethod - def _compute_system_stat_status(cls, stat): - system_stat_thresholds_mapper = { - 'cpu_load': [0, 5, 20], - 'memory_used': [0, 85, 95], - 'disk_used': [0, 80, 99] - } - system_status = {} - for stat_key, thresholds in system_stat_thresholds_mapper.items(): - stat_value = getattr(stat, stat_key) - if stat_value is None: - msg = 'stat: {}, stat_key: {}, stat_value: {}' - logger.debug(msg.format(stat, stat_key, stat_value)) - stat_value = 0 - status = cls._common_compute_system_status(stat_value, thresholds) - system_status[stat_key] = status - return system_status - - @classmethod - def compute_component_status(cls, stat): - if not stat: - return const.ComponentStatusChoices.offline - system_status_values = cls._compute_system_stat_status(stat).values() - if const.ComponentStatusChoices.critical in system_status_values: - return const.ComponentStatusChoices.critical - elif const.ComponentStatusChoices.high in system_status_values: - return const.ComponentStatusChoices.high - else: - return const.ComponentStatusChoices.normal - - class TypedComponentsStatusMetricsUtil(object): def __init__(self): self.components = [] @@ -126,7 +15,7 @@ class TypedComponentsStatusMetricsUtil(object): self.get_components() def get_components(self): - from .models import Terminal + from ..models import Terminal components = Terminal.objects.filter(is_deleted=False).order_by('type') grouped_components = groupby(components, lambda c: c.type) grouped_components = [(i[0], list(i[1])) for i in grouped_components] @@ -251,10 +140,3 @@ class ComponentsPrometheusMetricsUtil(TypedComponentsStatusMetricsUtil): prometheus_metrics_text = '\n'.join(prometheus_metrics) return prometheus_metrics_text - -def is_session_approver(session_id, user_id): - ticket = TicketSession.get_ticket_by_session_id(session_id) - if not ticket: - return False - ok = ticket.has_all_assignee(user_id) - return ok diff --git a/apps/applications/utils/db_port_mapper.py b/apps/terminal/utils/db_port_mapper.py similarity index 57% rename from apps/applications/utils/db_port_mapper.py rename to apps/terminal/utils/db_port_mapper.py index 13e185b2e..2ca84cc0e 100644 --- a/apps/applications/utils/db_port_mapper.py +++ b/apps/terminal/utils/db_port_mapper.py @@ -6,6 +6,7 @@ from applications.models import Application from common.utils import get_logger from common.utils import get_object_or_none from orgs.utils import tmp_to_root_org +from common.exceptions import JMSException logger = get_logger(__file__) @@ -22,24 +23,23 @@ class DBPortManager(object): self.port_limit = settings.MAGNUS_DB_PORTS_LIMIT_COUNT self.port_end = self.port_start + self.port_limit # 可以使用的端口列表 - self.all_usable_ports = [i for i in range(self.port_start, self.port_end+1)] + self.all_available_ports = list(range(self.port_start, self.port_end + 1)) @property def magnus_listen_port_range(self): return f'{self.port_start}-{self.port_end}' def init(self): - db_ids = Application.objects.filter(category=AppCategory.db).values_list('id', flat=True) + with tmp_to_root_org(): + db_ids = Application.objects.filter(category=AppCategory.db).values_list('id', flat=True) db_ids = [str(i) for i in db_ids] - mapper = dict(zip(self.all_usable_ports, list(db_ids))) + mapper = dict(zip(self.all_available_ports, list(db_ids))) self.set_mapper(mapper) def add(self, db: Application): mapper = self.get_mapper() - usable_port = self.get_next_usable_port() - if not usable_port: - return False - mapper.update({usable_port: str(db.id)}) + available_port = self.get_next_available_port() + mapper.update({available_port: str(db.id)}) self.set_mapper(mapper) return True @@ -54,43 +54,42 @@ class DBPortManager(object): for port, db_id in mapper.items(): if db_id == str(db.id): return port - logger.warning( - 'Not matched db port, db_id: {}, mapper length: {}'.format(db.id, len(mapper)) + raise JMSException( + 'Not matched db port, db id: {}, mapper length: {}'.format(db.id, len(mapper)) ) def get_db_by_port(self, port): mapper = self.get_mapper() db_id = mapper.get(port, None) - if db_id: - with tmp_to_root_org(): - db = get_object_or_none(Application, id=db_id) - if not db: - msg = 'Database not exists, database id: {}'.format(db_id) - else: - msg = '' - else: - db = None - msg = 'Port not in port-db mapper, port: {}'.format(port) - return db, msg + if not db_id: + raise JMSException('Database not in port-db mapper, port: {}'.format(port)) + with tmp_to_root_org(): + db = get_object_or_none(Application, id=db_id) + if not db: + raise JMSException('Database not exists, db id: {}'.format(db_id)) + return db - def get_next_usable_port(self): + def get_next_available_port(self): already_use_ports = self.get_already_use_ports() - usable_ports = sorted(list(set(self.all_usable_ports) - set(already_use_ports))) - if len(usable_ports) > 1: - port = usable_ports[0] - logger.debug('Get next usable port: {}'.format(port)) - return port - - msg = 'No port is usable, All usable port count: {}, Already use port count: {}'.format( - len(self.all_usable_ports), len(already_use_ports) - ) - logger.warning(msg) + available_ports = sorted(list(set(self.all_available_ports) - set(already_use_ports))) + if len(available_ports) <= 0: + raise JMSException( + 'No port is available, All available port count: {}, Already use port count: {}' + ''.format(len(self.all_available_ports), len(already_use_ports)) + ) + port = available_ports[0] + logger.debug('Get next available port: {}'.format(port)) + return port def get_already_use_ports(self): mapper = self.get_mapper() return sorted(list(mapper.keys())) def get_mapper(self): + mapper = cache.get(self.CACHE_KEY, {}) + if not mapper: + # redis 可能被清空,重新初始化一下 + self.init() return cache.get(self.CACHE_KEY, {}) def set_mapper(self, value): diff --git a/apps/terminal/utils/session_replay.py b/apps/terminal/utils/session_replay.py new file mode 100644 index 000000000..f1b061cb0 --- /dev/null +++ b/apps/terminal/utils/session_replay.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +import os +from itertools import groupby, chain + +from django.conf import settings +from django.core.files.storage import default_storage + +import jms_storage + +from common.utils import get_logger +from ..models import ReplayStorage + + +logger = get_logger(__name__) + + +def find_session_replay_local(session): + # 存在外部存储上,所有可能的路径名 + session_paths = session.get_all_possible_relative_path() + + # 存在本地存储上,所有可能的路径名 + local_paths = session.get_all_possible_local_path() + + for _local_path in chain(session_paths, local_paths): + if default_storage.exists(_local_path): + url = default_storage.url(_local_path) + return _local_path, url + return None, None + + +def download_session_replay(session): + replay_storages = ReplayStorage.objects.all() + configs = { + storage.name: storage.config + for storage in replay_storages + if not storage.type_null_or_server + } + if settings.SERVER_REPLAY_STORAGE: + configs['SERVER_REPLAY_STORAGE'] = settings.SERVER_REPLAY_STORAGE + if not configs: + msg = "Not found replay file, and not remote storage set" + return None, msg + storage = jms_storage.get_multi_object_storage(configs) + + # 获取外部存储路径名 + session_path = session.find_ok_relative_path_in_storage(storage) + if not session_path: + msg = "Not found session replay file" + return None, msg + + # 通过外部存储路径名后缀,构造真实的本地存储路径 + local_path = session.get_local_path_by_relative_path(session_path) + + # 保存到storage的路径 + target_path = os.path.join(default_storage.base_location, local_path) + target_dir = os.path.dirname(target_path) + if not os.path.isdir(target_dir): + os.makedirs(target_dir, exist_ok=True) + + ok, err = storage.download(session_path, target_path) + if not ok: + msg = "Failed download replay file: {}".format(err) + logger.error(msg) + return None, msg + url = default_storage.url(local_path) + return local_path, url + + +def get_session_replay_url(session): + local_path, url = find_session_replay_local(session) + if local_path is None: + local_path, url = download_session_replay(session) + return local_path, url + From 32b6a1f1a419e86aa0cf6c64dbcdc264f5804db5 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 22 Sep 2022 19:09:39 +0800 Subject: [PATCH 34/63] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/utils/__init__.py | 1 - apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 70 +++++++++++---------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 69 +++++++++++--------------- apps/terminal/models/endpoint.py | 1 - apps/terminal/utils/db_port_mapper.py | 20 +++++--- 7 files changed, 74 insertions(+), 95 deletions(-) diff --git a/apps/applications/utils/__init__.py b/apps/applications/utils/__init__.py index de3a7cd10..5efec40b2 100644 --- a/apps/applications/utils/__init__.py +++ b/apps/applications/utils/__init__.py @@ -2,4 +2,3 @@ # from .kubernetes_util import * -from .db_port_mapper import * diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 447ad1294..2be8bf9ac 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac618645a3defe4a3cd217dac34b95e6edd10ec30fc2273cd13f8da1c00f9fcf -size 132101 +oid sha256:529cf82721ab8594a7ee8e4e1bff1f80fb702d3bfcbb5fb6e7bfb8b897d4920b +size 132560 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 11caa1be4..9a11dd47b 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-22 17:23+0800\n" +"POT-Creation-Date: 2022-09-22 19:01+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -29,7 +29,7 @@ msgstr "Acls" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:13 terminal/models/endpoint.py:90 +#: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87 #: terminal/models/storage.py:26 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:669 @@ -38,12 +38,12 @@ msgid "Name" msgstr "名前" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:93 +#: assets/models/user.py:251 terminal/models/endpoint.py:90 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:94 +#: assets/models/user.py:251 terminal/models/endpoint.py:91 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" @@ -61,7 +61,7 @@ msgstr "アクティブ" #: assets/models/domain.py:65 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 -#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:100 +#: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97 #: terminal/models/storage.py:29 terminal/models/terminal.py:114 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: users/models/group.py:16 users/models/user.py:706 @@ -185,7 +185,7 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:53 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 msgid "IP" msgstr "IP" @@ -388,7 +388,7 @@ msgstr "クラスター" #: applications/serializers/attrs/application_category/db.py:11 #: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:14 +#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:15 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "ホスト" @@ -4951,34 +4951,34 @@ msgstr "ストレージが無効です" msgid "Command record" msgstr "コマンドレコード" -#: terminal/models/endpoint.py:16 +#: terminal/models/endpoint.py:17 msgid "HTTPS Port" msgstr "HTTPS ポート" -#: terminal/models/endpoint.py:17 terminal/models/terminal.py:107 +#: terminal/models/endpoint.py:18 terminal/models/terminal.py:107 msgid "HTTP Port" msgstr "HTTP ポート" -#: terminal/models/endpoint.py:18 terminal/models/terminal.py:106 +#: terminal/models/endpoint.py:19 terminal/models/terminal.py:106 msgid "SSH Port" msgstr "SSH ポート" -#: terminal/models/endpoint.py:19 +#: terminal/models/endpoint.py:20 msgid "RDP Port" msgstr "RDP ポート" -#: terminal/models/endpoint.py:26 terminal/models/endpoint.py:98 -#: terminal/serializers/endpoint.py:56 terminal/serializers/storage.py:38 +#: terminal/models/endpoint.py:27 terminal/models/endpoint.py:95 +#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38 #: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 #: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 msgid "Endpoint" msgstr "エンドポイント" -#: terminal/models/endpoint.py:91 +#: terminal/models/endpoint.py:88 msgid "IP group" msgstr "IP グループ" -#: terminal/models/endpoint.py:103 +#: terminal/models/endpoint.py:100 msgid "Endpoint rule" msgstr "エンドポイントルール" @@ -5159,21 +5159,21 @@ msgstr "レベル" msgid "Batch danger command alert" msgstr "一括危険コマンド警告" -#: terminal/serializers/endpoint.py:13 +#: terminal/serializers/endpoint.py:14 msgid "Magnus listen db port" msgstr "Magnus がリッスンするデータベース ポート" -#: terminal/serializers/endpoint.py:16 +#: terminal/serializers/endpoint.py:17 msgid "Magnus Listen port range" msgstr "Magnus がリッスンするポート範囲" -#: terminal/serializers/endpoint.py:18 +#: terminal/serializers/endpoint.py:19 msgid "" "The range of ports that Magnus listens on is modified in the configuration " "file" msgstr "Magnus がリッスンするポート範囲を構成ファイルで変更してください" -#: terminal/serializers/endpoint.py:50 +#: terminal/serializers/endpoint.py:51 msgid "" "If asset IP addresses under different endpoints conflict, use asset labels" msgstr "" @@ -5302,6 +5302,16 @@ msgstr "見つかりません" msgid "view" msgstr "表示" +#: terminal/utils/db_port_mapper.py:77 +msgid "" +"No ports can be used, check and modify the limit on the number of ports that " +"Magnus listens on in the configuration file." +msgstr "使用できるポートがありません。設定ファイルで Magnus がリッスンするポート数の制限を確認して変更してください. " + +#: terminal/utils/db_port_mapper.py:79 +msgid "All available port count: {}, Already use port count: {}" +msgstr "使用可能なすべてのポート数: {}、すでに使用しているポート数: {}" + #: tickets/apps.py:7 msgid "Tickets" msgstr "チケット" @@ -6996,25 +7006,3 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "コミュニティ版" - -#~ msgid "Magnus currently supports only 11g and 12c connections" -#~ msgstr "" -#~ "現在、Magnusは11gおよび12cバージョンへの接続のみをサポートしています" - -#~ msgid "MySQL Port" -#~ msgstr "MySQL ポート" - -#~ msgid "MariaDB Port" -#~ msgstr "MariaDB ポート" - -#~ msgid "PostgreSQL Port" -#~ msgstr "PostgreSQL ポート" - -#~ msgid "Redis Port" -#~ msgstr "Redis ポート" - -#~ msgid "Oracle 11g Port" -#~ msgstr "Oracle 11g ポート" - -#~ msgid "Oracle 12c Port" -#~ msgstr "Oracle 12c ポート" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 99fd77b14..a804b70c8 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc9d0fe73352de035ee77c477e143d6209a18ba533e232f0a39f8c7440379f04 -size 108810 +oid sha256:a346a8166af782cbc41eac33475b4cfac2e3713b26f84ddf6fa532742133b89d +size 109216 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index dcb0f325f..f307cbb71 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-22 17:23+0800\n" +"POT-Creation-Date: 2022-09-22 19:01+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -28,7 +28,7 @@ msgstr "访问控制" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:13 terminal/models/endpoint.py:90 +#: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87 #: terminal/models/storage.py:26 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:669 @@ -37,12 +37,12 @@ msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:93 +#: assets/models/user.py:251 terminal/models/endpoint.py:90 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:94 +#: assets/models/user.py:251 terminal/models/endpoint.py:91 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -60,7 +60,7 @@ msgstr "激活中" #: assets/models/domain.py:65 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 -#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:100 +#: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97 #: terminal/models/storage.py:29 terminal/models/terminal.py:114 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 #: users/models/group.py:16 users/models/user.py:706 @@ -183,7 +183,7 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:53 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 msgid "IP" msgstr "IP" @@ -383,7 +383,7 @@ msgstr "集群" #: applications/serializers/attrs/application_category/db.py:11 #: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:14 +#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:15 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "主机" @@ -4874,34 +4874,34 @@ msgstr "存储无效" msgid "Command record" msgstr "命令记录" -#: terminal/models/endpoint.py:16 +#: terminal/models/endpoint.py:17 msgid "HTTPS Port" msgstr "HTTPS 端口" -#: terminal/models/endpoint.py:17 terminal/models/terminal.py:107 +#: terminal/models/endpoint.py:18 terminal/models/terminal.py:107 msgid "HTTP Port" msgstr "HTTP 端口" -#: terminal/models/endpoint.py:18 terminal/models/terminal.py:106 +#: terminal/models/endpoint.py:19 terminal/models/terminal.py:106 msgid "SSH Port" msgstr "SSH 端口" -#: terminal/models/endpoint.py:19 +#: terminal/models/endpoint.py:20 msgid "RDP Port" msgstr "RDP 端口" -#: terminal/models/endpoint.py:26 terminal/models/endpoint.py:98 -#: terminal/serializers/endpoint.py:56 terminal/serializers/storage.py:38 +#: terminal/models/endpoint.py:27 terminal/models/endpoint.py:95 +#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38 #: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 #: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 msgid "Endpoint" msgstr "端点" -#: terminal/models/endpoint.py:91 +#: terminal/models/endpoint.py:88 msgid "IP group" msgstr "IP 组" -#: terminal/models/endpoint.py:103 +#: terminal/models/endpoint.py:100 msgid "Endpoint rule" msgstr "端点规则" @@ -5082,21 +5082,21 @@ msgstr "级别" msgid "Batch danger command alert" msgstr "批量危险命令告警" -#: terminal/serializers/endpoint.py:13 +#: terminal/serializers/endpoint.py:14 msgid "Magnus listen db port" msgstr "Magnus 监听的数据库端口" -#: terminal/serializers/endpoint.py:16 +#: terminal/serializers/endpoint.py:17 msgid "Magnus Listen port range" msgstr "Magnus 监听的端口范围" -#: terminal/serializers/endpoint.py:18 +#: terminal/serializers/endpoint.py:19 msgid "" "The range of ports that Magnus listens on is modified in the configuration " "file" msgstr "请在配置文件中修改 Magnus 监听的端口范围" -#: terminal/serializers/endpoint.py:50 +#: terminal/serializers/endpoint.py:51 msgid "" "If asset IP addresses under different endpoints conflict, use asset labels" msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现" @@ -5223,6 +5223,16 @@ msgstr "没有发现" msgid "view" msgstr "查看" +#: terminal/utils/db_port_mapper.py:77 +msgid "" +"No ports can be used, check and modify the limit on the number of ports that " +"Magnus listens on in the configuration file." +msgstr "没有端口可以使用,检查并修改配置文件中 Magnus 监听的端口数量限制。" + +#: terminal/utils/db_port_mapper.py:79 +msgid "All available port count: {}, Already use port count: {}" +msgstr "所有可用端口数量:{},已使用端口数量:{}" + #: tickets/apps.py:7 msgid "Tickets" msgstr "工单管理" @@ -6897,24 +6907,3 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" - -#~ msgid "Magnus currently supports only 11g and 12c connections" -#~ msgstr "目前 Magnus 只支持连接 11g、12c 版本" - -#~ msgid "MySQL Port" -#~ msgstr "MySQL 端口" - -#~ msgid "MariaDB Port" -#~ msgstr "MariaDB 端口" - -#~ msgid "PostgreSQL Port" -#~ msgstr "PostgreSQL 端口" - -#~ msgid "Redis Port" -#~ msgstr "Redis 端口" - -#~ msgid "Oracle 11g Port" -#~ msgstr "Oracle 11g 端口" - -#~ msgid "Oracle 12c Port" -#~ msgstr "Oracle 12c 端口" diff --git a/apps/terminal/models/endpoint.py b/apps/terminal/models/endpoint.py index 9d65fa271..fec3a1282 100644 --- a/apps/terminal/models/endpoint.py +++ b/apps/terminal/models/endpoint.py @@ -6,7 +6,6 @@ from ..utils import db_port_manager, DBPortManager from common.db.models import JMSModel from common.db.fields import PortField from common.utils.ip import contains_ip -from common.exceptions import JMSException db_port_manager: DBPortManager diff --git a/apps/terminal/utils/db_port_mapper.py b/apps/terminal/utils/db_port_mapper.py index 2ca84cc0e..d485aaf81 100644 --- a/apps/terminal/utils/db_port_mapper.py +++ b/apps/terminal/utils/db_port_mapper.py @@ -1,12 +1,13 @@ -from common.decorator import Singleton +from django.utils.translation import ugettext_lazy as _ from django.core.cache import cache from django.conf import settings + +from common.decorator import Singleton +from common.utils import get_logger, get_object_or_none +from common.exceptions import JMSException from applications.const import AppCategory from applications.models import Application -from common.utils import get_logger -from common.utils import get_object_or_none from orgs.utils import tmp_to_root_org -from common.exceptions import JMSException logger = get_logger(__file__) @@ -23,7 +24,7 @@ class DBPortManager(object): self.port_limit = settings.MAGNUS_DB_PORTS_LIMIT_COUNT self.port_end = self.port_start + self.port_limit # 可以使用的端口列表 - self.all_available_ports = list(range(self.port_start, self.port_end + 1)) + self.all_available_ports = list(range(self.port_start, self.port_end)) @property def magnus_listen_port_range(self): @@ -73,10 +74,13 @@ class DBPortManager(object): already_use_ports = self.get_already_use_ports() available_ports = sorted(list(set(self.all_available_ports) - set(already_use_ports))) if len(available_ports) <= 0: - raise JMSException( - 'No port is available, All available port count: {}, Already use port count: {}' - ''.format(len(self.all_available_ports), len(already_use_ports)) + msg = _('No ports can be used, check and modify the limit on the number ' + 'of ports that Magnus listens on in the configuration file.') + tips = _('All available port count: {}, Already use port count: {}').format( + len(self.all_available_ports), len(already_use_ports) ) + error = msg + tips + raise JMSException(error) port = available_ports[0] logger.debug('Get next available port: {}'.format(port)) return port From 55a10a8d1df0d71287f5a15b0bed25d7e71ef2e6 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 22 Sep 2022 19:18:38 +0800 Subject: [PATCH 35/63] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20DBPortManger?= =?UTF-8?q?=20=E5=A4=84=E7=90=86=20port=20=E7=9A=84=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/utils/db_port_mapper.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/terminal/utils/db_port_mapper.py b/apps/terminal/utils/db_port_mapper.py index d485aaf81..a2874e334 100644 --- a/apps/terminal/utils/db_port_mapper.py +++ b/apps/terminal/utils/db_port_mapper.py @@ -60,6 +60,10 @@ class DBPortManager(object): ) def get_db_by_port(self, port): + try: + port = int(port) + except Exception as e: + raise JMSException('Port type error: {}'.format(e)) mapper = self.get_mapper() db_id = mapper.get(port, None) if not db_id: From 24708a6c5e7d02d4725a6a6df1430d787dd8eb13 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 22 Sep 2022 19:22:17 +0800 Subject: [PATCH 36/63] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3=E8=8C=83=E5=9B=B4=E6=98=BE=E7=A4=BA=E4=B8=BA=2030000-?= =?UTF-8?q?30999?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/utils/db_port_mapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/utils/db_port_mapper.py b/apps/terminal/utils/db_port_mapper.py index a2874e334..ab52aa2a3 100644 --- a/apps/terminal/utils/db_port_mapper.py +++ b/apps/terminal/utils/db_port_mapper.py @@ -28,7 +28,7 @@ class DBPortManager(object): @property def magnus_listen_port_range(self): - return f'{self.port_start}-{self.port_end}' + return f'{self.port_start}-{self.port_end - 1}' def init(self): with tmp_to_root_org(): From a7cd0bc0fe44e49bd24b50b820f8f7e06b132495 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Mon, 26 Sep 2022 12:02:28 +0800 Subject: [PATCH 37/63] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E5=90=8E=E7=A9=BA=E6=A0=BC=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/crypto.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py index da921b947..994c0f087 100644 --- a/apps/common/utils/crypto.py +++ b/apps/common/utils/crypto.py @@ -82,7 +82,8 @@ class PiicoSM4EcbCrypto(BaseCrypto): return self.cipher.encrypt(self.to_16(data)) def _decrypt(self, data: bytes) -> bytes: - return self.cipher.decrypt(data) + bs = self.cipher.decrypt(data) + return bs[:bs.index(0)] class AESCrypto: From db04f6ca18538aed6dc4d8e22c00cb5ec5c15eac Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Fri, 23 Sep 2022 18:34:33 +0800 Subject: [PATCH 38/63] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=9B=BD?= =?UTF-8?q?=E5=AF=86=E9=85=8D=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/libs.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index a6cb88a0d..a1672e126 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -9,7 +9,6 @@ from .base import ( ) from ..const import CONFIG, PROJECT_DIR - REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. @@ -64,7 +63,6 @@ SWAGGER_SETTINGS = { 'DEFAULT_INFO': 'jumpserver.views.swagger.api_info', } - # Captcha settings, more see https://django-simple-captcha.readthedocs.io/en/latest/advanced.html CAPTCHA_IMAGE_SIZE = (180, 38) CAPTCHA_FOREGROUND_COLOR = '#001100' @@ -81,7 +79,6 @@ BOOTSTRAP3 = { 'required_css_class': 'required', } - # Django channels support websocket if not REDIS_USE_SSL: redis_ssl = None @@ -101,14 +98,13 @@ CHANNEL_LAYERS = { 'address': (CONFIG.REDIS_HOST, CONFIG.REDIS_PORT), 'db': CONFIG.REDIS_DB_WS, 'password': CONFIG.REDIS_PASSWORD or None, - 'ssl': redis_ssl + 'ssl': redis_ssl }], }, }, } ASGI_APPLICATION = 'jumpserver.routing.application' - # Dump all celery log to here CELERY_LOG_DIR = os.path.join(PROJECT_DIR, 'data', 'celery') @@ -149,3 +145,8 @@ REDIS_PORT = CONFIG.REDIS_PORT REDIS_PASSWORD = CONFIG.REDIS_PASSWORD DJANGO_REDIS_SCAN_ITERSIZE = 1000 + +# GM DEVICE +GMSSL_ENABLED = CONFIG.GMSSL_ENABLED +PIICO_DEVICE_ENABLE = CONFIG.PIICO_DEVICE_ENABLE +PIICO_DRIVER_PATH = CONFIG.PIICO_DRIVER_PATH From 308d87d021bc5b0bb2e909d610b9f5f9c1d7a929 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Mon, 26 Sep 2022 11:53:25 +0800 Subject: [PATCH 39/63] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0PIICO=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E9=85=8D=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/libs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index a1672e126..10f69cffd 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -147,6 +147,5 @@ REDIS_PASSWORD = CONFIG.REDIS_PASSWORD DJANGO_REDIS_SCAN_ITERSIZE = 1000 # GM DEVICE -GMSSL_ENABLED = CONFIG.GMSSL_ENABLED PIICO_DEVICE_ENABLE = CONFIG.PIICO_DEVICE_ENABLE PIICO_DRIVER_PATH = CONFIG.PIICO_DRIVER_PATH From 1d3135d2d70b6ce9eb8ac52ceef0d28cfa9960c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Sat, 24 Sep 2022 17:25:10 +0800 Subject: [PATCH 40/63] =?UTF-8?q?perf:=20flower=20=E5=BC=80=E5=90=AF?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/management/commands/services/services/flower.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/common/management/commands/services/services/flower.py b/apps/common/management/commands/services/services/flower.py index 402e9f37d..38dc72be8 100644 --- a/apps/common/management/commands/services/services/flower.py +++ b/apps/common/management/commands/services/services/flower.py @@ -19,11 +19,13 @@ class FlowerService(BaseService): 'celery', '-A', 'ops', 'flower', - '-l', 'INFO', + '-logging=info', '--url_prefix=/core/flower', '--auto_refresh=False', '--max_tasks=1000', - '--tasks_columns=uuid,name,args,state,received,started,runtime,worker' + '--persistent=True', + '-db=/opt/jumpserver/data/flower.db', + '--state_save_interval=600000' ] return cmd From 1bc6e50b06af33aa02f34915a76a57a9cfe5cd5c Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Mon, 26 Sep 2022 14:52:13 +0800 Subject: [PATCH 41/63] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=8E=BB?= =?UTF-8?q?=E9=99=A4=E7=BB=93=E5=B0=BE=E7=A9=BA=E5=AD=97=E8=8A=82=E7=9A=84?= =?UTF-8?q?=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py index 994c0f087..1407c7d7d 100644 --- a/apps/common/utils/crypto.py +++ b/apps/common/utils/crypto.py @@ -83,7 +83,7 @@ class PiicoSM4EcbCrypto(BaseCrypto): def _decrypt(self, data: bytes) -> bytes: bs = self.cipher.decrypt(data) - return bs[:bs.index(0)] + return bs.rstrip(b'\0') class AESCrypto: From f6cc7046a246c2af029875df6d8e4fbaeeec0eb2 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Mon, 26 Sep 2022 20:34:59 +0800 Subject: [PATCH 42/63] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=A9=BA?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=8A=A0=E5=AF=86=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/sdk/gm/piico/cipher.py | 2 +- apps/common/utils/crypto.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/common/sdk/gm/piico/cipher.py b/apps/common/sdk/gm/piico/cipher.py index 7f3a0b7b0..250e19d9b 100644 --- a/apps/common/sdk/gm/piico/cipher.py +++ b/apps/common/sdk/gm/piico/cipher.py @@ -33,7 +33,7 @@ class EBCCipher: def __padding(val): # padding val = bytes(val) - while len(val) % 16 != 0: + while len(val) == 0 or len(val) % 16 != 0: val += b'\0' return val diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py index 1407c7d7d..b39dedad8 100644 --- a/apps/common/utils/crypto.py +++ b/apps/common/utils/crypto.py @@ -233,6 +233,8 @@ class Crypto: return self.cryptos[0] def encrypt(self, text): + if text is None: + return text return self.encryptor.encrypt(text) def decrypt(self, text): From bb25bf762141a5945e007b71b9a17b8816a6d997 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 27 Sep 2022 15:16:26 +0800 Subject: [PATCH 43/63] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=A7=A3?= =?UTF-8?q?=E5=AF=86=E5=BC=82=E5=B8=B8=E6=8A=9B=E5=87=BA=E8=8C=83=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py index b39dedad8..4ae6814c5 100644 --- a/apps/common/utils/crypto.py +++ b/apps/common/utils/crypto.py @@ -244,7 +244,7 @@ class Crypto: if origin_text: # 有时不同算法解密不报错,但是返回空字符串 return origin_text - except (TypeError, ValueError, UnicodeDecodeError, IndexError): + except Exception: continue From 218f917f69b486079cfd3449eaeb460bad3002d2 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 27 Sep 2022 15:44:38 +0800 Subject: [PATCH 44/63] =?UTF-8?q?fix:=20=E9=94=81=E5=AE=9A=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E5=8C=85=E7=89=88=E6=9C=AC=20pyOpenSSL=3D=3D22.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 3850821d4..b8fb4d09a 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -133,6 +133,7 @@ pymssql==2.1.5 django-mysql==3.9.0 django-redis==5.2.0 python-redis-lock==3.7.0 +pyOpenSSL==22.0.0 redis==4.3.3 pymongo==4.2.0 # Debug From 78de2a24038953a098964f58af1c11e5b2553631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Wed, 28 Sep 2022 10:36:47 +0800 Subject: [PATCH 45/63] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Dockerfile.l?= =?UTF-8?q?oong64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 7 ++-- Dockerfile.loong64 | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 Dockerfile.loong64 diff --git a/Dockerfile b/Dockerfile index cd53b4a58..804adff2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,17 +34,18 @@ ARG TOOLS=" \ wget" RUN sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/sources.list \ + && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ && apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${TOOLS} \ && localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 \ - && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && mkdir -p /root/.ssh/ \ && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ + && apt-get clean all \ && rm -rf /var/lib/apt/lists/* ARG TARGETARCH @@ -70,8 +71,9 @@ ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR # 因为以 jms 或者 jumpserver 开头的 mirror 上可能没有 RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_MIRROR} \ + && pip config set global.index-url ${PIP_MIRROR} \ && pip install --no-cache-dir $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ - && pip install --no-cache-dir -r requirements/requirements.txt -i ${PIP_MIRROR} \ + && pip install --no-cache-dir -r requirements/requirements.txt \ && rm -rf ~/.cache/pip ARG VERSION @@ -93,4 +95,3 @@ ENV LANG=zh_CN.UTF-8 EXPOSE 8070 EXPOSE 8080 ENTRYPOINT ["./entrypoint.sh"] - diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 new file mode 100644 index 000000000..d039ad894 --- /dev/null +++ b/Dockerfile.loong64 @@ -0,0 +1,89 @@ +FROM python:3.8-slim +ARG TARGETARCH +MAINTAINER JumpServer Team + +ARG BUILD_DEPENDENCIES=" \ + g++ \ + make \ + pkg-config" + +ARG DEPENDENCIES=" \ + default-libmysqlclient-dev \ + freetds-dev \ + libpq-dev \ + libffi-dev \ + libjpeg62-turbo-dev \ + libldap2-dev \ + libsasl2-dev \ + libxml2-dev \ + libxmlsec1-dev \ + libxmlsec1-openssl \ + libaio-dev \ + openssh-client \ + sshpass" + +ARG TOOLS=" \ + ca-certificates \ + curl \ + default-mysql-client \ + gettext \ + iputils-ping \ + locales \ + netcat \ + redis-server \ + telnet \ + vim \ + wget" + +RUN set -ex \ + && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && apt-get update \ + && apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \ + && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ + && apt-get -y install --no-install-recommends ${TOOLS} \ + && localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 \ + && mkdir -p /root/.ssh/ \ + && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ + && sed -i "s@# alias l@alias l@g" ~/.bashrc \ + && echo "set mouse-=a" > ~/.vimrc \ + && echo "no" | dpkg-reconfigure dash \ + && apt-get clean all \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /tmp/build +COPY ./requirements ./requirements + +ARG PIP_MIRROR=https://pypi.douban.com/simple +ENV PIP_MIRROR=$PIP_MIRROR +ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple +ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR +# 因为以 jms 或者 jumpserver 开头的 mirror 上可能没有 +RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_MIRROR} \ + && pip config set global.index-url ${PIP_MIRROR} \ + && pip install --no-cache-dir https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.1-cp38-cp38-linux_loongarch64.whl \ + && pip install --no-cache-dir https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp38-cp38-linux_loongarch64.whl \ + && pip install --no-cache-dir $(grep 'PyNaCl' requirements/requirements.txt) \ + && GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install --no-cache-dir grpcio \ + && pip install --no-cache-dir $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ + && pip install --no-cache-dir -r requirements/requirements.txt \ + && rm -rf ~/.cache/pip + +ARG VERSION +ENV VERSION=$VERSION + +ADD . . +RUN cd utils \ + && bash -ixeu build.sh \ + && mv ../release/jumpserver /opt/jumpserver \ + && rm -rf /tmp/build \ + && echo > /opt/jumpserver/config.yml + +WORKDIR /opt/jumpserver +VOLUME /opt/jumpserver/data +VOLUME /opt/jumpserver/logs + +ENV LANG=zh_CN.UTF-8 + +EXPOSE 8070 +EXPOSE 8080 +ENTRYPOINT ["./entrypoint.sh"] From 1c8ad40565deddd9e2162b4e741deed39fb0ef50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Wed, 28 Sep 2022 10:57:00 +0800 Subject: [PATCH 46/63] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E5=8C=85=E7=94=9F=E6=88=90=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 804adff2d..e92628221 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,12 +39,12 @@ RUN sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/source && apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${TOOLS} \ - && localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 \ && mkdir -p /root/.ssh/ \ && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ + && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ && apt-get clean all \ && rm -rf /var/lib/apt/lists/* From df2858470a5281be1671d62054e435879613e01a Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 28 Sep 2022 16:10:41 +0800 Subject: [PATCH 47/63] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=AD=98=E5=82=A8es=E5=A4=B1=E6=95=88=E6=97=B6,=20?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E3=80=81=E5=91=BD=E4=BB=A4=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=88=9B=E5=BB=BA=E5=92=8C=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/backends/command/es.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index 20602769c..2aa9be1af 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -184,9 +184,14 @@ class CommandStore(object): return Command.from_multi_dict(source_data) def count(self, **query): - body = self.get_query_body(**query) - data = self.es.count(index=self.query_index, doc_type=self.doc_type, body=body) - return data["count"] + try: + body = self.get_query_body(**query) + data = self.es.count(index=self.query_index, doc_type=self.doc_type, body=body) + count = data["count"] + except Exception as e: + logger.error('ES count error: {}'.format(e)) + count = 0 + return count def __getattr__(self, item): return getattr(self.es, item) From 65269db849825365fbf6644b5a7040e106988dba Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 28 Sep 2022 16:21:50 +0800 Subject: [PATCH 48/63] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Des=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E5=A4=B1=E6=95=88=E6=97=B6=EF=BC=8C=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E5=88=97=E8=A1=A8=E9=A1=B5=E9=9D=A2=E6=8A=A5?= =?UTF-8?q?=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/backends/command/es.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index 2aa9be1af..4d830b1b2 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -169,6 +169,14 @@ class CommandStore(object): return self.es.index(index=self.index, doc_type=self.doc_type, body=data) def filter(self, query: dict, from_=None, size=None, sort=None): + try: + data = self._filter(query, from_, size, sort) + except Exception as e: + logger.error('ES filter error: {}'.format(e)) + data = [] + return data + + def _filter(self, query: dict, from_=None, size=None, sort=None): body = self.get_query_body(**query) data = self.es.search( From 28d19fd91fedbd3467299f449af9b976c86cd70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 29 Sep 2022 14:10:26 +0800 Subject: [PATCH 49/63] =?UTF-8?q?perf:=20=E6=9E=84=E5=BB=BA=E6=97=B6?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 70 ++++++++++++++++++++++++---------------------- Dockerfile.loong64 | 28 ++++++++++--------- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/Dockerfile b/Dockerfile index e92628221..36f1bd7a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,42 @@ FROM python:3.8-slim +ARG TARGETARCH MAINTAINER JumpServer Team ARG BUILD_DEPENDENCIES=" \ - g++ \ - make \ - pkg-config" + g++ \ + make \ + pkg-config" ARG DEPENDENCIES=" \ - default-libmysqlclient-dev \ - freetds-dev \ - libpq-dev \ - libffi-dev \ - libldap2-dev \ - libsasl2-dev \ - libxml2-dev \ - libxmlsec1-dev \ - libxmlsec1-openssl \ - libaio-dev \ - openssh-client \ - sshpass" + default-libmysqlclient-dev \ + freetds-dev \ + libpq-dev \ + libffi-dev \ + libldap2-dev \ + libsasl2-dev \ + libxml2-dev \ + libxmlsec1-dev \ + libxmlsec1-openssl \ + libaio-dev \ + openssh-client \ + sshpass" ARG TOOLS=" \ - curl \ - default-mysql-client \ - iproute2 \ - iputils-ping \ - locales \ - procps \ - redis-tools \ - telnet \ - vim \ - unzip \ - wget" + ca-certificates \ + curl \ + default-mysql-client \ + iputils-ping \ + locales \ + procps \ + redis-tools \ + telnet \ + vim \ + unzip \ + wget" -RUN sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/sources.list \ +RUN --mount=type=cache,target=/var/cache/apt \ + sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/sources.list \ + && rm -f /etc/apt/apt.conf.d/docker-clean \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ && apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \ @@ -48,7 +51,6 @@ RUN sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/source && apt-get clean all \ && rm -rf /var/lib/apt/lists/* -ARG TARGETARCH ARG ORACLE_LIB_MAJOR=19 ARG ORACLE_LIB_MINOR=10 ENV ORACLE_FILE="instantclient-basiclite-linux.${TARGETARCH:-amd64}-${ORACLE_LIB_MAJOR}.${ORACLE_LIB_MINOR}.0.0.0dbru.zip" @@ -69,12 +71,14 @@ ARG PIP_MIRROR=https://pypi.douban.com/simple ENV PIP_MIRROR=$PIP_MIRROR ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR -# 因为以 jms 或者 jumpserver 开头的 mirror 上可能没有 -RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_MIRROR} \ + +RUN --mount=type=cache,target=/root/.cache/pip \ + set -ex \ && pip config set global.index-url ${PIP_MIRROR} \ - && pip install --no-cache-dir $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ - && pip install --no-cache-dir -r requirements/requirements.txt \ - && rm -rf ~/.cache/pip + && pip install --upgrade pip \ + && pip install --upgrade setuptools wheel \ + && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ + && pip install -r requirements/requirements.txt ARG VERSION ENV VERSION=$VERSION diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index d039ad894..2bc5e397c 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -12,7 +12,6 @@ ARG DEPENDENCIES=" \ freetds-dev \ libpq-dev \ libffi-dev \ - libjpeg62-turbo-dev \ libldap2-dev \ libsasl2-dev \ libxml2-dev \ @@ -26,27 +25,28 @@ ARG TOOLS=" \ ca-certificates \ curl \ default-mysql-client \ - gettext \ iputils-ping \ locales \ netcat \ redis-server \ telnet \ vim \ + unzip \ wget" -RUN set -ex \ +RUN --mount=type=cache,target=/var/cache/apt \ + set -ex \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ && apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${TOOLS} \ - && localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 \ && mkdir -p /root/.ssh/ \ && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ + && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ && apt-get clean all \ && rm -rf /var/lib/apt/lists/* @@ -57,16 +57,18 @@ ARG PIP_MIRROR=https://pypi.douban.com/simple ENV PIP_MIRROR=$PIP_MIRROR ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR -# 因为以 jms 或者 jumpserver 开头的 mirror 上可能没有 -RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_MIRROR} \ + +RUN --mount=type=cache,target=/root/.cache/pip \ + set -ex \ && pip config set global.index-url ${PIP_MIRROR} \ - && pip install --no-cache-dir https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.1-cp38-cp38-linux_loongarch64.whl \ - && pip install --no-cache-dir https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp38-cp38-linux_loongarch64.whl \ - && pip install --no-cache-dir $(grep 'PyNaCl' requirements/requirements.txt) \ - && GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install --no-cache-dir grpcio \ - && pip install --no-cache-dir $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ - && pip install --no-cache-dir -r requirements/requirements.txt \ - && rm -rf ~/.cache/pip + && pip install --upgrade pip \ + && pip install --upgrade setuptools wheel \ + && pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.1-cp38-cp38-linux_loongarch64.whl \ + && pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp38-cp38-linux_loongarch64.whl \ + && pip install $(grep 'PyNaCl' requirements/requirements.txt) \ + && GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \ + && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ + && pip install -r requirements/requirements.txt ARG VERSION ENV VERSION=$VERSION From 41732d7a7bf8a16fa153014db893331f93bd5ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 29 Sep 2022 14:32:02 +0800 Subject: [PATCH 50/63] =?UTF-8?q?perf:=20=E4=B8=8D=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E6=B8=85=E7=90=86=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 +--- Dockerfile.loong64 | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 36f1bd7a9..f81008ddc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,9 +47,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ - && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ - && apt-get clean all \ - && rm -rf /var/lib/apt/lists/* + && echo "zh_CN.UTF-8" | dpkg-reconfigure locales ARG ORACLE_LIB_MAJOR=19 ARG ORACLE_LIB_MINOR=10 diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 2bc5e397c..2cf3af9d9 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -46,9 +46,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ - && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ - && apt-get clean all \ - && rm -rf /var/lib/apt/lists/* + && echo "zh_CN.UTF-8" | dpkg-reconfigure locales WORKDIR /tmp/build COPY ./requirements ./requirements From 2f6c9f8260f3472b60b5fed848eb92808ef32e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 29 Sep 2022 15:13:50 +0800 Subject: [PATCH 51/63] =?UTF-8?q?perf:=20=E6=B8=85=E7=90=86=E4=B8=8D?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E7=9A=84=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 ++- Dockerfile.loong64 | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f81008ddc..aeecf7885 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,7 +47,8 @@ RUN --mount=type=cache,target=/var/cache/apt \ && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ - && echo "zh_CN.UTF-8" | dpkg-reconfigure locales + && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ + && rm -rf /var/lib/apt/lists/* ARG ORACLE_LIB_MAJOR=19 ARG ORACLE_LIB_MINOR=10 diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 2cf3af9d9..013a10476 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -46,7 +46,8 @@ RUN --mount=type=cache,target=/var/cache/apt \ && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ - && echo "zh_CN.UTF-8" | dpkg-reconfigure locales + && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ + && rm -rf /var/lib/apt/lists/* WORKDIR /tmp/build COPY ./requirements ./requirements From 7ac00d5fdf31b24a2ca5bf2fe31e4176b39e1bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 29 Sep 2022 15:35:05 +0800 Subject: [PATCH 52/63] =?UTF-8?q?perf:=20=E5=A4=9A=E6=AD=A5=E9=AA=A4?= =?UTF-8?q?=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 22 +++++++++++++--------- Dockerfile.loong64 | 22 +++++++++++++--------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Dockerfile b/Dockerfile index aeecf7885..b10cec761 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,13 @@ +FROM python:3.8-slim as stage-build +ARG TARGETARCH + +ARG VERSION +ENV VERSION=$VERSION + +WORKDIR /opt/jumpserver +ADD . . +RUN cd utils && bash -ixeu build.sh + FROM python:3.8-slim ARG TARGETARCH MAINTAINER JumpServer Team @@ -79,15 +89,9 @@ RUN --mount=type=cache,target=/root/.cache/pip \ && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ && pip install -r requirements/requirements.txt -ARG VERSION -ENV VERSION=$VERSION - -ADD . . -RUN cd utils \ - && bash -ixeu build.sh \ - && mv ../release/jumpserver /opt/jumpserver \ - && rm -rf /tmp/build \ - && echo > /opt/jumpserver/config.yml +COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver +RUN echo > /opt/jumpserver/config.yml \ + && rm -rf /tmp/build WORKDIR /opt/jumpserver VOLUME /opt/jumpserver/data diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 013a10476..e8f64bc95 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -1,3 +1,13 @@ +FROM python:3.8-slim as stage-build +ARG TARGETARCH + +ARG VERSION +ENV VERSION=$VERSION + +WORKDIR /opt/jumpserver +ADD . . +RUN cd utils && bash -ixeu build.sh + FROM python:3.8-slim ARG TARGETARCH MAINTAINER JumpServer Team @@ -69,15 +79,9 @@ RUN --mount=type=cache,target=/root/.cache/pip \ && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ && pip install -r requirements/requirements.txt -ARG VERSION -ENV VERSION=$VERSION - -ADD . . -RUN cd utils \ - && bash -ixeu build.sh \ - && mv ../release/jumpserver /opt/jumpserver \ - && rm -rf /tmp/build \ - && echo > /opt/jumpserver/config.yml +COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver +RUN echo > /opt/jumpserver/config.yml \ + && rm -rf /tmp/build WORKDIR /opt/jumpserver VOLUME /opt/jumpserver/data From d573ade5251daaa19a54c68f404c27bebb57a7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 29 Sep 2022 17:25:52 +0800 Subject: [PATCH 53/63] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=BC=93=E5=AD=98=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile.loong64 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index b10cec761..019383a1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ ARG TOOLS=" \ unzip \ wget" -RUN --mount=type=cache,target=/var/cache/apt \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/sources.list \ && rm -f /etc/apt/apt.conf.d/docker-clean \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index e8f64bc95..308b6b658 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -44,7 +44,7 @@ ARG TOOLS=" \ unzip \ wget" -RUN --mount=type=cache,target=/var/cache/apt \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ set -ex \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ From 15363a7f728939a59f3529056e39a85127ba0dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 29 Sep 2022 17:39:04 +0800 Subject: [PATCH 54/63] =?UTF-8?q?perf:=20=E6=9B=B4=E6=96=B0=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile.loong64 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 019383a1f..d78bd2e5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ ARG TOOLS=" \ unzip \ wget" -RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/sources.list \ && rm -f /etc/apt/apt.conf.d/docker-clean \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 308b6b658..f27bd562d 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -44,7 +44,7 @@ ARG TOOLS=" \ unzip \ wget" -RUN --mount=type=cache,target=/var/cache/apt,sharing=private \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ set -ex \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ From d1acab3aa971b4459dd58dfcc14c597f75f6966f Mon Sep 17 00:00:00 2001 From: evlic Date: Fri, 30 Sep 2022 17:14:50 +0800 Subject: [PATCH 55/63] docs: fix README ambiguity --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59c4e55cf..08bf36443 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 - [沐瞳游戏:通过JumpServer管控多项目分布式资产](https://blog.fit2cloud.com/?p=3213) - [携程:JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851) - [大智慧:JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882) -- [小红书:的JumpServer堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516) +- [小红书:JumpServer 堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516) - [中手游:JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732) - [中通快递:JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708) - [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687) From df31f47c689470eb26c616f87a6efffba0777a77 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Sun, 9 Oct 2022 18:59:39 +0800 Subject: [PATCH 56/63] =?UTF-8?q?feat:=20=E5=91=BD=E4=BB=A4=E8=BF=87?= =?UTF-8?q?=E6=BB=A4=E5=99=A8=E6=94=AF=E6=8C=81=E5=85=B3=E8=81=94=E8=8A=82?= =?UTF-8?q?=E7=82=B9;=20=E6=B7=BB=E5=8A=A0=E7=AB=AF=E7=82=B9=E8=A7=84?= =?UTF-8?q?=E5=88=99=E8=BF=81=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/system_user.py | 5 ++- .../migrations/0092_commandfilter_nodes.py | 18 +++++++++ apps/assets/models/asset.py | 2 +- apps/assets/models/cmd_filter.py | 23 ++++++++++-- apps/assets/serializers/cmd_filter.py | 2 +- .../migrations/0053_auto_20221009_1755.py | 37 +++++++++++++++++++ 6 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 apps/assets/migrations/0092_commandfilter_nodes.py create mode 100644 apps/terminal/migrations/0053_auto_20221009_1755.py diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index f95303a5e..64457db75 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -208,7 +208,8 @@ class SystemUserTaskApi(generics.CreateAPIView): class SystemUserCommandFilterRuleListApi(generics.ListAPIView): rbac_perms = { - 'list': 'assets.view_commandfilterule' + 'list': 'assets.view_commandfilterule', + 'GET': 'assets.view_commandfilterule', } def get_serializer_class(self): @@ -223,12 +224,14 @@ class SystemUserCommandFilterRuleListApi(generics.ListAPIView): if not system_user: system_user_id = self.request.query_params.get('system_user_id') asset_id = self.request.query_params.get('asset_id') + node_id = self.request.query_params.get('node_id') application_id = self.request.query_params.get('application_id') rules = CommandFilterRule.get_queryset( user_id=user_id, user_group_id=user_group_id, system_user_id=system_user_id, asset_id=asset_id, + node_id=node_id, application_id=application_id ) return rules diff --git a/apps/assets/migrations/0092_commandfilter_nodes.py b/apps/assets/migrations/0092_commandfilter_nodes.py new file mode 100644 index 000000000..b3c1916e3 --- /dev/null +++ b/apps/assets/migrations/0092_commandfilter_nodes.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.15 on 2022-10-09 09:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0091_auto_20220629_1826'), + ] + + operations = [ + migrations.AddField( + model_name='commandfilter', + name='nodes', + field=models.ManyToManyField(blank=True, related_name='cmd_filters', to='assets.Node', verbose_name='Nodes'), + ), + ] diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 7dde0862f..95ba28d21 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -116,7 +116,7 @@ class NodesRelationMixin: nodes = [] for node in self.get_nodes(): _nodes = node.get_ancestors(with_self=True) - nodes.append(_nodes) + nodes.extend(list(_nodes)) if flat: nodes = list(reduce(lambda x, y: set(x) | set(y), nodes)) return nodes diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index c7fa33aae..2daf308a3 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _ from users.models import User, UserGroup from applications.models import Application -from ..models import SystemUser, Asset +from ..models import SystemUser, Asset, Node from common.utils import lazyproperty, get_logger, get_object_or_none from orgs.mixins.models import OrgModelMixin @@ -33,6 +33,10 @@ class CommandFilter(OrgModelMixin): 'users.UserGroup', related_name='cmd_filters', blank=True, verbose_name=_("User group"), ) + nodes = models.ManyToManyField( + 'assets.Node', related_name='cmd_filters', blank=True, + verbose_name=_("Nodes") + ) assets = models.ManyToManyField( 'assets.Asset', related_name='cmd_filters', blank=True, verbose_name=_("Asset") @@ -189,7 +193,8 @@ class CommandFilterRule(OrgModelMixin): @classmethod def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None, - asset_id=None, application_id=None, org_id=None): + asset_id=None, node_id=None, application_id=None, org_id=None): + # user & user_group user_groups = [] user = get_object_or_none(User, pk=user_id) if user: @@ -198,8 +203,18 @@ class CommandFilterRule(OrgModelMixin): if user_group: org_id = user_group.org_id user_groups.append(user_group) - system_user = get_object_or_none(SystemUser, pk=system_user_id) + + # asset & node + nodes = [] asset = get_object_or_none(Asset, pk=asset_id) + if asset: + nodes.extend(asset.get_all_nodes()) + node = get_object_or_none(Node, pk=node_id) + if node: + org_id = node.org_id + nodes.append(node) + + system_user = get_object_or_none(SystemUser, pk=system_user_id) application = get_object_or_none(Application, pk=application_id) q = Q() if user: @@ -212,6 +227,8 @@ class CommandFilterRule(OrgModelMixin): if asset: org_id = asset.org_id q |= Q(assets=asset) + if nodes: + q |= Q(nodes__in=set(nodes)) if application: org_id = application.org_id q |= Q(applications=application) diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index 9a33dd6fa..9bd5d38cc 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -21,7 +21,7 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer): 'comment', 'created_by', ] fields_fk = ['rules'] - fields_m2m = ['users', 'user_groups', 'system_users', 'assets', 'applications'] + fields_m2m = ['users', 'user_groups', 'system_users', 'nodes', 'assets', 'applications'] fields = fields_small + fields_fk + fields_m2m extra_kwargs = { 'rules': {'read_only': True}, diff --git a/apps/terminal/migrations/0053_auto_20221009_1755.py b/apps/terminal/migrations/0053_auto_20221009_1755.py new file mode 100644 index 000000000..0a6e30993 --- /dev/null +++ b/apps/terminal/migrations/0053_auto_20221009_1755.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.15 on 2022-10-09 09:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0052_auto_20220713_1417'), + ] + + operations = [ + migrations.RemoveField( + model_name='endpoint', + name='mariadb_port', + ), + migrations.RemoveField( + model_name='endpoint', + name='mysql_port', + ), + migrations.RemoveField( + model_name='endpoint', + name='oracle_11g_port', + ), + migrations.RemoveField( + model_name='endpoint', + name='oracle_12c_port', + ), + migrations.RemoveField( + model_name='endpoint', + name='postgresql_port', + ), + migrations.RemoveField( + model_name='endpoint', + name='redis_port', + ), + ] From fae5d07df62df36e1a402cea794a0a4add9a4504 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Sun, 9 Oct 2022 19:52:00 +0800 Subject: [PATCH 57/63] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=BF=87=E6=BB=A4=E5=99=A8=E6=94=AF=E6=8C=81=E5=85=B3?= =?UTF-8?q?=E8=81=94=E8=8A=82=E7=82=B9;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/cmd_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 2daf308a3..7f44094e0 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -212,7 +212,7 @@ class CommandFilterRule(OrgModelMixin): node = get_object_or_none(Node, pk=node_id) if node: org_id = node.org_id - nodes.append(node) + nodes.extend(list(node.get_ancestors(with_self=True))) system_user = get_object_or_none(SystemUser, pk=system_user_id) application = get_object_or_none(Application, pk=application_id) From a487d30001d3ffbba0c026598d8f650cad1acff8 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Sun, 9 Oct 2022 20:12:55 +0800 Subject: [PATCH 58/63] =?UTF-8?q?perf:=20=E5=AF=86=E7=A0=81=E9=A6=96?= =?UTF-8?q?=E4=BD=8D=E4=B8=8D=E5=8C=85=E5=90=AB=E7=89=B9=E6=AE=8A=E5=AD=97?= =?UTF-8?q?=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/utils/random.py b/apps/common/utils/random.py index db3b39c05..fae1d80bd 100644 --- a/apps/common/utils/random.py +++ b/apps/common/utils/random.py @@ -35,7 +35,7 @@ def random_string(length, lower=True, upper=True, digit=True, special_char=False if special_char: spc = random.choice(string_punctuation) - i = random.choice(range(len(password))) + i = random.choice(range(1, len(password))) password[i] = spc password = ''.join(password) From ca17faaf0171cedc2222bcd9c78458d306721e93 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 10 Oct 2022 14:01:19 +0800 Subject: [PATCH 59/63] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E5=B7=A5=E5=8D=95=E6=97=A0=E5=A4=87=E6=B3=A8=E4=BF=A1?= =?UTF-8?q?=E6=81=AFbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/tickets/serializers/ticket/apply_application.py | 2 +- apps/tickets/serializers/ticket/apply_asset.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/tickets/serializers/ticket/apply_application.py b/apps/tickets/serializers/ticket/apply_application.py index 12b3f230d..740eda645 100644 --- a/apps/tickets/serializers/ticket/apply_application.py +++ b/apps/tickets/serializers/ticket/apply_application.py @@ -19,7 +19,7 @@ class ApplyApplicationSerializer(BaseApplyAssetApplicationSerializer, TicketAppl class Meta: model = ApplyApplicationTicket writeable_fields = [ - 'id', 'title', 'type', 'apply_category', + 'id', 'title', 'type', 'apply_category', 'comment', 'apply_type', 'apply_applications', 'apply_system_users', 'apply_actions', 'apply_date_start', 'apply_date_expired', 'org_id' ] diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 93a4026c1..b1de8cf53 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -23,7 +23,7 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria model = ApplyAssetTicket writeable_fields = [ 'id', 'title', 'type', 'apply_nodes', 'apply_assets', - 'apply_system_users', 'apply_actions', + 'apply_system_users', 'apply_actions', 'comment', 'apply_date_start', 'apply_date_expired', 'org_id' ] fields = TicketApplySerializer.Meta.fields + \ From df99067ee3fc6e8ea327009838574a971529550a Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 11 Oct 2022 16:38:41 +0800 Subject: [PATCH 60/63] =?UTF-8?q?perf:=20=E5=88=A0=E9=99=A4=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E8=AE=A2=E9=98=85=E6=97=B6=20websocket=20=E9=87=8D?= =?UTF-8?q?=E8=BF=9E=E7=9A=84=20redis=20=E6=96=AD=E5=BC=80=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/connection.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/common/utils/connection.py b/apps/common/utils/connection.py index 381f29443..9d1e7fd9d 100644 --- a/apps/common/utils/connection.py +++ b/apps/common/utils/connection.py @@ -53,7 +53,9 @@ class Subscription: error(msg, item) logger.error('Subscribe handler handle msg error: {}'.format(e)) except Exception as e: - logger.error('Consume msg error: {}'.format(e)) + # 正常的 websocket 断开时, redis 会断开连接,避免日志太多 + # logger.error('Consume msg error: {}'.format(e)) + pass try: complete() From 188a2846ede2df7d1c56c469b504b5ec22d2dc6d Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 11 Oct 2022 18:37:49 +0800 Subject: [PATCH 61/63] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20OAuth2=20?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=9C=AC=E5=9C=B0=E8=A2=AB=E7=A6=81=E7=94=A8?= =?UTF-8?q?=E5=90=8E,=E9=A1=B5=E9=9D=A2=E4=B8=80=E7=9B=B4=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E7=9A=84=E9=97=AE=E9=A2=98.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/oauth2/views.py | 4 +++- apps/jumpserver/settings/auth.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/authentication/backends/oauth2/views.py b/apps/authentication/backends/oauth2/views.py index d3f4865a2..c0fe06759 100644 --- a/apps/authentication/backends/oauth2/views.py +++ b/apps/authentication/backends/oauth2/views.py @@ -55,7 +55,9 @@ class OAuth2AuthCallbackView(View): ) logger.debug(log_prompt.format('Redirect')) - return HttpResponseRedirect(settings.AUTH_OAUTH2_AUTHENTICATION_FAILURE_REDIRECT_URI) + # OAuth2 服务端认证成功, 但是用户被禁用了, 这时候需要调用服务端的logout + redirect_url = settings.AUTH_OAUTH2_PROVIDER_END_SESSION_ENDPOINT + return HttpResponseRedirect(redirect_url) class OAuth2EndSessionView(View): diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 462aefc57..2cf47e9fe 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -161,6 +161,8 @@ AUTH_OAUTH2_CLIENT_SECRET = CONFIG.AUTH_OAUTH2_CLIENT_SECRET AUTH_OAUTH2_CLIENT_ID = CONFIG.AUTH_OAUTH2_CLIENT_ID AUTH_OAUTH2_SCOPE = CONFIG.AUTH_OAUTH2_SCOPE AUTH_OAUTH2_USER_ATTR_MAP = CONFIG.AUTH_OAUTH2_USER_ATTR_MAP +AUTH_OAUTH2_LOGOUT_COMPLETELY = CONFIG.AUTH_OAUTH2_LOGOUT_COMPLETELY +AUTH_OAUTH2_PROVIDER_END_SESSION_ENDPOINT = CONFIG.AUTH_OAUTH2_PROVIDER_END_SESSION_ENDPOINT AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:oauth2:login-callback' AUTH_OAUTH2_AUTHENTICATION_REDIRECT_URI = '/' AUTH_OAUTH2_AUTHENTICATION_FAILURE_REDIRECT_URI = '/' From c610ec797f2916357cfcf5fe399914af62bbd669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Thu, 13 Oct 2022 13:29:41 +0800 Subject: [PATCH 62/63] docs: Change README description Well --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 08bf36443..f4d9bcc16 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,7 @@ JumpServer 使用 Python 开发,配备了业界领先的 Web Terminal 方案 JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。 -改变世界,从一点点开始 ... -> 如需进一步了解 JumpServer 开源项目,推荐阅读 [JumpServer 的初心和使命](https://mp.weixin.qq.com/s/S6q_2rP_9MwaVwyqLQnXzA) ### 特色优势 From 3bd7410ab8ee8a00973dc18177fd6503a16c661f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chuailei000=E2=80=9D?= <2280131253@qq.com> Date: Thu, 13 Oct 2022 14:29:42 +0800 Subject: [PATCH 63/63] perf: update jquery --- apps/ops/templates/ops/celery_task_log.html | 2 +- apps/static/js/jquery-3.1.1.min.js | 4 ---- apps/static/js/jquery-3.6.1.min.js | 2 ++ apps/templates/_head_css_js.html | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) delete mode 100755 apps/static/js/jquery-3.1.1.min.js create mode 100755 apps/static/js/jquery-3.6.1.min.js diff --git a/apps/ops/templates/ops/celery_task_log.html b/apps/ops/templates/ops/celery_task_log.html index 599893db8..2305218a9 100644 --- a/apps/ops/templates/ops/celery_task_log.html +++ b/apps/ops/templates/ops/celery_task_log.html @@ -2,7 +2,7 @@ {% load i18n %} {% trans 'Task log' %} - + diff --git a/apps/static/js/jquery-3.1.1.min.js b/apps/static/js/jquery-3.1.1.min.js deleted file mode 100755 index 4c5be4c0f..000000000 --- a/apps/static/js/jquery-3.1.1.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */ -!function(a,b){"use strict";"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){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=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={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.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=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$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("fieldset");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=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;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 function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(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 qa(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},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=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),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){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.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===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(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,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):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(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!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&&C.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.escape=function(a){return(a+"").replace(ba,ca)},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:V,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(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===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 V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.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(_,aa).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("(^|"+K+")"+a+"("+K+"|$)"))&&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(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},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,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/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=I(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(P,"$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(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).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:oa(!1),disabled:oa(!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 X.test(a.nodeName)},input:function(a){return W.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:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(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}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};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=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return 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){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R), -a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.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 V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),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),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==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,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.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 Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(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&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)), -void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" +