From 79208a95c17556a79a938aaa45ec59eb98c5f7f4 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Fri, 23 Nov 2018 16:36:28 +0800 Subject: [PATCH 01/80] =?UTF-8?q?[Update]=20authentication=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=87=AA=E5=AE=9A=E4=B9=89=20LDAPBackends=20(#2081)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/ldap/__init__.py | 0 apps/authentication/ldap/backends.py | 95 ++++++++++++++++++++++++++++ apps/jumpserver/conf.py | 1 + apps/jumpserver/settings.py | 9 +-- requirements/requirements.txt | 3 +- 5 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 apps/authentication/ldap/__init__.py create mode 100644 apps/authentication/ldap/backends.py diff --git a/apps/authentication/ldap/__init__.py b/apps/authentication/ldap/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/authentication/ldap/backends.py b/apps/authentication/ldap/backends.py new file mode 100644 index 000000000..9b9ed24c3 --- /dev/null +++ b/apps/authentication/ldap/backends.py @@ -0,0 +1,95 @@ +# coding:utf-8 +# + +import ldap +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist +from django_auth_ldap.backend import _LDAPUser, LDAPBackend +from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion + +logger = _LDAPConfig.get_logger() + + +class LDAPAuthorizationBackend(LDAPBackend): + """ + Override this class to override _LDAPUser to LDAPUser + """ + + def authenticate(self, request=None, username=None, password=None, **kwargs): + if password or self.settings.PERMIT_EMPTY_PASSWORD: + ldap_user = LDAPUser(self, username=username.strip(), request=request) + user = self.authenticate_ldap_user(ldap_user, password) + else: + logger.debug('Rejecting empty password for {}'.format(username)) + user = None + + return user + + def get_user(self, user_id): + user = None + + try: + user = self.get_user_model().objects.get(pk=user_id) + LDAPUser(self, user=user) # This sets user.ldap_user + except ObjectDoesNotExist: + pass + + return user + + def get_group_permissions(self, user, obj=None): + if not hasattr(user, 'ldap_user') and self.settings.AUTHORIZE_ALL_USERS: + LDAPUser(self, user=user) # This sets user.ldap_user + + if hasattr(user, 'ldap_user'): + permissions = user.ldap_user.get_group_permissions() + else: + permissions = set() + + return permissions + + def populate_user(self, username): + ldap_user = LDAPUser(self, username=username) + user = ldap_user.populate_user() + + return user + + +class LDAPUser(_LDAPUser): + + def _search_for_user_dn(self): + """ + This method was overridden because the AUTH_LDAP_USER_SEARCH + configuration in the settings.py file + is configured with a `lambda` problem value + """ + + user_search_union = [ + LDAPSearch( + USER_SEARCH, ldap.SCOPE_SUBTREE, + settings.AUTH_LDAP_SEARCH_FILTER + ) + for USER_SEARCH in str(settings.AUTH_LDAP_SEARCH_OU).split("|") + ] + + search = LDAPSearchUnion(*user_search_union) + if search is None: + raise ImproperlyConfigured( + 'AUTH_LDAP_USER_SEARCH must be an LDAPSearch instance.' + ) + + results = search.execute(self.connection, {'user': self._username}) + if results is not None and len(results) == 1: + (user_dn, self._user_attrs) = next(iter(results)) + else: + user_dn = None + + return user_dn + + def _populate_user_from_attributes(self): + super()._populate_user_from_attributes() + if not hasattr(self._user, 'email') or '@' not in self._user.email: + email = '{}@{}'.format(self._user.username, settings.EMAIL_SUFFIX) + setattr(self._user, 'email', email) + + + diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index aceb3c841..bbf02498b 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -311,6 +311,7 @@ defaults = { 'CSRF_COOKIE_DOMAIN': None, 'SESSION_COOKIE_AGE': 3600 * 24, 'AUTH_OPENID': False, + 'EMAIL_SUFFIX': 'jumpserver.org' } diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index fff5b64db..0dd8d5cbf 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -362,11 +362,6 @@ AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org' AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)' AUTH_LDAP_START_TLS = False AUTH_LDAP_USER_ATTR_MAP = {"username": "cn", "name": "sn", "email": "mail"} -AUTH_LDAP_USER_SEARCH_UNION = lambda: [ - LDAPSearch(USER_SEARCH, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER) - for USER_SEARCH in str(AUTH_LDAP_SEARCH_OU).split("|") -] -AUTH_LDAP_USER_SEARCH = lambda: LDAPSearchUnion(*AUTH_LDAP_USER_SEARCH_UNION()) AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER AUTH_LDAP_GROUP_SEARCH = LDAPSearch( @@ -377,7 +372,7 @@ AUTH_LDAP_CONNECTION_OPTIONS = { } AUTH_LDAP_GROUP_CACHE_TIMEOUT = 1 AUTH_LDAP_ALWAYS_UPDATE_USER = True -AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend' +AUTH_LDAP_BACKEND = 'authentication.ldap.backends.LDAPAuthorizationBackend' if AUTH_LDAP: AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND) @@ -510,3 +505,5 @@ SWAGGER_SETTINGS = { }, } +# Default email suffix +EMAIL_SUFFIX = CONFIG.EMAIL_SUFFIX diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 66a86114d..70864c1ff 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -15,7 +15,7 @@ coreschema==0.0.4 cryptography==2.3.1 decorator==4.1.2 Django==2.1 -django-auth-ldap==1.3.0 +django-auth-ldap==1.7.0 django-bootstrap3==9.1.0 django-celery-beat==1.1.1 django-filter==2.0.0 @@ -76,4 +76,5 @@ aliyun-python-sdk-core-v3==2.9.1 aliyun-python-sdk-ecs==4.10.1 python-keycloak==0.13.3 python-keycloak-client==0.1.3 +python-ldap==3.1.0 rest_condition==1.0.3 From 646a29108ca4fb25cf13dbdc23e0ace207d206eb Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Fri, 23 Nov 2018 17:00:35 +0800 Subject: [PATCH 02/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E8=AE=BE=E7=BD=AE=E6=A8=A1=E5=9D=97=E6=8E=92=E7=89=88?= =?UTF-8?q?=EF=BC=8C=E7=BF=BB=E8=AF=91=20(#2083)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/forms.py | 2 +- .../templates/common/security_setting.html | 4 +- apps/common/views.py | 2 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 57933 -> 58344 bytes apps/locale/zh/LC_MESSAGES/django.po | 174 +++++++++--------- 5 files changed, 92 insertions(+), 90 deletions(-) diff --git a/apps/common/forms.py b/apps/common/forms.py index d843eec0b..830c81cf4 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -189,7 +189,7 @@ class SecuritySettingForm(BaseForm): initial=9999, label=_("Password expiration time"), min_value=1, help_text=_( - "Tip: (unit/day) " + "Tip: (unit: day) " "If the user does not update the password during the time, " "the user password will expire failure;" "The password expiration reminder mail will be automatic sent to the user " diff --git a/apps/common/templates/common/security_setting.html b/apps/common/templates/common/security_setting.html index 56705f372..adc00191f 100644 --- a/apps/common/templates/common/security_setting.html +++ b/apps/common/templates/common/security_setting.html @@ -39,9 +39,9 @@ {% endif %} {% csrf_token %} -

{% trans "User login settings" %}

+

{% trans "Security setting" %}

{% for field in form %} - {% if forloop.counter == 5 %} + {% if forloop.counter == 6 %}

{% trans "Password check rule" %}

{% endif %} diff --git a/apps/common/views.py b/apps/common/views.py index e8ccfe6ba..2500d96a3 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -52,7 +52,7 @@ class EmailSettingView(SuperUserRequiredMixin, TemplateView): form = self.form_class(request.POST) if form.is_valid(): form.save() - msg = _("Update setting successfully, please restart program") + msg = _("Update setting successfully") messages.success(request, msg) return redirect('settings:email-setting') else: diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 91637b2230eb48e2c8ea2071a1e05198d209cb13..6d771b7f5362b4891ac0bf194a0036babd1744a7 100644 GIT binary patch delta 8653 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 delta 8249 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 m|2s7LSWmY_sPXY9!z*8!yXivl%*!jkyi~k0^oP\n" "Language-Team: Jumpserver team\n" @@ -126,7 +126,7 @@ msgstr "端口" #: perms/templates/perms/asset_permission_create_update.html:40 #: perms/templates/perms/asset_permission_list.html:56 #: perms/templates/perms/asset_permission_list.html:148 -#: terminal/backends/command/models.py:13 terminal/models.py:134 +#: terminal/backends/command/models.py:13 terminal/models.py:138 #: terminal/templates/terminal/command_list.html:40 #: terminal/templates/terminal/command_list.html:73 #: terminal/templates/terminal/session_list.html:41 @@ -154,7 +154,7 @@ msgstr "不能包含特殊字符" #: assets/templates/assets/domain_list.html:25 #: assets/templates/assets/label_list.html:14 #: assets/templates/assets/system_user_detail.html:58 -#: assets/templates/assets/system_user_list.html:29 common/models.py:31 +#: assets/templates/assets/system_user_list.html:29 common/models.py:29 #: common/templates/common/command_storage_create.html:41 #: common/templates/common/replay_storage_create.html:44 #: common/templates/common/terminal_setting.html:80 @@ -164,9 +164,9 @@ msgstr "不能包含特殊字符" #: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_list.html:53 #: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:18 -#: terminal/models.py:161 terminal/templates/terminal/terminal_detail.html:43 +#: terminal/models.py:165 terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 -#: users/models/user.py:52 users/templates/users/_select_user_modal.html:13 +#: users/models/user.py:53 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_detail.html:63 #: users/templates/users/user_group_detail.html:55 #: users/templates/users/user_group_list.html:12 @@ -191,7 +191,7 @@ msgstr "名称" #: assets/templates/assets/system_user_list.html:30 #: audits/templates/audits/login_log_list.html:49 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15 -#: users/forms.py:33 users/models/authentication.py:74 users/models/user.py:50 +#: users/forms.py:33 users/models/authentication.py:77 users/models/user.py:51 #: users/templates/users/_select_user_modal.html:14 #: users/templates/users/login.html:64 #: users/templates/users/user_detail.html:67 @@ -216,7 +216,7 @@ msgstr "密码或密钥密码" msgid "Password" msgstr "密码" -#: assets/forms/user.py:29 users/models/user.py:79 +#: assets/forms/user.py:29 users/models/user.py:80 msgid "Private key" msgstr "ssh私钥" @@ -388,7 +388,7 @@ msgstr "标签管理" #: assets/templates/assets/system_user_detail.html:100 #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:37 #: perms/models.py:84 perms/templates/perms/asset_permission_detail.html:98 -#: users/models/user.py:93 users/templates/users/user_detail.html:111 +#: users/models/user.py:94 users/templates/users/user_detail.html:111 #: xpack/plugins/cloud/models.py:46 xpack/plugins/cloud/models.py:140 msgid "Created by" msgstr "创建者" @@ -426,11 +426,11 @@ msgstr "创建日期" #: assets/templates/assets/domain_list.html:28 #: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_list.html:37 -#: assets/templates/assets/user_asset_list.html:170 common/models.py:36 +#: assets/templates/assets/user_asset_list.html:170 common/models.py:34 #: ops/models/adhoc.py:43 orgs/models.py:17 perms/models.py:39 #: perms/models.py:86 perms/templates/perms/asset_permission_detail.html:102 #: terminal/models.py:28 terminal/templates/terminal/terminal_detail.html:63 -#: users/models/group.py:15 users/models/user.py:85 +#: users/models/group.py:15 users/models/user.py:86 #: users/templates/users/user_detail.html:127 #: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_list.html:14 @@ -461,7 +461,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:71 +#: assets/models/cluster.py:22 users/models/user.py:72 #: users/templates/users/user_detail.html:76 msgid "Phone" msgstr "手机" @@ -487,7 +487,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:397 +#: users/models/user.py:422 msgid "System" msgstr "系统" @@ -515,7 +515,7 @@ msgstr "BGP全网通" msgid "Regex" msgstr "正则表达式" -#: assets/models/cmd_filter.py:35 terminal/models.py:140 +#: assets/models/cmd_filter.py:35 terminal/models.py:144 #: terminal/templates/terminal/command_list.html:55 #: terminal/templates/terminal/command_list.html:71 #: terminal/templates/terminal/session_detail.html:48 @@ -616,12 +616,12 @@ msgstr "默认资产组" #: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/asset_permission_list.html:54 #: perms/templates/perms/asset_permission_list.html:142 templates/index.html:87 -#: terminal/backends/command/models.py:12 terminal/models.py:133 +#: terminal/backends/command/models.py:12 terminal/models.py:137 #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:310 -#: users/models/user.py:32 users/models/user.py:385 +#: users/models/user.py:33 users/models/user.py:410 #: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:13 users/views/user.py:384 #: xpack/plugins/orgs/forms.py:26 @@ -631,7 +631,7 @@ msgid "User" msgstr "用户" #: assets/models/label.py:19 assets/models/node.py:20 -#: assets/templates/assets/label_list.html:15 common/models.py:32 +#: assets/templates/assets/label_list.html:15 common/models.py:30 msgid "Value" msgstr "值" @@ -699,7 +699,7 @@ msgstr "登录模式" #: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_list.html:58 #: perms/templates/perms/asset_permission_list.html:154 templates/_nav.html:25 -#: terminal/backends/command/models.py:14 terminal/models.py:135 +#: terminal/backends/command/models.py:14 terminal/models.py:139 #: terminal/templates/terminal/command_list.html:48 #: terminal/templates/terminal/command_list.html:74 #: terminal/templates/terminal/session_list.html:49 @@ -1624,7 +1624,7 @@ msgstr "系统用户集群资产" #: audits/templates/audits/ftp_log_list.html:73 #: audits/templates/audits/operate_log_list.html:70 #: audits/templates/audits/password_change_log_list.html:52 -#: terminal/models.py:137 terminal/templates/terminal/session_list.html:74 +#: terminal/models.py:141 terminal/templates/terminal/session_list.html:74 #: terminal/templates/terminal/terminal_detail.html:47 msgid "Remote addr" msgstr "远端地址" @@ -1639,7 +1639,7 @@ msgid "Filename" msgstr "文件名" #: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76 -#: ops/templates/ops/task_list.html:39 users/models/authentication.py:70 +#: ops/templates/ops/task_list.html:39 users/models/authentication.py:73 #: users/templates/users/user_detail.html:456 xpack/plugins/cloud/api.py:61 msgid "Success" msgstr "成功" @@ -1665,7 +1665,7 @@ msgstr "修改者" #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/task_history.html:58 perms/models.py:35 -#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:144 +#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:148 #: terminal/templates/terminal/session_list.html:78 msgid "Date start" msgstr "开始日期" @@ -1707,19 +1707,19 @@ msgid "City" msgstr "城市" #: audits/templates/audits/login_log_list.html:54 users/forms.py:168 -#: users/models/authentication.py:79 users/models/user.py:74 +#: users/models/authentication.py:82 users/models/user.py:75 #: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" #: audits/templates/audits/login_log_list.html:55 -#: users/models/authentication.py:80 xpack/plugins/cloud/models.py:192 +#: users/models/authentication.py:83 xpack/plugins/cloud/models.py:192 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69 msgid "Reason" msgstr "原因" #: audits/templates/audits/login_log_list.html:56 -#: users/models/authentication.py:81 xpack/plugins/cloud/models.py:191 +#: users/models/authentication.py:84 xpack/plugins/cloud/models.py:191 #: xpack/plugins/cloud/models.py:208 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 @@ -1955,7 +1955,7 @@ msgid "" "Tip: (unit/minute) if the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" -"提示: (单位: 分钟) 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录." +"提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" #: common/forms.py:181 msgid "Connection max idle time" @@ -1964,7 +1964,7 @@ msgstr "SSH最大空闲时间" #: common/forms.py:183 msgid "" "If idle time more than it, disconnect connection(only ssh now) Unit: minute" -msgstr "提示: (单位: 分钟) 如果超过该配置没有操作,连接会被断开(仅ssh) " +msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" #: common/forms.py:189 msgid "Password expiration time" @@ -1972,11 +1972,13 @@ msgstr "密码过期时间" #: common/forms.py:192 msgid "" -"Tip: (unit/day) If the user does not update the password during the time, " +"Tip: (unit: day) If the user does not update the password during the time, " "the user password will expire failure;The password expiration reminder mail " "will be automatic sent to the user by system within 5 days (daily) before " "the password expires" msgstr "" +"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒" +"邮件将在密码过期前5天内由系统(每天)自动发送给用户" #: common/forms.py:201 msgid "Password minimum length" @@ -2030,7 +2032,7 @@ msgstr "" msgid "discard time" msgstr "" -#: common/models.py:35 users/models/authentication.py:51 +#: common/models.py:33 users/models/authentication.py:54 #: users/templates/users/user_detail.html:96 msgid "Enabled" msgstr "启用" @@ -2072,6 +2074,7 @@ msgstr "终端设置" #: common/templates/common/email_setting.html:27 #: common/templates/common/ldap_setting.html:27 #: common/templates/common/security_setting.html:27 +#: common/templates/common/security_setting.html:42 #: common/templates/common/terminal_setting.html:31 common/views.py:152 msgid "Security setting" msgstr "安全设置" @@ -2138,10 +2141,6 @@ msgstr "端点后缀" msgid "Region" msgstr "地域" -#: common/templates/common/security_setting.html:42 -msgid "User login settings" -msgstr "用户登录设置" - #: common/templates/common/security_setting.html:46 msgid "Password check rule" msgstr "密码校验规则" @@ -2199,7 +2198,7 @@ msgstr "创建录像存储" msgid "Create command storage" msgstr "创建命令存储" -#: jumpserver/views.py:180 +#: jumpserver/views.py:185 msgid "" "
Luna is a separately deployed program, you need to deploy Luna, coco, " "configure nginx for url distribution,
If you see this page, " @@ -2457,7 +2456,7 @@ msgstr "组织管理" #: perms/forms.py:31 perms/models.py:30 perms/models.py:80 #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:14 -#: users/forms.py:280 users/models/group.py:26 users/models/user.py:58 +#: users/forms.py:280 users/models/group.py:26 users/models/user.py:59 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:211 #: users/templates/users/user_list.html:26 @@ -2475,7 +2474,7 @@ msgstr "资产和节点至少选一个" #: perms/models.py:36 perms/models.py:83 #: perms/templates/perms/asset_permission_detail.html:90 -#: users/models/user.py:90 users/templates/users/user_detail.html:107 +#: users/models/user.py:91 users/templates/users/user_detail.html:107 #: users/templates/users/user_profile.html:116 msgid "Date expired" msgstr "失效日期" @@ -2952,43 +2951,43 @@ msgstr "SSH端口" msgid "HTTP Port" msgstr "HTTP端口" -#: terminal/models.py:105 +#: terminal/models.py:109 msgid "Session Online" msgstr "在线会话" -#: terminal/models.py:106 +#: terminal/models.py:110 msgid "CPU Usage" msgstr "CPU使用" -#: terminal/models.py:107 +#: terminal/models.py:111 msgid "Memory Used" msgstr "内存使用" -#: terminal/models.py:108 +#: terminal/models.py:112 msgid "Connections" msgstr "连接数" -#: terminal/models.py:109 +#: terminal/models.py:113 msgid "Threads" msgstr "线程数" -#: terminal/models.py:110 +#: terminal/models.py:114 msgid "Boot Time" msgstr "运行时间" -#: terminal/models.py:139 terminal/templates/terminal/session_list.html:104 +#: terminal/models.py:143 terminal/templates/terminal/session_list.html:104 msgid "Replay" msgstr "回放" -#: terminal/models.py:143 +#: terminal/models.py:147 msgid "Date last active" msgstr "最后活跃日期" -#: terminal/models.py:145 +#: terminal/models.py:149 msgid "Date end" msgstr "结束日期" -#: terminal/models.py:162 +#: terminal/models.py:166 msgid "Args" msgstr "参数" @@ -3142,66 +3141,66 @@ msgstr "用户 {} 密码已经过期,请更新。" msgid "Please carry seed value and conduct MFA secondary certification" msgstr "请携带seed值, 进行MFA二次认证" -#: users/api/auth.py:205 +#: users/api/auth.py:204 msgid "Please verify the user name and password first" msgstr "请先进行用户名和密码验证" -#: users/api/auth.py:217 +#: users/api/auth.py:216 msgid "MFA certification failed" msgstr "MFA认证失败" -#: users/api/user.py:138 +#: users/api/user.py:140 msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/authentication.py:56 +#: users/authentication.py:53 msgid "Invalid signature header. No credentials provided." msgstr "" -#: users/authentication.py:59 +#: users/authentication.py:56 msgid "Invalid signature header. Signature string should not contain spaces." msgstr "" -#: users/authentication.py:66 +#: users/authentication.py:63 msgid "Invalid signature header. Format like AccessKeyId:Signature" msgstr "" -#: users/authentication.py:70 +#: users/authentication.py:67 msgid "" "Invalid signature header. Signature string should not contain invalid " "characters." msgstr "" -#: users/authentication.py:90 users/authentication.py:106 +#: users/authentication.py:87 users/authentication.py:103 msgid "Invalid signature." msgstr "" -#: users/authentication.py:97 +#: users/authentication.py:94 msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" msgstr "" -#: users/authentication.py:102 +#: users/authentication.py:99 msgid "Expired, more than 15 minutes" msgstr "" -#: users/authentication.py:109 +#: users/authentication.py:106 msgid "User disabled." msgstr "用户已禁用" -#: users/authentication.py:124 +#: users/authentication.py:121 msgid "Invalid token header. No credentials provided." msgstr "" -#: users/authentication.py:127 +#: users/authentication.py:124 msgid "Invalid token header. Sign string should not contain spaces." msgstr "" -#: users/authentication.py:134 +#: users/authentication.py:131 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "" -#: users/authentication.py:145 +#: users/authentication.py:142 msgid "Invalid token or cache refreshed." msgstr "" @@ -3209,7 +3208,7 @@ msgstr "" msgid "MFA code" msgstr "MFA 验证码" -#: users/forms.py:52 users/models/user.py:62 +#: users/forms.py:52 users/models/user.py:63 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 #: users/templates/users/user_list.html:25 @@ -3237,7 +3236,7 @@ msgstr "添加到用户组" msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:114 users/forms.py:245 users/serializers.py:49 +#: users/forms.py:114 users/forms.py:245 users/serializers/v1.py:51 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" @@ -3297,7 +3296,7 @@ msgstr "自动配置并下载SSH密钥" msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:256 users/models/user.py:82 +#: users/forms.py:256 users/models/user.py:83 #: users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 #: users/templates/users/user_profile.html:68 @@ -3311,106 +3310,106 @@ msgstr "ssh公钥" msgid "Select users" msgstr "选择用户" -#: users/models/authentication.py:36 +#: users/models/authentication.py:39 msgid "Private Token" msgstr "ssh密钥" -#: users/models/authentication.py:50 users/templates/users/user_detail.html:98 +#: users/models/authentication.py:53 users/templates/users/user_detail.html:98 msgid "Disabled" msgstr "禁用" -#: users/models/authentication.py:52 users/models/authentication.py:62 +#: users/models/authentication.py:55 users/models/authentication.py:65 msgid "-" msgstr "" -#: users/models/authentication.py:63 +#: users/models/authentication.py:66 msgid "Username/password check failed" msgstr "用户名/密码 校验失败" -#: users/models/authentication.py:64 +#: users/models/authentication.py:67 msgid "MFA authentication failed" msgstr "MFA 认证失败" -#: users/models/authentication.py:65 +#: users/models/authentication.py:68 msgid "Username does not exist" msgstr "用户名不存在" -#: users/models/authentication.py:66 +#: users/models/authentication.py:69 msgid "Password expired" msgstr "密码过期" -#: users/models/authentication.py:71 xpack/plugins/cloud/models.py:184 +#: users/models/authentication.py:74 xpack/plugins/cloud/models.py:184 #: xpack/plugins/cloud/models.py:198 msgid "Failed" msgstr "失败" -#: users/models/authentication.py:75 +#: users/models/authentication.py:78 msgid "Login type" msgstr "登录方式" -#: users/models/authentication.py:76 +#: users/models/authentication.py:79 msgid "Login ip" msgstr "登录IP" -#: users/models/authentication.py:77 +#: users/models/authentication.py:80 msgid "Login city" msgstr "登录城市" -#: users/models/authentication.py:78 +#: users/models/authentication.py:81 msgid "User agent" msgstr "Agent" -#: users/models/authentication.py:82 +#: users/models/authentication.py:85 msgid "Date login" msgstr "登录日期" -#: users/models/user.py:31 users/models/user.py:393 +#: users/models/user.py:32 users/models/user.py:418 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:33 +#: users/models/user.py:34 msgid "Application" msgstr "应用程序" -#: users/models/user.py:36 users/templates/users/user_profile.html:92 +#: users/models/user.py:37 users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:167 #: users/templates/users/user_profile.html:170 msgid "Disable" msgstr "禁用" -#: users/models/user.py:37 users/templates/users/user_profile.html:90 +#: users/models/user.py:38 users/templates/users/user_profile.html:90 #: users/templates/users/user_profile.html:174 msgid "Enable" msgstr "启用" -#: users/models/user.py:38 users/templates/users/user_profile.html:88 +#: users/models/user.py:39 users/templates/users/user_profile.html:88 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:54 users/templates/users/user_detail.html:71 +#: users/models/user.py:55 users/templates/users/user_detail.html:71 #: users/templates/users/user_profile.html:59 msgid "Email" msgstr "邮件" -#: users/models/user.py:65 +#: users/models/user.py:66 msgid "Avatar" msgstr "头像" -#: users/models/user.py:68 users/templates/users/user_detail.html:82 +#: users/models/user.py:69 users/templates/users/user_detail.html:82 msgid "Wechat" msgstr "微信" -#: users/models/user.py:97 users/templates/users/user_detail.html:103 +#: users/models/user.py:98 users/templates/users/user_detail.html:103 #: users/templates/users/user_list.html:27 #: users/templates/users/user_profile.html:100 msgid "Source" msgstr "用户来源" -#: users/models/user.py:101 +#: users/models/user.py:102 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:396 +#: users/models/user.py:421 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -4555,6 +4554,9 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "User login settings" +#~ msgstr "用户登录设置" + #~ msgid "Bit" #~ msgstr " 位" From 3e3ab556d3e386b2d01611949b33481ec93eb102 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 23 Nov 2018 18:28:02 +0800 Subject: [PATCH 03/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings.py | 20 ++++++++++---------- config_example.py | 4 +++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 6801be6c5..1d7a2a8de 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -14,7 +14,7 @@ import os import sys import ldap -from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion +# from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion from django.urls import reverse_lazy from .conf import load_user_config @@ -362,11 +362,11 @@ AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org' AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)' AUTH_LDAP_START_TLS = False AUTH_LDAP_USER_ATTR_MAP = {"username": "cn", "name": "sn", "email": "mail"} -AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU -AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER -AUTH_LDAP_GROUP_SEARCH = LDAPSearch( - AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER -) +# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU +# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER +# AUTH_LDAP_GROUP_SEARCH = LDAPSearch( +# AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER +# ) AUTH_LDAP_CONNECTION_OPTIONS = { ldap.OPT_TIMEOUT: 5 } @@ -397,10 +397,10 @@ if AUTH_OPENID: # Celery using redis as broker CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % { - 'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '', - 'host': CONFIG.REDIS_HOST or '127.0.0.1', - 'port': CONFIG.REDIS_PORT or 6379, - 'db': CONFIG.REDIS_DB_CELERY_BROKER or 3, + 'password': CONFIG.REDIS_PASSWORD, + 'host': CONFIG.REDIS_HOST, + 'port': CONFIG.REDIS_PORT, + 'db': CONFIG.REDIS_DB_CELERY_BROKER, } CELERY_TASK_SERIALIZER = 'pickle' CELERY_RESULT_SERIALIZER = 'pickle' diff --git a/config_example.py b/config_example.py index d907b17f7..d2255a0d9 100644 --- a/config_example.py +++ b/config_example.py @@ -72,7 +72,9 @@ class Config: # Redis配置 REDIS_HOST = '127.0.0.1' REDIS_PORT = 6379 - REDIS_PASSWORD = '' + # REDIS_PASSWORD = '' + # REDIS_DB_CELERY_BROKER = 3 + # REDIS_DB_CACHE = 4 # Use OpenID authorization # 使用OpenID 来进行认证设置 From c4d6f32528d85937908b7fd22322661603ec6b44 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 26 Nov 2018 10:22:17 +0800 Subject: [PATCH 04/80] =?UTF-8?q?[Update]=20=E5=A2=9E=E5=8A=A0=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E8=B6=85=E6=97=B6=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jms b/jms index e1d12b7be..1bb6bb56c 100755 --- a/jms +++ b/jms @@ -29,7 +29,7 @@ HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080 DEBUG = CONFIG.DEBUG LOG_LEVEL = CONFIG.LOG_LEVEL -START_TIMEOUT = 15 +START_TIMEOUT = 40 WORKERS = 4 DAEMON = False From 67f52888f62ced2cbb0cb2a957a4d21cb70f3c8c Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 26 Nov 2018 10:30:40 +0800 Subject: [PATCH 05/80] =?UTF-8?q?[Update]=20=E6=B5=8F=E8=A7=88=E5=99=A8?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E7=81=AB=E6=B0=94=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 1 + apps/jumpserver/settings.py | 1 + config_example.py | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index bbf02498b..ec5111e1b 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -310,6 +310,7 @@ defaults = { 'SESSION_COOKIE_DOMAIN': None, 'CSRF_COOKIE_DOMAIN': None, 'SESSION_COOKIE_AGE': 3600 * 24, + 'SESSION_EXPIRE_AT_BROWSER_CLOSE': False, 'AUTH_OPENID': False, 'EMAIL_SUFFIX': 'jumpserver.org' } diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 1d7a2a8de..9ed022df9 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -146,6 +146,7 @@ LOGIN_URL = reverse_lazy('users:login') SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE +SESSION_EXPIRE_AT_BROWSER_CLOSE = CONFIG.SESSION_EXPIRE_AT_BROWSER_CLOSE MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' # Database diff --git a/config_example.py b/config_example.py index d2255a0d9..dfcc876a3 100644 --- a/config_example.py +++ b/config_example.py @@ -44,6 +44,11 @@ class Config: # LOG_LEVEL = 'DEBUG' # LOG_DIR = os.path.join(BASE_DIR, 'logs') + # Session expiration setting, Default 24 hour, Also set expired on on browser close + # 浏览器Session过期时间,默认24小时, 也可以设置浏览器关闭则过期 + # SESSION_COOKIE_AGE = 3600 * 24 + # SESSION_EXPIRE_AT_BROWSER_CLOSE = False + # Database setting, Support sqlite3, mysql, postgres .... # 数据库设置 # See https://docs.djangoproject.com/en/1.10/ref/settings/#databases From da8fec77bbb7ef844ed67a14d86c7fa10dae0d96 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 26 Nov 2018 10:38:56 +0800 Subject: [PATCH 06/80] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 2 +- apps/jumpserver/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index ec5111e1b..08716f93b 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -301,7 +301,7 @@ defaults = { 'REDIS_HOST': '127.0.0.1', 'REDIS_PORT': 6379, 'REDIS_PASSWORD': '', - 'REDIS_DB_CELERY_BROKER': 3, + 'REDIS_DB_CELERY': 3, 'REDIS_DB_CACHE': 4, 'CAPTCHA_TEST_MODE': None, 'TOKEN_EXPIRATION': 3600, diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 9ed022df9..963fd60a7 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -401,7 +401,7 @@ CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % { 'password': CONFIG.REDIS_PASSWORD, 'host': CONFIG.REDIS_HOST, 'port': CONFIG.REDIS_PORT, - 'db': CONFIG.REDIS_DB_CELERY_BROKER, + 'db': CONFIG.REDIS_DB_CELERY, } CELERY_TASK_SERIALIZER = 'pickle' CELERY_RESULT_SERIALIZER = 'pickle' From 060248d1ca7d42f7fd0bae05b67ae58492b12f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Mon, 26 Nov 2018 18:50:30 +0800 Subject: [PATCH 07/80] Req (#2098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 修改requirements * [Update] 修改requirements * [Update] 编译语言 --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 58344 -> 58614 bytes apps/locale/zh/LC_MESSAGES/django.po | 19 +++++++++---------- requirements/requirements.txt | 3 +-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 6d771b7f5362b4891ac0bf194a0036babd1744a7..ff5e85b7856944cdb5a6897c688c94e2388f79ea 100644 GIT binary patch delta 14343 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 V&EXkq8gGs`9~Qd#nagW(|3BJ+f8qcD diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 05685912a..d0ec9afb8 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-23 16:44+0800\n" +"POT-Creation-Date: 2018-11-26 18:31+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -1981,8 +1981,8 @@ msgid "" "will be automatic sent to the user by system within 5 days (daily) before " "the password expires" msgstr "" -"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒" -"邮件将在密码过期前5天内由系统(每天)自动发送给用户" +"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" +"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" #: common/forms.py:202 msgid "Password minimum length" @@ -2189,17 +2189,11 @@ msgstr "不能包含特殊字符" msgid "Settings" msgstr "系统设置" -#: common/views.py:29 common/views.py:81 common/views.py:112 +#: common/views.py:29 common/views.py:55 common/views.py:81 common/views.py:112 #: common/views.py:162 msgid "Update setting successfully" msgstr "更新设置成功" -#: common/views.py:55 -#, fuzzy -#| msgid "Update setting successfully" -msgid "Update setting successfully, please restart program" -msgstr "更新设置成功" - #: common/views.py:127 msgid "Create replay storage" msgstr "创建录像存储" @@ -4568,6 +4562,11 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#, fuzzy +#~| msgid "Update setting successfully" +#~ msgid "Update setting successfully, please restart program" +#~ msgstr "更新设置成功" + #~ msgid "User login settings" #~ msgstr "用户登录设置" diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 70864c1ff..93fa69a42 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -52,7 +52,6 @@ Pillow==4.3.0 pyasn1==0.4.2 pycparser==2.19 pycrypto==2.6.1 -pyldap==2.4.45 pyotp==2.2.6 PyNaCl==1.2.1 python-dateutil==2.6.1 @@ -76,5 +75,5 @@ aliyun-python-sdk-core-v3==2.9.1 aliyun-python-sdk-ecs==4.10.1 python-keycloak==0.13.3 python-keycloak-client==0.1.3 -python-ldap==3.1.0 rest_condition==1.0.3 +python-ldap==3.1.0 From f380d82b550ed59c4b35a88ff1cb4c1332429f90 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 27 Nov 2018 10:21:47 +0800 Subject: [PATCH 08/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=B0=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20(#2100)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 不修改admin用户的用户来源 * [Bugfix] 修复Default组织下用户列表会显示所有用户,并查看详情会报错 * [Update] 关闭telnet资产测试可连接性 * [Update] 只有ssh协议资产可测试连接性 --- apps/assets/templates/assets/asset_detail.html | 2 +- apps/authentication/signals_handlers.py | 11 +++++++++-- apps/users/api/user.py | 2 +- apps/users/signals_handler.py | 7 ------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html index 378de8b98..51e5adaf2 100644 --- a/apps/assets/templates/assets/asset_detail.html +++ b/apps/assets/templates/assets/asset_detail.html @@ -159,7 +159,7 @@ - {% if asset.is_unixlike %} + {% if asset.protocol == 'ssh' %} {% trans 'Refresh hardware' %}: diff --git a/apps/authentication/signals_handlers.py b/apps/authentication/signals_handlers.py index 7cf240386..d45ea1dfa 100644 --- a/apps/authentication/signals_handlers.py +++ b/apps/authentication/signals_handlers.py @@ -1,7 +1,8 @@ from django.http.request import QueryDict -from django.contrib.auth.signals import user_logged_out -from django.dispatch import receiver from django.conf import settings +from django.dispatch import receiver +from django.contrib.auth.signals import user_logged_out +from django_auth_ldap.backend import populate_user from .openid import client from .signals import post_create_openid_user @@ -31,3 +32,9 @@ def on_post_create_openid_user(sender, user=None, **kwargs): user.source = user.SOURCE_OPENID user.save() + +@receiver(populate_user) +def on_ldap_create_user(sender, user, ldap_user, **kwargs): + if user and user.name != 'admin': + user.source = user.SOURCE_LDAP + user.save() diff --git a/apps/users/api/user.py b/apps/users/api/user.py index b343dca78..c01f5b1d3 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -39,7 +39,7 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet): def get_queryset(self): queryset = super().get_queryset() - if current_org.is_real(): + if current_org.is_real() or current_org.is_default(): org_users = current_org.get_org_users() queryset = queryset.filter(id__in=org_users) return queryset diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py index 7473434d0..1bc3ef430 100644 --- a/apps/users/signals_handler.py +++ b/apps/users/signals_handler.py @@ -2,7 +2,6 @@ # from django.dispatch import receiver -from django_auth_ldap.backend import populate_user # from django.db.models.signals import post_save from common.utils import get_logger @@ -30,9 +29,3 @@ def on_user_create(sender, user=None, **kwargs): if user.email: send_user_created_mail(user) - -@receiver(populate_user) -def on_ldap_create_user(sender, user, ldap_user, **kwargs): - if user: - user.source = user.SOURCE_LDAP - user.save() From 9499a16a8b069e61f29aca4c97e60f3293c79f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 27 Nov 2018 11:00:39 +0800 Subject: [PATCH 09/80] =?UTF-8?q?[Update]=20=E9=BB=98=E8=AE=A4=E4=B8=AD?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E7=9C=8B=E5=88=B0=E6=89=80=E6=9C=89=E7=94=A8?= =?UTF-8?q?=E6=88=B7=20(#2102)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/models.py | 10 +++------- apps/users/api/user.py | 5 +---- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/apps/orgs/models.py b/apps/orgs/models.py index ca43ea8eb..d6abe1f00 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -68,14 +68,10 @@ class Organization(models.Model): def get_org_users(self, include_app=False): from users.models import User - if self.is_default(): - users = User.objects.filter(orgs__isnull=True) - elif not self.is_real(): - users = User.objects.all() - elif self.is_root(): - users = User.objects.all() - else: + if self.is_real(): users = self.users.all() + else: + users = User.objects.all() if not include_app: users = users.exclude(role=User.ROLE_APP) return users diff --git a/apps/users/api/user.py b/apps/users/api/user.py index c01f5b1d3..583a8c557 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -38,10 +38,7 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet): pagination_class = LimitOffsetPagination def get_queryset(self): - queryset = super().get_queryset() - if current_org.is_real() or current_org.is_default(): - org_users = current_org.get_org_users() - queryset = queryset.filter(id__in=org_users) + queryset = current_org.get_org_users() return queryset def get_permissions(self): From 24fe3ade9cdbc0dfd463b62e48505a1d40433af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Wed, 28 Nov 2018 10:02:58 +0800 Subject: [PATCH 10/80] =?UTF-8?q?[Update]=20=E9=BB=98=E8=AE=A4=E4=B8=AD?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E7=9C=8B=E5=88=B0=E6=89=80=E6=9C=89=E7=94=A8?= =?UTF-8?q?=E6=88=B7=20(#2106)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 0b0fdbfc827d0ac320d4e40d7cd0425a0890b5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Wed, 28 Nov 2018 18:15:02 +0800 Subject: [PATCH 11/80] =?UTF-8?q?[Update]=20=E6=97=A5=E5=BF=97=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E4=B8=AD=E6=94=B9=E5=AF=86=E6=97=A5=E5=BF=97=E6=9D=83?= =?UTF-8?q?=E9=99=90=20(#2108)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 日志审计中改密日志权限 * [Update] 日志审计中改密日志权限 --- apps/audits/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/audits/views.py b/apps/audits/views.py index c64bbe5b4..f41b674e5 100644 --- a/apps/audits/views.py +++ b/apps/audits/views.py @@ -122,7 +122,10 @@ class PasswordChangeLogList(AdminUserRequiredMixin, DatetimeSearchMixin, ListVie date_from = date_to = None def get_queryset(self): - self.queryset = super().get_queryset() + users = current_org.get_org_users() + self.queryset = super().get_queryset().filter( + user__in=[user.__str__() for user in users] + ) self.user = self.request.GET.get('user') filter_kwargs = dict() From 8f479e364b69602c17a29b9f71ed9210c3a2a4be Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Thu, 29 Nov 2018 16:01:03 +0800 Subject: [PATCH 12/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E5=A4=8D=E5=B0=8F?= =?UTF-8?q?=E7=BB=86=E8=8A=82=20(#2111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 命令/录像存储,禁用default删除按钮 * [Update] 修复 _asset_list_modal 取消资产全选时不能映射到资产选择框的bug --- apps/assets/templates/assets/domain_create_update.html | 4 +--- apps/assets/templates/assets/label_create_update.html | 7 +------ apps/common/models.py | 10 ++++++++++ apps/common/templates/common/terminal_setting.html | 4 ++-- .../perms/asset_permission_create_update.html | 9 +++------ apps/static/js/jumpserver.js | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/assets/templates/assets/domain_create_update.html b/apps/assets/templates/assets/domain_create_update.html index 3ad8724e9..8af8728c8 100644 --- a/apps/assets/templates/assets/domain_create_update.html +++ b/apps/assets/templates/assets/domain_create_update.html @@ -32,9 +32,7 @@ $(document).ready(function () { }) .on('click', '#btn_asset_modal_confirm', function () { var assets = asset_table2.selected; - $.each(assets, function (id, data) { - $('.select2').val(assets).trigger('change'); - }); + $('.select2').val(assets).trigger('change'); $("#asset_list_modal").modal('hide'); }) diff --git a/apps/assets/templates/assets/label_create_update.html b/apps/assets/templates/assets/label_create_update.html index b26ec6464..0ff094e69 100644 --- a/apps/assets/templates/assets/label_create_update.html +++ b/apps/assets/templates/assets/label_create_update.html @@ -35,12 +35,7 @@ $(document).ready(function () { }) .on('click', '#btn_asset_modal_confirm', function () { var assets = asset_table2.selected; - $('.select2 option:selected').each(function (i, data) { - assets.push($(data).attr('value')) - }); - $.each(assets, function (id, data) { - $('.select2').val(assets).trigger('change'); - }); + $('.select2').val(assets).trigger('change'); $("#asset_list_modal").modal('hide'); }) diff --git a/apps/common/models.py b/apps/common/models.py index a864e9535..cc5ba8fc5 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -64,6 +64,11 @@ class Setting(models.Model): @classmethod def save_storage(cls, name, data): + """ + :param name: TERMINAL_REPLAY_STORAGE or TERMINAL_COMMAND_STORAGE + :param data: {} + :return: Setting object + """ obj = cls.objects.filter(name=name).first() if not obj: obj = cls() @@ -79,6 +84,11 @@ class Setting(models.Model): @classmethod def delete_storage(cls, name, storage_name): + """ + :param name: TERMINAL_REPLAY_STORAGE or TERMINAL_COMMAND_STORAGE + :param storage_name: "" + :return: bool + """ obj = cls.objects.filter(name=name).first() if not obj: return False diff --git a/apps/common/templates/common/terminal_setting.html b/apps/common/templates/common/terminal_setting.html index 0069c05a2..994d1714b 100644 --- a/apps/common/templates/common/terminal_setting.html +++ b/apps/common/templates/common/terminal_setting.html @@ -87,7 +87,7 @@ {{ name }} {{ setting.TYPE }} - {% trans 'Delete' %} + {% trans 'Delete' %} {% endfor %} @@ -109,7 +109,7 @@ {{ name }} {{ setting.TYPE }} - {% trans 'Delete' %} + {% trans 'Delete' %} {% endfor %} diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html index 2d79890f6..0b2c5b93e 100644 --- a/apps/perms/templates/perms/asset_permission_create_update.html +++ b/apps/perms/templates/perms/asset_permission_create_update.html @@ -112,12 +112,9 @@ $(document).ready(function () { }) .on('click', '#btn_asset_modal_confirm', function () { var assets = asset_table2.selected; - $('.select2 option:selected').each(function (i, data) { - assets.push($(data).attr('value')) - }); - $.each(assets, function (id, data) { - $('.select2').val(assets).trigger('change'); - }); + + $('.select2').val(assets).trigger('change'); + $("#asset_list_modal").modal('hide'); }); diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 466267c33..b3cec3bd7 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -535,7 +535,7 @@ jumpserver.initServerSideDataTable = function (options) { if (type === 'row') { var rows = table.rows(indexes).data(); $.each(rows, function (id, row) { - if (row.id){ + if (row.id && $.inArray(row.id, table.selected) === -1){ table.selected.push(row.id) } }) From e22e832d49b7e6556c4cf894af0f225024bc48ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Fri, 30 Nov 2018 15:39:13 +0800 Subject: [PATCH 13/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=9B=B4=E6=96=B0=E6=97=B6=20=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=92=8C=E6=A0=87=E7=AD=BE=E6=98=AF=E5=A2=9E=E5=8A=A0=E8=80=8C?= =?UTF-8?q?=E4=B8=8D=E6=98=AF=E8=A6=86=E7=9B=96=20(#2115)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/forms/asset.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py index 28c78e981..1ec2ea24f 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -142,14 +142,14 @@ class AssetBulkUpdateForm(OrgModelForm): if k in changed_fields} assets = cleaned_data.pop('assets') labels = cleaned_data.pop('labels', []) - nodes = cleaned_data.pop('nodes') + nodes = cleaned_data.pop('nodes', None) assets = Asset.objects.filter(id__in=[asset.id for asset in assets]) assets.update(**cleaned_data) if labels: - for label in labels: - label.assets.add(*tuple(assets)) + for asset in assets: + asset.labels.set(labels) if nodes: - for node in nodes: - node.assets.add(*tuple(assets)) + for asset in assets: + asset.nodes.set(nodes) return assets From d91599ffabfff63772b86ff44627205d626ac17b Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 4 Dec 2018 10:01:40 +0800 Subject: [PATCH 14/80] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=20(#2117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 更新依赖 * [Update] 修改翻译小细节 --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 58614 -> 58608 bytes apps/locale/zh/LC_MESSAGES/django.po | 2 +- requirements/requirements.txt | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index ff5e85b7856944cdb5a6897c688c94e2388f79ea..6e8970e9cca6c1e254be8d95fa6d854ca3838c20 100644 GIT binary patch delta 1555 zcmXZcSxD4T6vy#fmS#<-mn@xpIhjn~O*Q8zf}rN4nHvmP}c#x=P5m2)ew3e)kXN03kO z0BbOcnYd%v8liSxk12Qq=V2>u!5%Eeq}R?RV=3xp_MlE)j@4L&y%@zNyz|DnLiB=f zo!g`W+=cgXE>3ypTpDJW`KZS0QH?j7JI!*NufnO!AIAhdW8<|ro&2KZjmW_~ca2~M z6K$vyJhp-!%X?8LA2j2r4SYx4%(&$#BYxc?T)=n%YQ7ltYWHF~o<`kp0|r>%wNC`j zb(+sm?|#Vg-=>TD3(qtcq83<+S}sLv1`G?vLl8`V^tYx1yfgjrtH)SbhL?(??NtC*z(U)Y-%p^ST)}TktFo+(Qp@ z$Na{3Q46)B8h2nWK0$r0$8awOJ~;Oc52C)dWgneehhdLkCBX=0VluzCy0TnU9zvbG z8nxp(^9pVtzlCKOMQx<;vtJ*?Ch}6$_!pdn`m34Dj~>Z2{yT-wm|6}E=1`VJ5p8T(1bu)0k076O?z#VShxh*PXJ^Q-&XBu9iC&Cz zZlue(9Q=b(xc!B1K7J%GwLGuexp4AB=5h11S!-UyS@xdMecp9DZz}j= z1CwTOkN-dK{2=OJ#TboOJ%R*+ z2Y3X>Fc!1>tr2SH)fkDFaXvQUX6(QWj2LikI%c7+dJpR41z3c|_!P(R0^SRVAC?Ou$=Q>YuR!En}h%~OGM zt!5|cvmdbhuNn5vUudqm1hqgCYQc0H&$aPF%gfA4)COx%2W^n7?-~howGS~1+fj|9 zhWzn(GZA(2)u?(ps$mvR!+ccZgQ$<76!k4XiRE|}*I@X2zfT5wYM4o&M*A=gi%<*P zMm21+`ZIG7b(IsA|3l4-81@@4Fqfmg?a4URANBecS^eBF_pibg2Gp?0CU%&8sK(={ z4gJ7H7&PK96hMt9qWWyG@olK*cB8(8g_iF}-Skn^!B32Meo$qFI`fj*VBW?u9=L}d zCXD)x@1PcHMm2tf@%RMwT7Sk}7&XS`@c` Date: Mon, 10 Dec 2018 10:11:54 +0800 Subject: [PATCH 15/80] Command (#2134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 任务区分org * [Update] 修改翻译 * [Update] 使用id而不是hostname * [Update] 执行命令 * [Update] 修改一些东西 * [Update] 暂存 * [Update] 用户执行命令 * [Update] 添加资产授权模块-tree * [Update] 暂时这样 * [Update] 批量命令执行 * [Update] 修改表结构 * [Update] 更新翻译 * [Update] 删除cloud模块无效中文翻译 --- apps/assets/models/__init__.py | 3 - apps/assets/models/asset.py | 15 +- apps/assets/models/cmd_filter.py | 29 +- apps/assets/models/user.py | 9 + apps/assets/serializers/node.py | 4 +- apps/assets/signals_handler.py | 2 +- apps/assets/tasks.py | 210 +- apps/assets/templates/assets/asset_list.html | 2 +- .../templates/assets/user_asset_list.html | 17 +- apps/assets/utils.py | 11 +- apps/assets/views/asset.py | 1 + apps/audits/urls/view_urls.py | 1 + apps/audits/views.py | 37 +- apps/common/signals_handler.py | 2 + apps/common/templatetags/common_tags.py | 10 + apps/common/tree.py | 95 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 58608 -> 60006 bytes apps/locale/zh/LC_MESSAGES/django.po | 477 +- apps/ops/ansible/callback.py | 132 +- apps/ops/ansible/runner.py | 21 +- apps/ops/api/__init__.py | 5 + apps/ops/{api.py => api/adhoc.py} | 50 +- apps/ops/api/celery.py | 53 + apps/ops/api/command.py | 27 + apps/ops/celery/signal_handler.py | 1 - apps/ops/forms.py | 17 + apps/ops/inventory.py | 17 +- .../ops/migrations/0003_auto_20181207_1744.py | 56 + apps/ops/migrations/0004_adhoc_run_as.py | 20 + apps/ops/models/__init__.py | 3 +- apps/ops/models/adhoc.py | 32 +- apps/ops/models/command.py | 71 + apps/ops/serializers.py | 33 +- apps/ops/tasks.py | 8 +- apps/ops/templates/ops/celery_task_log.html | 17 +- .../ops/command_execution_create.html | 254 + .../templates/ops/command_execution_list.html | 95 + apps/ops/templates/ops/task_detail.html | 7 +- apps/ops/urls/api_urls.py | 3 +- apps/ops/urls/view_urls.py | 3 + apps/ops/utils.py | 28 +- apps/ops/views/__init__.py | 3 + apps/ops/{views.py => views/adhoc.py} | 52 +- apps/ops/views/celery.py | 14 + apps/ops/views/command.py | 76 + apps/perms/api.py | 100 +- apps/perms/serializers.py | 35 +- apps/perms/urls/api_urls.py | 5 + apps/perms/utils.py | 47 +- .../css/plugins/codemirror/ambiance.css | 77 + .../css/plugins/codemirror/codemirror.css | 309 + .../js/plugins/codemirror/codemirror.js | 7830 ++++++++++ .../js/plugins/codemirror/mode/index.html | 125 + .../static/js/plugins/codemirror/mode/meta.js | 144 + .../plugins/codemirror/mode/shell/index.html | 66 + .../js/plugins/codemirror/mode/shell/shell.js | 138 + .../js/plugins/codemirror/mode/shell/test.js | 58 + .../js/plugins/xterm/addons/attach/attach.js | 105 + .../plugins/xterm/addons/attach/attach.js.map | 1 + .../static/js/plugins/xterm/addons/fit/fit.js | 51 + .../js/plugins/xterm/addons/fit/fit.js.map | 1 + .../xterm/addons/fullscreen/fullscreen.css | 10 + .../xterm/addons/fullscreen/fullscreen.js | 27 + .../xterm/addons/fullscreen/fullscreen.js.map | 1 + .../js/plugins/xterm/addons/search/search.js | 126 + .../plugins/xterm/addons/search/search.js.map | 1 + .../xterm/addons/terminado/terminado.js | 69 + .../xterm/addons/terminado/terminado.js.map | 1 + .../plugins/xterm/addons/webLinks/webLinks.js | 41 + .../xterm/addons/webLinks/webLinks.js.map | 1 + .../xterm/addons/winptyCompat/winptyCompat.js | 29 + .../addons/winptyCompat/winptyCompat.js.map | 1 + .../js/plugins/xterm/addons/zmodem/zmodem.js | 45 + .../plugins/xterm/addons/zmodem/zmodem.js.map | 1 + apps/static/js/plugins/xterm/xterm.css | 2211 +-- apps/static/js/plugins/xterm/xterm.js | 12344 ++++++++++------ .../plugins/ztree/jquery.ztree.exhide.min.js | 23 + apps/templates/_nav.html | 12 +- apps/templates/_nav_user.html | 5 + .../templates/users/user_granted_asset.html | 22 +- requirements/requirements.txt | 2 +- 81 files changed, 18809 insertions(+), 7278 deletions(-) create mode 100644 apps/common/tree.py create mode 100644 apps/ops/api/__init__.py rename apps/ops/{api.py => api/adhoc.py} (57%) create mode 100644 apps/ops/api/celery.py create mode 100644 apps/ops/api/command.py create mode 100644 apps/ops/forms.py create mode 100644 apps/ops/migrations/0003_auto_20181207_1744.py create mode 100644 apps/ops/migrations/0004_adhoc_run_as.py create mode 100644 apps/ops/models/command.py create mode 100644 apps/ops/templates/ops/command_execution_create.html create mode 100644 apps/ops/templates/ops/command_execution_list.html create mode 100644 apps/ops/views/__init__.py rename apps/ops/{views.py => views/adhoc.py} (68%) create mode 100644 apps/ops/views/celery.py create mode 100644 apps/ops/views/command.py create mode 100755 apps/static/css/plugins/codemirror/ambiance.css create mode 100755 apps/static/css/plugins/codemirror/codemirror.css create mode 100755 apps/static/js/plugins/codemirror/codemirror.js create mode 100755 apps/static/js/plugins/codemirror/mode/index.html create mode 100755 apps/static/js/plugins/codemirror/mode/meta.js create mode 100755 apps/static/js/plugins/codemirror/mode/shell/index.html create mode 100755 apps/static/js/plugins/codemirror/mode/shell/shell.js create mode 100755 apps/static/js/plugins/codemirror/mode/shell/test.js create mode 100644 apps/static/js/plugins/xterm/addons/attach/attach.js create mode 100644 apps/static/js/plugins/xterm/addons/attach/attach.js.map create mode 100644 apps/static/js/plugins/xterm/addons/fit/fit.js create mode 100644 apps/static/js/plugins/xterm/addons/fit/fit.js.map create mode 100644 apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.css create mode 100644 apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js create mode 100644 apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js.map create mode 100644 apps/static/js/plugins/xterm/addons/search/search.js create mode 100644 apps/static/js/plugins/xterm/addons/search/search.js.map create mode 100644 apps/static/js/plugins/xterm/addons/terminado/terminado.js create mode 100644 apps/static/js/plugins/xterm/addons/terminado/terminado.js.map create mode 100644 apps/static/js/plugins/xterm/addons/webLinks/webLinks.js create mode 100644 apps/static/js/plugins/xterm/addons/webLinks/webLinks.js.map create mode 100644 apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js create mode 100644 apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js.map create mode 100644 apps/static/js/plugins/xterm/addons/zmodem/zmodem.js create mode 100644 apps/static/js/plugins/xterm/addons/zmodem/zmodem.js.map create mode 100644 apps/static/js/plugins/ztree/jquery.ztree.exhide.min.js diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 4a3b67469..c60830fba 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# from .user import * from .label import Label from .cluster import * diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 5c8f0e2cb..cde9cde2e 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -145,6 +145,13 @@ class Asset(OrgModelMixin): return True, '' return False, warning + def support_ansible(self): + if self.platform in ("Windows", "Windows2016", "Other"): + return False + if self.protocol != 'ssh': + return False + return True + def is_unixlike(self): if self.platform not in ("Windows", "Windows2016"): return True @@ -257,7 +264,8 @@ class Asset(OrgModelMixin): from random import seed, choice import forgery_py from django.db import IntegrityError - + from .node import Node + nodes = list(Node.objects.all()) seed() for i in range(count): ip = [str(i) for i in random.sample(range(255), 4)] @@ -268,6 +276,11 @@ class Asset(OrgModelMixin): created_by='Fake') try: asset.save() + if nodes and len(nodes) > 3: + _nodes = random.sample(nodes, 3) + else: + _nodes = [Node.default_node()] + asset.nodes.set(_nodes) asset.system_users = [choice(SystemUser.objects.all()) for i in range(3)] logger.debug('Generate fake asset : %s' % asset.ip) except IntegrityError: diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 170921318..ea2059d51 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # import uuid +import re from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator @@ -35,7 +36,7 @@ class CommandFilterRule(OrgModelMixin): (TYPE_COMMAND, _('Command')), ) - ACTION_DENY, ACTION_ALLOW = range(2) + ACTION_DENY, ACTION_ALLOW, ACTION_UNKNOWN = range(3) ACTION_CHOICES = ( (ACTION_DENY, _('Deny')), (ACTION_ALLOW, _('Allow')), @@ -53,8 +54,34 @@ class CommandFilterRule(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True) created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by')) + __pattern = None + class Meta: ordering = ('-priority', 'action') + @property + def _pattern(self): + if self.__pattern: + return self.__pattern + if self.type == 'command': + regex = [] + for cmd in self.content.split('\r\n'): + cmd = cmd.replace(' ', '\s+') + regex.append(r'\b{0}\b'.format(cmd)) + self.__pattern = re.compile(r'{}'.format('|'.join(regex))) + else: + self.__pattern = re.compile(r'{0}'.format(self.content)) + return self.__pattern + + def match(self, data): + found = self._pattern.search(data) + if not found: + return self.ACTION_UNKNOWN, '' + + if self.action == self.ACTION_ALLOW: + return self.ACTION_ALLOW, found.group() + else: + return self.ACTION_DENY, found.group() + def __str__(self): return '{} % {}'.format(self.type, self.content) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 918440081..f5c8e17a1 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -173,6 +173,15 @@ class SystemUser(AssetUser): ).distinct() return rules + def is_command_can_run(self, command): + for rule in self.cmd_filter_rules: + action, matched_cmd = rule.match(command) + if action == rule.ACTION_ALLOW: + return True, None + elif action == rule.ACTION_DENY: + return False, matched_cmd + return True, None + @classmethod def get_system_user_by_id_or_cached(cls, sid): cached = cache.get(cls.cache_key.format(sid)) diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index a57da2cdc..f1be42d06 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -68,6 +68,8 @@ class NodeSerializer(serializers.ModelSerializer): @staticmethod def get_assets_amount(obj): + if hasattr(obj, 'assets_amount'): + return obj.assets_amount return obj.get_all_assets().count() @staticmethod @@ -86,7 +88,7 @@ class NodeSerializer(serializers.ModelSerializer): class NodeAssetsSerializer(serializers.ModelSerializer): - assets = serializers.PrimaryKeyRelatedField(many=True, queryset = Asset.objects.all()) + assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) class Meta: model = Node diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 71866c5d0..9028f52c3 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -58,7 +58,7 @@ def on_system_user_nodes_change(sender, instance=None, **kwargs): def on_system_user_assets_change(sender, instance=None, **kwargs): if instance and kwargs["action"] == "post_add": assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) - push_system_user_to_assets(instance, assets) + push_system_user_to_assets.delay(instance, assets) @receiver(m2m_changed, sender=Asset.nodes.through) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 57a4e7bec..4b4ec2867 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -4,14 +4,15 @@ import re import os from celery import shared_task +from ops.celery import app as celery_app from django.core.cache import cache from django.utils.translation import ugettext as _ -from common.utils import get_object_or_none, capacity_convert, \ +from common.utils import capacity_convert, \ sum_capacity, encrypt_password, get_logger from ops.celery.utils import register_as_period_task, after_app_shutdown_clean, \ after_app_ready_start -from ops.celery import app as celery_app +from orgs.utils import set_to_root_org from .models import SystemUser, AdminUser, Asset from . import const @@ -20,34 +21,34 @@ from . import const FORKS = 10 TIMEOUT = 60 logger = get_logger(__file__) -CACHE_MAX_TIME = 60*60*60 +CACHE_MAX_TIME = 60*60*2 disk_pattern = re.compile(r'^hd|sd|xvd|vd') PERIOD_TASK = os.environ.get("PERIOD_TASK", "off") @shared_task -def set_assets_hardware_info(result, **kwargs): +def set_assets_hardware_info(assets, result, **kwargs): """ Using ops task run result, to update asset info @shared_task must be exit, because we using it as a task callback, is must be a celery task also + :param assets: :param result: :param kwargs: {task_name: ""} :return: """ result_raw = result[0] assets_updated = [] - for hostname, info in result_raw.get('ok', {}).items(): + success_result = result_raw.get('ok', {}) + + for asset in assets: + hostname = asset.hostname + info = success_result.get(hostname, {}) info = info.get('setup', {}).get('ansible_facts', {}) if not info: - logger.error("Get asset info failed: {}".format(hostname)) + logger.error(_("Get asset info failed: {}").format(hostname)) continue - - asset = Asset.objects.get_object_by_fullname(hostname) - if not asset: - continue - ___vendor = info.get('ansible_system_vendor', 'Unknown') ___model = info.get('ansible_product_name', 'Unknown') ___sn = info.get('ansible_product_serial', 'Unknown') @@ -94,34 +95,43 @@ def update_assets_hardware_info_util(assets, task_name=None): from ops.utils import update_or_create_ansible_task if task_name is None: task_name = _("Update some assets hardware info") - # task_name = _("更新资产硬件信息") tasks = const.UPDATE_ASSETS_HARDWARE_TASKS - hostname_list = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] - if not hostname_list: - logger.info("Not hosts get, may be asset is not active or not unixlike platform") + hosts = [] + for asset in assets: + if not asset.is_active: + msg = _("Asset has been disabled, skipped: {}").format(asset) + logger.info(msg) + continue + if not asset.support_ansible(): + msg = _("Asset may not be support ansible, skipped: {}").format(asset) + logger.info(msg) + continue + hosts.append(asset) + if not hosts: + logger.info(_("No assets matched, stop task")) return {} + created_by = str(assets[0].org_id) task, created = update_or_create_ansible_task( - task_name, hosts=hostname_list, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, created_by='System', + task_name, hosts=hosts, tasks=tasks, created_by=created_by, + pattern='all', options=const.TASK_OPTIONS, run_as_admin=True, ) result = task.run() # Todo: may be somewhere using # Manual run callback function - set_assets_hardware_info(result) + set_assets_hardware_info(assets, result) return result @shared_task def update_asset_hardware_info_manual(asset): - task_name = _("Update asset hardware info") + task_name = _("Update asset hardware info: {}").format(asset.hostname) # task_name = _("更新资产硬件信息") - return update_assets_hardware_info_util([asset], task_name=task_name) + return update_assets_hardware_info_util( + [asset], task_name=task_name + ) -@celery_app.task -@register_as_period_task(interval=3600) -@after_app_ready_start -@after_app_shutdown_clean +@shared_task def update_assets_hardware_info_period(): """ Update asset hardware period task @@ -132,25 +142,28 @@ def update_assets_hardware_info_period(): return from ops.utils import update_or_create_ansible_task + from orgs.models import Organization + orgs = Organization.objects.all().values_list('id', flat=True) + orgs.append('') task_name = _("Update assets hardware info period") - # task_name = _("定期更新资产硬件信息") - hostname_list = [ - asset.fullname for asset in Asset.objects.all() - if asset.is_active and asset.is_unixlike() - ] - tasks = const.UPDATE_ASSETS_HARDWARE_TASKS - - # Only create, schedule by celery beat - update_or_create_ansible_task( - task_name, hosts=hostname_list, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, created_by='System', - interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name, - ) + # for org_id in orgs: + # org_id = str(org_id) + # hostname_list = [ + # asset for asset in Asset.objects.all() + # if asset.is_active and asset.is_unixlike() + # ] + # tasks = const.UPDATE_ASSETS_HARDWARE_TASKS + # + # # Only create, schedule by celery beat + # update_or_create_ansible_task( + # task_name, hosts=hostname_list, tasks=tasks, pattern='all', + # options=const.TASK_OPTIONS, run_as_admin=True, created_by='System', + # interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name, + # ) ## ADMIN USER CONNECTIVE ## -@shared_task def set_admin_user_connectability_info(result, **kwargs): admin_user = kwargs.get("admin_user") task_name = kwargs.get("task_name") @@ -182,36 +195,39 @@ def test_admin_user_connectability_util(admin_user, task_name): from ops.utils import update_or_create_ansible_task assets = admin_user.get_related_assets() - hosts = [asset.fullname for asset in assets - if asset.is_active and asset.is_unixlike()] + hosts = [] + for asset in assets: + if not asset.is_active: + msg = _("Asset has been disabled, skipped: {}").format(asset) + logger.info(msg) + continue + if not asset.support_ansible(): + msg = _("Asset may not be support ansible, skipped: {}").format(asset) + logger.info(msg) + continue + hosts.append(asset) if not hosts: - return + logger.info(_("No assets matched, stop task")) + return {} tasks = const.TEST_ADMIN_USER_CONN_TASKS task, created = update_or_create_ansible_task( task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, created_by='System', + options=const.TASK_OPTIONS, run_as_admin=True, created_by=admin_user.org_id, ) result = task.run() set_admin_user_connectability_info(result, admin_user=admin_user.name) return result -@celery_app.task +@shared_task @register_as_period_task(interval=3600) -@after_app_ready_start -@after_app_shutdown_clean def test_admin_user_connectability_period(): """ A period task that update the ansible task period """ - if PERIOD_TASK != "on": - logger.debug("Period task disabled, test admin user connectability pass") - return - admin_users = AdminUser.objects.all() for admin_user in admin_users: - task_name = _("Test admin user connectability period: {}".format(admin_user.name)) - # task_name = _("定期测试管理账号可连接性: {}".format(admin_user.name)) + task_name = _("Test admin user connectability period: {}").format(admin_user.name) test_admin_user_connectability_util(admin_user, task_name) @@ -229,14 +245,25 @@ def test_asset_connectability_util(assets, task_name=None): if task_name is None: task_name = _("Test assets connectability") # task_name = _("测试资产可连接性") - hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] + hosts = [] + for asset in assets: + if not asset.is_active: + msg = _("Asset has been disabled, skip: {}").format(asset) + logger.info(msg) + continue + if not asset.support_ansible(): + msg = _("Asset may not be support ansible, skip: {}").format(asset) + logger.info(msg) + continue + hosts.append(asset) if not hosts: - logger.info("No hosts, passed") + logger.info(_("No assets, task stop")) return {} tasks = const.TEST_ADMIN_USER_CONN_TASKS + created_by = assets[0].org_id task, created = update_or_create_ansible_task( task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, created_by='System', + options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by, ) result = task.run() summary = result[1] @@ -250,7 +277,8 @@ def test_asset_connectability_util(assets, task_name=None): @shared_task def test_asset_connectability_manual(asset): - summary = test_asset_connectability_util([asset]) + task_name = _("Test assets connectability: {}").format(asset) + summary = test_asset_connectability_util([asset], task_name=task_name) if summary.get('dark'): return False, summary['dark'] @@ -267,7 +295,7 @@ def set_system_user_connectablity_info(result, **kwargs): system_user = kwargs.get("system_user") if system_user is None: system_user = task_name.split(":")[-1] - cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(system_user) + cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(str(system_user.id)) cache.set(cache_key, summary, CACHE_MAX_TIME) @@ -281,19 +309,28 @@ def test_system_user_connectability_util(system_user, assets, task_name): :return: """ from ops.utils import update_or_create_ansible_task - # assets = system_user.get_assets() - hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] + hosts = [] tasks = const.TEST_SYSTEM_USER_CONN_TASKS + for asset in assets: + if not asset.is_active: + msg = _("Asset has been disabled, skip: {}").format(asset) + logger.info(msg) + continue + if not asset.support_ansible(): + msg = _("Asset may not be support ansible, skip: {}").format(asset) + logger.info(msg) + continue + hosts.append(asset) if not hosts: - logger.info("No hosts, passed") + logger.info(_("No assets matched, stop task")) return {} task, created = update_or_create_ansible_task( task_name, hosts=hosts, tasks=tasks, pattern='all', options=const.TASK_OPTIONS, - run_as=system_user.name, created_by="System", + run_as=system_user, created_by=system_user.org_id, ) result = task.run() - set_system_user_connectablity_info(result, system_user=system_user.name) + set_system_user_connectablity_info(result, system_user=system_user) return result @@ -313,17 +350,13 @@ def test_system_user_connectability_a_asset(system_user, asset): @shared_task -@register_as_period_task(interval=3600) -@after_app_ready_start -@after_app_shutdown_clean def test_system_user_connectability_period(): if PERIOD_TASK != "on": logger.debug("Period task disabled, test system user connectability pass") return - system_users = SystemUser.objects.all() for system_user in system_users: - task_name = _("Test system user connectability period: {}".format(system_user)) + task_name = _("Test system user connectability period: {}").format(system_user) # task_name = _("定期测试系统用户可连接性: {}".format(system_user)) test_system_user_connectability_util(system_user, task_name) @@ -374,28 +407,33 @@ def get_push_system_user_tasks(system_user): @shared_task -def push_system_user_util(system_users, assets, task_name): +def push_system_user_util(system_user, assets, task_name): from ops.utils import update_or_create_ansible_task - tasks = [] - for system_user in system_users: - if not system_user.is_need_push(): - msg = "push system user `{}` passed, may be not auto push or ssh " \ - "protocol is not ssh".format(system_user.name) + if not system_user.is_need_push(): + msg = _("Push system user task skip, auto push not enable or " + "protocol is not ssh: {}").format(system_user.name) + logger.info(msg) + return + + tasks = get_push_system_user_tasks(system_user) + hosts = [] + for asset in assets: + if not asset.is_active: + msg = _("Asset has been disabled, skip: {}").format(asset) logger.info(msg) continue - tasks.extend(get_push_system_user_tasks(system_user)) - - if not tasks: - logger.info("Not tasks, passed") - return {} - - hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] + if not asset.support_ansible(): + msg = _("Asset may not be support ansible, skip: {}").format(asset) + logger.info(msg) + continue + hosts.append(asset) if not hosts: - logger.info("Not hosts, passed") + logger.info(_("No assets matched, stop task")) return {} task, created = update_or_create_ansible_task( task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, created_by='System' + options=const.TASK_OPTIONS, run_as_admin=True, + created_by=system_user.org_id, ) return task.run() @@ -403,24 +441,22 @@ def push_system_user_util(system_users, assets, task_name): @shared_task def push_system_user_to_assets_manual(system_user): assets = system_user.get_assets() - # task_name = "推送系统用户到入资产: {}".format(system_user.name) task_name = _("Push system users to assets: {}").format(system_user.name) - return push_system_user_util([system_user], assets, task_name=task_name) + return push_system_user_util(system_user, assets, task_name=task_name) @shared_task def push_system_user_a_asset_manual(system_user, asset): task_name = _("Push system users to asset: {} => {}").format( - system_user.name, asset.fullname + system_user.name, asset ) - return push_system_user_util([system_user], [asset], task_name=task_name) + return push_system_user_util(system_user, [asset], task_name=task_name) @shared_task def push_system_user_to_assets(system_user, assets): - # task_name = _("推送系统用户到入资产: {}").format(system_user.name) task_name = _("Push system users to assets: {}").format(system_user.name) - return push_system_user_util.delay([system_user], assets, task_name) + return push_system_user_util(system_user, assets, task_name) # @shared_task diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 03eadde8e..7e093688f 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -452,7 +452,7 @@ $(document).ready(function(){ $.each(rows, function (index, obj) { assets.push(obj.id) }); - var _node_id = current_node ? current_node : null; + var _node_id = current_node ? current_node.node_id : null; $.ajax({ url: "{% url "assets:asset-export" %}", method: 'POST', diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html index 8a563cadc..a88b9d839 100644 --- a/apps/assets/templates/assets/user_asset_list.html +++ b/apps/assets/templates/assets/user_asset_list.html @@ -71,7 +71,6 @@ function initTable() { } else { inited = true; } - console.log("init table") url = "{% url 'api-perms:my-assets' %}"; var options = { ele: $('#user_assets_table'), @@ -108,7 +107,8 @@ function initTable() { function onSelected(event, treeNode) { url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}'; - url = url.replace("{{ DEFAULT_PK }}", treeNode.node_id); + var node_id = treeNode.meta.node.id; + url = url.replace("{{ DEFAULT_PK }}", node_id); setCookie('node_selected', treeNode.id); asset_table.ajax.url(url); asset_table.ajax.reload(); @@ -131,21 +131,10 @@ function initTree() { }; var zNodes = []; - $.get("{% url 'api-perms:my-nodes' %}", function(data, status){ - $.each(data, function (index, value) { - value["node_id"] = value["id"]; - value["id"] = value["tree_id"]; - if (value["tree_id"] !== value["tree_parent"]) { - value["pId"] = value["tree_parent"]; - } - value["isParent"] = value["is_node"]; - value['name'] = value['value']; - }); + $.get("{% url 'api-perms:my-nodes-assets-as-tree' %}?show_assets=0", function(data, status){ zNodes = data; $.fn.zTree.init($("#assetTree"), setting, zNodes); zTree = $.fn.zTree.getZTreeObj("assetTree"); - var root = zTree.getNodes()[0]; - zTree.expandNode(root); }); } diff --git a/apps/assets/utils.py b/apps/assets/utils.py index 749e4c3c3..c5aac6e7a 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -9,7 +9,11 @@ from .models import Asset, SystemUser, Label def get_assets_by_id_list(id_list): - return Asset.objects.filter(id__in=id_list) + return Asset.objects.filter(id__in=id_list).filter(is_active=True) + + +def get_system_users_by_id_list(id_list): + return SystemUser.objects.filter(id__in=id_list) def get_assets_by_fullname_list(hostname_list): @@ -21,6 +25,11 @@ def get_system_user_by_name(name): return system_user +def get_system_user_by_id(id): + system_user = get_object_or_none(SystemUser, id=id) + return system_user + + class LabelFilter: def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 572772dc5..b34508dbd 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -216,6 +216,7 @@ class AssetExportView(LoginRequiredMixin, View): return HttpResponse('Json object not valid', status=400) if not assets_id: + print(node_id) node = get_object_or_none(Node, id=node_id) if node_id else Node.root() assets = node.get_all_assets() for asset in assets: diff --git a/apps/audits/urls/view_urls.py b/apps/audits/urls/view_urls.py index f90a8a7db..473a2d83f 100644 --- a/apps/audits/urls/view_urls.py +++ b/apps/audits/urls/view_urls.py @@ -13,4 +13,5 @@ urlpatterns = [ path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'), path('operate-log/', views.OperateLogListView.as_view(), name='operate-log-list'), path('password-change-log/', views.PasswordChangeLogList.as_view(), name='password-change-log-list'), + path('command-execution-log/', views.CommandExecutionListView.as_view(), name='command-execution-log-list'), ] diff --git a/apps/audits/views.py b/apps/audits/views.py index f41b674e5..ed109352c 100644 --- a/apps/audits/views.py +++ b/apps/audits/views.py @@ -7,6 +7,8 @@ from common.mixins import DatetimeSearchMixin from common.permissions import AdminUserRequiredMixin from orgs.utils import current_org +from ops.views import CommandExecutionListView as UserCommandExecutionListView +from users.models import User from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog @@ -187,7 +189,7 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): def get_context_data(self, **kwargs): context = { - 'app': _('Users'), + 'app': _('Audits'), 'action': _('Login log'), 'date_from': self.date_from, 'date_to': self.date_to, @@ -196,4 +198,35 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): 'user_list': self.get_org_users(), } kwargs.update(context) - return super().get_context_data(**kwargs) \ No newline at end of file + return super().get_context_data(**kwargs) + + +class CommandExecutionListView(UserCommandExecutionListView): + user_id = None + + def get_queryset(self): + queryset = self._get_queryset() + self.user_id = self.request.GET.get('user') + org_users = self.get_user_list() + if self.user_id: + queryset = queryset.filter(user=self.user_id) + else: + queryset = queryset.filter(user__in=org_users) + return queryset + + def get_user_list(self): + users = current_org.get_org_users() + return users + + def get_context_data(self, **kwargs): + context = { + 'app': _('Audits'), + 'action': _('Command execution list'), + 'date_from': self.date_from, + 'date_to': self.date_to, + 'user_list': self.get_user_list(), + 'keyword': self.keyword, + 'user_id': self.user_id, + } + kwargs.update(context) + return super().get_context_data(**kwargs) diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py index 92d4bce97..207dd2ce5 100644 --- a/apps/common/signals_handler.py +++ b/apps/common/signals_handler.py @@ -65,6 +65,8 @@ def refresh_all_settings_on_django_ready(sender, **kwargs): @receiver(pre_save, dispatch_uid="my_unique_identifier") def on_create_set_created_by(sender, instance=None, **kwargs): + if getattr(instance, '_ignore_auto_created_by', False) is True: + return if hasattr(instance, 'created_by') and not instance.created_by: if current_request and current_request.user.is_authenticated: instance.created_by = current_request.user.name diff --git a/apps/common/templatetags/common_tags.py b/apps/common/templatetags/common_tags.py index d77263955..d9f2c373d 100644 --- a/apps/common/templatetags/common_tags.py +++ b/apps/common/templatetags/common_tags.py @@ -111,3 +111,13 @@ def sort(data): @register.filter def subtract(value, arg): return value - arg + + +@register.filter +def state_show(state): + success = ' ' + failed = ' ' + if state: + return success + else: + return failed diff --git a/apps/common/tree.py b/apps/common/tree.py new file mode 100644 index 000000000..df8da256f --- /dev/null +++ b/apps/common/tree.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import serializers + + +class TreeNode: + id = "" + name = "" + comment = "" + title = "" + isParent = False + pId = "" + open = False + iconSkin = "" + meta = {} + + _tree = None + + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + + @classmethod + def root(cls): + return cls(id="#", name='Root', title='Root', isParent=True, open=True) + + def get_parent(self): + return self._tree.get_node(self.pId) + + def get_parents(self): + parent = self.get_parent() + if parent == self._tree.root: + return [] + parents = [parent] + parents.extend(parent.get_parents()) + return parents + + def add_child(self, child): + self._tree.add_node(child, self) + + def __str__(self): + return '<{}: {}>'.format(self.id, self.name) + + __repr__ = __str__ + + def __gt__(self, other): + if self.isParent and not other.isParent: + return False + return self.id > other.id + + def __eq__(self, other): + return self.id == other.id + + def __lt__(self, other): + if self.isParent and not other.isParent: + return True + return self.id < other.id + + +class Tree: + def __init__(self): + self.nodes = {} + self.root = TreeNode.root() + self.root._tree = self + + def add_node(self, node, parent=None): + node._tree = self + + if not parent: + parent = self.root + if parent.id not in self.nodes and parent != self.root: + raise ValueError("Parent not in tree") + elif node in parent.get_parents(): + raise ValueError("Parent must not be node parent") + node.pId = parent.id + parent.isParent = True + self.nodes[node.id] = node + + def get_nodes(self): + return sorted(self.nodes.values()) + + def get_node(self, tid): + return self.nodes.get(tid) or TreeNode.root() + + +class TreeNodeSerializer(serializers.Serializer): + id = serializers.CharField(max_length=128) + name = serializers.CharField(max_length=128) + title = serializers.CharField(max_length=128) + pId = serializers.CharField(max_length=128) + isParent = serializers.BooleanField(default=False) + open = serializers.BooleanField(default=False) + iconSkin = serializers.CharField(max_length=128, allow_blank=True) + meta = serializers.JSONField() diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 6e8970e9cca6c1e254be8d95fa6d854ca3838c20..250375107f5741ce064735d7c885afe6591c0366 100644 GIT binary patch delta 18829 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 F{{z&#X&FMK-#$pXUuLO3(k8my) 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b90c5bc4a..3aa1ee7f2 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-26 18:31+0800\n" +"POT-Creation-Date: 2018-12-07 18:11+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -44,7 +44,7 @@ msgstr "节点管理" #: assets/forms/asset.py:116 assets/models/asset.py:88 #: assets/models/cluster.py:19 assets/models/user.py:73 #: assets/templates/assets/asset_detail.html:77 templates/_nav.html:24 -#: xpack/plugins/cloud/models.py:137 +#: xpack/plugins/cloud/models.py:123 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:67 #: xpack/plugins/orgs/templates/orgs/org_list.html:18 msgid "Admin user" @@ -63,7 +63,7 @@ msgstr "标签" #: assets/forms/asset.py:37 assets/forms/asset.py:76 assets/models/asset.py:79 #: assets/models/domain.py:24 assets/models/domain.py:50 -#: assets/templates/assets/user_asset_list.html:168 +#: assets/templates/assets/user_asset_list.html:157 #: xpack/plugins/orgs/templates/orgs/org_list.html:17 msgid "Domain" msgstr "网域" @@ -75,7 +75,7 @@ msgstr "网域" #: perms/forms.py:44 perms/models.py:79 #: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:151 -#: xpack/plugins/cloud/models.py:136 +#: xpack/plugins/cloud/models.py:122 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:63 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 msgid "Node" @@ -109,13 +109,13 @@ msgstr "选择资产" #: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/system_user_asset.html:52 -#: assets/templates/assets/user_asset_list.html:163 +#: assets/templates/assets/user_asset_list.html:152 #: common/templates/common/replay_storage_create.html:60 msgid "Port" msgstr "端口" #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:253 assets/templates/assets/admin_user_list.html:28 +#: assets/models/asset.py:260 assets/templates/assets/admin_user_list.html:28 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/label_list.html:16 @@ -131,7 +131,7 @@ msgstr "端口" #: terminal/templates/terminal/command_list.html:73 #: terminal/templates/terminal/session_list.html:41 #: terminal/templates/terminal/session_list.html:72 -#: xpack/plugins/cloud/models.py:207 +#: xpack/plugins/cloud/models.py:186 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 #: xpack/plugins/orgs/templates/orgs/org_list.html:16 msgid "Asset" @@ -143,7 +143,7 @@ msgstr "不能包含特殊字符" #: assets/forms/domain.py:59 assets/forms/user.py:80 assets/forms/user.py:143 #: assets/models/base.py:22 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:19 assets/models/domain.py:18 +#: assets/models/cmd_filter.py:20 assets/models/domain.py:18 #: assets/models/group.py:20 assets/models/label.py:18 #: assets/templates/assets/admin_user_detail.html:56 #: assets/templates/assets/admin_user_list.html:26 @@ -159,7 +159,7 @@ msgstr "不能包含特殊字符" #: common/templates/common/replay_storage_create.html:44 #: common/templates/common/terminal_setting.html:80 #: common/templates/common/terminal_setting.html:102 ops/models/adhoc.py:37 -#: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35 +#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:35 #: orgs/models.py:12 perms/models.py:28 #: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_list.html:53 @@ -173,7 +173,7 @@ msgstr "不能包含特殊字符" #: users/templates/users/user_list.html:23 #: users/templates/users/user_profile.html:51 #: users/templates/users/user_pubkey_update.html:53 -#: xpack/plugins/cloud/models.py:40 xpack/plugins/cloud/models.py:132 +#: xpack/plugins/cloud/models.py:48 xpack/plugins/cloud/models.py:118 #: xpack/plugins/cloud/templates/cloud/account_detail.html:52 #: xpack/plugins/cloud/templates/cloud/account_list.html:12 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:55 @@ -264,7 +264,7 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写" #: assets/templates/assets/domain_gateway_list.html:57 #: assets/templates/assets/system_user_asset.html:51 #: assets/templates/assets/user_asset_list.html:46 -#: assets/templates/assets/user_asset_list.html:162 +#: assets/templates/assets/user_asset_list.html:151 #: audits/templates/audits/login_log_list.html:52 common/forms.py:135 #: perms/templates/perms/asset_permission_asset.html:55 #: users/templates/users/user_granted_asset.html:45 @@ -278,7 +278,7 @@ msgstr "IP" #: assets/templates/assets/asset_list.html:92 #: assets/templates/assets/system_user_asset.html:50 #: assets/templates/assets/user_asset_list.html:45 -#: assets/templates/assets/user_asset_list.html:161 common/forms.py:134 +#: assets/templates/assets/user_asset_list.html:150 common/forms.py:134 #: perms/templates/perms/asset_permission_asset.html:54 #: users/templates/users/user_granted_asset.html:44 #: users/templates/users/user_group_granted_asset.html:44 @@ -290,20 +290,20 @@ msgstr "主机名" #: assets/templates/assets/domain_gateway_list.html:59 #: assets/templates/assets/system_user_detail.html:70 #: assets/templates/assets/system_user_list.html:31 -#: assets/templates/assets/user_asset_list.html:164 +#: assets/templates/assets/user_asset_list.html:153 #: terminal/templates/terminal/session_list.html:75 msgid "Protocol" msgstr "协议" #: assets/models/asset.py:77 assets/templates/assets/asset_detail.html:101 -#: assets/templates/assets/user_asset_list.html:165 +#: assets/templates/assets/user_asset_list.html:154 msgid "Platform" msgstr "系统平台" -#: assets/models/asset.py:84 assets/models/cmd_filter.py:20 +#: assets/models/asset.py:84 assets/models/cmd_filter.py:21 #: assets/models/domain.py:52 assets/models/label.py:21 #: assets/templates/assets/asset_detail.html:109 -#: assets/templates/assets/user_asset_list.html:169 +#: assets/templates/assets/user_asset_list.html:158 msgid "Is active" msgstr "激活" @@ -356,7 +356,7 @@ msgid "Disk info" msgstr "硬盘信息" #: assets/models/asset.py:115 assets/templates/assets/asset_detail.html:105 -#: assets/templates/assets/user_asset_list.html:166 +#: assets/templates/assets/user_asset_list.html:155 msgid "OS" msgstr "操作系统" @@ -379,8 +379,8 @@ msgid "Labels" msgstr "标签管理" #: assets/models/asset.py:127 assets/models/base.py:30 -#: assets/models/cluster.py:28 assets/models/cmd_filter.py:24 -#: assets/models/cmd_filter.py:54 assets/models/group.py:21 +#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 +#: assets/models/cmd_filter.py:55 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/asset_detail.html:121 #: assets/templates/assets/cmd_filter_detail.html:77 @@ -389,7 +389,7 @@ msgstr "标签管理" #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:37 #: perms/models.py:84 perms/templates/perms/asset_permission_detail.html:98 #: users/models/user.py:94 users/templates/users/user_detail.html:111 -#: xpack/plugins/cloud/models.py:46 xpack/plugins/cloud/models.py:140 +#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:126 msgid "Created by" msgstr "创建者" @@ -399,12 +399,12 @@ msgstr "创建者" #: assets/templates/assets/cmd_filter_detail.html:69 #: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/system_user_detail.html:96 -#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63 +#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 #: orgs/models.py:16 perms/models.py:38 perms/models.py:85 #: perms/templates/perms/asset_permission_detail.html:94 #: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 #: users/templates/users/user_group_detail.html:63 -#: xpack/plugins/cloud/models.py:47 xpack/plugins/cloud/models.py:141 +#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 #: xpack/plugins/cloud/templates/cloud/account_detail.html:68 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:79 #: xpack/plugins/orgs/templates/orgs/org_detail.html:60 @@ -412,8 +412,8 @@ msgid "Date created" msgstr "创建日期" #: assets/models/asset.py:132 assets/models/base.py:27 -#: assets/models/cluster.py:29 assets/models/cmd_filter.py:21 -#: assets/models/cmd_filter.py:51 assets/models/domain.py:19 +#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 +#: assets/models/cmd_filter.py:52 assets/models/domain.py:19 #: assets/models/domain.py:51 assets/models/group.py:23 #: assets/models/label.py:22 assets/templates/assets/admin_user_detail.html:72 #: assets/templates/assets/admin_user_list.html:32 @@ -426,7 +426,7 @@ msgstr "创建日期" #: assets/templates/assets/domain_list.html:28 #: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_list.html:37 -#: assets/templates/assets/user_asset_list.html:170 common/models.py:34 +#: assets/templates/assets/user_asset_list.html:159 common/models.py:34 #: ops/models/adhoc.py:43 orgs/models.py:17 perms/models.py:39 #: perms/models.py:86 perms/templates/perms/asset_permission_detail.html:102 #: terminal/models.py:28 terminal/templates/terminal/terminal_detail.html:63 @@ -434,8 +434,8 @@ msgstr "创建日期" #: users/templates/users/user_detail.html:127 #: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_list.html:14 -#: users/templates/users/user_profile.html:134 xpack/plugins/cloud/models.py:45 -#: xpack/plugins/cloud/models.py:138 +#: users/templates/users/user_profile.html:134 xpack/plugins/cloud/models.py:53 +#: xpack/plugins/cloud/models.py:124 #: xpack/plugins/cloud/templates/cloud/account_detail.html:72 #: xpack/plugins/cloud/templates/cloud/account_list.html:15 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:71 @@ -511,11 +511,12 @@ msgstr "北京电信" msgid "BGP full netcom" msgstr "BGP全网通" -#: assets/models/cmd_filter.py:34 +#: assets/models/cmd_filter.py:35 msgid "Regex" msgstr "正则表达式" -#: assets/models/cmd_filter.py:35 terminal/models.py:144 +#: assets/models/cmd_filter.py:36 ops/models/command.py:19 +#: ops/templates/ops/command_execution_list.html:46 terminal/models.py:144 #: terminal/templates/terminal/command_list.html:55 #: terminal/templates/terminal/command_list.html:71 #: terminal/templates/terminal/session_detail.html:48 @@ -523,19 +524,19 @@ msgstr "正则表达式" msgid "Command" msgstr "命令" -#: assets/models/cmd_filter.py:40 +#: assets/models/cmd_filter.py:41 msgid "Deny" msgstr "拒绝" -#: assets/models/cmd_filter.py:41 +#: assets/models/cmd_filter.py:42 msgid "Allow" msgstr "允许" -#: assets/models/cmd_filter.py:45 +#: assets/models/cmd_filter.py:46 msgid "Filter" msgstr "过滤器" -#: assets/models/cmd_filter.py:46 +#: assets/models/cmd_filter.py:47 #: assets/templates/assets/cmd_filter_rule_list.html:58 #: audits/templates/audits/login_log_list.html:50 #: common/templates/common/command_storage_create.html:31 @@ -545,25 +546,25 @@ msgstr "过滤器" msgid "Type" msgstr "类型" -#: assets/models/cmd_filter.py:47 assets/models/user.py:115 +#: assets/models/cmd_filter.py:48 assets/models/user.py:115 #: assets/templates/assets/cmd_filter_rule_list.html:60 msgid "Priority" msgstr "优先级" -#: assets/models/cmd_filter.py:47 +#: assets/models/cmd_filter.py:48 msgid "1-100, the higher will be match first" msgstr "优先级可选范围为1-100,1最低优先级,100最高优先级" -#: assets/models/cmd_filter.py:49 +#: assets/models/cmd_filter.py:50 #: assets/templates/assets/cmd_filter_rule_list.html:59 msgid "Content" msgstr "内容" -#: assets/models/cmd_filter.py:49 +#: assets/models/cmd_filter.py:50 msgid "One line one command" msgstr "每行一个命令" -#: assets/models/cmd_filter.py:50 +#: assets/models/cmd_filter.py:51 #: assets/templates/assets/admin_user_list.html:33 #: assets/templates/assets/asset_list.html:97 #: assets/templates/assets/cmd_filter_list.html:28 @@ -611,7 +612,9 @@ msgstr "默认资产组" #: audits/templates/audits/operate_log_list.html:33 #: audits/templates/audits/operate_log_list.html:66 #: audits/templates/audits/password_change_log_list.html:33 -#: audits/templates/audits/password_change_log_list.html:50 perms/forms.py:28 +#: audits/templates/audits/password_change_log_list.html:50 +#: ops/templates/ops/command_execution_list.html:22 +#: ops/templates/ops/command_execution_list.html:45 perms/forms.py:28 #: perms/models.py:29 #: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/asset_permission_list.html:54 @@ -692,7 +695,7 @@ msgstr "Shell" msgid "Login mode" msgstr "登录模式" -#: assets/models/user.py:191 assets/templates/assets/user_asset_list.html:167 +#: assets/models/user.py:200 assets/templates/assets/user_asset_list.html:156 #: audits/models.py:19 audits/templates/audits/ftp_log_list.html:49 #: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:40 #: perms/models.py:33 perms/models.py:81 @@ -713,47 +716,85 @@ msgstr "系统用户" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/tasks.py:96 +#: assets/tasks.py:50 +msgid "Get asset info failed: {}" +msgstr "获取资产信息失败:{}" + +#: assets/tasks.py:97 msgid "Update some assets hardware info" msgstr "更新资产硬件信息" -#: assets/tasks.py:116 -msgid "Update asset hardware info" -msgstr "更新资产硬件信息" +#: assets/tasks.py:102 assets/tasks.py:201 +msgid "Asset has been disabled, skipped: {}" +msgstr "资产或许不支持ansible, 跳过: {}" -#: assets/tasks.py:135 +#: assets/tasks.py:106 assets/tasks.py:205 +msgid "Asset may not be support ansible, skipped: {}" +msgstr "资产或许不支持ansible, 跳过: {}" + +#: assets/tasks.py:111 assets/tasks.py:210 assets/tasks.py:325 +#: assets/tasks.py:431 +msgid "No assets matched, stop task" +msgstr "没有匹配到资产,结束任务" + +#: assets/tasks.py:127 +msgid "Update asset hardware info: {}" +msgstr "更新资产硬件信息: {}" + +#: assets/tasks.py:148 msgid "Update assets hardware info period" msgstr "定期更新资产硬件信息" -#: assets/tasks.py:213 +#: assets/tasks.py:230 msgid "Test admin user connectability period: {}" msgstr "定期测试管理账号可连接性: {}" -#: assets/tasks.py:220 +#: assets/tasks.py:236 msgid "Test admin user connectability: {}" msgstr "测试管理行号可连接性: {}" -#: assets/tasks.py:230 +#: assets/tasks.py:246 msgid "Test assets connectability" msgstr "测试资产可连接性" -#: assets/tasks.py:302 +#: assets/tasks.py:251 assets/tasks.py:316 assets/tasks.py:422 +msgid "Asset has been disabled, skip: {}" +msgstr "资产被禁用,跳过:{}" + +#: assets/tasks.py:255 assets/tasks.py:320 assets/tasks.py:426 +msgid "Asset may not be support ansible, skip: {}" +msgstr "资产或许不支持ansible, 跳过: {}" + +#: assets/tasks.py:260 +msgid "No assets, task stop" +msgstr "没有匹配到资产,结束任务" + +#: assets/tasks.py:280 +msgid "Test assets connectability: {}" +msgstr "测试资产可连接性: {}" + +#: assets/tasks.py:339 msgid "Test system user connectability: {}" msgstr "测试系统用户可连接性: {}" -#: assets/tasks.py:309 +#: assets/tasks.py:346 msgid "Test system user connectability: {} => {}" msgstr "测试系统用户可连接性: {} => {}" -#: assets/tasks.py:326 +#: assets/tasks.py:359 msgid "Test system user connectability period: {}" msgstr "定期测试系统用户可连接性: {}" -#: assets/tasks.py:407 assets/tasks.py:422 +#: assets/tasks.py:413 +msgid "" +"Push system user task skip, auto push not enable or protocol is not ssh: {}" +msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh: {}" + +#: assets/tasks.py:444 assets/tasks.py:458 msgid "Push system users to assets: {}" msgstr "推送系统用户到入资产: {}" -#: assets/tasks.py:413 +#: assets/tasks.py:450 msgid "Push system users to asset: {} => {}" msgstr "推送系统用户到入资产: {} => {}" @@ -1544,7 +1585,7 @@ msgstr "批量更新资产" msgid "Update asset" msgstr "更新资产" -#: assets/views/asset.py:292 +#: assets/views/asset.py:293 msgid "already exists" msgstr "已经存在" @@ -1639,6 +1680,7 @@ msgid "Filename" msgstr "文件名" #: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76 +#: ops/templates/ops/command_execution_list.html:49 #: ops/templates/ops/task_list.html:39 users/models/authentication.py:73 #: users/templates/users/user_detail.html:456 xpack/plugins/cloud/api.py:61 msgid "Success" @@ -1664,6 +1706,7 @@ msgstr "修改者" #: audits/templates/audits/ftp_log_list.html:77 #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:61 +#: ops/templates/ops/command_execution_list.html:50 #: ops/templates/ops/task_history.html:58 perms/models.py:35 #: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:148 #: terminal/templates/terminal/session_list.html:78 @@ -1679,6 +1722,8 @@ msgstr "选择用户" #: audits/templates/audits/login_log_list.html:40 #: audits/templates/audits/operate_log_list.html:58 #: audits/templates/audits/password_change_log_list.html:42 +#: ops/templates/ops/command_execution_list.html:30 +#: ops/templates/ops/command_execution_list.html:35 #: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26 #: templates/_base_list.html:43 templates/_header_bar.html:8 #: terminal/templates/terminal/command_list.html:60 @@ -1691,7 +1736,7 @@ msgstr "搜索" #: audits/templates/audits/login_log_list.html:48 #: ops/templates/ops/adhoc_detail.html:49 #: ops/templates/ops/adhoc_history_detail.html:49 -#: ops/templates/ops/task_detail.html:55 +#: ops/templates/ops/task_detail.html:56 #: terminal/templates/terminal/session_list.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:64 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 @@ -1713,14 +1758,14 @@ msgid "MFA" msgstr "MFA" #: audits/templates/audits/login_log_list.html:55 -#: users/models/authentication.py:83 xpack/plugins/cloud/models.py:192 +#: users/models/authentication.py:83 xpack/plugins/cloud/models.py:171 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69 msgid "Reason" msgstr "原因" #: audits/templates/audits/login_log_list.html:56 -#: users/models/authentication.py:84 xpack/plugins/cloud/models.py:191 -#: xpack/plugins/cloud/models.py:208 +#: users/models/authentication.py:84 xpack/plugins/cloud/models.py:170 +#: xpack/plugins/cloud/models.py:187 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 msgid "Status" @@ -1739,35 +1784,31 @@ msgstr "日期" msgid "Datetime" msgstr "日期" -#: audits/views.py:66 audits/views.py:110 audits/views.py:143 -#: templates/_nav.html:73 +#: audits/views.py:68 audits/views.py:112 audits/views.py:148 +#: audits/views.py:192 audits/views.py:223 templates/_nav.html:71 msgid "Audits" msgstr "日志审计" -#: audits/views.py:67 templates/_nav.html:77 +#: audits/views.py:69 templates/_nav.html:75 msgid "FTP log" msgstr "FTP日志" -#: audits/views.py:111 templates/_nav.html:78 +#: audits/views.py:113 templates/_nav.html:76 msgid "Operate log" msgstr "操作日志" -#: audits/views.py:144 templates/_nav.html:79 +#: audits/views.py:149 templates/_nav.html:77 msgid "Password change log" msgstr "改密日志" -#: audits/views.py:187 templates/_nav.html:10 users/views/group.py:28 -#: users/views/group.py:44 users/views/group.py:60 users/views/group.py:76 -#: users/views/group.py:92 users/views/login.py:346 users/views/user.py:68 -#: users/views/user.py:83 users/views/user.py:111 users/views/user.py:192 -#: users/views/user.py:353 users/views/user.py:403 users/views/user.py:437 -msgid "Users" -msgstr "用户管理" - -#: audits/views.py:188 templates/_nav.html:76 +#: audits/views.py:193 templates/_nav.html:74 msgid "Login log" msgstr "登录日志" +#: audits/views.py:224 ops/views/command.py:44 +msgid "Command execution list" +msgstr "命令执行列表" + #: common/api.py:22 msgid "Test mail sent to {}, please check" msgstr "邮件已经发送{}, 请检查" @@ -2084,7 +2125,8 @@ msgid "Security setting" msgstr "安全设置" #: common/templates/common/command_storage_create.html:50 -#: ops/models/adhoc.py:159 ops/templates/ops/adhoc_detail.html:53 +#: ops/models/adhoc.py:161 ops/templates/ops/adhoc_detail.html:53 +#: ops/templates/ops/command_execution_list.html:44 #: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38 msgid "Hosts" msgstr "主机" @@ -2102,7 +2144,7 @@ msgid "Doc type" msgstr "文档类型" #: common/templates/common/replay_storage_create.html:53 -#: templates/index.html:91 +#: ops/models/adhoc.py:162 templates/index.html:91 msgid "Host" msgstr "主机" @@ -2139,7 +2181,7 @@ msgid "Endpoint suffix" msgstr "端点后缀" #: common/templates/common/replay_storage_create.html:130 -#: xpack/plugins/cloud/models.py:206 +#: xpack/plugins/cloud/models.py:185 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 msgid "Region" @@ -2185,7 +2227,7 @@ msgstr "不能包含特殊字符" #: common/views.py:18 common/views.py:44 common/views.py:70 common/views.py:99 #: common/views.py:126 common/views.py:138 common/views.py:151 -#: templates/_nav.html:116 +#: templates/_nav.html:106 msgid "Settings" msgstr "系统设置" @@ -2212,7 +2254,7 @@ msgstr "" "div>
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运" -#: ops/api.py:81 +#: ops/api/celery.py:32 msgid "Waiting ..." msgstr "" @@ -2232,116 +2274,132 @@ msgstr "" msgid "Callback" msgstr "回调" -#: ops/models/adhoc.py:156 ops/templates/ops/adhoc_detail.html:114 +#: ops/models/adhoc.py:158 ops/templates/ops/adhoc_detail.html:114 msgid "Tasks" msgstr "任务" -#: ops/models/adhoc.py:157 ops/templates/ops/adhoc_detail.html:57 +#: ops/models/adhoc.py:159 ops/templates/ops/adhoc_detail.html:57 #: ops/templates/ops/task_adhoc.html:60 msgid "Pattern" msgstr "模式" -#: ops/models/adhoc.py:158 ops/templates/ops/adhoc_detail.html:61 +#: ops/models/adhoc.py:160 ops/templates/ops/adhoc_detail.html:61 msgid "Options" msgstr "选项" -#: ops/models/adhoc.py:160 +#: ops/models/adhoc.py:163 msgid "Run as admin" msgstr "再次执行" -#: ops/models/adhoc.py:161 ops/templates/ops/adhoc_detail.html:72 -#: ops/templates/ops/adhoc_detail.html:77 ops/templates/ops/task_adhoc.html:61 -msgid "Run as" -msgstr "用户" - -#: ops/models/adhoc.py:162 ops/templates/ops/adhoc_detail.html:82 +#: ops/models/adhoc.py:165 ops/templates/ops/adhoc_detail.html:82 #: ops/templates/ops/task_adhoc.html:62 msgid "Become" msgstr "Become" -#: ops/models/adhoc.py:163 users/templates/users/user_group_detail.html:59 +#: ops/models/adhoc.py:166 users/templates/users/user_group_detail.html:59 #: xpack/plugins/cloud/templates/cloud/account_detail.html:64 #: xpack/plugins/orgs/templates/orgs/org_detail.html:56 msgid "Create by" msgstr "创建者" -#: ops/models/adhoc.py:327 +#: ops/models/adhoc.py:321 msgid "Start time" msgstr "开始时间" -#: ops/models/adhoc.py:328 +#: ops/models/adhoc.py:322 msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:329 ops/templates/ops/adhoc_history.html:57 +#: ops/models/adhoc.py:323 ops/templates/ops/adhoc_history.html:57 #: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41 msgid "Time" msgstr "时间" -#: ops/models/adhoc.py:330 ops/templates/ops/adhoc_detail.html:106 +#: ops/models/adhoc.py:324 ops/templates/ops/adhoc_detail.html:106 #: ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/adhoc_history_detail.html:69 -#: ops/templates/ops/task_detail.html:83 ops/templates/ops/task_history.html:61 +#: ops/templates/ops/task_detail.html:84 ops/templates/ops/task_history.html:61 msgid "Is finished" msgstr "是否完成" -#: ops/models/adhoc.py:331 ops/templates/ops/adhoc_history.html:56 +#: ops/models/adhoc.py:325 ops/templates/ops/adhoc_history.html:56 #: ops/templates/ops/task_history.html:62 msgid "Is success" msgstr "是否成功" -#: ops/models/adhoc.py:332 +#: ops/models/adhoc.py:326 msgid "Adhoc raw result" msgstr "结果" -#: ops/models/adhoc.py:333 +#: ops/models/adhoc.py:327 msgid "Adhoc result summary" msgstr "汇总" +#: ops/models/command.py:20 xpack/plugins/cloud/models.py:169 +msgid "Result" +msgstr "结果" + +#: ops/models/command.py:52 +msgid "Task start" +msgstr "任务开始: " + +#: ops/models/command.py:64 +msgid "Command `{}` is forbidden ........" +msgstr "命令 `{}` 不允许被执行 ......." + +#: ops/models/command.py:70 +msgid "Task end" +msgstr "任务结束" + #: ops/templates/ops/adhoc_detail.html:19 #: ops/templates/ops/adhoc_history.html:19 msgid "Version detail" msgstr "版本详情" #: ops/templates/ops/adhoc_detail.html:22 -#: ops/templates/ops/adhoc_history.html:22 ops/views.py:105 +#: ops/templates/ops/adhoc_history.html:22 ops/views/adhoc.py:124 msgid "Version run history" msgstr "执行历史" +#: ops/templates/ops/adhoc_detail.html:72 +#: ops/templates/ops/adhoc_detail.html:77 ops/templates/ops/task_adhoc.html:61 +msgid "Run as" +msgstr "用户" + #: ops/templates/ops/adhoc_detail.html:94 ops/templates/ops/task_list.html:36 msgid "Run times" msgstr "执行次数" -#: ops/templates/ops/adhoc_detail.html:98 ops/templates/ops/task_detail.html:75 +#: ops/templates/ops/adhoc_detail.html:98 ops/templates/ops/task_detail.html:76 msgid "Last run" msgstr "最后运行" #: ops/templates/ops/adhoc_detail.html:102 #: ops/templates/ops/adhoc_history_detail.html:65 -#: ops/templates/ops/task_detail.html:79 +#: ops/templates/ops/task_detail.html:80 msgid "Time delta" msgstr "运行时间" #: ops/templates/ops/adhoc_detail.html:110 #: ops/templates/ops/adhoc_history_detail.html:73 -#: ops/templates/ops/task_detail.html:87 +#: ops/templates/ops/task_detail.html:88 msgid "Is success " msgstr "成功" #: ops/templates/ops/adhoc_detail.html:131 -#: ops/templates/ops/task_detail.html:108 +#: ops/templates/ops/task_detail.html:109 msgid "Last run failed hosts" msgstr "最后运行失败主机" #: ops/templates/ops/adhoc_detail.html:151 #: ops/templates/ops/adhoc_detail.html:176 -#: ops/templates/ops/task_detail.html:128 -#: ops/templates/ops/task_detail.html:153 +#: ops/templates/ops/task_detail.html:129 +#: ops/templates/ops/task_detail.html:154 msgid "No hosts" msgstr "没有主机" #: ops/templates/ops/adhoc_detail.html:161 -#: ops/templates/ops/task_detail.html:138 +#: ops/templates/ops/task_detail.html:139 msgid "Last run success hosts" msgstr "最后运行成功主机" @@ -2361,11 +2419,12 @@ msgstr "失败/成功/总" msgid "Version" msgstr "版本" -#: ops/templates/ops/adhoc_history_detail.html:19 ops/views.py:118 +#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:137 msgid "Run history detail" msgstr "执行历史详情" #: ops/templates/ops/adhoc_history_detail.html:22 +#: ops/templates/ops/command_execution_list.html:47 #: terminal/backends/command/models.py:16 msgid "Output" msgstr "输出" @@ -2391,22 +2450,40 @@ msgstr "没有资产" msgid "Success assets" msgstr "成功资产" -#: ops/templates/ops/task_adhoc.html:19 ops/templates/ops/task_detail.html:19 -#: ops/templates/ops/task_history.html:19 ops/views.py:53 +#: ops/templates/ops/command_execution_create.html:67 +#: terminal/templates/terminal/session_detail.html:91 +#: terminal/templates/terminal/session_detail.html:100 +msgid "Go" +msgstr "" + +#: ops/templates/ops/command_execution_create.html:244 +msgid "Pending" +msgstr "" + +#: ops/templates/ops/command_execution_list.html:48 +msgid "Finished" +msgstr "结束" + +#: ops/templates/ops/command_execution_list.html:51 +msgid "Date finished" +msgstr "结束日期" + +#: ops/templates/ops/task_adhoc.html:19 ops/templates/ops/task_detail.html:20 +#: ops/templates/ops/task_history.html:19 ops/views/adhoc.py:72 msgid "Task detail" msgstr "任务详情" -#: ops/templates/ops/task_adhoc.html:22 ops/templates/ops/task_detail.html:22 -#: ops/templates/ops/task_history.html:22 ops/views.py:66 +#: ops/templates/ops/task_adhoc.html:22 ops/templates/ops/task_detail.html:23 +#: ops/templates/ops/task_history.html:22 ops/views/adhoc.py:85 msgid "Task versions" msgstr "任务各版本" -#: ops/templates/ops/task_adhoc.html:25 ops/templates/ops/task_detail.html:25 +#: ops/templates/ops/task_adhoc.html:25 ops/templates/ops/task_detail.html:26 #: ops/templates/ops/task_history.html:25 msgid "Run history" msgstr "执行历史" -#: ops/templates/ops/task_adhoc.html:28 ops/templates/ops/task_detail.html:28 +#: ops/templates/ops/task_adhoc.html:28 ops/templates/ops/task_detail.html:29 #: ops/templates/ops/task_history.html:28 msgid "Last run output" msgstr "输出" @@ -2415,15 +2492,15 @@ msgstr "输出" msgid "Versions of " msgstr "版本" -#: ops/templates/ops/task_detail.html:67 +#: ops/templates/ops/task_detail.html:68 msgid "Total versions" msgstr "版本数量" -#: ops/templates/ops/task_detail.html:71 +#: ops/templates/ops/task_detail.html:72 msgid "Latest version" msgstr "最新版本" -#: ops/templates/ops/task_detail.html:91 +#: ops/templates/ops/task_detail.html:92 msgid "Contents" msgstr "内容" @@ -2440,19 +2517,28 @@ msgstr "执行" msgid "Task start: " msgstr "任务开始: " -#: ops/views.py:36 ops/views.py:52 ops/views.py:65 ops/views.py:78 -#: ops/views.py:91 ops/views.py:104 ops/views.py:117 +#: ops/utils.py:51 +msgid "Update task content: {}" +msgstr "更新任务内容: {}" + +#: ops/views/adhoc.py:49 ops/views/adhoc.py:71 ops/views/adhoc.py:84 +#: ops/views/adhoc.py:97 ops/views/adhoc.py:110 ops/views/adhoc.py:123 +#: ops/views/adhoc.py:136 ops/views/command.py:43 ops/views/command.py:67 msgid "Ops" msgstr "作业中心" -#: ops/views.py:37 templates/_nav.html:67 +#: ops/views/adhoc.py:50 templates/_nav.html:66 msgid "Task list" msgstr "任务列表" -#: ops/views.py:79 +#: ops/views/adhoc.py:98 msgid "Task run history" msgstr "执行历史" +#: ops/views/command.py:68 templates/_nav.html:78 templates/_nav_user.html:9 +msgid "Command execution" +msgstr "命令执行" + #: orgs/mixins.py:77 orgs/models.py:24 msgid "Organization" msgstr "组织管理" @@ -2602,7 +2688,7 @@ msgstr "文档" msgid "Commercial support" msgstr "商业支持" -#: templates/_header_bar.html:89 templates/_nav_user.html:9 users/forms.py:147 +#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:147 #: users/templates/users/_user.html:39 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 @@ -2694,6 +2780,14 @@ msgstr "" "\"%(user_pubkey_update)s\"> 链接 更新\n" " " +#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 +#: users/views/group.py:60 users/views/group.py:76 users/views/group.py:92 +#: users/views/login.py:346 users/views/user.py:68 users/views/user.py:83 +#: users/views/user.py:111 users/views/user.py:192 users/views/user.py:353 +#: users/views/user.py:403 users/views/user.py:437 +msgid "Users" +msgstr "用户管理" + #: templates/_nav.html:13 users/views/user.py:69 msgid "User list" msgstr "用户列表" @@ -2718,11 +2812,11 @@ msgstr "历史会话" msgid "Commands" msgstr "命令记录" -#: templates/_nav.html:48 templates/_nav_user.html:14 +#: templates/_nav.html:48 templates/_nav_user.html:19 msgid "Web terminal" msgstr "Web终端" -#: templates/_nav.html:53 templates/_nav_user.html:19 +#: templates/_nav.html:53 templates/_nav_user.html:24 msgid "File manager" msgstr "文件管理" @@ -2733,19 +2827,19 @@ msgstr "文件管理" msgid "Terminal" msgstr "终端管理" -#: templates/_nav.html:64 +#: templates/_nav.html:63 msgid "Job Center" msgstr "作业中心" -#: templates/_nav.html:94 +#: templates/_nav.html:84 msgid "XPack" msgstr "" -#: templates/_nav.html:102 xpack/plugins/cloud/views.py:26 +#: templates/_nav.html:92 xpack/plugins/cloud/views.py:26 msgid "Account list" msgstr "账户列表" -#: templates/_nav.html:103 +#: templates/_nav.html:93 msgid "Sync instance" msgstr "同步实例" @@ -3021,11 +3115,6 @@ msgstr "该会话没有命令记录" msgid "Replay session" msgstr "回放会话" -#: terminal/templates/terminal/session_detail.html:91 -#: terminal/templates/terminal/session_detail.html:100 -msgid "Go" -msgstr "" - #: terminal/templates/terminal/session_detail.html:97 msgid "Monitor session" msgstr "监控" @@ -3153,7 +3242,7 @@ msgstr "请先进行用户名和密码验证" msgid "MFA certification failed" msgstr "MFA认证失败" -#: users/api/user.py:140 +#: users/api/user.py:137 msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" @@ -3342,8 +3431,8 @@ msgstr "用户名不存在" msgid "Password expired" msgstr "密码过期" -#: users/models/authentication.py:74 xpack/plugins/cloud/models.py:184 -#: xpack/plugins/cloud/models.py:198 +#: users/models/authentication.py:74 xpack/plugins/cloud/models.py:163 +#: xpack/plugins/cloud/models.py:177 msgid "Failed" msgstr "失败" @@ -3427,7 +3516,7 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:44 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:51 -#: xpack/plugins/cloud/models.py:51 xpack/plugins/cloud/models.py:133 +#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:119 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:59 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:13 msgid "Account" @@ -4255,7 +4344,7 @@ msgstr "MFA 解绑成功" msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" -#: xpack/plugins/cloud/api.py:60 xpack/plugins/cloud/providers/base.py:83 +#: xpack/plugins/cloud/api.py:60 xpack/plugins/cloud/providers/base.py:84 msgid "Account unavailable" msgstr "账户无效" @@ -4295,145 +4384,99 @@ msgstr "选择管理员" msgid "Cloud center" msgstr "云管中心" -#: xpack/plugins/cloud/models.py:30 -msgid "Aliyun" -msgstr "阿里云" - -#: xpack/plugins/cloud/models.py:31 -msgid "AWS (China)" -msgstr "AWS (中国)" - -#: xpack/plugins/cloud/models.py:32 -msgid "AWS (International)" -msgstr "AWS (国际)" - -#: xpack/plugins/cloud/models.py:35 +#: xpack/plugins/cloud/models.py:43 msgid "Available" msgstr "有效" -#: xpack/plugins/cloud/models.py:36 +#: xpack/plugins/cloud/models.py:44 msgid "Unavailable" msgstr "无效" -#: xpack/plugins/cloud/models.py:41 +#: xpack/plugins/cloud/models.py:49 #: xpack/plugins/cloud/templates/cloud/account_detail.html:56 #: xpack/plugins/cloud/templates/cloud/account_list.html:13 msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:42 +#: xpack/plugins/cloud/models.py:50 msgid "Access key id" msgstr "" -#: xpack/plugins/cloud/models.py:43 +#: xpack/plugins/cloud/models.py:51 msgid "Access key secret" msgstr "" -#: xpack/plugins/cloud/models.py:44 +#: xpack/plugins/cloud/models.py:52 #: xpack/plugins/cloud/templates/cloud/account_detail.html:60 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "账户状态" -#: xpack/plugins/cloud/models.py:134 +#: xpack/plugins/cloud/models.py:120 msgid "Regions" msgstr "地域" -#: xpack/plugins/cloud/models.py:135 +#: xpack/plugins/cloud/models.py:121 msgid "Instances" msgstr "实例" -#: xpack/plugins/cloud/models.py:139 +#: xpack/plugins/cloud/models.py:125 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:75 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17 msgid "Date last sync" msgstr "最后同步日期" -#: xpack/plugins/cloud/models.py:145 xpack/plugins/cloud/models.py:189 +#: xpack/plugins/cloud/models.py:131 xpack/plugins/cloud/models.py:168 msgid "Sync instance task" msgstr "同步实例任务" -#: xpack/plugins/cloud/models.py:185 xpack/plugins/cloud/models.py:199 +#: xpack/plugins/cloud/models.py:164 xpack/plugins/cloud/models.py:178 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/models.py:186 +#: xpack/plugins/cloud/models.py:165 msgid "Partial succeed" msgstr "" -#: xpack/plugins/cloud/models.py:190 -msgid "Result" -msgstr "结果" - -#: xpack/plugins/cloud/models.py:193 xpack/plugins/cloud/models.py:209 +#: xpack/plugins/cloud/models.py:172 xpack/plugins/cloud/models.py:188 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:71 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:68 msgid "Date sync" msgstr "同步日期" -#: xpack/plugins/cloud/models.py:200 +#: xpack/plugins/cloud/models.py:179 msgid "Exist" msgstr "存在" -#: xpack/plugins/cloud/models.py:203 +#: xpack/plugins/cloud/models.py:182 msgid "Sync task" msgstr "同步任务" -#: xpack/plugins/cloud/models.py:204 +#: xpack/plugins/cloud/models.py:183 msgid "Sync instance task history" msgstr "同步实例任务历史" -#: xpack/plugins/cloud/models.py:205 +#: xpack/plugins/cloud/models.py:184 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/providers/base.py:73 -msgid "任务执行开始: {}" -msgstr "" +#: xpack/plugins/cloud/providers/aliyun.py:14 +msgid "Aliyun" +msgstr "阿里云" -#: xpack/plugins/cloud/providers/base.py:77 -msgid "检测账户有效性: {}" -msgstr "" +#: xpack/plugins/cloud/providers/aws.py:11 +msgid "AWS (China)" +msgstr "AWS (中国)" -#: xpack/plugins/cloud/providers/base.py:80 -msgid "账户无效!" -msgstr "" +#: xpack/plugins/cloud/providers/aws.py:12 +msgid "AWS (International)" +msgstr "AWS (国际)" -#: xpack/plugins/cloud/providers/base.py:85 -msgid "账户有效!" -msgstr "" - -#: xpack/plugins/cloud/providers/base.py:91 -msgid "任务执行结束!" -msgstr "" - -#: xpack/plugins/cloud/providers/base.py:93 -msgid "" -"查看任务详细信息路径: XPack -> 云管中心 -> 任务列表 -> 任务详情(点击任务名" -"称) -> 查看同步历史列表/实例列表" -msgstr "" - -#: xpack/plugins/cloud/providers/base.py:130 -msgid "同步实例列表: {}" -msgstr "" - -#: xpack/plugins/cloud/providers/base.py:139 -msgid "同步地域列表: {}" -msgstr "" - -#: xpack/plugins/cloud/providers/base.py:143 -msgid "地域: {}" -msgstr "" - -#: xpack/plugins/cloud/providers/base.py:154 -msgid "实例: {}, 地域: {}" -msgstr "" - -#: xpack/plugins/cloud/providers/base.py:160 -msgid "正在创建资产..." -msgstr "" +#: xpack/plugins/cloud/providers/qcloud.py:14 +msgid "Qcloud" +msgstr "腾讯云" #: xpack/plugins/cloud/templates/cloud/account_detail.html:22 #: xpack/plugins/cloud/views.py:72 @@ -4562,6 +4605,20 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#, fuzzy +#~| msgid "Audits" +#~ msgid "Audit" +#~ msgstr "日志审计" + +#~ msgid "User id" +#~ msgstr "用户" + +#~ msgid "Start execute" +#~ msgstr "开始执行" + +#~ msgid "Start" +#~ msgstr "开始" + #, fuzzy #~| msgid "Update setting successfully" #~ msgid "Update setting successfully, please restart program" diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 0af872b25..c666ce294 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -1,18 +1,16 @@ # ~*~ coding: utf-8 ~*~ -import sys +import datetime +from collections import defaultdict +from ansible import constants as C from ansible.plugins.callback import CallbackBase from ansible.plugins.callback.default import CallbackModule - -from .display import TeeObj +from ansible.plugins.callback.minimal import CallbackModule as CMDCallBackModule -class AdHocResultCallback(CallbackModule): - """ - Task result Callback - """ - def __init__(self, display=None, options=None, file_obj=None): +class CallbackMixin: + def __init__(self, display=None): # result_raw example: { # "ok": {"hostname": {"task_name": {},...},..}, # "failed": {"hostname": {"task_name": {}..}, ..}, @@ -20,63 +18,129 @@ class AdHocResultCallback(CallbackModule): # "skipped": {"hostname": {"task_name": {}, ..}, ..}, # } # results_summary example: { - # "contacted": {"hostname",...}, + # "contacted": {"hostname": {"task_name": {}}, "hostname": {}}, # "dark": {"hostname": {"task_name": {}, "task_name": {}},...,}, + # "success": True # } - self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={}) - self.results_summary = dict(contacted=[], dark={}) + self.results_raw = dict( + ok=defaultdict(dict), + failed=defaultdict(dict), + unreachable=defaultdict(dict), + skippe=defaultdict(dict), + ) + self.results_summary = dict( + contacted=defaultdict(dict), + dark=defaultdict(dict), + success=True + ) + self.results = { + 'raw': self.results_raw, + 'summary': self.results_summary, + } super().__init__() - if file_obj is not None: - sys.stdout = TeeObj(file_obj) + if display: + self._display = display + self._display.columns = 79 - def gather_result(self, t, res): - self._clean_results(res._result, res._task.action) - host = res._host.get_name() - task_name = res.task_name - task_result = res._result + def display(self, msg): + self._display.display(msg) - if self.results_raw[t].get(host): - self.results_raw[t][host][task_name] = task_result - else: - self.results_raw[t][host] = {task_name: task_result} + def gather_result(self, t, result): + self._clean_results(result._result, result._task.action) + host = result._host.get_name() + task_name = result.task_name + task_result = result._result + + self.results_raw[t][host][task_name] = task_result self.clean_result(t, host, task_name, task_result) + +class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule): + """ + Task result Callback + """ def clean_result(self, t, host, task_name, task_result): contacted = self.results_summary["contacted"] dark = self.results_summary["dark"] - if t in ("ok", "skipped") and host not in dark: - if host not in contacted: - contacted.append(host) - else: - if dark.get(host): - dark[host][task_name] = task_result.values + + if task_result.get('rc') is not None: + cmd = task_result.get('cmd') + if isinstance(cmd, list): + cmd = " ".join(cmd) else: - dark[host] = {task_name: task_result} - if host in contacted: - contacted.remove(host) + cmd = str(cmd) + detail = { + 'cmd': cmd, + 'stderr': task_result.get('stderr'), + 'stdout': task_result.get('stdout'), + 'rc': task_result.get('rc'), + 'delta': task_result.get('delta'), + 'msg': task_result.get('msg', '') + } + else: + detail = { + "changed": task_result.get('changed', False), + "msg": task_result.get('msg', '') + } + + if t in ("ok", "skipped"): + contacted[host][task_name] = detail + else: + dark[host][task_name] = detail def v2_runner_on_failed(self, result, ignore_errors=False): + self.results_summary['success'] = False self.gather_result("failed", result) - super().v2_runner_on_failed(result, ignore_errors=ignore_errors) + + if result._task.action in C.MODULE_NO_JSON: + CMDCallBackModule.v2_runner_on_failed(self, + result, ignore_errors=ignore_errors + ) + else: + super().v2_runner_on_failed( + result, ignore_errors=ignore_errors + ) def v2_runner_on_ok(self, result): self.gather_result("ok", result) - super().v2_runner_on_ok(result) + if result._task.action in C.MODULE_NO_JSON: + CMDCallBackModule.v2_runner_on_ok(self, result) + else: + super().v2_runner_on_ok(result) def v2_runner_on_skipped(self, result): self.gather_result("skipped", result) super().v2_runner_on_skipped(result) def v2_runner_on_unreachable(self, result): + self.results_summary['success'] = False self.gather_result("unreachable", result) super().v2_runner_on_unreachable(result) + def on_playbook_start(self, name): + date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.display( + "{} Start task: {}\r\n".format(date_start, name) + ) + + def on_playbook_end(self, name): + date_finished = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.display( + "{} Task finish\r\n".format(date_finished) + ) + + def display_skipped_hosts(self): + pass + + def display_ok_hosts(self): + pass + class CommandResultCallback(AdHocResultCallback): """ Command result callback """ - def __init__(self, display=None): + def __init__(self, display=None, **kwargs): # results_command: { # "cmd": "", # "stderr": "", diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index 3e168e987..cc48b2447 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -17,7 +17,7 @@ from common.utils import get_logger from .exceptions import AnsibleError -__all__ = ["AdHocRunner", "PlayBookRunner"] +__all__ = ["AdHocRunner", "PlayBookRunner", "CommandRunner"] C.HOST_KEY_CHECKING = False logger = get_logger(__name__) @@ -45,7 +45,7 @@ def get_default_options(): listtasks=False, listhosts=False, syntax=False, - timeout=60, + timeout=30, connection='ssh', module_path='', forks=10, @@ -145,7 +145,7 @@ class AdHocRunner: ) def get_result_callback(self, file_obj=None): - return self.__class__.results_callback_class(file_obj=file_obj) + return self.__class__.results_callback_class() @staticmethod def check_module_args(module_name, module_args=''): @@ -177,17 +177,16 @@ class AdHocRunner: options = self.__class__.default_options return options - def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no', file_obj=None): + def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'): """ :param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ] :param pattern: all, *, or others :param play_name: The play name :param gather_facts: - :param file_obj: logging to file_obj :return: """ self.check_pattern(pattern) - self.results_callback = self.get_result_callback(file_obj) + self.results_callback = self.get_result_callback() cleaned_tasks = self.clean_tasks(tasks) play_source = dict( @@ -211,10 +210,6 @@ class AdHocRunner: stdout_callback=self.results_callback, passwords=self.options.passwords, ) - print("Get matched hosts: {}".format( - self.inventory.get_matched_hosts(pattern) - )) - try: tqm.run(play) return self.results_callback @@ -229,16 +224,14 @@ class CommandRunner(AdHocRunner): results_callback_class = CommandResultCallback modules_choices = ('shell', 'raw', 'command', 'script') - def execute(self, cmd, pattern, module=None): + def execute(self, cmd, pattern, module='shell'): if module and module not in self.modules_choices: raise AnsibleError("Module should in {}".format(self.modules_choices)) - else: - module = "shell" tasks = [ {"action": {"module": module, "args": cmd}} ] hosts = self.inventory.get_hosts(pattern=pattern) - name = "Run command {} on {}".format(cmd, ", ".join([host.name for host in hosts])) + name = "Run command {} on {}'s hosts".format(cmd, len(hosts)) return self.run(tasks, pattern, play_name=name) diff --git a/apps/ops/api/__init__.py b/apps/ops/api/__init__.py new file mode 100644 index 000000000..e59889cd2 --- /dev/null +++ b/apps/ops/api/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# +from .adhoc import * +from .celery import * +from .command import * diff --git a/apps/ops/api.py b/apps/ops/api/adhoc.py similarity index 57% rename from apps/ops/api.py rename to apps/ops/api/adhoc.py index f052abf4f..e1121a7af 100644 --- a/apps/ops/api.py +++ b/apps/ops/api/adhoc.py @@ -1,26 +1,32 @@ -# ~*~ coding: utf-8 ~*~ -import uuid -import os +# -*- coding: utf-8 -*- +# -from django.core.cache import cache from django.shortcuts import get_object_or_404 -from django.utils.translation import ugettext as _ from rest_framework import viewsets, generics from rest_framework.views import Response from common.permissions import IsOrgAdmin -from .models import Task, AdHoc, AdHocRunHistory, CeleryTask -from .serializers import TaskSerializer, AdHocSerializer, \ +from orgs.utils import current_org +from ..models import Task, AdHoc, AdHocRunHistory +from ..serializers import TaskSerializer, AdHocSerializer, \ AdHocRunHistorySerializer -from .tasks import run_ansible_task +from ..tasks import run_ansible_task + +__all__ = [ + 'TaskViewSet', 'TaskRun', 'AdHocViewSet', 'AdHocRunHistoryViewSet' +] class TaskViewSet(viewsets.ModelViewSet): queryset = Task.objects.all() serializer_class = TaskSerializer permission_classes = (IsOrgAdmin,) - # label = None - # help_text = '' + + def get_queryset(self): + queryset = super().get_queryset() + if current_org: + queryset = queryset.filter(created_by=current_org.id) + return queryset class TaskRun(generics.RetrieveAPIView): @@ -47,7 +53,7 @@ class AdHocViewSet(viewsets.ModelViewSet): return self.queryset -class AdHocRunHistorySet(viewsets.ModelViewSet): +class AdHocRunHistoryViewSet(viewsets.ModelViewSet): queryset = AdHocRunHistory.objects.all() serializer_class = AdHocRunHistorySerializer permission_classes = (IsOrgAdmin,) @@ -66,28 +72,6 @@ class AdHocRunHistorySet(viewsets.ModelViewSet): return self.queryset -class CeleryTaskLogApi(generics.RetrieveAPIView): - permission_classes = (IsOrgAdmin,) - buff_size = 1024 * 10 - end = False - queryset = CeleryTask.objects.all() - def get(self, request, *args, **kwargs): - mark = request.query_params.get("mark") or str(uuid.uuid4()) - task = self.get_object() - log_path = task.full_log_path - if not log_path or not os.path.isfile(log_path): - return Response({"data": _("Waiting ...")}, status=203) - - with open(log_path, 'r') as f: - offset = cache.get(mark, 0) - f.seek(offset) - data = f.read(self.buff_size).replace('\n', '\r\n') - mark = str(uuid.uuid4()) - cache.set(mark, f.tell(), 5) - - if data == '' and task.is_finished(): - self.end = True - return Response({"data": data, 'end': self.end, 'mark': mark}) diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py new file mode 100644 index 000000000..2640cffec --- /dev/null +++ b/apps/ops/api/celery.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# +import uuid +import os + +from celery.result import AsyncResult +from django.core.cache import cache +from django.utils.translation import ugettext as _ +from rest_framework import generics +from rest_framework.views import Response + +from common.permissions import IsOrgAdmin, IsValidUser +from ..models import CeleryTask +from ..serializers import CeleryResultSerializer + + +__all__ = ['CeleryTaskLogApi', 'CeleryResultApi'] + + +class CeleryTaskLogApi(generics.RetrieveAPIView): + permission_classes = (IsValidUser,) + buff_size = 1024 * 10 + end = False + queryset = CeleryTask.objects.all() + + def get(self, request, *args, **kwargs): + mark = request.query_params.get("mark") or str(uuid.uuid4()) + task = self.get_object() + log_path = task.full_log_path + + if not log_path or not os.path.isfile(log_path): + return Response({"data": _("Waiting ...")}, status=203) + + with open(log_path, 'r') as f: + offset = cache.get(mark, 0) + f.seek(offset) + data = f.read(self.buff_size).replace('\n', '\r\n') + mark = str(uuid.uuid4()) + cache.set(mark, f.tell(), 5) + + if data == '' and task.is_finished(): + self.end = True + return Response({"data": data, 'end': self.end, 'mark': mark}) + + +class CeleryResultApi(generics.RetrieveAPIView): + permission_classes = (IsValidUser,) + serializer_class = CeleryResultSerializer + + def get_object(self): + pk = self.kwargs.get('pk') + return AsyncResult(pk) + diff --git a/apps/ops/api/command.py b/apps/ops/api/command.py new file mode 100644 index 000000000..9d83d1464 --- /dev/null +++ b/apps/ops/api/command.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import viewsets + +from common.permissions import IsValidUser +from ..models import CommandExecution +from ..serializers import CommandExecutionSerializer +from ..tasks import run_command_execution + + +class CommandExecutionViewSet(viewsets.ModelViewSet): + serializer_class = CommandExecutionSerializer + permission_classes = (IsValidUser,) + task = None + + def get_queryset(self): + return CommandExecution.objects.filter( + user_id=str(self.request.user.id) + ) + + def perform_create(self, serializer): + instance = serializer.save() + instance.user = self.request.user + instance.save() + run_command_execution.apply_async( + args=(instance.id,), task_id=str(instance.id) + ) diff --git a/apps/ops/celery/signal_handler.py b/apps/ops/celery/signal_handler.py index c64befbb7..961c18ad3 100644 --- a/apps/ops/celery/signal_handler.py +++ b/apps/ops/celery/signal_handler.py @@ -27,7 +27,6 @@ def on_app_ready(sender=None, headers=None, body=None, **kwargs): if cache.get("CELERY_APP_READY", 0) == 1: return cache.set("CELERY_APP_READY", 1, 10) - logger.debug("App ready signal recv") tasks = get_after_app_ready_tasks() logger.debug("Start need start task: [{}]".format( ", ".join(tasks)) diff --git a/apps/ops/forms.py b/apps/ops/forms.py new file mode 100644 index 000000000..6658980f8 --- /dev/null +++ b/apps/ops/forms.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# +from django import forms + +from assets.models import SystemUser +from .models import CommandExecution + + +class CommandExecutionForm(forms.ModelForm): + class Meta: + model = CommandExecution + fields = ['run_as', 'command'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + run_as_field = self.fields.get('run_as') + run_as_field.queryset = SystemUser.objects.all() diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py index 5b5e98551..7e21d156d 100644 --- a/apps/ops/inventory.py +++ b/apps/ops/inventory.py @@ -2,7 +2,7 @@ # from .ansible.inventory import BaseInventory -from assets.utils import get_assets_by_fullname_list, get_system_user_by_name +from assets.utils import get_assets_by_id_list, get_system_user_by_id __all__ = [ 'JMSInventory' @@ -14,19 +14,18 @@ class JMSInventory(BaseInventory): JMS Inventory is the manager with jumpserver assets, so you can write you own manager, construct you inventory """ - def __init__(self, hostname_list, run_as_admin=False, run_as=None, become_info=None): + def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None): """ - :param hostname_list: ["test1", ] + :param host_id_list: ["test1", ] :param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同 :param run_as: 是否统一使用某个系统用户去执行 :param become_info: 是否become成某个用户去执行 """ - self.hostname_list = hostname_list + self.assets = assets self.using_admin = run_as_admin self.run_as = run_as self.become_info = become_info - assets = self.get_jms_assets() host_list = [] for asset in assets: @@ -43,14 +42,10 @@ class JMSInventory(BaseInventory): host.update(become_info) super().__init__(host_list=host_list) - def get_jms_assets(self): - assets = get_assets_by_fullname_list(self.hostname_list) - return assets - def convert_to_ansible(self, asset, run_as_admin=False): info = { 'id': asset.id, - 'hostname': asset.fullname, + 'hostname': asset.hostname, 'ip': asset.ip, 'port': asset.port, 'vars': dict(), @@ -75,7 +70,7 @@ class JMSInventory(BaseInventory): return info def get_run_user_info(self): - system_user = get_system_user_by_name(self.run_as) + system_user = self.run_as if not system_user: return {} else: diff --git a/apps/ops/migrations/0003_auto_20181207_1744.py b/apps/ops/migrations/0003_auto_20181207_1744.py new file mode 100644 index 000000000..d3ef032e9 --- /dev/null +++ b/apps/ops/migrations/0003_auto_20181207_1744.py @@ -0,0 +1,56 @@ +# Generated by Django 2.1.4 on 2018-12-07 09:44 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0023_auto_20181016_1650'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ops', '0002_celerytask'), + ] + + operations = [ + migrations.CreateModel( + name='CommandExecution', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('command', models.TextField(verbose_name='Command')), + ('_result', models.TextField(blank=True, null=True, verbose_name='Result')), + ('is_finished', models.BooleanField(default=False)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_start', models.DateTimeField(null=True)), + ('date_finished', models.DateTimeField(null=True)), + ('hosts', models.ManyToManyField(to='assets.Asset')), + ('run_as', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.RemoveField( + model_name='adhoc', + name='run_as', + ), + migrations.AddField( + model_name='adhoc', + name='hosts', + field=models.ManyToManyField(to='assets.Asset', verbose_name='Host'), + ), + migrations.AlterField( + model_name='task', + name='created_by', + field=models.CharField(blank=True, default='', max_length=128), + ), + migrations.AlterField( + model_name='task', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), + migrations.AlterUniqueTogether( + name='task', + unique_together={('name', 'created_by')}, + ), + ] diff --git a/apps/ops/migrations/0004_adhoc_run_as.py b/apps/ops/migrations/0004_adhoc_run_as.py new file mode 100644 index 000000000..8e49eaa2b --- /dev/null +++ b/apps/ops/migrations/0004_adhoc_run_as.py @@ -0,0 +1,20 @@ +# Generated by Django 2.1.4 on 2018-12-07 09:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0023_auto_20181016_1650'), + ('ops', '0003_auto_20181207_1744'), + ] + + operations = [ + migrations.AddField( + model_name='adhoc', + name='run_as', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser'), + ), + ] diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py index 68920eb42..0a9ed463c 100644 --- a/apps/ops/models/__init__.py +++ b/apps/ops/models/__init__.py @@ -2,4 +2,5 @@ # from .adhoc import * -from .celery import * \ No newline at end of file +from .celery import * +from .command import * diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 187790b62..1f322f944 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -34,16 +34,17 @@ class Task(models.Model): One task can have some versions of adhoc, run a task only run the latest version adhoc """ id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) + name = models.CharField(max_length=128, verbose_name=_('Name')) interval = models.IntegerField(verbose_name=_("Interval"), null=True, blank=True, help_text=_("Units: seconds")) crontab = models.CharField(verbose_name=_("Crontab"), null=True, blank=True, max_length=128, help_text=_("5 * * * *")) is_periodic = models.BooleanField(default=False) callback = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Callback")) # Callback must be a registered celery task is_deleted = models.BooleanField(default=False) comment = models.TextField(blank=True, verbose_name=_("Comment")) - created_by = models.CharField(max_length=128, blank=True, null=True, default='') + created_by = models.CharField(max_length=128, blank=True, default='') date_created = models.DateTimeField(auto_now_add=True) __latest_adhoc = None + _ignore_auto_created_by = True @property def short_id(self): @@ -94,7 +95,7 @@ class Task(models.Model): update_fields=None): from ..tasks import run_ansible_task super().save( - force_insert=force_insert, force_update=force_update, + force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields, ) @@ -108,7 +109,7 @@ class Task(models.Model): crontab = self.crontab tasks = { - self.name: { + self.__str__(): { "task": run_ansible_task.name, "interval": interval, "crontab": crontab, @@ -119,11 +120,11 @@ class Task(models.Model): } create_or_update_celery_periodic_tasks(tasks) else: - disable_celery_periodic_task(self.name) + disable_celery_periodic_task(self.__str__()) def delete(self, using=None, keep_parents=False): super().delete(using=using, keep_parents=keep_parents) - delete_celery_periodic_task(self.name) + delete_celery_periodic_task(self.__str__()) @property def schedule(self): @@ -133,10 +134,11 @@ class Task(models.Model): return None def __str__(self): - return self.name + return self.name + '@' + str(self.created_by) class Meta: db_table = 'ops_task' + unique_together = ('name', 'created_by') get_latest_by = 'date_created' @@ -157,8 +159,9 @@ class AdHoc(models.Model): pattern = models.CharField(max_length=64, default='{}', verbose_name=_('Pattern')) _options = models.CharField(max_length=1024, default='', verbose_name=_('Options')) _hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2'] + hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host")) run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin')) - run_as = models.CharField(max_length=128, default='', verbose_name=_("Run as")) + run_as = models.ForeignKey('assets.SystemUser', null=True, on_delete=models.CASCADE) _become = models.CharField(max_length=1024, default='', verbose_name=_("Become")) created_by = models.CharField(max_length=64, default='', null=True, verbose_name=_('Create by')) date_created = models.DateTimeField(auto_now_add=True) @@ -174,14 +177,6 @@ class AdHoc(models.Model): else: raise SyntaxError('Tasks should be a list: {}'.format(item)) - @property - def hosts(self): - return json.loads(self._hosts) - - @hosts.setter - def hosts(self, item): - self._hosts = json.dumps(item) - @property def inventory(self): if self.become: @@ -194,7 +189,7 @@ class AdHoc(models.Model): become_info = None inventory = JMSInventory( - self.hosts, run_as_admin=self.run_as_admin, + self.hosts.all(), run_as_admin=self.run_as_admin, run_as=self.run_as, become_info=become_info ) return inventory @@ -242,14 +237,13 @@ class AdHoc(models.Model): history.timedelta = time.time() - time_start history.save() - def _run_only(self, file_obj=None): + def _run_only(self): runner = AdHocRunner(self.inventory, options=self.options) try: result = runner.run( self.tasks, self.pattern, self.task.name, - file_obj=file_obj, ) return result.results_raw, result.results_summary except AnsibleError as e: diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py new file mode 100644 index 000000000..b2639c6ff --- /dev/null +++ b/apps/ops/models/command.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# +import uuid +import json + +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext +from django.db import models + +from ..ansible.runner import CommandRunner +from ..inventory import JMSInventory + + +class CommandExecution(models.Model): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + hosts = models.ManyToManyField('assets.Asset') + run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE) + command = models.TextField(verbose_name=_("Command")) + _result = models.TextField(blank=True, null=True, verbose_name=_('Result')) + user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True) + is_finished = models.BooleanField(default=False) + date_created = models.DateTimeField(auto_now_add=True) + date_start = models.DateTimeField(null=True) + date_finished = models.DateTimeField(null=True) + + def __str__(self): + return self.command[:10] + + @property + def inventory(self): + return JMSInventory(self.hosts.all(), run_as=self.run_as) + + @property + def result(self): + if self._result: + return json.loads(self._result) + else: + return {} + + @result.setter + def result(self, item): + self._result = json.dumps(item) + + @property + def is_success(self): + if 'error' in self.result: + return False + return True + + def run(self): + print('-'*10 + ' ' + ugettext('Task start') + ' ' + '-'*10) + self.date_start = timezone.now() + ok, msg = self.run_as.is_command_can_run(self.command) + if ok: + runner = CommandRunner(self.inventory) + try: + result = runner.execute(self.command, 'all') + self.result = result.results_command + except Exception as e: + print("Error occur: {}".format(e)) + self.result = {"error": str(e)} + else: + msg = _("Command `{}` is forbidden ........").format(self.command) + print('\033[31m' + msg + '\033[0m') + self.result = {"error": msg} + self.is_finished = True + self.date_finished = timezone.now() + self.save() + print('-'*10 + ' ' + ugettext('Task end') + ' ' + '-'*10) + return self.result diff --git a/apps/ops/serializers.py b/apps/ops/serializers.py index a395a427e..13423486f 100644 --- a/apps/ops/serializers.py +++ b/apps/ops/serializers.py @@ -1,8 +1,19 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals from rest_framework import serializers +from django.shortcuts import reverse -from .models import Task, AdHoc, AdHocRunHistory +from .models import Task, AdHoc, AdHocRunHistory, CommandExecution + + +class CeleryResultSerializer(serializers.Serializer): + id = serializers.UUIDField() + result = serializers.JSONField() + state = serializers.CharField(max_length=16) + + +class CeleryTaskSerializer(serializers.Serializer): + pass class TaskSerializer(serializers.ModelSerializer): @@ -51,3 +62,23 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer): fields = super().get_field_names(declared_fields, info) fields.extend(['summary', 'short_id']) return fields + + +class CommandExecutionSerializer(serializers.ModelSerializer): + result = serializers.JSONField(read_only=True) + log_url = serializers.SerializerMethodField() + + class Meta: + model = CommandExecution + fields = [ + 'id', 'hosts', 'run_as', 'command', 'result', 'log_url', + 'is_finished', 'date_created', 'date_finished' + ] + read_only_fields = [ + 'id', 'result', 'is_finished', 'log_url', 'date_created', + 'date_finished' + ] + + @staticmethod + def get_log_url(obj): + return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id}) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 8df803207..bcdb09cee 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -2,7 +2,7 @@ from celery import shared_task, subtask from common.utils import get_logger, get_object_or_none -from .models import Task +from .models import Task, CommandExecution logger = get_logger(__file__) @@ -28,6 +28,12 @@ def run_ansible_task(tid, callback=None, **kwargs): logger.error("No task found") +@shared_task +def run_command_execution(cid, **kwargs): + execution = get_object_or_none(CommandExecution, id=cid) + return execution.run() + + @shared_task def hello(name, callback=None): print("Hello {}".format(name)) diff --git a/apps/ops/templates/ops/celery_task_log.html b/apps/ops/templates/ops/celery_task_log.html index 13885c2cc..66b3177c7 100644 --- a/apps/ops/templates/ops/celery_task_log.html +++ b/apps/ops/templates/ops/celery_task_log.html @@ -27,6 +27,7 @@ var end = false; var error = false; var interval = 200; + var success = true; function calWinSize() { var t = $('#marker'); @@ -34,20 +35,19 @@ {#colWidth = 1.00 * t.width() / 6;#} } function resize() { - {#console.log(rowHeight, window.innerHeight);#} - {#console.log(colWidth, window.innerWidth);#} var rows = Math.floor(window.innerHeight / rowHeight) - 1; var cols = Math.floor(window.innerWidth / colWidth) - 2; - console.log(rows, cols); term.resize(cols, rows); } function requestAndWrite() { - if (!end) { + if (!end && success) { + success = false; $.ajax({ url: url + '?mark=' + mark, method: "GET", contentType: "application/json; charset=utf-8" }).done(function(data, textStatue, jqXHR) { + success = true; if (jqXHR.status === 203) { error = true; term.write('.'); @@ -64,7 +64,14 @@ } } $(document).ready(function () { - term = new Terminal(); + term = new Terminal({ + cursorBlink: false, + screenKeys: false, + fontFamily: '"Monaco", "Consolas", "monospace"', + fontSize: 12, + rightClickSelectsWord: true, + disableStdin: true + }); term.open(document.getElementById('term')); term.resize(80, 24); resize(); diff --git a/apps/ops/templates/ops/command_execution_create.html b/apps/ops/templates/ops/command_execution_create.html new file mode 100644 index 000000000..250acc4ea --- /dev/null +++ b/apps/ops/templates/ops/command_execution_create.html @@ -0,0 +1,254 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} +{% load bootstrap3 %} + +{% block custom_head_css_js %} + + + + + + + + + + +{% endblock %} + +{% block content %} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+{% endblock %} + +{% block custom_foot_js %} + + +{% endblock %} \ No newline at end of file diff --git a/apps/ops/templates/ops/command_execution_list.html b/apps/ops/templates/ops/command_execution_list.html new file mode 100644 index 000000000..542847b73 --- /dev/null +++ b/apps/ops/templates/ops/command_execution_list.html @@ -0,0 +1,95 @@ +{% extends '_base_list.html' %} +{% load i18n %} +{% load static %} +{% load common_tags %} + +{% block content_left_head %} +{% endblock %} + +{% block table_search %} +
+
+
+ + + to + +
+
+ {% if user_list %} +
+ +
+ {% endif %} +
+ +
+
+
+ +
+
+
+{% endblock %} + +{% block table_head %} + + {% trans 'Hosts' %} + {% trans 'User' %} + {% trans 'Command' %} + {% trans 'Output' %} + {% trans 'Finished' %} + {% trans 'Success' %} + {% trans 'Date start' %} + {% trans 'Date finished' %} +{% endblock %} + +{% block table_body %} + {% for object in object_list %} + + + {{ object.hosts.count }} + {{ object.user }} + {{ object.command| truncatechars:16 }} + 查看 + {{ object.is_finished | state_show | safe }} + {{ object.is_success | state_show | safe }} + {{ object.date_start }} + {{ object.date_finished }} + + {% endfor %} +{% endblock %} + +{% block custom_foot_js %} + + +{% endblock %} + diff --git a/apps/ops/templates/ops/task_detail.html b/apps/ops/templates/ops/task_detail.html index cd39d0a2c..d8f9520be 100644 --- a/apps/ops/templates/ops/task_detail.html +++ b/apps/ops/templates/ops/task_detail.html @@ -4,10 +4,11 @@ {% block custom_head_css_js %} - + - + {% endblock %} + {% block content %}
diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index d1e815800..615f53a8b 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -11,11 +11,12 @@ app_name = "ops" router = DefaultRouter() router.register(r'tasks', api.TaskViewSet, 'task') router.register(r'adhoc', api.AdHocViewSet, 'adhoc') -router.register(r'history', api.AdHocRunHistorySet, 'history') +router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution') urlpatterns = [ path('tasks//run/', api.TaskRun.as_view(), name='task-run'), path('celery/task//log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'), + path('celery/task//result/', api.CeleryResultApi.as_view(), name='celery-result'), ] urlpatterns += router.urls diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py index f49506e80..f8428a667 100644 --- a/apps/ops/urls/view_urls.py +++ b/apps/ops/urls/view_urls.py @@ -18,4 +18,7 @@ urlpatterns = [ path('adhoc//history/', views.AdHocHistoryView.as_view(), name='adhoc-history'), path('adhoc/history//', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'), path('celery/task//log/', views.CeleryTaskLogView.as_view(), name='celery-task-log'), + + path('command-execution/', views.CommandExecutionListView.as_view(), name='command-execution-list'), + path('command-execution/start/', views.CommandExecutionStartView.as_view(), name='command-execution-start'), ] diff --git a/apps/ops/utils.py b/apps/ops/utils.py index 55862dd44..d52f9c743 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -1,5 +1,7 @@ # ~*~ coding: utf-8 ~*~ +from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, get_object_or_none +from orgs.utils import set_to_root_org from .models import Task, AdHoc logger = get_logger(__file__) @@ -10,15 +12,14 @@ def get_task_by_id(task_id): def update_or_create_ansible_task( - task_name, hosts, tasks, + task_name, hosts, tasks, created_by, interval=None, crontab=None, is_periodic=False, callback=None, pattern='all', options=None, - run_as_admin=False, run_as="", become_info=None, - created_by=None, + run_as_admin=False, run_as=None, become_info=None, ): if not hosts or not tasks or not task_name: return - + set_to_root_org() defaults = { 'name': task_name, 'interval': interval, @@ -29,22 +30,27 @@ def update_or_create_ansible_task( } created = False - task, _ = Task.objects.update_or_create( - defaults=defaults, name=task_name, + task, ok = Task.objects.update_or_create( + defaults=defaults, name=task_name, created_by=created_by ) - - adhoc = task.latest_adhoc + adhoc = task.get_latest_adhoc() new_adhoc = AdHoc(task=task, pattern=pattern, run_as_admin=run_as_admin, run_as=run_as) - new_adhoc.hosts = hosts new_adhoc.tasks = tasks new_adhoc.options = options new_adhoc.become = become_info - if not adhoc or adhoc != new_adhoc: - print("Task create new adhoc: {}".format(task_name)) + hosts_same = True + if adhoc: + old_hosts = set([str(asset.id) for asset in adhoc.hosts.all()]) + new_hosts = set([str(asset.id) for asset in hosts]) + hosts_same = old_hosts == new_hosts + + if not adhoc or adhoc != new_adhoc or not hosts_same: + logger.info(_("Update task content: {}").format(task_name)) new_adhoc.save() + new_adhoc.hosts.set(hosts) task.latest_adhoc = new_adhoc created = True return task, created diff --git a/apps/ops/views/__init__.py b/apps/ops/views/__init__.py new file mode 100644 index 000000000..58bb835a6 --- /dev/null +++ b/apps/ops/views/__init__.py @@ -0,0 +1,3 @@ +from .adhoc import * +from .celery import * +from .command import * \ No newline at end of file diff --git a/apps/ops/views.py b/apps/ops/views/adhoc.py similarity index 68% rename from apps/ops/views.py rename to apps/ops/views/adhoc.py index 1bbfbfc23..737047290 100644 --- a/apps/ops/views.py +++ b/apps/ops/views/adhoc.py @@ -2,14 +2,22 @@ from django.utils.translation import ugettext as _ from django.conf import settings -from django.views.generic import ListView, DetailView, TemplateView +from django.views.generic import ListView, DetailView from common.mixins import DatetimeSearchMixin -from .models import Task, AdHoc, AdHocRunHistory, CeleryTask -from common.permissions import SuperUserRequiredMixin, AdminUserRequiredMixin +from common.permissions import AdminUserRequiredMixin +from orgs.utils import current_org +from ..models import Task, AdHoc, AdHocRunHistory -class TaskListView(SuperUserRequiredMixin, DatetimeSearchMixin, ListView): +__all__ = [ + 'TaskListView', 'TaskDetailView', 'TaskHistoryView', + 'TaskAdhocView', 'AdHocDetailView', 'AdHocHistoryDetailView', + 'AdHocHistoryView' +] + + +class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): paginate_by = settings.DISPLAY_PER_PAGE model = Task ordering = ('-date_created',) @@ -18,18 +26,23 @@ class TaskListView(SuperUserRequiredMixin, DatetimeSearchMixin, ListView): keyword = '' def get_queryset(self): - self.queryset = super().get_queryset() + queryset = super().get_queryset() + if current_org.is_real(): + queryset = queryset.filter(created_by=current_org.id) + else: + queryset = queryset.filter(created_by='') + self.keyword = self.request.GET.get('keyword', '') - self.queryset = self.queryset.filter( + queryset = queryset.filter( date_created__gt=self.date_from, date_created__lt=self.date_to ) if self.keyword: - self.queryset = self.queryset.filter( + queryset = queryset.filter( name__icontains=self.keyword, ) - return self.queryset + return queryset def get_context_data(self, **kwargs): context = { @@ -43,10 +56,16 @@ class TaskListView(SuperUserRequiredMixin, DatetimeSearchMixin, ListView): return super().get_context_data(**kwargs) -class TaskDetailView(SuperUserRequiredMixin, DetailView): +class TaskDetailView(AdminUserRequiredMixin, DetailView): model = Task template_name = 'ops/task_detail.html' + def get_queryset(self): + queryset = super().get_queryset() + if current_org: + queryset = queryset.filter(created_by=current_org.id) + return queryset + def get_context_data(self, **kwargs): context = { 'app': _('Ops'), @@ -56,7 +75,7 @@ class TaskDetailView(SuperUserRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class TaskAdhocView(SuperUserRequiredMixin, DetailView): +class TaskAdhocView(AdminUserRequiredMixin, DetailView): model = Task template_name = 'ops/task_adhoc.html' @@ -69,7 +88,7 @@ class TaskAdhocView(SuperUserRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class TaskHistoryView(SuperUserRequiredMixin, DetailView): +class TaskHistoryView(AdminUserRequiredMixin, DetailView): model = Task template_name = 'ops/task_history.html' @@ -82,7 +101,7 @@ class TaskHistoryView(SuperUserRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class AdHocDetailView(SuperUserRequiredMixin, DetailView): +class AdHocDetailView(AdminUserRequiredMixin, DetailView): model = AdHoc template_name = 'ops/adhoc_detail.html' @@ -95,7 +114,7 @@ class AdHocDetailView(SuperUserRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class AdHocHistoryView(SuperUserRequiredMixin, DetailView): +class AdHocHistoryView(AdminUserRequiredMixin, DetailView): model = AdHoc template_name = 'ops/adhoc_history.html' @@ -108,7 +127,7 @@ class AdHocHistoryView(SuperUserRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class AdHocHistoryDetailView(SuperUserRequiredMixin, DetailView): +class AdHocHistoryDetailView(AdminUserRequiredMixin, DetailView): model = AdHocRunHistory template_name = 'ops/adhoc_history_detail.html' @@ -121,6 +140,5 @@ class AdHocHistoryDetailView(SuperUserRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class CeleryTaskLogView(AdminUserRequiredMixin, DetailView): - template_name = 'ops/celery_task_log.html' - model = CeleryTask + + diff --git a/apps/ops/views/celery.py b/apps/ops/views/celery.py new file mode 100644 index 000000000..f3da0b17b --- /dev/null +++ b/apps/ops/views/celery.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# +from django.views.generic import DetailView + +from common.permissions import AdminUserRequiredMixin +from ..models import CeleryTask + + +__all__ = ['CeleryTaskLogView'] + + +class CeleryTaskLogView(AdminUserRequiredMixin, DetailView): + template_name = 'ops/celery_task_log.html' + model = CeleryTask diff --git a/apps/ops/views/command.py b/apps/ops/views/command.py new file mode 100644 index 000000000..1c495e44a --- /dev/null +++ b/apps/ops/views/command.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# + +from django.utils.translation import ugettext as _ +from django.conf import settings +from django.views.generic import ListView, TemplateView + +from common.mixins import DatetimeSearchMixin +from ..models import CommandExecution +from ..forms import CommandExecutionForm + + +__all__ = [ + 'CommandExecutionListView', 'CommandExecutionStartView' +] + + +class CommandExecutionListView(DatetimeSearchMixin, ListView): + template_name = 'ops/command_execution_list.html' + model = CommandExecution + paginate_by = settings.DISPLAY_PER_PAGE + ordering = ('-date_created',) + context_object_name = 'task_list' + keyword = '' + + def _get_queryset(self): + self.keyword = self.request.GET.get('keyword', '') + queryset = super().get_queryset() + if self.date_from: + queryset = queryset.filter(date_start__gte=self.date_from) + if self.date_to: + queryset = queryset.filter(date_start__lte=self.date_to) + if self.keyword: + queryset = queryset.filter(command__icontains=self.keyword) + return queryset + + def get_queryset(self): + queryset = self._get_queryset().filter(user=self.request.user) + return queryset + + def get_context_data(self, **kwargs): + context = { + 'app': _('Ops'), + 'action': _('Command execution list'), + 'date_from': self.date_from, + 'date_to': self.date_to, + 'keyword': self.keyword, + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class CommandExecutionStartView(TemplateView): + template_name = 'ops/command_execution_create.html' + form_class = CommandExecutionForm + + def get_user_system_users(self): + from perms.utils import AssetPermissionUtil + user = self.request.user + util = AssetPermissionUtil(user) + system_users = [s for s in util.get_system_users() if s.protocol == 'ssh'] + return system_users + + def get_context_data(self, **kwargs): + system_users = self.get_user_system_users() + context = { + 'app': _('Ops'), + 'action': _('Command execution'), + 'form': self.get_form(), + 'system_users': system_users + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + def get_form(self): + return self.form_class() diff --git a/apps/perms/api.py b/apps/perms/api.py index 98287212f..37497e2f1 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -3,18 +3,20 @@ from django.shortcuts import get_object_or_404 from rest_framework.views import APIView, Response -from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView +from rest_framework.generics import ListAPIView, get_object_or_404, \ + RetrieveUpdateAPIView from rest_framework import viewsets from rest_framework.pagination import LimitOffsetPagination from common.utils import set_or_append_attr_bulk from common.permissions import IsValidUser, IsOrgAdmin, IsOrgAdminOrAppUser +from common.tree import TreeNode, TreeNodeSerializer from orgs.mixins import RootOrgViewMixin +from orgs.utils import set_to_root_org from .utils import AssetPermissionUtil from .models import AssetPermission from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \ NodeGrantedSerializer, SystemUser, NodeSerializer -from orgs.utils import set_to_root_org from . import serializers from .mixins import AssetsFilterMixin @@ -25,6 +27,7 @@ __all__ = [ 'UserGroupGrantedNodesApi', 'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi', 'ValidateUserAssetPermissionApi', 'AssetPermissionRemoveUserApi', 'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi', 'AssetPermissionAddAssetApi', 'UserGrantedNodeChildrenApi', + 'UserGrantedNodesWithAssetsAsTreeApi', ] @@ -186,6 +189,99 @@ class UserGrantedNodesWithAssetsApi(AssetsFilterMixin, ListAPIView): return super().get_permissions() +class UserGrantedNodesWithAssetsAsTreeApi(ListAPIView): + serializer_class = TreeNodeSerializer + permission_classes = (IsOrgAdminOrAppUser,) + show_assets = True + system_user_id = None + + def change_org_if_need(self): + if self.request.user.is_superuser or \ + self.request.user.is_app or \ + self.kwargs.get('pk') is None: + set_to_root_org() + + def get(self, request, *args, **kwargs): + self.show_assets = request.query_params.get('show_assets', '1') == '1' + self.system_user_id = request.query_params.get('system_user') + return super().get(request, *args, **kwargs) + + @staticmethod + def parse_node_to_tree_node(node): + name = '{} ({})'.format(node.value, node.assets_amount) + node_serializer = serializers.GrantedNodeSerializer(node) + data = { + 'id': node.key, + 'name': name, + 'title': name, + 'pId': node.parent_key, + 'isParent': True, + 'open': node.is_root(), + 'meta': { + 'node': node_serializer.data, + 'type': 'node' + } + } + tree_node = TreeNode(**data) + return tree_node + + @staticmethod + def parse_asset_to_tree_node(node, asset, system_users): + system_user_serializer = serializers.GrantedSystemUserSerializer( + system_users, many=True + ) + asset_serializer = serializers.GrantedAssetSerializer(asset) + icon_skin = 'file' + if asset.platform.lower() == 'windows': + icon_skin = 'windows' + elif asset.platform.lower() == 'linux': + icon_skin = 'linux' + data = { + 'id': str(asset.id), + 'name': asset.hostname, + 'title': asset.ip, + 'pId': node.key, + 'isParent': False, + 'open': False, + 'iconSkin': icon_skin, + 'meta': { + 'system_users': system_user_serializer.data, + 'type': 'asset', + 'asset': asset_serializer.data + } + } + tree_node = TreeNode(**data) + return tree_node + + def get_permissions(self): + if self.kwargs.get('pk') is None: + self.permission_classes = (IsValidUser,) + return super().get_permissions() + + def get_queryset(self): + self.change_org_if_need() + user_id = self.kwargs.get('pk', '') + queryset = [] + if not user_id: + user = self.request.user + else: + user = get_object_or_404(User, id=user_id) + util = AssetPermissionUtil(user) + if self.system_user_id: + util.filter_permission_with_system_user(system_user=self.system_user_id) + nodes = util.get_nodes_with_assets() + for node, assets in nodes.items(): + data = self.parse_node_to_tree_node(node) + queryset.append(data) + if not self.show_assets: + continue + for asset, system_users in assets.items(): + data = self.parse_asset_to_tree_node(node, asset, system_users) + queryset.append(data) + queryset = sorted(queryset) + return queryset + + class UserGrantedNodeAssetsApi(AssetsFilterMixin, ListAPIView): """ 查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产 diff --git a/apps/perms/serializers.py b/apps/perms/serializers.py index cc5d0edcd..e307a8ed4 100644 --- a/apps/perms/serializers.py +++ b/apps/perms/serializers.py @@ -5,9 +5,16 @@ from rest_framework import serializers from common.fields import StringManyToManyField from .models import AssetPermission -from assets.models import Node +from assets.models import Node, Asset, SystemUser from assets.serializers import AssetGrantedSerializer +__all__ = [ + 'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer', + 'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer', + 'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', + 'GrantedAssetSerializer', 'GrantedSystemUserSerializer', +] + class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer): class Meta: @@ -74,3 +81,29 @@ class AssetPermissionNodeSerializer(serializers.ModelSerializer): @staticmethod def get_tree_parent(obj): return obj.parent_key + + +class GrantedNodeSerializer(serializers.ModelSerializer): + class Meta: + model = Node + fields = [ + 'id', 'name', 'key', 'value', + ] + + +class GrantedAssetSerializer(serializers.ModelSerializer): + class Meta: + model = Asset + fields = [ + 'id', 'hostname', 'ip', 'port', 'protocol', 'platform', + 'domain', 'is_active', 'comment' + ] + + +class GrantedSystemUserSerializer(serializers.ModelSerializer): + class Meta: + model = SystemUser + fields = [ + 'id', 'name', 'username', 'protocol', 'priority', + 'login_mode', 'comment' + ] diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index adf6b45e6..8e4716fac 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -29,6 +29,11 @@ urlpatterns = [ api.UserGrantedNodesWithAssetsApi.as_view(), name='user-nodes-assets'), path('user/nodes-assets/', api.UserGrantedNodesWithAssetsApi.as_view(), name='my-nodes-assets'), + path('user//nodes-assets/tree/', + api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-assets-as-tree'), + path('user/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), + name='my-nodes-assets-as-tree'), + # 查询某个用户组授权的资产和资产组 path('user-group//assets/', diff --git a/apps/perms/utils.py b/apps/perms/utils.py index 8ebe696e0..8b79175aa 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -11,36 +11,41 @@ from .hands import Node logger = get_logger(__file__) -class Tree: +class GenerateTree: def __init__(self): - self.__all_nodes = Node.objects.all().prefetch_related('assets') + """ + nodes: {"node_instance": { + "asset_instance": set("system_user") + } + """ + self.__all_nodes = Node.objects.all() self.__node_asset_map = defaultdict(set) self.nodes = defaultdict(dict) - self.root = Node.root() - self.init_node_asset_map() - - def init_node_asset_map(self): - for node in self.__all_nodes: - assets = [a.id for a in node.assets.all()] - for asset in assets: - self.__node_asset_map[str(asset)].add(node) def add_asset(self, asset, system_users): - nodes = self.__node_asset_map.get(str(asset.id), []) + nodes = asset.nodes.all() self.add_nodes(nodes) for node in nodes: self.nodes[node][asset].update(system_users) + def get_nodes(self): + for node in self.nodes: + assets = set(self.nodes.get(node).keys()) + for n in self.nodes.keys(): + if n.key.startswith(node.key + ':'): + assets.update(set(self.nodes[n].keys())) + node.assets_amount = len(assets) + return self.nodes + def add_node(self, node): if node in self.nodes: return else: self.nodes[node] = defaultdict(set) - if node.key == self.root.key: + if node.is_root(): return - parent_key = ':'.join(node.key.split(':')[:-1]) for n in self.__all_nodes: - if n.key == parent_key: + if n.key == node.parent_key: self.add_node(n) break @@ -107,6 +112,9 @@ class AssetPermissionUtil: self._permissions = permissions return permissions + def filter_permission_with_system_user(self, system_user): + self._permissions = self.permissions.filter(system_users=system_user) + def get_nodes_direct(self): """ 返回用户/组授权规则直接关联的节点 @@ -150,10 +158,17 @@ class AssetPermissionUtil: :return: """ assets = self.get_assets() - tree = Tree() + tree = GenerateTree() for asset, system_users in assets.items(): tree.add_asset(asset, system_users) - return tree.nodes + return tree.get_nodes() + + def get_system_users(self): + system_users = set() + permissions = self.permissions.prefetch_related('system_users') + for perm in permissions: + system_users.update(perm.system_users.all()) + return system_users def is_obj_attr_has(obj, val, attrs=("hostname", "ip", "comment")): diff --git a/apps/static/css/plugins/codemirror/ambiance.css b/apps/static/css/plugins/codemirror/ambiance.css new file mode 100755 index 000000000..c844566ea --- /dev/null +++ b/apps/static/css/plugins/codemirror/ambiance.css @@ -0,0 +1,77 @@ +/* ambiance theme for codemirror */ + +/* Color scheme */ + +.cm-s-ambiance .cm-keyword { color: #cda869; } +.cm-s-ambiance .cm-atom { color: #CF7EA9; } +.cm-s-ambiance .cm-number { color: #78CF8A; } +.cm-s-ambiance .cm-def { color: #aac6e3; } +.cm-s-ambiance .cm-variable { color: #ffb795; } +.cm-s-ambiance .cm-variable-2 { color: #eed1b3; } +.cm-s-ambiance .cm-variable-3 { color: #faded3; } +.cm-s-ambiance .cm-property { color: #eed1b3; } +.cm-s-ambiance .cm-operator {color: #fa8d6a;} +.cm-s-ambiance .cm-comment { color: #555; font-style:italic; } +.cm-s-ambiance .cm-string { color: #8f9d6a; } +.cm-s-ambiance .cm-string-2 { color: #9d937c; } +.cm-s-ambiance .cm-meta { color: #D2A8A1; } +.cm-s-ambiance .cm-qualifier { color: yellow; } +.cm-s-ambiance .cm-builtin { color: #9999cc; } +.cm-s-ambiance .cm-bracket { color: #24C2C7; } +.cm-s-ambiance .cm-tag { color: #fee4ff } +.cm-s-ambiance .cm-attribute { color: #9B859D; } +.cm-s-ambiance .cm-header {color: blue;} +.cm-s-ambiance .cm-quote { color: #24C2C7; } +.cm-s-ambiance .cm-hr { color: pink; } +.cm-s-ambiance .cm-link { color: #F4C20B; } +.cm-s-ambiance .cm-special { color: #FF9D00; } +.cm-s-ambiance .cm-error { color: #AF2018; } + +.cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0; } +.cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22; } + +.cm-s-ambiance .CodeMirror-selected { + background: rgba(255, 255, 255, 0.15); +} +.cm-s-ambiance.CodeMirror-focused .CodeMirror-selected { + background: rgba(255, 255, 255, 0.10); +} + +/* Editor styling */ + +.cm-s-ambiance.CodeMirror { + line-height: 1.40em; + color: #E6E1DC; + background-color: #202020; + -webkit-box-shadow: inset 0 0 10px black; + -moz-box-shadow: inset 0 0 10px black; + box-shadow: inset 0 0 10px black; +} + +.cm-s-ambiance .CodeMirror-gutters { + background: #3D3D3D; + border-right: 1px solid #4D4D4D; + box-shadow: 0 10px 20px black; +} + +.cm-s-ambiance .CodeMirror-linenumber { + text-shadow: 0px 1px 1px #4d4d4d; + color: #111; + padding: 0 5px; +} + +.cm-s-ambiance .CodeMirror-guttermarker { color: #aaa; } +.cm-s-ambiance .CodeMirror-guttermarker-subtle { color: #111; } + +.cm-s-ambiance .CodeMirror-lines .CodeMirror-cursor { + border-left: 1px solid #7991E8; +} + +.cm-s-ambiance .CodeMirror-activeline-background { + background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031); +} + +.cm-s-ambiance.CodeMirror, +.cm-s-ambiance .CodeMirror-gutters { + background-image: url("data:image/png;base64,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"); +} diff --git a/apps/static/css/plugins/codemirror/codemirror.css b/apps/static/css/plugins/codemirror/codemirror.css new file mode 100755 index 000000000..68c67b170 --- /dev/null +++ b/apps/static/css/plugins/codemirror/codemirror.css @@ -0,0 +1,309 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; +} +.CodeMirror-scroll { + /* Set scrolling behaviour here */ + overflow: auto; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror div.CodeMirror-cursor { + border-left: 1px solid black; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +@-moz-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@-webkit-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} + +/* Can style cursor different in overwrite (non-insert) mode */ +div.CodeMirror-overwrite div.CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + line-height: 1; + position: relative; + overflow: hidden; + background: white; + color: black; +} + +.CodeMirror-scroll { + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actuall scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + padding-bottom: 30px; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + -moz-box-sizing: content-box; + box-sizing: content-box; + padding-bottom: 30px; + margin-bottom: -32px; + display: inline-block; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-wrap .CodeMirror-scroll { + overflow-x: hidden; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} +.CodeMirror-measure pre { position: static; } + +.CodeMirror div.CodeMirror-cursor { + position: absolute; + border-right: none; + width: 0; +} + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } diff --git a/apps/static/js/plugins/codemirror/codemirror.js b/apps/static/js/plugins/codemirror/codemirror.js new file mode 100755 index 000000000..4f8a23bde --- /dev/null +++ b/apps/static/js/plugins/codemirror/codemirror.js @@ -0,0 +1,7830 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// This is CodeMirror (http://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + module.exports = mod(); + else if (typeof define == "function" && define.amd) // AMD + return define([], mod); + else // Plain browser env + this.CodeMirror = mod(); +})(function() { + "use strict"; + + // BROWSER SNIFFING + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + + var gecko = /gecko\/\d/i.test(navigator.userAgent); + // ie_uptoN means Internet Explorer version N or lower + var ie_upto10 = /MSIE \d/.test(navigator.userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent); + var ie = ie_upto10 || ie_11up; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); + var webkit = /WebKit\//.test(navigator.userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var presto = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var khtml = /KHTML\//.test(navigator.userAgent); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); + var phantom = /PhantomJS/.test(navigator.userAgent); + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var windows = /win/i.test(navigator.platform); + + var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) presto_version = Number(presto_version[1]); + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && ie_version >= 9); + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + // EDITOR CONSTRUCTOR + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); + + this.options = options = options ? copyObj(options) : {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + setGuttersForLineNumbers(options); + + var doc = options.value; + if (typeof doc == "string") doc = new Doc(doc, options.mode); + this.doc = doc; + + var display = this.display = new Display(place, doc); + display.wrapper.CodeMirror = this; + updateGutters(this); + themeChanged(this); + if (options.lineWrapping) + this.display.wrapper.className += " CodeMirror-wrap"; + if (options.autofocus && !mobile) focusInput(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput + draggingText: false, + highlight: new Delayed() // stores highlight worker timeout + }; + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) setTimeout(bind(resetInput, this, true), 20); + + registerEventHandlers(this); + ensureGlobalHandlers(); + + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); + + if ((options.autofocus && !mobile) || activeElt() == display.input) + setTimeout(bind(onFocus, this), 20); + else + onBlur(this); + + for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) + optionHandlers[opt](this, options[opt], Init); + maybeUpdateLineNumberWidth(this); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); + endOperation(this); + } + + // DISPLAY CONSTRUCTOR + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc) { + var d = this; + + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) input.style.width = "1000px"; + else input.setAttribute("wrap", "off"); + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) input.style.border = "1px solid black"; + input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false"); + + // Wraps and hides input textarea + d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The fake scrollbar elements. + d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = elt("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV, + d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + // Needed to hide big blue blinking cursor on Mobile Safari + if (ios) input.style.width = "0px"; + if (!webkit) d.scroller.draggable = true; + // Needed to handle Tab key in KHTML + if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; } + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px"; + + if (place.appendChild) place.appendChild(d.wrapper); + else place(d.wrapper); + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + // Information about the rendered lines. + d.view = []; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastSizeC = 0; + d.updateLineNumbers = null; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // See readInput and resetInput + d.prevInput = ""; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + d.pollingFast = false; + // Self-resetting timeout for the poller + d.poll = new Delayed(); + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks when resetInput has punted to just putting a short + // string into the textarea instead of the full selection. + d.inaccurateSelection = false; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; + } + + // STATE UPDATES + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function(line) { + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + }); + cm.doc.frontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) regChange(cm); + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function(){updateScrollbars(cm);}, 100); + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function(line) { + if (lineIsHidden(cm.doc, line)) return 0; + + var widgetsHeight = 0; + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; + } + + if (wrapping) + return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; + else + return widgetsHeight + th; + }; + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function(line) { + var estHeight = est(line); + if (estHeight != line.height) updateLineHeight(line, estHeight); + }); + } + + function keyMapChanged(cm) { + var map = keyMap[cm.options.keyMap], style = map.style; + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + + (style ? " cm-keymap-" + style : ""); + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + for (var i = 0; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + updateGutterSpace(cm); + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; + cm.display.sizer.style.marginLeft = width + "px"; + cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0; + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) return 0; + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found = merged.find(0, true); + len -= cur.text.length - found.from.ch; + cur = found.to.line; + len += cur.text.length - found.to.ch; + } + return len; + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function(line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); + } + } + + // SCROLLBARS + + function hScrollbarTakesSpace(cm) { + return cm.display.scroller.clientHeight - cm.display.wrapper.clientHeight < scrollerCutOff - 3; + } + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var scroll = cm.display.scroller; + return { + clientHeight: scroll.clientHeight, + barHeight: cm.display.scrollbarV.clientHeight, + scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth, + hScrollbarTakesSpace: hScrollbarTakesSpace(cm), + barWidth: cm.display.scrollbarH.clientWidth, + docHeight: Math.round(cm.doc.height + paddingVert(cm.display)) + }; + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbars(cm, measure) { + if (!measure) measure = measureForScrollbars(cm); + var d = cm.display, sWidth = scrollbarWidth(d.measure); + var scrollHeight = measure.docHeight + scrollerCutOff; + var needsH = measure.scrollWidth > measure.clientWidth; + if (needsH && measure.scrollWidth <= measure.clientWidth + 1 && + sWidth > 0 && !measure.hScrollbarTakesSpace) + needsH = false; // (Issue #2562) + var needsV = scrollHeight > measure.clientHeight; + + if (needsV) { + d.scrollbarV.style.display = "block"; + d.scrollbarV.style.bottom = needsH ? sWidth + "px" : "0"; + // A bug in IE8 can cause this value to be negative, so guard it. + d.scrollbarV.firstChild.style.height = + Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px"; + } else { + d.scrollbarV.style.display = ""; + d.scrollbarV.firstChild.style.height = "0"; + } + if (needsH) { + d.scrollbarH.style.display = "block"; + d.scrollbarH.style.right = needsV ? sWidth + "px" : "0"; + d.scrollbarH.firstChild.style.width = + (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px"; + } else { + d.scrollbarH.style.display = ""; + d.scrollbarH.firstChild.style.width = "0"; + } + if (needsH && needsV) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = sWidth + "px"; + } else d.scrollbarFiller.style.display = ""; + if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sWidth + "px"; + d.gutterFiller.style.width = d.gutters.offsetWidth + "px"; + } else d.gutterFiller.style.display = ""; + + if (!cm.state.checkedOverlayScrollbar && measure.clientHeight > 0) { + if (sWidth === 0) { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = w; + var barMouseDown = function(e) { + if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH) + operation(cm, onMouseDown)(e); + }; + on(d.scrollbarV, "mousedown", barMouseDown); + on(d.scrollbarH, "mousedown", barMouseDown); + } + cm.state.checkedOverlayScrollbar = true; + } + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewport may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) + return {from: ensureFrom, + to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)}; + if (Math.min(ensureTo, doc.lastLine()) >= to) + return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight), + to: ensureTo}; + } + return {from: from, to: Math.max(to, from + 1)}; + } + + // LINE NUMBERS + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) if (!view[i].hidden) { + if (cm.options.fixedGutter && view[i].gutter) + view[i].gutter.style.left = left; + var align = view[i].alignable; + if (align) for (var j = 0; j < align.length; j++) + align[j].style.left = left; + } + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px"; + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) return false; + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm); + return true; + } + return false; + } + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)); + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; + } + + // DISPLAY DRAWING + + function DisplayUpdate(cm, viewport, force) { + var display = cm.display; + + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.oldViewFrom = display.viewFrom; this.oldViewTo = display.viewTo; + this.oldScrollerWidth = display.scroller.clientWidth; + this.force = force; + this.dims = getDimensions(cm); + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc; + if (update.editorIsHidden) { + resetView(cm); + return false; + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + countDirtyView(cm) == 0) + return false; + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm); + update.dims = getDimensions(cm); + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastSizeC != update.wrapperHeight; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !update.force && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false; + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var focused = activeElt(); + if (toUpdate > 4) display.lineDiv.style.display = "none"; + patchDisplay(cm, display.updateLineNumbers, update.dims); + if (toUpdate > 4) display.lineDiv.style.display = ""; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); + + // Prevent selection and cursors from interfering with the scroll + // width. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + + if (different) { + display.lastSizeC = update.wrapperHeight; + startWorker(cm, 400); + } + + display.updateLineNumbers = null; + + return true; + } + + function postUpdateDisplay(cm, update) { + var force = update.force, viewport = update.viewport; + for (var first = true;; first = false) { + if (first && cm.options.lineWrapping && update.oldScrollerWidth != cm.display.scroller.clientWidth) { + force = true; + } else { + force = false; + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - scrollerCutOff - + cm.display.scroller.clientHeight, viewport.top)}; + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + break; + } + if (!updateDisplayIfNeeded(cm, update)) break; + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + } + + signalLater(cm, "update", cm); + if (cm.display.viewFrom != update.oldViewFrom || cm.display.viewTo != update.oldViewTo) + signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + } + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px"; + cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px"; + } + + function checkForWebkitWidthBug(cm, measure) { + // Work around Webkit bug where it sometimes reserves space for a + // non-existing phantom scrollbar in the scroller (Issue #2420) + if (cm.display.sizer.offsetWidth + cm.display.gutters.offsetWidth < cm.display.scroller.clientWidth - 1) { + cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = "0px"; + cm.display.gutters.style.height = measure.docHeight + "px"; + } + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height; + if (cur.hidden) continue; + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + } + var diff = cur.line.height - height; + if (height < 2) height = textHeight(display); + if (diff > .001 || diff < -.001) { + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) for (var j = 0; j < cur.rest.length; j++) + updateWidgetHeight(cur.rest[j]); + } + } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) + line.widgets[i].height = line.widgets[i].node.offsetHeight; + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; + width[cm.options.gutters[i]] = n.clientWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth}; + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + node.style.display = "none"; + else + node.parentNode.removeChild(node); + return next; + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) { + } else if (!lineView.node) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) cur = rm(cur); + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) cur = rm(cur); + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") updateLineText(cm, lineView); + else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); + else if (type == "class") updateLineClasses(lineView); + else if (type == "widget") updateLineWidgets(lineView, dims); + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + lineView.text.parentNode.replaceChild(lineView.node, lineView.text); + lineView.node.appendChild(lineView.text); + if (ie && ie_version < 8) lineView.node.style.zIndex = 2; + } + return lineView.node; + } + + function updateLineBackground(lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) cls += " CodeMirror-linebackground"; + if (lineView.background) { + if (cls) lineView.background.className = cls; + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built; + } + return buildLineContent(cm, lineView); + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) lineView.node = built.pre; + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(lineView) { + updateLineBackground(lineView); + if (lineView.line.wrapClass) + ensureLineWrapped(lineView).className = lineView.line.wrapClass; + else if (lineView.node != lineView.text) + lineView.node.className = ""; + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = + wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " + + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), + lineView.text); + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + + cm.display.lineNumInnerWidth + "px")); + if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); + } + } + } + + function updateLineWidgets(lineView, dims) { + if (lineView.alignable) lineView.alignable = null; + for (var node = lineView.node.firstChild, next; node; node = next) { + var next = node.nextSibling; + if (node.className == "CodeMirror-linewidget") + lineView.node.removeChild(node); + } + insertLineWidgets(lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) lineView.bgClass = built.bgClass; + if (built.textClass) lineView.textClass = built.textClass; + + updateLineClasses(lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(lineView, dims); + return lineView.node; + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(lineView, dims) { + insertLineWidgetsFor(lineView.line, lineView, dims, true); + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + insertLineWidgetsFor(lineView.rest[i], lineView, dims, false); + } + + function insertLineWidgetsFor(line, lineView, dims, allowAbove) { + if (!line.widgets) return; + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.ignoreEvents = true; + positionLineWidget(widget, node, lineView, dims); + if (allowAbove && widget.above) + wrap.insertBefore(node, lineView.gutter || lineView.text); + else + wrap.appendChild(node); + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; + } + } + + // POSITION OBJECT + + // A Pos instance represents a position within the text. + var Pos = CodeMirror.Pos = function(line, ch) { + if (!(this instanceof Pos)) return new Pos(line, ch); + this.line = line; this.ch = ch; + }; + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; }; + + function copyPos(x) {return Pos(x.line, x.ch);} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } + + // SELECTION / CURSOR + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + function Selection(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + } + + Selection.prototype = { + primary: function() { return this.ranges[this.primIndex]; }, + equals: function(other) { + if (other == this) return true; + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false; + for (var i = 0; i < this.ranges.length; i++) { + var here = this.ranges[i], there = other.ranges[i]; + if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false; + } + return true; + }, + deepCopy: function() { + for (var out = [], i = 0; i < this.ranges.length; i++) + out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); + return new Selection(out, this.primIndex); + }, + somethingSelected: function() { + for (var i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].empty()) return true; + return false; + }, + contains: function(pos, end) { + if (!end) end = pos; + for (var i = 0; i < this.ranges.length; i++) { + var range = this.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + return i; + } + return -1; + } + }; + + function Range(anchor, head) { + this.anchor = anchor; this.head = head; + } + + Range.prototype = { + from: function() { return minPos(this.anchor, this.head); }, + to: function() { return maxPos(this.anchor, this.head); }, + empty: function() { + return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; + } + }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(ranges, primIndex) { + var prim = ranges[primIndex]; + ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + if (cmp(prev.to(), cur.from()) >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) --primIndex; + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex); + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0); + } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} + function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0); + var last = doc.first + doc.size - 1; + if (pos.line > last) return Pos(last, getLine(doc, last).text.length); + return clipToLen(pos, getLine(doc, pos.line).text.length); + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) return Pos(pos.line, linelen); + else if (ch < 0) return Pos(pos.line, 0); + else return pos; + } + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} + function clipPosArray(doc, array) { + for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]); + return out; + } + + // SELECTION UPDATES + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(doc, range, head, other) { + if (doc.cm && doc.cm.display.shift || doc.extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head); + } else { + return new Range(other || head, head); + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options) { + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + for (var out = [], i = 0; i < doc.sel.ranges.length; i++) + out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); + var newSel = normalizeSelection(out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); + } + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); + if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1); + else return sel; + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + sel = filterSelectionChange(doc, sel); + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm) + ensureCursorVisible(doc.cm); + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) return; + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) out = sel.ranges.slice(0, i); + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel; + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, bias, mayClear) { + var flipped = false, curPos = pos; + var dir = bias || 1; + doc.cantEdit = false; + search: for (;;) { + var line = getLine(doc, curPos.line); + if (line.markedSpans) { + for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} + } + } + if (!m.atomic) continue; + var newPos = m.find(dir < 0 ? -1 : 1); + if (cmp(newPos, curPos) == 0) { + newPos.ch += dir; + if (newPos.ch < 0) { + if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); + else newPos = null; + } else if (newPos.ch > line.text.length) { + if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); + else newPos = null; + } + if (!newPos) { + if (flipped) { + // Driven in a corner -- no valid cursor position found at all + // -- try again *with* clearing, if we didn't already + if (!mayClear) return skipAtomic(doc, pos, bias, true); + // Otherwise, turn off editing until further notice, and return the start of the doc + doc.cantEdit = true; + return Pos(doc.first, 0); + } + flipped = true; newPos = pos; dir = -dir; + } + } + curPos = newPos; + continue search; + } + } + } + return curPos; + } + } + + // SELECTION DRAWING + + // Redraw the selection and/or cursor + function drawSelection(cm) { + var display = cm.display, doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); + + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + var collapsed = range.empty(); + if (collapsed || cm.options.showCursorWhenSelecting) + drawSelectionCursor(cm, range, curFragment); + if (!collapsed) + drawSelectionRange(cm, range, selFragment); + } + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + } + + return result; + } + + function showSelection(cm, drawn) { + removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + cm.display.inputDiv.style.top = drawn.teTop + "px"; + cm.display.inputDiv.style.left = drawn.teLeft + "px"; + } + } + + function updateSelection(cm) { + showSelection(cm, drawSelection(cm)); + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, range, output) { + var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right; + + function add(left, top, width, bottom) { + if (top < 0) top = 0; + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + + "px; height: " + (bottom - top) + "px")); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); + } + + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = leftSide; + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom); + left = leftSide; + if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); + } + if (toArg == null && to == lineLen) right = rightSide; + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; + if (left < leftSide + 1) left = leftSide; + add(left, rightPos.top, right - left, rightPos.bottom); + }); + return {start: start, end: end}; + } + + var sFrom = range.from(), sTo = range.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + add(leftSide, leftEnd.bottom, null, rightStart.top); + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) return; + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); + else if (cm.options.cursorBlinkRate < 0) + display.cursorDiv.style.visibility = "hidden"; + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)); + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.frontier < doc.first) doc.frontier = doc.first; + if (doc.frontier >= cm.display.viewTo) return; + var end = +new Date + cm.options.workTime; + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + var changedLines = []; + + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { + if (doc.frontier >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles; + var highlighted = highlightLine(cm, line, state, true); + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) line.styleClasses = newCls; + else if (oldCls) line.styleClasses = null; + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); + for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; + if (ischange) changedLines.push(doc.frontier); + line.stateAfter = copyState(doc.mode, state); + } else { + processLine(cm, line.text, state); + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; + } + ++doc.frontier; + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true; + } + }); + if (changedLines.length) runInOp(cm, function() { + for (var i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text"); + }); + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) return doc.first; + var line = getLine(doc, search - 1); + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + + function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!state) state = startState(doc.mode); + else state = copyState(doc.mode, state); + doc.iter(pos, n, function(line) { + processLine(cm, line.text, state); + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo; + line.stateAfter = save ? copyState(doc.mode, state) : null; + ++pos; + }); + if (precise) doc.frontier = pos; + return state; + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop;} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} + function paddingH(display) { + if (display.cachedPaddingH) return display.cachedPaddingH; + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; + return data; + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && cm.display.scroller.clientWidth; + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + heights.push((cur.bottom + next.top) / 2 - rect.top); + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + return {map: lineView.measure.map, cache: lineView.measure.cache}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineView.rest[i] == line) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineNo(lineView.rest[i]) > lineN) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}; + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view; + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + return cm.display.view[findViewIndex(cm, lineN)]; + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + return ext; + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) + view = null; + else if (view && view.changes) + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + if (!view) + view = updateExternalMeasurement(cm, line); + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + }; + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) ch = -1; + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + prepared.rect = prepared.view.text.getBoundingClientRect(); + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) prepared.cache[key] = found; + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom}; + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function measureCharInner(cm, prepared, ch, bias) { + var map = prepared.map; + + var node, start, end, collapse; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + var mStart = map[i], mEnd = map[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) collapse = "right"; + } + if (start != null) { + node = map[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + collapse = bias; + if (bias == "left" && start == 0) + while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2]; + collapse = "left"; + } + if (bias == "right" && start == mEnd - mStart) + while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2]; + collapse = "right"; + } + break; + } + } + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start; + while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end; + if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) { + rect = node.parentNode.getBoundingClientRect(); + } else if (ie && cm.options.lineWrapping) { + var rects = range(node, start, end).getClientRects(); + if (rects.length) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = nullRect; + } else { + rect = range(node, start, end).getBoundingClientRect() || nullRect; + } + if (rect.left || rect.right || start == 0) break; + end = start; + start = start - 1; + collapse = "right"; + } + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) collapse = bias = "right"; + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = node.getBoundingClientRect(); + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; + else + rect = nullRect; + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; + var heights = prepared.view.measure.heights; + for (var i = 0; i < heights.length - 1; i++) + if (mid < heights[i]) break; + var top = i ? heights[i - 1] : 0, bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) result.bogus = true; + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + + return result; + } + + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + return rect; + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY}; + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + lineView.measure.caches[i] = {}; + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + clearLineMeasurementCacheFor(cm.display.view[i]); + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; + cm.display.lineNumChars = null; + } + + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"/null (editor), or "page". + function intoCoordSystem(cm, lineObj, rect, context) { + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]); + rect.top += size; rect.bottom += size; + } + if (context == "line") return rect; + if (!context) context = "local"; + var yOff = heightAtLine(lineObj); + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect; + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"/null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords; + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) lineObj = getLine(cm.doc, pos.line); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); + if (right) m.left = m.right; else m.right = m.left; + return intoCoordSystem(cm, lineObj, m, context); + } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } + var order = getOrder(lineObj), ch = pos.ch; + if (!order) return get(ch); + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0, pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height}; + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, outside, xRel) { + var pos = Pos(line, ch); + pos.xRel = xRel; + if (outside) pos.outside = true; + return pos; + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); + if (x < 0) x = 0; + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var merged = collapsedSpanAtEnd(lineObj); + var mergedPos = merged && merged.find(0, true); + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + lineN = lineNo(lineObj = mergedPos.to.line); + else + return found; + } + } + + function coordsCharInner(cm, lineObj, lineNo, x, y) { + var innerOff = y - heightAtLine(lineObj); + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + + function getX(ch) { + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure); + wrongLine = true; + if (innerOff > sp.bottom) return sp.left - adjust; + else if (innerOff < sp.top) return sp.left + adjust; + else wrongLine = false; + return sp.left; + } + + var bidi = getOrder(lineObj), dist = lineObj.text.length; + var from = lineLeft(lineObj), to = lineRight(lineObj); + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; + + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); + // Do a binary search between these bounds. + for (;;) { + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var xDiff = x - (ch == from ? fromX : toX); + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, + xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); + return pos; + } + var step = Math.ceil(dist / 2), middle = from + step; + if (bidi) { + middle = from; + for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); + } + var middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} + } + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) return display.cachedTextHeight; + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) display.cachedTextHeight = height; + removeChildren(display.measure); + return height || 1; + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) return display.cachedCharWidth; + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) display.cachedCharWidth = width; + return width || 10; + } + + // OPERATIONS + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var operationGroup = null; + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: null, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + id: ++nextOpId // Unique ID + }; + if (operationGroup) { + operationGroup.ops.push(cm.curOp); + } else { + cm.curOp.ownsGroup = operationGroup = { + ops: [cm.curOp], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + callbacks[i](); + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm); + } + } while (i < callbacks.length); + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp, group = op.ownsGroup; + if (!group) return; + + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + for (var i = 0; i < group.ops.length; i++) + group.ops[i].cm.curOp = null; + endOperations(group); + } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R1(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W1(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R2(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W2(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_finish(ops[i]); + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + if (op.updateMaxLine) findMaxLine(cm); + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) updateHeightsInViewport(cm); + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo + + scrollerCutOff - display.scroller.clientWidth); + } + + if (op.updatedDisplay || op.selectionChanged) + op.newSelectionNodes = drawSelection(cm); + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); + cm.display.maxLineChanged = false; + } + + if (op.newSelectionNodes) + showSelection(cm, op.newSelectionNodes); + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure); + if (op.updatedDisplay || op.startHeight != cm.doc.height) + updateScrollbars(cm, op.barMeasure); + + if (op.selectionChanged) restartBlink(cm); + + if (cm.state.focused && op.updateInput) + resetInput(cm, op.typing); + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.adjustWidthTo != null && Math.abs(op.barMeasure.scrollWidth - cm.display.scroller.scrollWidth) > 1) + updateScrollbars(cm); + + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + display.wheelStartX = display.wheelStartY = null; + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { + var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); + display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top; + } + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { + var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)); + display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left; + alignHorizontally(cm); + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) for (var i = 0; i < hidden.length; ++i) + if (!hidden[i].lines.length) signal(hidden[i], "hide"); + if (unhidden) for (var i = 0; i < unhidden.length; ++i) + if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); + + if (display.wrapper.offsetHeight) + doc.scrollTop = cm.display.scroller.scrollTop; + + // Apply workaround for two webkit bugs + if (op.updatedDisplay && webkit) { + if (cm.options.lineWrapping) + checkForWebkitWidthBug(cm, op.barMeasure); // (Issue #2420) + if (op.barMeasure.scrollWidth > op.barMeasure.clientWidth && + op.barMeasure.scrollWidth < op.barMeasure.clientWidth + 1 && + !hScrollbarTakesSpace(cm)) + updateScrollbars(cm); // (Issue #2562) + } + + // Fire change events, and delayed event handlers + if (op.changeObjs) + signal(cm, "changes", cm, op.changeObjs); + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) return f(); + startOperation(cm); + try { return f(); } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) return f.apply(cm, arguments); + startOperation(cm); + try { return f.apply(cm, arguments); } + finally { endOperation(cm); } + }; + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) return f.apply(this, arguments); + startOperation(this); + try { return f.apply(this, arguments); } + finally { endOperation(this); } + }; + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) return f.apply(this, arguments); + startOperation(cm); + try { return f.apply(this, arguments); } + finally { endOperation(cm); } + }; + } + + // VIEW TRACKING + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array; + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) from = cm.doc.first; + if (to == null) to = cm.doc.first + cm.doc.size; + if (!lendiff) lendiff = 0; + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + display.updateLineNumbers = from; + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + resetView(cm); + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut = viewCuttingPoint(cm, from, from, -1); + if (cut) { + display.view = display.view.slice(0, cut.index); + display.viewTo = cut.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + ext.lineN += lendiff; + else if (from < ext.lineN + ext.size) + display.externalMeasured = null; + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + display.externalMeasured = null; + + if (line < display.viewFrom || line >= display.viewTo) return; + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) return; + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) arr.push(type); + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) return null; + n -= cm.display.viewFrom; + if (n < 0) return null; + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) return i; + } + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + return {index: index, lineN: newN}; + for (var i = 0, n = cm.display.viewFrom; i < index; i++) + n += view[i].size; + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) return null; + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) return null; + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN}; + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); + else if (display.viewFrom < from) + display.view = display.view.slice(findViewIndex(cm, from)); + display.viewFrom = from; + if (display.viewTo < to) + display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); + else if (display.viewTo > to) + display.view = display.view.slice(0, findViewIndex(cm, to)); + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; + } + return dirty; + } + + // INPUT HANDLING + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + function slowPoll(cm) { + if (cm.display.pollingFast) return; + cm.display.poll.set(cm.options.pollInterval, function() { + readInput(cm); + if (cm.state.focused) slowPoll(cm); + }); + } + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + function fastPoll(cm) { + var missed = false; + cm.display.pollingFast = true; + function p() { + var changed = readInput(cm); + if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} + else {cm.display.pollingFast = false; slowPoll(cm);} + } + cm.display.poll.set(20, p); + } + + // This will be set to an array of strings when copying, so that, + // when pasting, we know what kind of selections the copied text + // was made out of. + var lastCopied = null; + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + function readInput(cm) { + var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput) + return false; + // See paste handler for more on the fakedLastChar kludge + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) return false; + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && cm.display.inputHasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + resetInput(cm); + return false; + } + + var withOp = !cm.curOp; + if (withOp) startOperation(cm); + cm.display.shift = false; + + if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu && !prevInput) + prevInput = "\u200b"; + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; + var inserted = text.slice(same), textLines = splitLines(inserted); + + // When pasing N lines into N selections, insert one line per selection + var multiPaste = null; + if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) { + if (lastCopied && lastCopied.join("\n") == inserted) + multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines); + else if (textLines.length == doc.sel.ranges.length) + multiPaste = map(textLines, function(l) { return [l]; }); + } + + // Normal behavior is to insert the new text into every selection + for (var i = doc.sel.ranges.length - 1; i >= 0; i--) { + var range = doc.sel.ranges[i]; + var from = range.from(), to = range.to(); + // Handle deletion + if (same < prevInput.length) + from = Pos(from.line, from.ch - (prevInput.length - same)); + // Handle overwrite + else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming) + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); + var updateInput = cm.curOp.updateInput; + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, + origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + // When an 'electric' character is inserted, immediately trigger a reindent + if (inserted && !cm.state.pasteIncoming && cm.options.electricChars && + cm.options.smartIndent && range.head.ch < 100 && + (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) { + var mode = cm.getModeAt(range.head); + var end = changeEnd(changeEvent); + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indentLine(cm, end.line, "smart"); + break; + } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch))) + indentLine(cm, end.line, "smart"); + } + } + } + ensureCursorVisible(cm); + cm.curOp.updateInput = updateInput; + cm.curOp.typing = true; + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; + else cm.display.prevInput = text; + if (withOp) endOperation(cm); + cm.state.pasteIncoming = cm.state.cutIncoming = false; + return true; + } + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + function resetInput(cm, typing) { + var minimal, selected, doc = cm.doc; + if (cm.somethingSelected()) { + cm.display.prevInput = ""; + var range = doc.sel.primary(); + minimal = hasCopyEvent && + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); + var content = minimal ? "-" : selected || cm.getSelection(); + cm.display.input.value = content; + if (cm.state.focused) selectInput(cm.display.input); + if (ie && ie_version >= 9) cm.display.inputHasSelection = content; + } else if (!typing) { + cm.display.prevInput = cm.display.input.value = ""; + if (ie && ie_version >= 9) cm.display.inputHasSelection = null; + } + cm.display.inaccurateSelection = minimal; + } + + function focusInput(cm) { + if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input)) + cm.display.input.focus(); + } + + function ensureFocus(cm) { + if (!cm.state.focused) { focusInput(cm); onFocus(cm); } + } + + function isReadOnly(cm) { + return cm.options.readOnly || cm.doc.cantEdit; + } + + // EVENT HANDLERS + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = cm.findWordAt(pos); + extendSelection(cm.doc, word.anchor, word.head); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); + // Prevent normal selection in the editor (we handle our own) + on(d.lineSpace, "selectstart", function(e) { + if (!eventInWidget(d, e)) e_preventDefault(e); + }); + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function() { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + on(d.scrollbarV, "scroll", function() { + if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop); + }); + on(d.scrollbarH, "scroll", function() { + if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft); + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); + + // Prevent clicks in the scrollbars from killing focus + function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); } + on(d.scrollbarH, "mousedown", reFocus); + on(d.scrollbarV, "mousedown", reFocus); + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); }); + on(d.input, "input", function() { + if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; + fastPoll(cm); + }); + on(d.input, "keydown", operation(cm, onKeyDown)); + on(d.input, "keypress", operation(cm, onKeyPress)); + on(d.input, "focus", bind(onFocus, cm)); + on(d.input, "blur", bind(onBlur, cm)); + + function drag_(e) { + if (!signalDOMEvent(cm, e)) e_stop(e); + } + if (cm.options.dragDrop) { + on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); + on(d.scroller, "dragenter", drag_); + on(d.scroller, "dragover", drag_); + on(d.scroller, "drop", operation(cm, onDrop)); + } + on(d.scroller, "paste", function(e) { + if (eventInWidget(d, e)) return; + cm.state.pasteIncoming = true; + focusInput(cm); + fastPoll(cm); + }); + on(d.input, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = d.input.selectionStart, end = d.input.selectionEnd; + d.input.value += "$"; + // The selection end needs to be set before the start, otherwise there + // can be an intermediate non-empty selection between the two, which + // can override the middle-click paste buffer on linux and cause the + // wrong thing to get pasted. + d.input.selectionEnd = end; + d.input.selectionStart = start; + cm.state.fakedLastChar = true; + } + cm.state.pasteIncoming = true; + fastPoll(cm); + }); + + function prepareCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (d.inaccurateSelection) { + d.prevInput = ""; + d.inaccurateSelection = false; + d.input.value = lastCopied.join("\n"); + selectInput(d.input); + } + } else { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + if (e.type == "cut") { + cm.setSelections(ranges, null, sel_dontScroll); + } else { + d.prevInput = ""; + d.input.value = text.join("\n"); + selectInput(d.input); + } + lastCopied = text; + } + if (e.type == "cut") cm.state.cutIncoming = true; + } + on(d.input, "cut", prepareCopyCut); + on(d.input, "copy", prepareCopyCut); + + // Needed to handle Tab key in KHTML + if (khtml) on(d.sizer, "mouseup", function() { + if (activeElt() == d.input) d.input.blur(); + focusInput(cm); + }); + } + + // Called when the window resizes + function onResize(cm) { + // Might be a text scaling operation, clear size caches. + var d = cm.display; + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + cm.setSize(); + } + + // MOUSE EVENTS + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true; + } + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal) { + var target = e_target(e); + if (target == display.scrollbarH || target == display.scrollbarV || + target == display.scrollbarFiller || target == display.gutterFiller) return null; + } + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e) { return null; } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords; + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + if (signalDOMEvent(this, e)) return; + var cm = this, display = cm.display; + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function(){display.scroller.draggable = true;}, 100); + } + return; + } + if (clickInGutter(cm, e)) return; + var start = posFromMouse(cm, e); + window.focus(); + + switch (e_button(e)) { + case 1: + if (start) + leftButtonDown(cm, e, start); + else if (e_target(e) == display.scroller) + e_preventDefault(e); + break; + case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; + if (start) extendSelection(cm.doc, start); + setTimeout(bind(focusInput, cm), 20); + e_preventDefault(e); + break; + case 3: + if (captureRightClick) onContextMenu(cm, e); + break; + } + } + + var lastClick, lastDoubleClick; + function leftButtonDown(cm, e, start) { + setTimeout(bind(ensureFocus, cm), 0); + + var now = +new Date, type; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { + type = "triple"; + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { + type = "double"; + lastDoubleClick = {time: now, pos: start}; + } else { + type = "single"; + lastClick = {time: now, pos: start}; + } + + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey; + if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && + type == "single" && sel.contains(start) > -1 && sel.somethingSelected()) + leftButtonStartDrag(cm, e, start, modifier); + else + leftButtonSelect(cm, e, start, type, modifier); + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, e, start, modifier) { + var display = cm.display; + var dragEnd = operation(cm, function(e2) { + if (webkit) display.scroller.draggable = false; + cm.state.draggingText = false; + off(document, "mouseup", dragEnd); + off(display.scroller, "drop", dragEnd); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + if (!modifier) + extendSelection(cm.doc, start); + focusInput(cm); + // Work around unexplainable focus problem in IE9 (#2127) + if (ie && ie_version == 9) + setTimeout(function() {document.body.focus(); focusInput(cm);}, 20); + } + }); + // Let the drag handler handle this. + if (webkit) display.scroller.draggable = true; + cm.state.draggingText = dragEnd; + // IE's approach to draggable + if (display.scroller.dragDrop) display.scroller.dragDrop(); + on(document, "mouseup", dragEnd); + on(display.scroller, "drop", dragEnd); + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, e, start, type, addNew) { + var display = cm.display, doc = cm.doc; + e_preventDefault(e); + + var ourRange, ourIndex, startSel = doc.sel; + if (addNew && !e.shiftKey) { + ourIndex = doc.sel.contains(start); + if (ourIndex > -1) + ourRange = doc.sel.ranges[ourIndex]; + else + ourRange = new Range(start, start); + } else { + ourRange = doc.sel.primary(); + } + + if (e.altKey) { + type = "rect"; + if (!addNew) ourRange = new Range(start, start); + start = posFromMouse(cm, e, true, true); + ourIndex = -1; + } else if (type == "double") { + var word = cm.findWordAt(start); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, word.anchor, word.head); + else + ourRange = word; + } else if (type == "triple") { + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, line.anchor, line.head); + else + ourRange = line; + } else { + ourRange = extendRange(doc, ourRange, start); + } + + if (!addNew) { + ourIndex = 0; + setSelection(doc, new Selection([ourRange], 0), sel_mouse); + startSel = doc.sel; + } else if (ourIndex > -1) { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); + } else { + ourIndex = doc.sel.ranges.length; + setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) return; + lastPos = pos; + + if (type == "rect") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); + else if (text.length > leftPos) + ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); + } + if (!ranges.length) ranges.push(new Range(start, start)); + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); + } else { + var oldRange = ourRange; + var anchor = oldRange.anchor, head = pos; + if (type != "single") { + if (type == "double") + var range = cm.findWordAt(pos); + else + var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); + if (cmp(range.anchor, anchor) > 0) { + head = range.head; + anchor = minPos(oldRange.from(), range.anchor); + } else { + head = range.anchor; + anchor = maxPos(oldRange.to(), range.head); + } + } + var ranges = startSel.ranges.slice(0); + ranges[ourIndex] = new Range(clipPos(doc, anchor), head); + setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, type == "rect"); + if (!cur) return; + if (cmp(cur, lastPos) != 0) { + ensureFocus(cm); + extendTo(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) setTimeout(operation(cm, function() { + if (counter != curCount) return; + display.scroller.scrollTop += outside; + extend(e); + }), 50); + } + } + + function done(e) { + counter = Infinity; + e_preventDefault(e); + focusInput(cm); + off(document, "mousemove", move); + off(document, "mouseup", up); + doc.history.lastSelOrigin = null; + } + + var move = operation(cm, function(e) { + if (!e_button(e)) done(e); + else extend(e); + }); + var up = operation(cm, done); + on(document, "mousemove", move); + on(document, "mouseup", up); + } + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent, signalfn) { + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; + if (prevent) e_preventDefault(e); + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalfn(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true, signalLater); + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + return; + e_preventDefault(e); + if (ie) lastDrop = +new Date; + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || isReadOnly(cm)) return; + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { + var reader = new FileReader; + reader.onload = operation(cm, function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); + } + }); + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(bind(focusInput, cm), 20); + return; + } + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey)) + var selected = cm.listSelections(); + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) for (var i = 0; i < selected.length; ++i) + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); + cm.replaceSelection(text, "around", "paste"); + focusInput(cm); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; + + e.dataTransfer.setData("Text", cm.getSelection()); + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) img.parentNode.removeChild(img); + } + } + + // SCROLL EVENTS + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) return; + cm.doc.scrollTop = val; + if (!gecko) updateDisplaySimple(cm, {top: val}); + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; + if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; + if (gecko) updateDisplaySimple(cm); + startWorker(cm, 100); + } + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; + if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val; + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) wheelPixelsPerUnit = -.53; + else if (gecko) wheelPixelsPerUnit = 15; + else if (chrome) wheelPixelsPerUnit = -.7; + else if (safari) wheelPixelsPerUnit = -1/3; + + function onScrollWheel(cm, e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; + else if (dy == null) dy = e.wheelDelta; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + if (!(dx && scroll.scrollWidth > scroll.clientWidth || + dy && scroll.scrollHeight > scroll.clientHeight)) return; + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer; + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy) + setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); + e_preventDefault(e); + display.wheelStartX = null; // Abort measurement, if in progress + return; + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) top = Math.max(0, top + pixels - 50); + else bot = Math.min(cm.doc.height, bot + pixels + 50); + updateDisplaySimple(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function() { + if (display.wheelStartX == null) return; + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) return; + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // KEY EVENTS + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false; + var prevShift = cm.display.shift, done = false; + try { + if (isReadOnly(cm)) cm.state.suppressEdits = true; + if (dropShift) cm.display.shift = false; + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done; + } + + // Collect the currently active keymaps. + function allKeyMaps(cm) { + var maps = cm.state.keyMaps.slice(0); + if (cm.options.extraKeys) maps.push(cm.options.extraKeys); + maps.push(cm.options.keyMap); + return maps; + } + + var maybeTransition; + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + // Handle automatic keymap transitions + var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; + clearTimeout(maybeTransition); + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { + if (getKeyMap(cm.options.keyMap) == startMap) { + cm.options.keyMap = (next.call ? next.call(null, cm) : next); + keyMapChanged(cm); + } + }, 50); + + var name = keyName(e, true), handled = false; + if (!name) return false; + var keymaps = allKeyMaps(cm); + + if (e.shiftKey) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);}) + || lookupKey(name, keymaps, function(b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); + }); + } else { + handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); + } + + if (handled) { + e_preventDefault(e); + restartBlink(cm); + signalLater(cm, "keyHandled", cm, name, e); + } + return handled; + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + var handled = lookupKey("'" + ch + "'", allKeyMaps(cm), + function(b) { return doHandleBinding(cm, b, true); }); + if (handled) { + e_preventDefault(e); + restartBlink(cm); + signalLater(cm, "keyHandled", cm, "'" + ch + "'", e); + } + return handled; + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + ensureFocus(cm); + if (signalDOMEvent(cm, e)) return; + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + cm.replaceSelection("", null, "cut"); + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + showCrossHair(cm); + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (e.keyCode == 16) this.doc.sel.shift = false; + signalDOMEvent(this, e); + } + + function onKeyPress(e) { + var cm = this; + if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (handleCharBinding(cm, e, ch)) return; + if (ie && ie_version >= 9) cm.display.inputHasSelection = null; + fastPoll(cm); + } + + // FOCUS/BLUR EVENTS + + function onFocus(cm) { + if (cm.options.readOnly == "nocursor") return; + if (!cm.state.focused) { + signal(cm, "focus", cm); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // The prevInput test prevents this from firing when a context + // menu is closed (since the resetInput would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + resetInput(cm); + if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730 + } + } + slowPoll(cm); + restartBlink(cm); + } + function onBlur(cm) { + if (cm.state.focused) { + signal(cm, "blur", cm); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); + } + + // CONTEXT MENU HANDLING + + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (signalDOMEvent(cm, e, "contextmenu")) return; + var display = cm.display; + if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return; + + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) return; // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); + + var oldCSS = display.input.style.cssText; + display.inputDiv.style.position = "absolute"; + display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) + focusInput(cm); + if (webkit) window.scrollTo(null, oldScrollY); + resetInput(cm); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) display.input.value = display.prevInput = " "; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (display.input.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = display.input.value = "\u200b" + (selected ? display.input.value : ""); + display.prevInput = selected ? "" : "\u200b"; + display.input.selectionStart = 1; display.input.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; + } + } + function rehide() { + display.inputDiv.style.position = "relative"; + display.input.style.cssText = oldCSS; + if (ie && ie_version < 9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; + slowPoll(cm); + + // Try to detect the user choosing select-all + if (display.input.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); + var i = 0, poll = function() { + if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0) + operation(cm, commands.selectAll)(cm); + else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); + else resetInput(cm); + }; + display.detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && ie_version >= 9) prepareSelectAllHack(); + if (captureRightClick) { + e_stop(e); + var mouseup = function() { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false, signal); + } + + // UPDATING + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); + }; + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) return pos; + if (cmp(pos, change.to) <= 0) return changeEnd(change); + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; + return Pos(line, ch); + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(out, doc.sel.primIndex); + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + return Pos(nw.line, pos.ch - old.ch + nw.ch); + else + return Pos(nw.line + (pos.line - old.line), pos.ch); + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex); + } + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function() { this.canceled = true; } + }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; + signal(doc, "beforeChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); + + if (obj.canceled) return null; + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); + if (doc.cm.state.suppressEdits) return; + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) return; + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}); + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return; + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + if (doc.cm && doc.cm.state.suppressEdits) return; + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + for (var i = 0; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + break; + } + if (i == source.length) return; + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return; + } + selAfter = event; + } + else break; + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + for (var i = event.changes.length - 1; i >= 0; --i) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return; + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + if (distance == 0) return; + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function(range) { + return new Range(Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch)); + }), doc.sel.primIndex); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + regLineChange(doc.cm, l, "gutter"); + } + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return; + } + if (change.from.line > doc.lastLine()) return; + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) selAfter = computeSelAfterChange(doc, change); + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); + else updateDoc(doc, change, spans); + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function(line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true; + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + signalCursorActivity(cm); + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function(line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) cm.curOp.updateMaxLine = true; + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + regLineChange(cm, from.line, "text"); + else + regChange(cm, from.line, to.line + 1, lendiff); + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) signalLater(cm, "change", cm, obj); + if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); + } + cm.display.selForContextMenu = null; + } + + function replaceRange(doc, code, from, to, origin) { + if (!to) to = from; + if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } + if (typeof code == "string") code = splitLines(code); + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, coords) { + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + + (coords.bottom - coords.top + scrollerCutOff) + "px; left: " + + coords.left + "px; width: 2px;"); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) margin = 0; + for (var limit = 0; limit < 5; limit++) { + var changed = false, coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; + } + if (!changed) return coords; + } + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); + if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, x1, y1, x2, y2) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = display.scroller.clientHeight - scrollerCutOff, result = {}; + if (y2 - y1 > screen) y2 = y1 + screen; + var docBottom = cm.doc.height + paddingVert(display); + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; + var screenw = display.scroller.clientWidth - scrollerCutOff - display.gutters.offsetWidth; + var tooWide = x2 - x1 > screenw; + if (tooWide) x2 = x1 + screenw; + if (x1 < 10) + result.scrollLeft = 0; + else if (x1 < screenleft) + result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); + else if (x2 > screenw + screenleft - 3) + result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; + + return result; + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollPos(cm, left, top) { + if (left != null || top != null) resolveScrollToPos(cm); + if (left != null) + cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left; + if (top != null) + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(), from = cur, to = cur; + if (!cm.options.lineWrapping) { + from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; + to = Pos(cur.line, cur.ch + 1); + } + cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true}; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos; + if (range) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); + var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), + Math.min(from.top, to.top) - range.margin, + Math.max(from.right, to.right), + Math.max(from.bottom, to.bottom) + range.margin); + cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + } + + // API UTILITIES + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) how = "add"; + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) how = "prev"; + else state = getStateBefore(cm, n); + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) line.stateAfter = null; + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { + if (!aggressive) return; + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); + else indentation = 0; + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i, new Range(pos, pos)); + break; + } + } + } + line.stateAfter = null; + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); + return line; + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break; + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function() { + for (var i = kill.length - 1; i >= 0; i--) + replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); + ensureCursorVisible(cm); + }); + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "char", "column" (like char, but doesn't + // cross line boundaries), "word" (across next word), or "group" (to + // the start of next group of word or non-word-non-whitespace + // chars). The visually param controls whether, in right-to-left + // text, direction 1 means to move towards the next index in the + // string, or towards the character to the right of the current + // position. The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var line = pos.line, ch = pos.ch, origDir = dir; + var lineObj = getLine(doc, line); + var possible = true; + function findNextLine() { + var l = line + dir; + if (l < doc.first || l >= doc.first + doc.size) return (possible = false); + line = l; + return lineObj = getLine(doc, l); + } + function moveOnce(boundToLine) { + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); + if (next == null) { + if (!boundToLine && findNextLine()) { + if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); + else ch = dir < 0 ? lineObj.text.length : 0; + } else return (possible = false); + } else ch = next; + return true; + } + + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break; + var cur = lineObj.text.charAt(ch) || "\n"; + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) type = "s"; + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce();} + break; + } + + if (type) sawType = type; + if (dir > 0 && !moveOnce(!first)) break; + } + } + var result = skipAtomic(doc, Pos(line, ch), origDir, true); + if (!possible) result.hitSide = true; + return result; + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + for (;;) { + var target = coordsChar(cm, x, y); + if (!target.outside) break; + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } + y += dir * 5; + } + return target; + } + + // EDITOR METHODS + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); focusInput(this); fastPoll(this);}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") return; + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + operation(this, optionHandlers[option])(this, value, old); + }, + + getOption: function(option) {return this.options[option];}, + getDoc: function() {return this.doc;}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](map); + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) { + maps.splice(i, 1); + return true; + } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) throw new Error("Overlays may not be stateful."); + this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this.state.modeGen++; + regChange(this); + return; + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); + }), + indentSelection: methodOp(function(how) { + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (!range.empty()) { + var from = range.from(), to = range.to(); + var start = Math.max(end, from.line); + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + indentLine(this, j, how); + var newRanges = this.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true); + end = range.head.line; + if (i == this.doc.sel.primIndex) ensureCursorVisible(this); + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + var doc = this.doc; + pos = clipPos(doc, pos); + var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode; + var line = getLine(doc, pos.line); + var stream = new StringStream(line.text, this.options.tabSize); + while (stream.pos < pos.ch && !stream.eol()) { + stream.start = stream.pos; + var style = readToken(mode, stream, state); + } + return {start: stream.start, + end: stream.pos, + string: stream.current(), + type: style || null, + state: state}; + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) type = styles[2]; + else for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else { type = styles[mid * 2 + 2]; break; } + } + var cut = type ? type.indexOf("cm-overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0]; + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) return helpers; + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]); + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) found.push(val); + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i = 0; i < help._global.length; i++) { + var cur = help._global[i]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val); + } + return found; + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getStateBefore(this, line + 1, precise); + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary(); + if (start == null) pos = range.head; + else if (typeof start == "object") pos = clipPos(this.doc, start); + else pos = start ? range.from() : range.to(); + return cursorCoords(this, pos, mode || "page"); + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page"); + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top); + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + var lineObj = getLine(this.doc, line); + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + + (end ? this.doc.height - heightAtLine(lineObj) : 0); + }, + + defaultTextHeight: function() { return textHeight(this.display); }, + defaultCharWidth: function() { return charWidth(this.display); }, + + setGutterMarker: methodOp(function(line, gutterID, value) { + return changeLine(this.doc, line, "gutter", function(line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) line.gutterMarkers = null; + return true; + }); + }), + + clearGutter: methodOp(function(gutterID) { + var cm = this, doc = cm.doc, i = doc.first; + doc.iter(function(line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + line.gutterMarkers[gutterID] = null; + regLineChange(cm, i, "gutter"); + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; + } + ++i; + }); + }), + + addLineWidget: methodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options); + }), + + removeLineWidget: function(widget) { widget.clear(); }, + + lineInfo: function(line) { + if (typeof line == "number") { + if (!isLine(this.doc, line)) return null; + var n = line; + line = getLine(this.doc, line); + if (!line) return null; + } else { + var n = lineNo(line); + if (n == null) return null; + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets}; + }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + top = pos.top - node.offsetHeight; + else if (pos.bottom + node.offsetHeight <= vspace) + top = pos.bottom; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; + node.style.left = left + "px"; + } + if (scroll) + scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd](this); + }, + + findPosH: function(from, amount, unit, visually) { + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually); + if (cur.hitSide) break; + } + return cur; + }, + + moveH: methodOp(function(dir, unit) { + var cm = this; + cm.extendSelectionsBy(function(range) { + if (cm.display.shift || cm.doc.extend || range.empty()) + return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually); + else + return dir < 0 ? range.from() : range.to(); + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + doc.replaceSelection("", null, "+delete"); + else + deleteNearSelection(this, function(range) { + var other = findPosH(doc, range.head, dir, unit, false); + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}; + }); + }), + + findPosV: function(from, amount, unit, goalColumn) { + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + var coords = cursorCoords(this, cur, "div"); + if (x == null) x = coords.left; + else coords.left = x; + cur = findPosV(this, coords, dir, unit); + if (cur.hitSide) break; + } + return cur; + }, + + moveV: methodOp(function(dir, unit) { + var cm = this, doc = this.doc, goals = []; + var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function(range) { + if (collapse) + return dir < 0 ? range.from() : range.to(); + var headPos = cursorCoords(cm, range.head, "div"); + if (range.goalColumn != null) headPos.left = range.goalColumn; + goals.push(headPos.left); + var pos = findPosV(cm, headPos, dir, unit); + if (unit == "page" && range == doc.sel.primary()) + addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top); + return pos; + }, sel_move); + if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) + doc.sel.ranges[i].goalColumn = goals[i]; + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function(ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)); + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; + if (this.state.overwrite = !this.state.overwrite) + addClass(this.display.cursorDiv, "CodeMirror-overwrite"); + else + rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return activeElt() == this.display.input; }, + + scrollTo: methodOp(function(x, y) { + if (x != null || y != null) resolveScrollToPos(this); + if (x != null) this.curOp.scrollLeft = x; + if (y != null) this.curOp.scrollTop = y; + }), + getScrollInfo: function() { + var scroller = this.display.scroller, co = scrollerCutOff; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, + clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) margin = this.options.cursorScrollMargin; + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null}; + } else if (range.from == null) { + range = {from: range, to: null}; + } + if (!range.to) range.to = range.from; + range.margin = margin || 0; + + if (range.from.line != null) { + resolveScrollToPos(this); + this.curOp.scrollToPos = range; + } else { + var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), + Math.min(range.from.top, range.to.top) - range.margin, + Math.max(range.from.right, range.to.right), + Math.max(range.from.bottom, range.to.bottom) + range.margin); + this.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + }), + + setSize: methodOp(function(width, height) { + var cm = this; + function interpret(val) { + return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; + } + if (width != null) cm.display.wrapper.style.width = interpret(width); + if (height != null) cm.display.wrapper.style.height = interpret(height); + if (cm.options.lineWrapping) clearLineMeasurementCache(this); + var lineNo = cm.display.viewFrom; + cm.doc.iter(lineNo, cm.display.viewTo, function(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) + if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; } + ++lineNo; + }); + cm.curOp.forceUpdate = true; + signal(cm, "refresh", this); + }), + + operation: function(f){return runInOp(this, f);}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + estimateLineHeights(this); + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + resetInput(this); + this.scrollTo(doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; + signalLater(this, "swapDoc", this, old); + return old; + }), + + getInputField: function(){return this.display.input;}, + getWrapperElement: function(){return this.display.wrapper;}, + getScrollerElement: function(){return this.display.scroller;}, + getGutterElement: function(){return this.display.gutters;} + }; + eventMixin(CodeMirror); + + // OPTION DEFAULTS + + // The default configuration options. + var defaults = CodeMirror.defaults = {}; + // Functions to run when options are changed. + var optionHandlers = CodeMirror.optionHandlers = {}; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) optionHandlers[name] = + notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; + } + + // Passed to option handlers when there is no old value. + var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function(cm, val) { + cm.setValue(val); + }, true); + option("mode", null, function(cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function(cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) { + cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + cm.refresh(); + }, true); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); + option("electricChars", true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function(cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", keyMapChanged); + option("extraKeys", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function(cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, updateScrollbars, true); + option("lineNumbers", false, function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + + option("readOnly", false, function(cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + cm.display.disabled = true; + } else { + cm.display.disabled = false; + if (!val) resetInput(cm); + } + }); + option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, true); + option("dragDrop", true); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; + }); + + option("tabindex", null, function(cm, val) { + cm.display.input.tabIndex = val || ""; + }); + option("autofocus", null); + + // MODE DEFINITION AND QUERYING + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2); + modes[name] = mode; + }; + + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") found = {name: found}; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return CodeMirror.resolveMode("application/xml"); + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) continue; + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; + + return modeObj; + }; + + // Minimal default mode. + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + }; + + // EXTENSIONS + + CodeMirror.defineExtension = function(name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; + CodeMirror.defineOption = option; + + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + + // MODE STATE HANDLING + + // Utility functions for working with state. Exported because nested + // modes need to do this for their inner modes. + + var copyState = CodeMirror.copyState = function(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + }; + + var startState = CodeMirror.startState = function(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + }; + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; + + // STANDARD COMMANDS + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);}, + singleSelection: function(cm) { + cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); + }, + killLine: function(cm) { + deleteNearSelection(cm, function(range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + return {from: range.head, to: Pos(range.head.line + 1, 0)}; + else + return {from: range.head, to: Pos(range.head.line, len)}; + } else { + return {from: range.from(), to: range.to()}; + } + }); + }, + deleteLine: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; + }); + }, + delLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), to: range.from()}; + }); + }, + delWrappedLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()}; + }); + }, + delWrappedLineRight: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos }; + }); + }, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + undoSelection: function(cm) {cm.undoSelection();}, + redoSelection: function(cm) {cm.redoSelection();}, + goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, + goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, + goLineStart: function(cm) { + cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1}); + }, + goLineStartSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + return lineStartSmart(cm, range.head); + }, {origin: "+move", bias: 1}); + }, + goLineEnd: function(cm) { + cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1}); + }, + goLineRight: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + }, sel_move); + }, + goLineLeft: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div"); + }, sel_move); + }, + goLineLeftSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); + return pos; + }, sel_move); + }, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goGroupRight: function(cm) {cm.moveH(1, "group");}, + goGroupLeft: function(cm) {cm.moveH(-1, "group");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharBefore: function(cm) {cm.deleteH(-1, "char");}, + delCharAfter: function(cm) {cm.deleteH(1, "char");}, + delWordBefore: function(cm) {cm.deleteH(-1, "word");}, + delWordAfter: function(cm) {cm.deleteH(1, "word");}, + delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, + delGroupAfter: function(cm) {cm.deleteH(1, "group");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t");}, + insertSoftTab: function(cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(new Array(tabSize - col % tabSize + 1).join(" ")); + } + cm.replaceSelections(spaces); + }, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.execCommand("insertTab"); + }, + transposeChars: function(cm) { + runInOp(cm, function() { + var ranges = cm.listSelections(), newSel = []; + for (var i = 0; i < ranges.length; i++) { + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (line) { + if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) + cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose"); + } + } + newSel.push(new Range(cur, cur)); + } + cm.setSelections(newSel); + }); + }, + newlineAndIndent: function(cm) { + runInOp(cm, function() { + var len = cm.listSelections().length; + for (var i = 0; i < len; i++) { + var range = cm.listSelections()[i]; + cm.replaceRange("\n", range.anchor, range.head, "+input"); + cm.indentLine(range.from().line + 1, null, true); + ensureCursorVisible(cm); + } + }); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + // STANDARD KEYMAPS + + var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + + // Given an array of keymaps and a key name, call handle on any + // bindings found, until that returns a truthy value, at which point + // we consider the key handled. Implements things like binding a key + // to false stopping further handling and keymap fallthrough. + var lookupKey = CodeMirror.lookupKey = function(name, maps, handle) { + function lookup(map) { + map = getKeyMap(map); + var found = map[name]; + if (found === false) return "stop"; + if (found != null && handle(found)) return true; + if (map.nofallthrough) return "stop"; + + var fallthrough = map.fallthrough; + if (fallthrough == null) return false; + if (Object.prototype.toString.call(fallthrough) != "[object Array]") + return lookup(fallthrough); + for (var i = 0; i < fallthrough.length; ++i) { + var done = lookup(fallthrough[i]); + if (done) return done; + } + return false; + } + + for (var i = 0; i < maps.length; ++i) { + var done = lookup(maps[i]); + if (done) return done != "stop"; + } + }; + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + var isModifierKey = CodeMirror.isModifierKey = function(event) { + var name = keyNames[event.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + }; + + // Look up the name of a key as indicated by an event object. + var keyName = CodeMirror.keyName = function(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) return false; + var name = keyNames[event.keyCode]; + if (name == null || event.altGraphKey) return false; + if (event.altKey) name = "Alt-" + name; + if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name; + if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name; + if (!noShift && event.shiftKey) name = "Shift-" + name; + return name; + }; + + // FROMTEXTAREA + + CodeMirror.fromTextArea = function(textarea, options) { + if (!options) options = {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabindex) + options.tabindex = textarea.tabindex; + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form, realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function() { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + textarea.style.display = "none"; + var cm = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + cm.save = save; + cm.getTextArea = function() { return textarea; }; + cm.toTextArea = function() { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + return cm; + }; + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = CodeMirror.StringStream = function(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + }; + + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == this.lineStart;}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } + }; + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + var TextMarker = CodeMirror.TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + }; + eventMixin(TextMarker); + + // Clear the marker. + TextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); + else if (cm) { + if (span.to != null) max = lineNo(line); + if (span.from != null) min = lineNo(line); + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)); + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { + var visual = visualLine(this.lines[i]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } + + if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) reCheckSelection(cm.doc); + } + if (cm) signalLater(cm, "markerCleared", cm, this); + if (withOp) endOperation(cm); + if (this.parent) this.parent.clear(); + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function(side, lineObj) { + if (side == null && this.type == "bookmark") side = 1; + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) return from; + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) return to; + } + } + return from && {from: from, to: to}; + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function() { + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) return; + runInOp(cm, function() { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + updateLineHeight(line, line.height + dHeight); + } + }); + }; + + TextMarker.prototype.attachLine = function(line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); + } + this.lines.push(line); + }; + TextMarker.prototype.detachLine = function(line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) return markTextShared(doc, from, to, options, type); + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) copyObj(options, marker, false); + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + return marker; + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.widgetNode.ignoreEvents = true; + if (options.insertLeft) marker.widgetNode.insertLeft = true; + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + throw new Error("Inserting collapsed marker partially overlapping an existing one"); + sawCollapsedSpans = true; + } + + if (marker.addToHistory) + addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function(line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + updateMaxLine = true; + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { + if (lineIsHidden(doc, line)) updateLineHeight(line, 0); + }); + + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); + + if (marker.readOnly) { + sawReadOnlySpans = true; + if (doc.history.done.length || doc.history.undone.length) + doc.clearHistory(); + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) cm.curOp.updateMaxLine = true; + if (marker.collapsed) + regChange(cm, from.line, to.line + 1); + else if (marker.className || marker.title || marker.startStyle || marker.endStyle) + for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); + if (marker.atomic) reCheckSelection(cm.doc); + signalLater(cm, "markerAdded", cm, marker); + } + return marker; + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + markers[i].parent = this; + }; + eventMixin(SharedTextMarker); + + SharedTextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + this.markers[i].clear(); + signalLater(this, "clear"); + }; + SharedTextMarker.prototype.find = function(side, lineObj) { + return this.primary.find(side, lineObj); + }; + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function(doc) { + if (widget) options.widgetNode = widget.cloneNode(true); + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + if (doc.linked[i].isParent) return; + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary); + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), + function(m) { return m.parent; }); + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], linked = [marker.primary.doc];; + linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + } + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } + } + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + for (var r, i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + // Add a span to a line. + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } + return nw; + } + function markedSpansAfter(old, endCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } + return nw; + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) return null; + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first); + if (last && last != first) last = clearEmptySpans(last); + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)); + for (var i = 0; i < gap; ++i) + newMarkers.push(gapMarkers); + newMarkers.push(last); + } + return newMarkers; + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1); + } + if (!spans.length) return null; + return spans; + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) return stretched; + if (!stretched) return old; + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + if (oldCur[k].marker == span.marker) continue spans; + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old; + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function(line) { + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark); + } + }); + if (!markers) return null; + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}); + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}); + parts.splice.apply(parts, newParts); + j += newParts.length - 1; + } + } + return parts; + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line); + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line); + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) return lenDiff; + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) return -fromCmp; + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) return toCmp; + return b.id - a.id; + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker; + } + return found; + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) continue; + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; + if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) || + fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight))) + return true; + } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line; + return line; + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + (lines || (lines = [])).push(line); + } + return lines; + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) return lineN; + return lineNo(vis); + } + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN; + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) return lineN; + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line; + return lineNo(line) + 1; + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) continue; + if (sp.from == null) return true; + if (sp.marker.widgetNode) continue; + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true; + } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true; + for (var sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true; + } + } + + // LINE WIDGETS + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt]; + this.cm = cm; + this.node = node; + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + addToScrollPos(cm, null, diff); + } + + LineWidget.prototype.clear = function() { + var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) return; + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); + if (!ws.length) line.widgets = null; + var height = widgetHeight(this); + runInOp(cm, function() { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + updateLineHeight(line, Math.max(0, line.height - height)); + }); + }; + LineWidget.prototype.changed = function() { + var oldH = this.height, cm = this.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) return; + runInOp(cm, function() { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + updateLineHeight(line, line.height + diff); + }); + }; + + function widgetHeight(widget) { + if (widget.height != null) return widget.height; + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + parentStyle += "margin-left: -" + widget.cm.getGutterElement().offsetWidth + "px;"; + removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } + return widget.height = widget.node.offsetHeight; + } + + function addLineWidget(cm, handle, node, options) { + var widget = new LineWidget(cm, node, options); + if (widget.noHScroll) cm.display.alignWidgets = true; + changeLine(cm.doc, handle, "widget", function(line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); + widget.line = line; + if (!lineIsHidden(cm.doc, line)) { + var aboveVisible = heightAtLine(line) < cm.doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) addToScrollPos(cm, null, widget.height); + cm.curOp.forceUpdate = true; + } + return true; + }); + return widget; + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); + Line.prototype.lineNo = function() { return lineNo(this); }; + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + if (line.order != null) line.order = null; + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) updateLineHeight(line, estHeight); + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + function extractLineClasses(type, output) { + if (type) for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) break; + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + output[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2]; + } + return type; + } + + function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state); + if (!mode.innerMode) return; + var inner = CodeMirror.innerMode(mode, state); + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); + } + + function readToken(mode, stream, state) { + for (var i = 0; i < 10; i++) { + var style = mode.token(stream, state); + if (stream.pos > stream.start) return style; + } + throw new Error("Mode " + mode.name + " failed to advance stream."); + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; + if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) processLine(cm, text, state, stream.pos); + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, state), lineClasses); + } + if (cm.options.addModeClass) { + var mName = CodeMirror.innerMode(mode, state).mode.name; + if (mName) style = "m-" + (style ? mName + " " + style : mName); + } + if (!flattenSpans || curStyle != style) { + if (curStart < stream.start) f(stream.start, curStyle); + curStart = stream.start; curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; + } + } + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, state, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function(end, style) { + st.push(end, style); + }, lineClasses, forceToEnd); + + // Run overlays, adjust style array. + for (var o = 0; o < cm.state.overlays.length; ++o) { + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); + i += 2; + at = Math.min(end, i_end); + } + if (!style) return; + if (overlay.opaque) { + st.splice(start, i - start, end, "cm-overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; + } + } + }, lineClasses); + } + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; + } + + function getLineStyles(cm, line) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + line.styles = result.styles; + if (result.classes) line.styleClasses = result.classes; + else if (line.styleClasses) line.styleClasses = null; + } + return line.styles; + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, state, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize); + stream.start = stream.pos = startAt || 0; + if (text == "") callBlankLine(mode, state); + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { + readToken(mode, stream, state); + stream.start = stream.pos; + } + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null; + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")); + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order; + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if ((ie || webkit) && cm.getOption("lineWrapping")) + builder.addToken = buildTokenSplitSpaces(builder.addToken); + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) + builder.addToken = buildTokenBadBidi(builder.addToken, order); + builder.map = []; + insertLineContent(line, builder, getLineStyles(cm, line)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); + (lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); + return builder; + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + return token; + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, title) { + if (!text) return; + var special = builder.cm.options.specialChars, mustWrap = false; + if (!special.test(text)) { + builder.col += text.length; + var content = document.createTextNode(text); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie && ie_version < 9) mustWrap = true; + builder.pos += text.length; + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(text.slice(pos, pos + skipped)); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + builder.col += tabWidth; + } else { + var txt = builder.cm.options.specialCharPlaceholder(m[0]); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt); + builder.pos++; + } + } + if (style || startStyle || endStyle || mustWrap) { + var fullStyle = style || ""; + if (startStyle) fullStyle += startStyle; + if (endStyle) fullStyle += endStyle; + var token = elt("span", [content], fullStyle); + if (title) token.title = title; + return builder.content.appendChild(token); + } + builder.content.appendChild(content); + } + + function buildTokenSplitSpaces(inner) { + function split(old) { + var out = " "; + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; + out += " "; + return out; + } + return function(builder, text, style, startStyle, endStyle, title) { + inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title); + }; + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function(builder, text, style, startStyle, endStyle, title) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + for (var i = 0; i < order.length; i++) { + var part = order[i]; + if (part.to > start && part.from <= start) break; + } + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title); + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + }; + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) { + builder.map.push(builder.pos, builder.pos + size, widget); + builder.content.appendChild(widget); + } + builder.pos += size; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); + return; + } + + var len = allText.length, pos = 0, i = 1, text = "", style; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = ""; + collapsed = null; nextChange = Infinity; + var foundBookmarks = []; + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (sp.from <= pos && (sp.to == null || sp.to > pos)) { + if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } + if (m.className) spanStyle += " " + m.className; + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; + if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + collapsed = sp; + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m); + } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) return; + } + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); + } + if (pos >= len) break; + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore); + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null;} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + for (var i = 0, added = []; i < text.length - 1; ++i) + added.push(new Line(text[i], spansFor(i), estimateHeight)); + update(lastLine, lastLine.text, lastSpans); + if (nlines) doc.remove(from.line, nlines); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + for (var added = [], i = 1; i < text.length - 1; ++i) + added.push(new Line(text[i], spansFor(i), estimateHeight)); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + for (var i = 1, added = []; i < text.length - 1; ++i) + added.push(new Line(text[i], spansFor(i), estimateHeight)); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1); + doc.insert(from.line + 1, added); + } + + signalLater(doc, "change", doc, change); + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, height = 0; i < lines.length; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) lines[i].parent = this; + }, + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + removeInner: function(at, n) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines); + }, + insertInner: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iterN: function(at, n, op) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + var nextDocId = 0; + var Doc = CodeMirror.Doc = function(text, mode, firstLine) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); + if (firstLine == null) firstLine = 0; + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.frontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + + if (typeof text == "string") text = splitLines(text); + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) this.iterN(from - this.first, to - from, op); + else this.iterN(this.first, this.first + this.size, from); + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) height += lines[i].height; + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: splitLines(code), origin: "setValue"}, true); + setSelection(this, simpleSelection(top)); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, + + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, + getLineNumber: function(line) {return lineNo(line);}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(line); + }, + + lineCount: function() {return this.size;}, + firstLine: function() {return this.first;}, + lastLine: function() {return this.first + this.size - 1;}, + + clipPos: function(pos) {return clipPos(this, pos);}, + + getCursor: function(start) { + var range = this.sel.primary(), pos; + if (start == null || start == "head") pos = range.head; + else if (start == "anchor") pos = range.anchor; + else if (start == "end" || start == "to" || start === false) pos = range.to(); + else pos = range.from(); + return pos; + }, + listSelections: function() { return this.sel.ranges; }, + somethingSelected: function() {return this.sel.somethingSelected();}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads, options)); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + extendSelections(this, map(this.sel.ranges, f), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return; + for (var i = 0, out = []; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)); + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex); + setSelection(this, normalizeSelection(out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) return lines; + else return lines.join(lineSep || "\n"); + }, + getSelections: function(lineSep) { + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) sel = sel.join(lineSep || "\n"); + parts[i] = sel; + } + return parts; + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + dup[i] = code; + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]); + if (newSel) setSelectionReplaceHistory(this, newSel); + else if (this.cm) ensureCursorVisible(this.cm); + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend;}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done; + for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone; + return {undo: done, redo: undone}; + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)}; + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, "class", function(line) { + var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; + if (!line[prop]) line[prop] = cls; + else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false; + else line[prop] += " " + cls; + return true; + }); + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, "class", function(line) { + var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) return false; + else if (cls == null) line[prop] = null; + else { + var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)")); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true; + }); + }), + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark"); + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker.parent || span.marker); + } + return markers; + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo = from.line; + this.iter(from.line, to.line + 1, function(line) { + var spans = line.markedSpans; + if (spans) for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(lineNo == from.line && from.ch > span.to || + span.from == null && lineNo != from.line|| + lineNo == to.line && span.from > to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker); + } + ++lineNo; + }); + return found; + }, + getAllMarks: function() { + var markers = []; + this.iter(function(line) { + var sps = line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker); + }); + return markers; + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first; + this.iter(function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos(this, Pos(lineNo, ch)); + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) return 0; + this.iter(this.first, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc; + }, + + linkedDoc: function(options) { + if (!options) options = {}; + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) from = options.from; + if (options.to != null && options.to < to) to = options.to; + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); + if (options.sharedHist) copy.history = this.history; + (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy; + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc; + if (this.linked) for (var i = 0; i < this.linked.length; ++i) { + var link = this.linked[i]; + if (link.doc != other) continue; + this.linked.splice(i, 1); + other.unlinkDoc(this); + detachSharedMarkers(findSharedMarkers(this)); + break; + } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode;}, + getEditor: function() {return this.cm;} + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor".split(" "); + for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments);}; + })(Doc.prototype[prop]); + + eventMixin(Doc); + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) continue; + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) continue; + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) throw new Error("This document is already in use."); + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + if (!cm.options.lineWrapping) findMaxLine(cm); + cm.options.mode = doc.modeOption; + regChange(cm); + } + + // LINE UTILITIES + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document."); + for (var chunk = doc; !chunk.lines;) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function(line) { + var text = line.text; + if (n == end.line) text = text.slice(0, end.ch); + if (n == start.line) text = text.slice(start.ch); + out.push(text); + ++n; + }); + return out; + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function(line) { out.push(line.text); }); + return out; + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) for (var n = line; n; n = n.parent) n.height += diff; + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first; + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i = 0; i < chunk.children.length; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) break; + else h += line.height; + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i = 0; i < p.children.length; ++i) { + var cur = p.children[i]; + if (cur == chunk) break; + else h += cur.height; + } + } + return h; + } + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line) { + var order = line.order; + if (order == null) order = line.order = bidiOrdering(line.text); + return order; + } + + // HISTORY + + function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = this.lastSelOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); + return histChange; + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) array.pop(); + else break; + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done); + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done); + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done); + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, ore are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + var last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + pushSelectionToHistory(doc.sel, hist.done); + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) hist.done.shift(); + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = hist.lastSelOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) signal(doc, "historyAdded"); + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + hist.done[hist.done.length - 1] = sel; + else + pushSelectionToHistory(sel, hist.done); + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastSelOp = opId; + if (options && options.clearRedo !== false) + clearSelectionEvents(hist.undone); + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + dest.push(sel); + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) return null; + for (var i = 0, out; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) return null; + for (var i = 0, nw = []; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])); + return nw; + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + for (var i = 0, copy = []; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue; + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m; + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } + } + } + return copy; + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue; + } + for (var j = 0; j < sub.changes.length; ++j) { + var cur = sub.changes[j]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break; + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // EVENT UTILITIES + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + var e_preventDefault = CodeMirror.e_preventDefault = function(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + }; + var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + }; + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } + var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);}; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var on = CodeMirror.on = function(emitter, type, f) { + if (emitter.addEventListener) + emitter.addEventListener(type, f, false); + else if (emitter.attachEvent) + emitter.attachEvent("on" + type, f); + else { + var map = emitter._handlers || (emitter._handlers = {}); + var arr = map[type] || (map[type] = []); + arr.push(f); + } + }; + + var off = CodeMirror.off = function(emitter, type, f) { + if (emitter.removeEventListener) + emitter.removeEventListener(type, f, false); + else if (emitter.detachEvent) + emitter.detachEvent("on" + type, f); + else { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + for (var i = 0; i < arr.length; ++i) + if (arr[i] == f) { arr.splice(i, 1); break; } + } + }; + + var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); + }; + + var orphanDelayedCallbacks = null; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + function signalLater(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); + } + function bnd(f) {return function(){f.apply(null, args);};}; + for (var i = 0; i < arr.length; ++i) + list.push(bnd(arr[i])); + } + + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) delayed[i](); + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) return; + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) + set.push(arr[i]); + } + + function hasHandler(emitter, type) { + var arr = emitter._handlers && emitter._handlers[type]; + return arr && arr.length > 0; + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // MISC UTILITIES + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerCutOff = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + function Delayed() {this.id = null;} + Delayed.prototype.set = function(ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + return n + (end - i); + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + }; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) nextTab = string.length; + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + return pos + Math.min(skipped, goal - col); + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) return pos; + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; + else if (ie) // Suppress mysterious IE10 errors + selectInput = function(node) { try { node.select(); } catch(_e) {} }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + if (array[i] == elt) return i; + return -1; + } + if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); }; + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); + return out; + } + if ([].map) map = function(array, f) { return array.map(f); }; + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + var ctor = function() {}; + ctor.prototype = base; + inst = new ctor(); + } + if (props) copyObj(props, inst); + return inst; + }; + + function copyObj(obj, target, overwrite) { + if (!target) target = {}; + for (var prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop]; + return target; + } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args);}; + } + + var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var isWordCharBasic = CodeMirror.isWordChar = function(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); + }; + function isWordChar(ch, helper) { + if (!helper) return isWordCharBasic(ch); + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; + return helper.test(ch); + } + + function isEmpty(obj) { + for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; + return true; + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } + + // DOM UTILITIES + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") e.appendChild(document.createTextNode(content)); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + var range; + if (document.createRange) range = function(node, start, end) { + var r = document.createRange(); + r.setEnd(node, end); + r.setStart(node, start); + return r; + }; + else range = function(node, start, end) { + var r = document.body.createTextRange(); + r.moveToElementText(node.parentNode); + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r; + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild); + return e; + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e); + } + + function contains(parent, child) { + if (parent.contains) + return parent.contains(child); + while (child = child.parentNode) + if (child == parent) return true; + } + + function activeElt() { return document.activeElement; } + // Older versions of IE throws unspecified error when touching + // document.activeElement in some cases (during loading, in iframe) + if (ie && ie_version < 11) activeElt = function() { + try { return document.activeElement; } + catch(e) { return document.body; } + }; + + function classTest(cls) { return new RegExp("\\b" + cls + "\\b\\s*"); } + function rmClass(node, cls) { + var test = classTest(cls); + if (test.test(node.className)) node.className = node.className.replace(test, ""); + } + function addClass(node, cls) { + if (!classTest(cls).test(node.className)) node.className += " " + cls; + } + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; + return b; + } + + // WINDOW-WIDE EVENTS + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.body.getElementsByClassName) return; + var byClass = document.body.getElementsByClassName("CodeMirror"); + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) f(cm); + } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) return; + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + knownScrollbarWidth = null; + forEachCodeMirror(onResize); + }, 100); + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function() { + forEachCodeMirror(onBlur); + }); + } + + // FEATURE DETECTION + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + var knownScrollbarWidth; + function scrollbarWidth(measure) { + if (knownScrollbarWidth != null) return knownScrollbarWidth; + var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll"); + removeChildrenAndAdd(measure, test); + if (test.offsetWidth) + knownScrollbarWidth = test.offsetHeight - test.clientHeight; + return knownScrollbarWidth || 0; + } + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); + } + if (zwspSupported) return elt("span", "\u200b"); + else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) return badBidiRects; + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780) + var r1 = range(txt, 1, 2).getBoundingClientRect(); + return badBidiRects = (r1.right - r0.right < 3); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result; + } : function(string){return string.split(/\r\n?|\n/);}; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + var hasCopyEvent = (function() { + var e = elt("div"); + if ("oncopy" in e) return true; + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function"; + })(); + + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects; + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; + } + + // KEY NAMES + + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr"); + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } + } + if (!found) f(from, to, "ltr"); + } + + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } + function bidiRight(part) { return part.level % 2 ? part.from : part.to; } + + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } + function lineRight(line) { + var order = getOrder(line); + if (!order) return line.text.length; + return bidiRight(lst(order)); + } + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) lineN = lineNo(visual); + var order = getOrder(visual); + var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); + return Pos(lineN, ch); + } + function lineEnd(cm, lineN) { + var merged, line = getLine(cm.doc, lineN); + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + lineN = null; + } + var order = getOrder(line); + var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); + return Pos(lineN == null ? lineNo(line) : lineN, ch); + } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS); + } + return start; + } + + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + bidiOther = null; + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) return i; + if ((cur.from == pos || cur.to == pos)) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + if (cur.from != cur.to) bidiOther = found; + return i; + } else { + if (cur.from != cur.to) bidiOther = i; + return found; + } + } + } + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar(line.text.charAt(pos))); + return pos; + } + + // This is needed in order to move 'visually' through bi-directional + // text -- i.e., pressing left should make the cursor go left, even + // when in RTL text. The tricky part is the 'jumps', where RTL and + // LTR text touch each other. This often requires the cursor offset + // to move more than one unit, in order to visually move one unit. + function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line); + if (!bidi) return moveLogically(line, start, dir, byUnit); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); + + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; + } else { + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); + } + } + } + + function moveLogically(line, start, dir, byUnit) { + var target = start + dir; + if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; + return target < 0 || target > line.text.length ? null : target; + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6ff + var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm"; + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code); + else if (0x590 <= code && code <= 0x5f4) return "R"; + else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600); + else if (0x6ee <= code && code <= 0x8ac) return "r"; + else if (0x2000 <= code && code <= 0x200b) return "w"; + else if (code == 0x200c) return "b"; + else return "L"; + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L"; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str) { + if (!bidiRE.test(str)) return false; + var len = str.length, types = []; + for (var i = 0, type; i < len; ++i) + types.push(type = charType(str.charCodeAt(i))); + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i = 0, prev = outerType; i < len; ++i) { + var type = types[i]; + if (type == "m") types[i] = prev; + else prev = type; + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (type == "1" && cur == "r") types[i] = "n"; + else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i = 1, prev = types[0]; i < len - 1; ++i) { + var type = types[i]; + if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; + else if (type == "," && prev == types[i+1] && + (prev == "1" || prev == "n")) types[i] = prev; + prev = type; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i = 0; i < len; ++i) { + var type = types[i]; + if (type == ",") types[i] = "N"; + else if (type == "%") { + for (var end = i + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (cur == "L" && type == "1") types[i] = "L"; + else if (isStrong.test(type)) cur = type; + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + var before = (i ? types[i-1] : outerType) == "L"; + var after = (end < len ? types[end] : outerType) == "L"; + var replace = before || after ? "L" : "R"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + var start = i; + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)); + } else { + var pos = i, at = order.length; + for (++i; i < len && types[i] != "L"; ++i) {} + for (var j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); + var nstart = j; + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)); + pos = j; + } else ++j; + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + if (order[0].level != lst(order).level) + order.push(new BidiSpan(order[0].level, len, len)); + + return order; + }; + })(); + + // THE END + + CodeMirror.version = "4.7.0"; + + return CodeMirror; +}); diff --git a/apps/static/js/plugins/codemirror/mode/index.html b/apps/static/js/plugins/codemirror/mode/index.html new file mode 100755 index 000000000..bb656d2a7 --- /dev/null +++ b/apps/static/js/plugins/codemirror/mode/index.html @@ -0,0 +1,125 @@ + + +CodeMirror: Language Modes + + + + + +
+ +

Language modes

+ +

This is a list of every mode in the distribution. Each mode lives +in a subdirectory of the mode/ directory, and typically +defines a single JavaScript file that implements the mode. Loading +such file will make the language available to CodeMirror, through +the mode +option.

+ +
+ +
+ +
diff --git a/apps/static/js/plugins/codemirror/mode/meta.js b/apps/static/js/plugins/codemirror/mode/meta.js new file mode 100755 index 000000000..cee33e542 --- /dev/null +++ b/apps/static/js/plugins/codemirror/mode/meta.js @@ -0,0 +1,144 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.modeInfo = [ + {name: "APL", mime: "text/apl", mode: "apl", ext: ["dyalog", "apl"]}, + {name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk"}, + {name: "C", mime: "text/x-csrc", mode: "clike", ext: ["c", "h"]}, + {name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "hpp", "h++"]}, + {name: "Cobol", mime: "text/x-cobol", mode: "cobol", ext: ["cob", "cpy"]}, + {name: "C#", mime: "text/x-csharp", mode: "clike", ext: ["cs"]}, + {name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj"]}, + {name: "CoffeeScript", mime: "text/x-coffeescript", mode: "coffeescript", ext: ["coffee"]}, + {name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"]}, + {name: "Cypher", mime: "application/x-cypher-query", mode: "cypher"}, + {name: "Cython", mime: "text/x-cython", mode: "python", ext: ["pyx", "pxd", "pxi"]}, + {name: "CSS", mime: "text/css", mode: "css", ext: ["css"]}, + {name: "CQL", mime: "text/x-cassandra", mode: "sql", ext: ["cql"]}, + {name: "D", mime: "text/x-d", mode: "d", ext: ["d"]}, + {name: "diff", mime: "text/x-diff", mode: "diff", ext: ["diff", "patch"]}, + {name: "DTD", mime: "application/xml-dtd", mode: "dtd", ext: ["dtd"]}, + {name: "Dylan", mime: "text/x-dylan", mode: "dylan", ext: ["dylan", "dyl", "intr"]}, + {name: "ECL", mime: "text/x-ecl", mode: "ecl", ext: ["ecl"]}, + {name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel", ext: ["e"]}, + {name: "Embedded Javascript", mime: "application/x-ejs", mode: "htmlembedded", ext: ["ejs"]}, + {name: "Erlang", mime: "text/x-erlang", mode: "erlang", ext: ["erl"]}, + {name: "Fortran", mime: "text/x-fortran", mode: "fortran", ext: ["f", "for", "f77", "f90"]}, + {name: "F#", mime: "text/x-fsharp", mode: "mllike", ext: ["fs"]}, + {name: "Gas", mime: "text/x-gas", mode: "gas", ext: ["s"]}, + {name: "Gherkin", mime: "text/x-feature", mode: "gherkin", ext: ["feature"]}, + {name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm"}, + {name: "Go", mime: "text/x-go", mode: "go", ext: ["go"]}, + {name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy"]}, + {name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]}, + {name: "Haskell", mime: "text/x-haskell", mode: "haskell", ext: ["hs"]}, + {name: "Haxe", mime: "text/x-haxe", mode: "haxe", ext: ["hx"]}, + {name: "HXML", mime: "text/x-hxml", mode: "haxe", ext: ["hxml"]}, + {name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded", ext: ["aspx"]}, + {name: "HTML", mime: "text/html", mode: "htmlmixed", ext: ["html", "htm"]}, + {name: "HTTP", mime: "message/http", mode: "http"}, + {name: "Jade", mime: "text/x-jade", mode: "jade", ext: ["jade"]}, + {name: "Java", mime: "text/x-java", mode: "clike", ext: ["java"]}, + {name: "Java Server Pages", mime: "application/x-jsp", mode: "htmlembedded", ext: ["jsp"]}, + {name: "JavaScript", mimes: ["text/javascript", "text/ecmascript", "application/javascript", "application/x-javascript", "application/ecmascript"], + mode: "javascript", ext: ["js"]}, + {name: "JSON", mimes: ["application/json", "application/x-json"], mode: "javascript", ext: ["json", "map"]}, + {name: "JSON-LD", mime: "application/ld+json", mode: "javascript"}, + {name: "Jinja2", mime: "null", mode: "jinja2"}, + {name: "Julia", mime: "text/x-julia", mode: "julia", ext: ["jl"]}, + {name: "Kotlin", mime: "text/x-kotlin", mode: "kotlin", ext: ["kt"]}, + {name: "LESS", mime: "text/x-less", mode: "css", ext: ["less"]}, + {name: "LiveScript", mime: "text/x-livescript", mode: "livescript", ext: ["ls"]}, + {name: "Lua", mime: "text/x-lua", mode: "lua", ext: ["lua"]}, + {name: "Markdown (GitHub-flavour)", mime: "text/x-markdown", mode: "markdown", ext: ["markdown", "md", "mkd"]}, + {name: "mIRC", mime: "text/mirc", mode: "mirc"}, + {name: "MariaDB SQL", mime: "text/x-mariadb", mode: "sql"}, + {name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]}, + {name: "MS SQL", mime: "text/x-mssql", mode: "sql"}, + {name: "MySQL", mime: "text/x-mysql", mode: "sql"}, + {name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx"}, + {name: "NTriples", mime: "text/n-triples", mode: "ntriples", ext: ["nt"]}, + {name: "OCaml", mime: "text/x-ocaml", mode: "mllike", ext: ["ml", "mli", "mll", "mly"]}, + {name: "Octave", mime: "text/x-octave", mode: "octave", ext: ["m"]}, + {name: "Pascal", mime: "text/x-pascal", mode: "pascal", ext: ["p", "pas"]}, + {name: "PEG.js", mime: "null", mode: "pegjs"}, + {name: "Perl", mime: "text/x-perl", mode: "perl", ext: ["pl", "pm"]}, + {name: "PHP", mime: "application/x-httpd-php", mode: "php", ext: ["php", "php3", "php4", "php5", "phtml"]}, + {name: "Pig", mime: "text/x-pig", mode: "pig"}, + {name: "Plain Text", mime: "text/plain", mode: "null", ext: ["txt", "text", "conf", "def", "list", "log"]}, + {name: "PLSQL", mime: "text/x-plsql", mode: "sql"}, + {name: "Properties files", mime: "text/x-properties", mode: "properties", ext: ["properties", "ini", "in"]}, + {name: "Python", mime: "text/x-python", mode: "python", ext: ["py", "pyw"]}, + {name: "Puppet", mime: "text/x-puppet", mode: "puppet", ext: ["pp"]}, + {name: "Q", mime: "text/x-q", mode: "q", ext: ["q"]}, + {name: "R", mime: "text/x-rsrc", mode: "r", ext: ["r"]}, + {name: "reStructuredText", mime: "text/x-rst", mode: "rst", ext: ["rst"]}, + {name: "Ruby", mime: "text/x-ruby", mode: "ruby", ext: ["rb"]}, + {name: "Rust", mime: "text/x-rustsrc", mode: "rust", ext: ["rs"]}, + {name: "Sass", mime: "text/x-sass", mode: "sass", ext: ["sass"]}, + {name: "Scala", mime: "text/x-scala", mode: "clike", ext: ["scala"]}, + {name: "Scheme", mime: "text/x-scheme", mode: "scheme", ext: ["scm", "ss"]}, + {name: "SCSS", mime: "text/x-scss", mode: "css", ext: ["scss"]}, + {name: "Shell", mime: "text/x-sh", mode: "shell", ext: ["sh", "ksh", "bash"]}, + {name: "Sieve", mime: "application/sieve", mode: "sieve"}, + {name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim"}, + {name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk", ext: ["st"]}, + {name: "Smarty", mime: "text/x-smarty", mode: "smarty", ext: ["tpl"]}, + {name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"}, + {name: "Solr", mime: "text/x-solr", mode: "solr"}, + {name: "SPARQL", mime: "application/x-sparql-query", mode: "sparql", ext: ["sparql"]}, + {name: "SQL", mime: "text/x-sql", mode: "sql", ext: ["sql"]}, + {name: "MariaDB", mime: "text/x-mariadb", mode: "sql"}, + {name: "sTeX", mime: "text/x-stex", mode: "stex"}, + {name: "LaTeX", mime: "text/x-latex", mode: "stex", ext: ["text", "ltx"]}, + {name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog", ext: ["v"]}, + {name: "Tcl", mime: "text/x-tcl", mode: "tcl", ext: ["tcl"]}, + {name: "Textile", mime: "text/x-textile", mode: "textile"}, + {name: "TiddlyWiki ", mime: "text/x-tiddlywiki", mode: "tiddlywiki"}, + {name: "Tiki wiki", mime: "text/tiki", mode: "tiki"}, + {name: "TOML", mime: "text/x-toml", mode: "toml"}, + {name: "Tornado", mime: "text/x-tornado", mode: "tornado"}, + {name: "Turtle", mime: "text/turtle", mode: "turtle", ext: ["ttl"]}, + {name: "TypeScript", mime: "application/typescript", mode: "javascript", ext: ["ts"]}, + {name: "VB.NET", mime: "text/x-vb", mode: "vb", ext: ["vb"]}, + {name: "VBScript", mime: "text/vbscript", mode: "vbscript"}, + {name: "Velocity", mime: "text/velocity", mode: "velocity", ext: ["vtl"]}, + {name: "Verilog", mime: "text/x-verilog", mode: "verilog", ext: ["v"]}, + {name: "XML", mimes: ["application/xml", "text/xml"], mode: "xml", ext: ["xml", "xsl", "xsd"]}, + {name: "XQuery", mime: "application/xquery", mode: "xquery", ext: ["xy", "xquery"]}, + {name: "YAML", mime: "text/x-yaml", mode: "yaml", ext: ["yaml"]}, + {name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]} + ]; + // Ensure all modes have a mime property for backwards compatibility + for (var i = 0; i < CodeMirror.modeInfo.length; i++) { + var info = CodeMirror.modeInfo[i]; + if (info.mimes) info.mime = info.mimes[0]; + } + + CodeMirror.findModeByMIME = function(mime) { + for (var i = 0; i < CodeMirror.modeInfo.length; i++) { + var info = CodeMirror.modeInfo[i]; + if (info.mime == mime) return info; + if (info.mimes) for (var j = 0; j < info.mimes.length; j++) + if (info.mimes[j] == mime) return info; + } + }; + + CodeMirror.findModeByExtension = function(ext) { + for (var i = 0; i < CodeMirror.modeInfo.length; i++) { + var info = CodeMirror.modeInfo[i]; + if (info.ext) for (var j = 0; j < info.ext.length; j++) + if (info.ext[j] == ext) return info; + } + }; +}); diff --git a/apps/static/js/plugins/codemirror/mode/shell/index.html b/apps/static/js/plugins/codemirror/mode/shell/index.html new file mode 100755 index 000000000..0b56300b1 --- /dev/null +++ b/apps/static/js/plugins/codemirror/mode/shell/index.html @@ -0,0 +1,66 @@ + + +CodeMirror: Shell mode + + + + + + + + + + +
+

Shell mode

+ + + + + + +

MIME types defined: text/x-sh.

+
diff --git a/apps/static/js/plugins/codemirror/mode/shell/shell.js b/apps/static/js/plugins/codemirror/mode/shell/shell.js new file mode 100755 index 000000000..8e31f6f30 --- /dev/null +++ b/apps/static/js/plugins/codemirror/mode/shell/shell.js @@ -0,0 +1,138 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode('shell', function() { + + var words = {}; + function define(style, string) { + var split = string.split(' '); + for(var i = 0; i < split.length; i++) { + words[split[i]] = style; + } + }; + + // Atoms + define('atom', 'true false'); + + // Keywords + define('keyword', 'if then do else elif while until for in esac fi fin ' + + 'fil done exit set unset export function'); + + // Commands + define('builtin', 'ab awk bash beep cat cc cd chown chmod chroot clear cp ' + + 'curl cut diff echo find gawk gcc get git grep kill killall ln ls make ' + + 'mkdir openssl mv nc node npm ping ps restart rm rmdir sed service sh ' + + 'shopt shred source sort sleep ssh start stop su sudo tee telnet top ' + + 'touch vi vim wall wc wget who write yes zsh'); + + function tokenBase(stream, state) { + if (stream.eatSpace()) return null; + + var sol = stream.sol(); + var ch = stream.next(); + + if (ch === '\\') { + stream.next(); + return null; + } + if (ch === '\'' || ch === '"' || ch === '`') { + state.tokens.unshift(tokenString(ch)); + return tokenize(stream, state); + } + if (ch === '#') { + if (sol && stream.eat('!')) { + stream.skipToEnd(); + return 'meta'; // 'comment'? + } + stream.skipToEnd(); + return 'comment'; + } + if (ch === '$') { + state.tokens.unshift(tokenDollar); + return tokenize(stream, state); + } + if (ch === '+' || ch === '=') { + return 'operator'; + } + if (ch === '-') { + stream.eat('-'); + stream.eatWhile(/\w/); + return 'attribute'; + } + if (/\d/.test(ch)) { + stream.eatWhile(/\d/); + if(stream.eol() || !/\w/.test(stream.peek())) { + return 'number'; + } + } + stream.eatWhile(/[\w-]/); + var cur = stream.current(); + if (stream.peek() === '=' && /\w+/.test(cur)) return 'def'; + return words.hasOwnProperty(cur) ? words[cur] : null; + } + + function tokenString(quote) { + return function(stream, state) { + var next, end = false, escaped = false; + while ((next = stream.next()) != null) { + if (next === quote && !escaped) { + end = true; + break; + } + if (next === '$' && !escaped && quote !== '\'') { + escaped = true; + stream.backUp(1); + state.tokens.unshift(tokenDollar); + break; + } + escaped = !escaped && next === '\\'; + } + if (end || !escaped) { + state.tokens.shift(); + } + return (quote === '`' || quote === ')' ? 'quote' : 'string'); + }; + }; + + var tokenDollar = function(stream, state) { + if (state.tokens.length > 1) stream.eat('$'); + var ch = stream.next(), hungry = /\w/; + if (ch === '{') hungry = /[^}]/; + if (ch === '(') { + state.tokens[0] = tokenString(')'); + return tokenize(stream, state); + } + if (!/\d/.test(ch)) { + stream.eatWhile(hungry); + stream.eat('}'); + } + state.tokens.shift(); + return 'def'; + }; + + function tokenize(stream, state) { + return (state.tokens[0] || tokenBase) (stream, state); + }; + + return { + startState: function() {return {tokens:[]};}, + token: function(stream, state) { + return tokenize(stream, state); + }, + lineComment: '#' + }; +}); + +CodeMirror.defineMIME('text/x-sh', 'shell'); + +}); diff --git a/apps/static/js/plugins/codemirror/mode/shell/test.js b/apps/static/js/plugins/codemirror/mode/shell/test.js new file mode 100755 index 000000000..a413b5a40 --- /dev/null +++ b/apps/static/js/plugins/codemirror/mode/shell/test.js @@ -0,0 +1,58 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({}, "shell"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + MT("var", + "text [def $var] text"); + MT("varBraces", + "text[def ${var}]text"); + MT("varVar", + "text [def $a$b] text"); + MT("varBracesVarBraces", + "text[def ${a}${b}]text"); + + MT("singleQuotedVar", + "[string 'text $var text']"); + MT("singleQuotedVarBraces", + "[string 'text ${var} text']"); + + MT("doubleQuotedVar", + '[string "text ][def $var][string text"]'); + MT("doubleQuotedVarBraces", + '[string "text][def ${var}][string text"]'); + MT("doubleQuotedVarPunct", + '[string "text ][def $@][string text"]'); + MT("doubleQuotedVarVar", + '[string "][def $a$b][string "]'); + MT("doubleQuotedVarBracesVarBraces", + '[string "][def ${a}${b}][string "]'); + + MT("notAString", + "text\\'text"); + MT("escapes", + "outside\\'\\\"\\`\\\\[string \"inside\\`\\'\\\"\\\\`\\$notAVar\"]outside\\$\\(notASubShell\\)"); + + MT("subshell", + "[builtin echo] [quote $(whoami)] s log, stardate [quote `date`]."); + MT("doubleQuotedSubshell", + "[builtin echo] [string \"][quote $(whoami)][string 's log, stardate `date`.\"]"); + + MT("hashbang", + "[meta #!/bin/bash]"); + MT("comment", + "text [comment # Blurb]"); + + MT("numbers", + "[number 0] [number 1] [number 2]"); + MT("keywords", + "[keyword while] [atom true]; [keyword do]", + " [builtin sleep] [number 3]", + "[keyword done]"); + MT("options", + "[builtin ls] [attribute -l] [attribute --human-readable]"); + MT("operator", + "[def var][operator =]value"); +})(); diff --git a/apps/static/js/plugins/xterm/addons/attach/attach.js b/apps/static/js/plugins/xterm/addons/attach/attach.js new file mode 100644 index 000000000..73411b2cd --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/attach/attach.js @@ -0,0 +1,105 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.attach = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;iterm;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n // TODO: This should be typed but there seem to be issues importing the type\n let myTextDecoder: any;\n\n addonTerminal.__getMessage = function(ev: MessageEvent): void {\n let str: string;\n\n if (typeof ev.data === 'object') {\n if (!myTextDecoder) {\n myTextDecoder = new TextDecoder();\n }\n if (ev.data instanceof ArrayBuffer) {\n str = myTextDecoder.decode(ev.data);\n displayData(str);\n } else {\n const fileReader = new FileReader();\n\n fileReader.addEventListener('load', () => {\n str = myTextDecoder.decode(this.result);\n displayData(str);\n });\n fileReader.readAsArrayBuffer(ev.data);\n }\n } else if (typeof ev.data === 'string') {\n displayData(ev.data);\n } else {\n throw Error(`Cannot handle \"${typeof ev.data}\" websocket message.`);\n }\n };\n\n /**\n * Push data to buffer or write it in the terminal.\n * This is used as a callback for FileReader.onload.\n *\n * @param str String decoded by FileReader.\n * @param data The data of the EventMessage.\n */\n function displayData(str?: string, data?: string): void {\n if (buffered) {\n addonTerminal.__pushToBuffer(str || data);\n } else {\n addonTerminal.write(str || data);\n }\n }\n\n addonTerminal.__sendData = (data: string) => {\n if (socket.readyState !== 1) {\n return;\n }\n socket.send(data);\n };\n\n addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));\n\n if (bidirectional) {\n addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData));\n }\n\n addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket)));\n addonTerminal._core.register(addSocketListener(socket, 'error', () => detach(addonTerminal, socket)));\n}\n\nfunction addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: Event) => any): IDisposable {\n socket.addEventListener(type, handler);\n return {\n dispose: () => {\n if (!handler) {\n // Already disposed\n return;\n }\n socket.removeEventListener(type, handler);\n handler = null;\n }\n };\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function detach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = term;\n addonTerminal.off('data', addonTerminal.__sendData);\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\n (terminalConstructor.prototype).attach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n attach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (terminalConstructor.prototype).detach = function (socket: WebSocket): void {\n detach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADmBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AA9EA;AAgFA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAXA;AAcA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/fit/fit.js b/apps/static/js/plugins/xterm/addons/fit/fit.js new file mode 100644 index 000000000..b137d99e4 --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/fit/fit.js @@ -0,0 +1,51 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;iterm)._core.viewport.scrollBarWidth;\n const geometry = {\n cols: Math.floor(availableWidth / (term)._core.renderer.dimensions.actualCellWidth),\n rows: Math.floor(availableHeight / (term)._core.renderer.dimensions.actualCellHeight)\n };\n return geometry;\n}\n\nexport function fit(term: Terminal): void {\n const geometry = proposeGeometry(term);\n if (geometry) {\n // Force a full render\n if (term.rows !== geometry.rows || term.cols !== geometry.cols) {\n (term)._core.renderer.clear();\n term.resize(geometry.cols, geometry.rows);\n }\n }\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).proposeGeometry = function (): IGeometry {\n return proposeGeometry(this);\n };\n\n (terminalConstructor.prototype).fit = function (): void {\n fit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADsBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.css b/apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.css new file mode 100644 index 000000000..60e8c5114 --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.css @@ -0,0 +1,10 @@ +.xterm.fullscreen { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: auto; + height: auto; + z-index: 255; +} diff --git a/apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js b/apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js new file mode 100644 index 000000000..d12b72e6b --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js @@ -0,0 +1,27 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fullscreen = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;iterminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {\n toggleFullScreen(this, fullscreen);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADYA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AAJA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/search/search.js b/apps/static/js/plugins/xterm/addons/search/search.js new file mode 100644 index 000000000..3a0a1d176 --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/search/search.js @@ -0,0 +1,126 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.search = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i= 0; y--) { + result = this._findInLine(term, y); + if (result) { + break; + } + } + if (!result) { + for (var y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) { + result = this._findInLine(term, y); + if (result) { + break; + } + } + } + return this._selectResult(result); + }; + SearchHelper.prototype._findInLine = function (term, y) { + var lowerStringLine = this._terminal._core.buffer.translateBufferLineToString(y, true).toLowerCase(); + var lowerTerm = term.toLowerCase(); + var searchIndex = lowerStringLine.indexOf(lowerTerm); + if (searchIndex >= 0) { + var line = this._terminal._core.buffer.lines.get(y); + for (var i = 0; i < searchIndex; i++) { + var charData = line[i]; + var char = charData[1]; + if (char.length > 1) { + searchIndex -= char.length - 1; + } + var charWidth = charData[2]; + if (charWidth === 0) { + searchIndex++; + } + } + return { + term: term, + col: searchIndex, + row: y + }; + } + }; + SearchHelper.prototype._selectResult = function (result) { + if (!result) { + return false; + } + this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length); + this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp); + return true; + }; + return SearchHelper; +}()); +exports.SearchHelper = SearchHelper; + +},{}],2:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SearchHelper_1 = require("./SearchHelper"); +function findNext(terminal, term) { + var addonTerminal = terminal; + if (!addonTerminal.__searchHelper) { + addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal); + } + return addonTerminal.__searchHelper.findNext(term); +} +exports.findNext = findNext; +function findPrevious(terminal, term) { + var addonTerminal = terminal; + if (!addonTerminal.__searchHelper) { + addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal); + } + return addonTerminal.__searchHelper.findPrevious(term); +} +exports.findPrevious = findPrevious; +function apply(terminalConstructor) { + terminalConstructor.prototype.findNext = function (term) { + return findNext(this, term); + }; + terminalConstructor.prototype.findPrevious = function (term) { + return findPrevious(this, term); + }; +} +exports.apply = apply; + +},{"./SearchHelper":1}]},{},[2])(2) +}); +//# sourceMappingURL=search.js.map diff --git a/apps/static/js/plugins/xterm/addons/search/search.js.map b/apps/static/js/plugins/xterm/addons/search/search.js.map new file mode 100644 index 000000000..eca9836aa --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/search/search.js.map @@ -0,0 +1 @@ +{"version":3,"file":"search.js","sources":["../../../src/addons/search/search.ts","../../../src/addons/search/SearchHelper.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { SearchHelper } from './SearchHelper';\nimport { Terminal } from 'xterm';\nimport { ISearchAddonTerminal } from './Interfaces';\n\n/**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\nexport function findNext(terminal: Terminal, term: string): boolean {\n const addonTerminal = terminal;\n if (!addonTerminal.__searchHelper) {\n addonTerminal.__searchHelper = new SearchHelper(addonTerminal);\n }\n return addonTerminal.__searchHelper.findNext(term);\n}\n\n/**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\nexport function findPrevious(terminal: Terminal, term: string): boolean {\n const addonTerminal = terminal;\n if (!addonTerminal.__searchHelper) {\n addonTerminal.__searchHelper = new SearchHelper(addonTerminal);\n }\n return addonTerminal.__searchHelper.findPrevious(term);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).findNext = function(term: string): boolean {\n return findNext(this, term);\n };\n\n (terminalConstructor.prototype).findPrevious = function(term: string): boolean {\n return findPrevious(this, term);\n };\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ISearchHelper, ISearchAddonTerminal } from './Interfaces';\n\ninterface ISearchResult {\n term: string;\n col: number;\n row: number;\n}\n\n/**\n * A class that knows how to search the terminal and how to display the results.\n */\nexport class SearchHelper implements ISearchHelper {\n constructor(private _terminal: ISearchAddonTerminal) {\n // TODO: Search for multiple instances on 1 line\n // TODO: Don't use the actual selection, instead use a \"find selection\" so multiple instances can be highlighted\n // TODO: Highlight other instances in the viewport\n // TODO: Support regex, case sensitivity, etc.\n }\n\n /**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n public findNext(term: string): boolean {\n if (!term || term.length === 0) {\n return false;\n }\n\n let result: ISearchResult;\n\n let startRow = this._terminal._core.buffer.ydisp;\n if (this._terminal._core.selectionManager.selectionEnd) {\n // Start from the selection end if there is a selection\n startRow = this._terminal._core.selectionManager.selectionEnd[1];\n }\n\n // Search from ydisp + 1 to end\n for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n\n // Search from the top to the current ydisp\n if (!result) {\n for (let y = 0; y < startRow; y++) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n public findPrevious(term: string): boolean {\n if (!term || term.length === 0) {\n return false;\n }\n\n let result: ISearchResult;\n\n let startRow = this._terminal._core.buffer.ydisp;\n if (this._terminal._core.selectionManager.selectionStart) {\n // Start from the selection end if there is a selection\n startRow = this._terminal._core.selectionManager.selectionStart[1];\n }\n\n // Search from ydisp + 1 to end\n for (let y = startRow - 1; y >= 0; y--) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n\n // Search from the top to the current ydisp\n if (!result) {\n for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Searches a line for a search term.\n * @param term Tne search term.\n * @param y The line to search.\n * @return The search result if it was found.\n */\n private _findInLine(term: string, y: number): ISearchResult {\n const lowerStringLine = this._terminal._core.buffer.translateBufferLineToString(y, true).toLowerCase();\n const lowerTerm = term.toLowerCase();\n let searchIndex = lowerStringLine.indexOf(lowerTerm);\n if (searchIndex >= 0) {\n const line = this._terminal._core.buffer.lines.get(y);\n for (let i = 0; i < searchIndex; i++) {\n const charData = line[i];\n // Adjust the searchIndex to normalize emoji into single chars\n const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];\n if (char.length > 1) {\n searchIndex -= char.length - 1;\n }\n // Adjust the searchIndex for empty characters following wide unicode\n // chars (eg. CJK)\n const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];\n if (charWidth === 0) {\n searchIndex++;\n }\n }\n return {\n term,\n col: searchIndex,\n row: y\n };\n }\n }\n\n /**\n * Selects and scrolls to a result.\n * @param result The result to select.\n * @return Whethera result was selected.\n */\n private _selectResult(result: ISearchResult): boolean {\n if (!result) {\n return false;\n }\n this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);\n this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp);\n return true;\n }\n}\n",null],"names":[],"mappings":"AEAA;;;ADgBA;AACA;AAAA;AAKA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAzIa;;;;;ADXb;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/terminado/terminado.js b/apps/static/js/plugins/xterm/addons/terminado/terminado.js new file mode 100644 index 000000000..315629d30 --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/terminado/terminado.js @@ -0,0 +1,69 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.terminado = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;iterm;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n addonTerminal.__getMessage = (ev: MessageEvent) => {\n const data = JSON.parse(ev.data);\n if (data[0] === 'stdout') {\n if (buffered) {\n addonTerminal.__pushToBuffer(data[1]);\n } else {\n addonTerminal.write(data[1]);\n }\n }\n };\n\n addonTerminal.__sendData = (data: string) => {\n socket.send(JSON.stringify(['stdin', data]));\n };\n\n addonTerminal.__setSize = (size: {rows: number, cols: number}) => {\n socket.send(JSON.stringify(['set_size', size.rows, size.cols]));\n };\n\n socket.addEventListener('message', addonTerminal.__getMessage);\n\n if (bidirectional) {\n addonTerminal.on('data', addonTerminal.__sendData);\n }\n addonTerminal.on('resize', addonTerminal.__setSize);\n\n socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));\n socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function terminadoDetach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = term;\n addonTerminal.off('data', addonTerminal.__sendData);\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket - The socket to attach the current terminal.\n * @param bidirectional - Whether the terminal should send data to the socket as well.\n * @param buffered - Whether the rendering of incoming data should happen instantly or at a\n * maximum frequency of 1 rendering per 10ms.\n */\n (terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n return terminadoAttach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {\n return terminadoDetach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADoBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AA/CA;AAuDA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAXA;AAaA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/webLinks/webLinks.js b/apps/static/js/plugins/xterm/addons/webLinks/webLinks.js new file mode 100644 index 000000000..42208846b --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/webLinks/webLinks.js @@ -0,0 +1,41 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.webLinks = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i void = handleLink, options: ILinkMatcherOptions = {}): void {\n options.matchIndex = 1;\n term.registerLinkMatcher(strictUrlRegex, handler, options);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void {\n webLinksInit(this, handler, options);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAQA;AAAA;AAAA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AAJA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js b/apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js new file mode 100644 index 000000000..641c56ff7 --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js @@ -0,0 +1,29 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.winptyCompat = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i= 0; + if (!isWindows) { + return; + } + addonTerminal.on('linefeed', function () { + var line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1); + var lastChar = line[addonTerminal.cols - 1]; + if (lastChar[3] !== 32) { + var nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y); + nextLine.isWrapped = true; + } + }); +} +exports.winptyCompatInit = winptyCompatInit; +function apply(terminalConstructor) { + terminalConstructor.prototype.winptyCompatInit = function () { + winptyCompatInit(this); + }; +} +exports.apply = apply; + +},{}]},{},[1])(1) +}); +//# sourceMappingURL=winptyCompat.js.map diff --git a/apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js.map b/apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js.map new file mode 100644 index 000000000..da8f4acb1 --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js.map @@ -0,0 +1 @@ +{"version":3,"file":"winptyCompat.js","sources":["../../../src/addons/winptyCompat/winptyCompat.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\nimport { IWinptyCompatAddonTerminal } from './Interfaces';\n\nexport function winptyCompatInit(terminal: Terminal): void {\n const addonTerminal = terminal;\n\n // Don't do anything when the platform is not Windows\n const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;\n if (!isWindows) {\n return;\n }\n\n // Winpty does not support wraparound mode which means that lines will never\n // be marked as wrapped. This causes issues for things like copying a line\n // retaining the wrapped new line characters or if consumers are listening\n // in on the data stream.\n //\n // The workaround for this is to listen to every incoming line feed and mark\n // the line as wrapped if the last character in the previous line is not a\n // space. This is certainly not without its problems, but generally on\n // Windows when text reaches the end of the terminal it's likely going to be\n // wrapped.\n addonTerminal.on('linefeed', () => {\n const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);\n const lastChar = line[addonTerminal.cols - 1];\n\n if (lastChar[3] !== 32 /* ' ' */) {\n const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);\n (nextLine).isWrapped = true;\n }\n });\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).winptyCompatInit = function (): void {\n winptyCompatInit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADQA;AACA;AAGA;AACA;AACA;AACA;AAYA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AA5BA;AA8BA;AACA;AACA;AACA;AACA;AAJA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/zmodem/zmodem.js b/apps/static/js/plugins/xterm/addons/zmodem/zmodem.js new file mode 100644 index 000000000..70a7ff773 --- /dev/null +++ b/apps/static/js/plugins/xterm/addons/zmodem/zmodem.js @@ -0,0 +1,45 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.zmodem = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i, )` - creates a Zmodem.Sentry\n * on the passed WebSocket object. The Object passed is optional and\n * can contain:\n * - noTerminalWriteOutsideSession: Suppress writes from the Sentry\n * object to the Terminal while there is no active Session. This\n * is necessary for compatibility with, for example, the\n * `attach.js` addon.\n *\n * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback.\n * Passes the zmodem.js Detection object.\n *\n * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback.\n *\n * You’ll need to provide logic to handle uploads and downloads.\n * See zmodem.js’s documentation for more details.\n *\n * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have\n * used the `attach` or `terminado` addons, you’ll need to suspend their\n * operation for the duration of the ZMODEM session. (The demo does this\n * via `detach()` and a re-`attach()`.)\n */\n\nlet zmodem;\n\nexport interface IZmodemOptions {\n noTerminalWriteOutsideSession?: boolean;\n}\n\nfunction zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {\n const term = this;\n const senderFunc = (octets: ArrayLike) => ws.send(new Uint8Array(octets));\n\n let zsentry;\n\n function shouldWrite(): boolean {\n return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;\n }\n\n zsentry = new zmodem.Sentry({\n to_terminal: (octets: ArrayLike) => {\n if (shouldWrite()) {\n term.write(\n String.fromCharCode.apply(String, octets)\n );\n }\n },\n sender: senderFunc,\n on_retract: () => (term).emit('zmodemRetract'),\n on_detect: (detection: any) => (term).emit('zmodemDetect', detection)\n });\n\n function handleWSMessage(evt: MessageEvent): void {\n\n // In testing with xterm.js’s demo the first message was\n // always text even if the rest were binary. While that\n // may be specific to xterm.js’s demo, ultimately we\n // should reject anything that isn’t binary.\n if (typeof evt.data === 'string') {\n if (shouldWrite()) {\n term.write(evt.data);\n }\n }\n else {\n zsentry.consume(evt.data);\n }\n }\n\n ws.binaryType = 'arraybuffer';\n ws.addEventListener('message', handleWSMessage);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n zmodem = (typeof window === 'object') ? (window).Zmodem : {Browser: null}; // Nullify browser for tests\n\n (terminalConstructor.prototype).zmodemAttach = zmodemAttach;\n (terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;\n}\n",null],"names":[],"mappings":"ACAA;;;ADoCA;AAMA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AALA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/xterm.css b/apps/static/js/plugins/xterm/xterm.css index 89daf9e3e..8e129f50f 100644 --- a/apps/static/js/plugins/xterm/xterm.css +++ b/apps/static/js/plugins/xterm/xterm.css @@ -1,8 +1,8 @@ /** - * xterm.js: xterm, in the browser - * Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License) + * Copyright (c) 2014 The xterm.js authors. All rights reserved. * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) * https://github.com/chjj/term.js + * @license MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,13 +31,11 @@ * other features. */ -/* - * Default style for xterm.js +/** + * Default styles for xterm.js */ -.terminal { - background-color: #000; - color: #fff; +.xterm { font-family: courier-new, courier, monospace; font-feature-settings: "liga" 0; position: relative; @@ -46,17 +44,22 @@ -webkit-user-select: none; } -.terminal.focus, -.terminal:focus { +.xterm.focus, +.xterm:focus { outline: none; } -.terminal .xterm-helpers { +.xterm .xterm-helpers { position: absolute; top: 0; + /** + * The z-index of the helpers must be higher than the canvases in order for + * IMEs to appear on top. + */ + z-index: 10; } -.terminal .xterm-helper-textarea { +.xterm .xterm-helper-textarea { /* * HACK: to fix IE's blinking cursor * Move textarea out of the screen to the far left, so that the cursor is not visible. @@ -74,57 +77,8 @@ resize: none; } -.terminal a { - color: inherit; - text-decoration: none; -} - -.terminal a:hover { - cursor: pointer; - text-decoration: underline; -} - -.terminal a.xterm-invalid-link:hover { - cursor: text; - text-decoration: none; -} - -.terminal .terminal-cursor { - position: relative; -} - -.terminal:not(.focus) .terminal-cursor { - outline: 1px solid #fff; - outline-offset: -1px; -} - -.terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor { - background-color: #fff; - color: #000; -} - -.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before, -.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before { - content: ''; - position: absolute; - background-color: #fff; -} - -.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before { - top: 0; - left: 0; - bottom: 0; - width: 1px; -} - -.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before { - bottom: 0; - left: 0; - right: 0; - height: 1px; -} - -.terminal .composition-view { +.xterm .composition-view { + /* TODO: Composition position got messed up somewhere */ background: #000; color: #FFF; display: none; @@ -133,2129 +87,78 @@ z-index: 1; } -.terminal .composition-view.active { +.xterm .composition-view.active { display: block; } -.terminal .xterm-viewport { +.xterm .xterm-viewport { /* On OS X this is required in order for the scroll bar to appear fully opaque */ background-color: #000; overflow-y: scroll; + cursor: default; + position: absolute; + right: 0; + left: 0; + top: 0; + bottom: 0; } -.terminal .xterm-wide-char, -.terminal .xterm-normal-char { - display: inline-block; +.xterm .xterm-screen { + position: relative; } -.terminal .xterm-rows { +.xterm .xterm-screen canvas { position: absolute; left: 0; top: 0; } -.terminal .xterm-rows > div { - /* Lines containing spans and text nodes ocassionally wrap despite being the same width (#327) */ - white-space: nowrap; -} - -.terminal .xterm-scroll-area { +.xterm .xterm-scroll-area { visibility: hidden; } -.terminal .xterm-char-measure-element { +.xterm-char-measure-element { display: inline-block; visibility: hidden; position: absolute; + top: 0; left: -9999em; + line-height: normal; } -.terminal.enable-mouse-events { +.xterm { + cursor: text; +} + +.xterm.enable-mouse-events { /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ cursor: default; } -.terminal .xterm-selection { +.xterm.xterm-cursor-pointer { + cursor: pointer; +} + +.xterm.xterm-cursor-crosshair { + /* Column selection mode */ + cursor: crosshair; +} + +.xterm .xterm-accessibility, +.xterm .xterm-message { position: absolute; - top: 0; left: 0; - z-index: 1; - opacity: 0.3; - pointer-events: none; + top: 0; + bottom: 0; + right: 0; + z-index: 100; + color: transparent; } -.terminal .xterm-selection div { +.xterm .live-region { position: absolute; - background-color: #fff; -} - -/* - * Determine default colors for xterm.js - */ -.terminal .xterm-bold { - font-weight: bold; -} - -.terminal .xterm-underline { - text-decoration: underline; -} - -.terminal .xterm-blink { - text-decoration: blink; -} - -.terminal .xterm-blink.xterm-underline { - text-decoration: blink underline; -} - -.terminal .xterm-hidden { - visibility: hidden; -} - -.terminal .xterm-color-0 { - color: #2e3436; -} - -.terminal .xterm-bg-color-0 { - background-color: #2e3436; -} - -.terminal .xterm-color-1 { - color: #cc0000; -} - -.terminal .xterm-bg-color-1 { - background-color: #cc0000; -} - -.terminal .xterm-color-2 { - color: #4e9a06; -} - -.terminal .xterm-bg-color-2 { - background-color: #4e9a06; -} - -.terminal .xterm-color-3 { - color: #c4a000; -} - -.terminal .xterm-bg-color-3 { - background-color: #c4a000; -} - -.terminal .xterm-color-4 { - color: #3465a4; -} - -.terminal .xterm-bg-color-4 { - background-color: #3465a4; -} - -.terminal .xterm-color-5 { - color: #75507b; -} - -.terminal .xterm-bg-color-5 { - background-color: #75507b; -} - -.terminal .xterm-color-6 { - color: #06989a; -} - -.terminal .xterm-bg-color-6 { - background-color: #06989a; -} - -.terminal .xterm-color-7 { - color: #d3d7cf; -} - -.terminal .xterm-bg-color-7 { - background-color: #d3d7cf; -} - -.terminal .xterm-color-8 { - color: #555753; -} - -.terminal .xterm-bg-color-8 { - background-color: #555753; -} - -.terminal .xterm-color-9 { - color: #ef2929; -} - -.terminal .xterm-bg-color-9 { - background-color: #ef2929; -} - -.terminal .xterm-color-10 { - color: #8ae234; -} - -.terminal .xterm-bg-color-10 { - background-color: #8ae234; -} - -.terminal .xterm-color-11 { - color: #fce94f; -} - -.terminal .xterm-bg-color-11 { - background-color: #fce94f; -} - -.terminal .xterm-color-12 { - color: #729fcf; -} - -.terminal .xterm-bg-color-12 { - background-color: #729fcf; -} - -.terminal .xterm-color-13 { - color: #ad7fa8; -} - -.terminal .xterm-bg-color-13 { - background-color: #ad7fa8; -} - -.terminal .xterm-color-14 { - color: #34e2e2; -} - -.terminal .xterm-bg-color-14 { - background-color: #34e2e2; -} - -.terminal .xterm-color-15 { - color: #eeeeec; -} - -.terminal .xterm-bg-color-15 { - background-color: #eeeeec; -} - -.terminal .xterm-color-16 { - color: #000000; -} - -.terminal .xterm-bg-color-16 { - background-color: #000000; -} - -.terminal .xterm-color-17 { - color: #00005f; -} - -.terminal .xterm-bg-color-17 { - background-color: #00005f; -} - -.terminal .xterm-color-18 { - color: #000087; -} - -.terminal .xterm-bg-color-18 { - background-color: #000087; -} - -.terminal .xterm-color-19 { - color: #0000af; -} - -.terminal .xterm-bg-color-19 { - background-color: #0000af; -} - -.terminal .xterm-color-20 { - color: #0000d7; -} - -.terminal .xterm-bg-color-20 { - background-color: #0000d7; -} - -.terminal .xterm-color-21 { - color: #0000ff; -} - -.terminal .xterm-bg-color-21 { - background-color: #0000ff; -} - -.terminal .xterm-color-22 { - color: #005f00; -} - -.terminal .xterm-bg-color-22 { - background-color: #005f00; -} - -.terminal .xterm-color-23 { - color: #005f5f; -} - -.terminal .xterm-bg-color-23 { - background-color: #005f5f; -} - -.terminal .xterm-color-24 { - color: #005f87; -} - -.terminal .xterm-bg-color-24 { - background-color: #005f87; -} - -.terminal .xterm-color-25 { - color: #005faf; -} - -.terminal .xterm-bg-color-25 { - background-color: #005faf; -} - -.terminal .xterm-color-26 { - color: #005fd7; -} - -.terminal .xterm-bg-color-26 { - background-color: #005fd7; -} - -.terminal .xterm-color-27 { - color: #005fff; -} - -.terminal .xterm-bg-color-27 { - background-color: #005fff; -} - -.terminal .xterm-color-28 { - color: #008700; -} - -.terminal .xterm-bg-color-28 { - background-color: #008700; -} - -.terminal .xterm-color-29 { - color: #00875f; -} - -.terminal .xterm-bg-color-29 { - background-color: #00875f; -} - -.terminal .xterm-color-30 { - color: #008787; -} - -.terminal .xterm-bg-color-30 { - background-color: #008787; -} - -.terminal .xterm-color-31 { - color: #0087af; -} - -.terminal .xterm-bg-color-31 { - background-color: #0087af; -} - -.terminal .xterm-color-32 { - color: #0087d7; -} - -.terminal .xterm-bg-color-32 { - background-color: #0087d7; -} - -.terminal .xterm-color-33 { - color: #0087ff; -} - -.terminal .xterm-bg-color-33 { - background-color: #0087ff; -} - -.terminal .xterm-color-34 { - color: #00af00; -} - -.terminal .xterm-bg-color-34 { - background-color: #00af00; -} - -.terminal .xterm-color-35 { - color: #00af5f; -} - -.terminal .xterm-bg-color-35 { - background-color: #00af5f; -} - -.terminal .xterm-color-36 { - color: #00af87; -} - -.terminal .xterm-bg-color-36 { - background-color: #00af87; -} - -.terminal .xterm-color-37 { - color: #00afaf; -} - -.terminal .xterm-bg-color-37 { - background-color: #00afaf; -} - -.terminal .xterm-color-38 { - color: #00afd7; -} - -.terminal .xterm-bg-color-38 { - background-color: #00afd7; -} - -.terminal .xterm-color-39 { - color: #00afff; -} - -.terminal .xterm-bg-color-39 { - background-color: #00afff; -} - -.terminal .xterm-color-40 { - color: #00d700; -} - -.terminal .xterm-bg-color-40 { - background-color: #00d700; -} - -.terminal .xterm-color-41 { - color: #00d75f; -} - -.terminal .xterm-bg-color-41 { - background-color: #00d75f; -} - -.terminal .xterm-color-42 { - color: #00d787; -} - -.terminal .xterm-bg-color-42 { - background-color: #00d787; -} - -.terminal .xterm-color-43 { - color: #00d7af; -} - -.terminal .xterm-bg-color-43 { - background-color: #00d7af; -} - -.terminal .xterm-color-44 { - color: #00d7d7; -} - -.terminal .xterm-bg-color-44 { - background-color: #00d7d7; -} - -.terminal .xterm-color-45 { - color: #00d7ff; -} - -.terminal .xterm-bg-color-45 { - background-color: #00d7ff; -} - -.terminal .xterm-color-46 { - color: #00ff00; -} - -.terminal .xterm-bg-color-46 { - background-color: #00ff00; -} - -.terminal .xterm-color-47 { - color: #00ff5f; -} - -.terminal .xterm-bg-color-47 { - background-color: #00ff5f; -} - -.terminal .xterm-color-48 { - color: #00ff87; -} - -.terminal .xterm-bg-color-48 { - background-color: #00ff87; -} - -.terminal .xterm-color-49 { - color: #00ffaf; -} - -.terminal .xterm-bg-color-49 { - background-color: #00ffaf; -} - -.terminal .xterm-color-50 { - color: #00ffd7; -} - -.terminal .xterm-bg-color-50 { - background-color: #00ffd7; -} - -.terminal .xterm-color-51 { - color: #00ffff; -} - -.terminal .xterm-bg-color-51 { - background-color: #00ffff; -} - -.terminal .xterm-color-52 { - color: #5f0000; -} - -.terminal .xterm-bg-color-52 { - background-color: #5f0000; -} - -.terminal .xterm-color-53 { - color: #5f005f; -} - -.terminal .xterm-bg-color-53 { - background-color: #5f005f; -} - -.terminal .xterm-color-54 { - color: #5f0087; -} - -.terminal .xterm-bg-color-54 { - background-color: #5f0087; -} - -.terminal .xterm-color-55 { - color: #5f00af; -} - -.terminal .xterm-bg-color-55 { - background-color: #5f00af; -} - -.terminal .xterm-color-56 { - color: #5f00d7; -} - -.terminal .xterm-bg-color-56 { - background-color: #5f00d7; -} - -.terminal .xterm-color-57 { - color: #5f00ff; -} - -.terminal .xterm-bg-color-57 { - background-color: #5f00ff; -} - -.terminal .xterm-color-58 { - color: #5f5f00; -} - -.terminal .xterm-bg-color-58 { - background-color: #5f5f00; -} - -.terminal .xterm-color-59 { - color: #5f5f5f; -} - -.terminal .xterm-bg-color-59 { - background-color: #5f5f5f; -} - -.terminal .xterm-color-60 { - color: #5f5f87; -} - -.terminal .xterm-bg-color-60 { - background-color: #5f5f87; -} - -.terminal .xterm-color-61 { - color: #5f5faf; -} - -.terminal .xterm-bg-color-61 { - background-color: #5f5faf; -} - -.terminal .xterm-color-62 { - color: #5f5fd7; -} - -.terminal .xterm-bg-color-62 { - background-color: #5f5fd7; -} - -.terminal .xterm-color-63 { - color: #5f5fff; -} - -.terminal .xterm-bg-color-63 { - background-color: #5f5fff; -} - -.terminal .xterm-color-64 { - color: #5f8700; -} - -.terminal .xterm-bg-color-64 { - background-color: #5f8700; -} - -.terminal .xterm-color-65 { - color: #5f875f; -} - -.terminal .xterm-bg-color-65 { - background-color: #5f875f; -} - -.terminal .xterm-color-66 { - color: #5f8787; -} - -.terminal .xterm-bg-color-66 { - background-color: #5f8787; -} - -.terminal .xterm-color-67 { - color: #5f87af; -} - -.terminal .xterm-bg-color-67 { - background-color: #5f87af; -} - -.terminal .xterm-color-68 { - color: #5f87d7; -} - -.terminal .xterm-bg-color-68 { - background-color: #5f87d7; -} - -.terminal .xterm-color-69 { - color: #5f87ff; -} - -.terminal .xterm-bg-color-69 { - background-color: #5f87ff; -} - -.terminal .xterm-color-70 { - color: #5faf00; -} - -.terminal .xterm-bg-color-70 { - background-color: #5faf00; -} - -.terminal .xterm-color-71 { - color: #5faf5f; -} - -.terminal .xterm-bg-color-71 { - background-color: #5faf5f; -} - -.terminal .xterm-color-72 { - color: #5faf87; -} - -.terminal .xterm-bg-color-72 { - background-color: #5faf87; -} - -.terminal .xterm-color-73 { - color: #5fafaf; -} - -.terminal .xterm-bg-color-73 { - background-color: #5fafaf; -} - -.terminal .xterm-color-74 { - color: #5fafd7; -} - -.terminal .xterm-bg-color-74 { - background-color: #5fafd7; -} - -.terminal .xterm-color-75 { - color: #5fafff; -} - -.terminal .xterm-bg-color-75 { - background-color: #5fafff; -} - -.terminal .xterm-color-76 { - color: #5fd700; -} - -.terminal .xterm-bg-color-76 { - background-color: #5fd700; -} - -.terminal .xterm-color-77 { - color: #5fd75f; -} - -.terminal .xterm-bg-color-77 { - background-color: #5fd75f; -} - -.terminal .xterm-color-78 { - color: #5fd787; -} - -.terminal .xterm-bg-color-78 { - background-color: #5fd787; -} - -.terminal .xterm-color-79 { - color: #5fd7af; -} - -.terminal .xterm-bg-color-79 { - background-color: #5fd7af; -} - -.terminal .xterm-color-80 { - color: #5fd7d7; -} - -.terminal .xterm-bg-color-80 { - background-color: #5fd7d7; -} - -.terminal .xterm-color-81 { - color: #5fd7ff; -} - -.terminal .xterm-bg-color-81 { - background-color: #5fd7ff; -} - -.terminal .xterm-color-82 { - color: #5fff00; -} - -.terminal .xterm-bg-color-82 { - background-color: #5fff00; -} - -.terminal .xterm-color-83 { - color: #5fff5f; -} - -.terminal .xterm-bg-color-83 { - background-color: #5fff5f; -} - -.terminal .xterm-color-84 { - color: #5fff87; -} - -.terminal .xterm-bg-color-84 { - background-color: #5fff87; -} - -.terminal .xterm-color-85 { - color: #5fffaf; -} - -.terminal .xterm-bg-color-85 { - background-color: #5fffaf; -} - -.terminal .xterm-color-86 { - color: #5fffd7; -} - -.terminal .xterm-bg-color-86 { - background-color: #5fffd7; -} - -.terminal .xterm-color-87 { - color: #5fffff; -} - -.terminal .xterm-bg-color-87 { - background-color: #5fffff; -} - -.terminal .xterm-color-88 { - color: #870000; -} - -.terminal .xterm-bg-color-88 { - background-color: #870000; -} - -.terminal .xterm-color-89 { - color: #87005f; -} - -.terminal .xterm-bg-color-89 { - background-color: #87005f; -} - -.terminal .xterm-color-90 { - color: #870087; -} - -.terminal .xterm-bg-color-90 { - background-color: #870087; -} - -.terminal .xterm-color-91 { - color: #8700af; -} - -.terminal .xterm-bg-color-91 { - background-color: #8700af; -} - -.terminal .xterm-color-92 { - color: #8700d7; -} - -.terminal .xterm-bg-color-92 { - background-color: #8700d7; -} - -.terminal .xterm-color-93 { - color: #8700ff; -} - -.terminal .xterm-bg-color-93 { - background-color: #8700ff; -} - -.terminal .xterm-color-94 { - color: #875f00; -} - -.terminal .xterm-bg-color-94 { - background-color: #875f00; -} - -.terminal .xterm-color-95 { - color: #875f5f; -} - -.terminal .xterm-bg-color-95 { - background-color: #875f5f; -} - -.terminal .xterm-color-96 { - color: #875f87; -} - -.terminal .xterm-bg-color-96 { - background-color: #875f87; -} - -.terminal .xterm-color-97 { - color: #875faf; -} - -.terminal .xterm-bg-color-97 { - background-color: #875faf; -} - -.terminal .xterm-color-98 { - color: #875fd7; -} - -.terminal .xterm-bg-color-98 { - background-color: #875fd7; -} - -.terminal .xterm-color-99 { - color: #875fff; -} - -.terminal .xterm-bg-color-99 { - background-color: #875fff; -} - -.terminal .xterm-color-100 { - color: #878700; -} - -.terminal .xterm-bg-color-100 { - background-color: #878700; -} - -.terminal .xterm-color-101 { - color: #87875f; -} - -.terminal .xterm-bg-color-101 { - background-color: #87875f; -} - -.terminal .xterm-color-102 { - color: #878787; -} - -.terminal .xterm-bg-color-102 { - background-color: #878787; -} - -.terminal .xterm-color-103 { - color: #8787af; -} - -.terminal .xterm-bg-color-103 { - background-color: #8787af; -} - -.terminal .xterm-color-104 { - color: #8787d7; -} - -.terminal .xterm-bg-color-104 { - background-color: #8787d7; -} - -.terminal .xterm-color-105 { - color: #8787ff; -} - -.terminal .xterm-bg-color-105 { - background-color: #8787ff; -} - -.terminal .xterm-color-106 { - color: #87af00; -} - -.terminal .xterm-bg-color-106 { - background-color: #87af00; -} - -.terminal .xterm-color-107 { - color: #87af5f; -} - -.terminal .xterm-bg-color-107 { - background-color: #87af5f; -} - -.terminal .xterm-color-108 { - color: #87af87; -} - -.terminal .xterm-bg-color-108 { - background-color: #87af87; -} - -.terminal .xterm-color-109 { - color: #87afaf; -} - -.terminal .xterm-bg-color-109 { - background-color: #87afaf; -} - -.terminal .xterm-color-110 { - color: #87afd7; -} - -.terminal .xterm-bg-color-110 { - background-color: #87afd7; -} - -.terminal .xterm-color-111 { - color: #87afff; -} - -.terminal .xterm-bg-color-111 { - background-color: #87afff; -} - -.terminal .xterm-color-112 { - color: #87d700; -} - -.terminal .xterm-bg-color-112 { - background-color: #87d700; -} - -.terminal .xterm-color-113 { - color: #87d75f; -} - -.terminal .xterm-bg-color-113 { - background-color: #87d75f; -} - -.terminal .xterm-color-114 { - color: #87d787; -} - -.terminal .xterm-bg-color-114 { - background-color: #87d787; -} - -.terminal .xterm-color-115 { - color: #87d7af; -} - -.terminal .xterm-bg-color-115 { - background-color: #87d7af; -} - -.terminal .xterm-color-116 { - color: #87d7d7; -} - -.terminal .xterm-bg-color-116 { - background-color: #87d7d7; -} - -.terminal .xterm-color-117 { - color: #87d7ff; -} - -.terminal .xterm-bg-color-117 { - background-color: #87d7ff; -} - -.terminal .xterm-color-118 { - color: #87ff00; -} - -.terminal .xterm-bg-color-118 { - background-color: #87ff00; -} - -.terminal .xterm-color-119 { - color: #87ff5f; -} - -.terminal .xterm-bg-color-119 { - background-color: #87ff5f; -} - -.terminal .xterm-color-120 { - color: #87ff87; -} - -.terminal .xterm-bg-color-120 { - background-color: #87ff87; -} - -.terminal .xterm-color-121 { - color: #87ffaf; -} - -.terminal .xterm-bg-color-121 { - background-color: #87ffaf; -} - -.terminal .xterm-color-122 { - color: #87ffd7; -} - -.terminal .xterm-bg-color-122 { - background-color: #87ffd7; -} - -.terminal .xterm-color-123 { - color: #87ffff; -} - -.terminal .xterm-bg-color-123 { - background-color: #87ffff; -} - -.terminal .xterm-color-124 { - color: #af0000; -} - -.terminal .xterm-bg-color-124 { - background-color: #af0000; -} - -.terminal .xterm-color-125 { - color: #af005f; -} - -.terminal .xterm-bg-color-125 { - background-color: #af005f; -} - -.terminal .xterm-color-126 { - color: #af0087; -} - -.terminal .xterm-bg-color-126 { - background-color: #af0087; -} - -.terminal .xterm-color-127 { - color: #af00af; -} - -.terminal .xterm-bg-color-127 { - background-color: #af00af; -} - -.terminal .xterm-color-128 { - color: #af00d7; -} - -.terminal .xterm-bg-color-128 { - background-color: #af00d7; -} - -.terminal .xterm-color-129 { - color: #af00ff; -} - -.terminal .xterm-bg-color-129 { - background-color: #af00ff; -} - -.terminal .xterm-color-130 { - color: #af5f00; -} - -.terminal .xterm-bg-color-130 { - background-color: #af5f00; -} - -.terminal .xterm-color-131 { - color: #af5f5f; -} - -.terminal .xterm-bg-color-131 { - background-color: #af5f5f; -} - -.terminal .xterm-color-132 { - color: #af5f87; -} - -.terminal .xterm-bg-color-132 { - background-color: #af5f87; -} - -.terminal .xterm-color-133 { - color: #af5faf; -} - -.terminal .xterm-bg-color-133 { - background-color: #af5faf; -} - -.terminal .xterm-color-134 { - color: #af5fd7; -} - -.terminal .xterm-bg-color-134 { - background-color: #af5fd7; -} - -.terminal .xterm-color-135 { - color: #af5fff; -} - -.terminal .xterm-bg-color-135 { - background-color: #af5fff; -} - -.terminal .xterm-color-136 { - color: #af8700; -} - -.terminal .xterm-bg-color-136 { - background-color: #af8700; -} - -.terminal .xterm-color-137 { - color: #af875f; -} - -.terminal .xterm-bg-color-137 { - background-color: #af875f; -} - -.terminal .xterm-color-138 { - color: #af8787; -} - -.terminal .xterm-bg-color-138 { - background-color: #af8787; -} - -.terminal .xterm-color-139 { - color: #af87af; -} - -.terminal .xterm-bg-color-139 { - background-color: #af87af; -} - -.terminal .xterm-color-140 { - color: #af87d7; -} - -.terminal .xterm-bg-color-140 { - background-color: #af87d7; -} - -.terminal .xterm-color-141 { - color: #af87ff; -} - -.terminal .xterm-bg-color-141 { - background-color: #af87ff; -} - -.terminal .xterm-color-142 { - color: #afaf00; -} - -.terminal .xterm-bg-color-142 { - background-color: #afaf00; -} - -.terminal .xterm-color-143 { - color: #afaf5f; -} - -.terminal .xterm-bg-color-143 { - background-color: #afaf5f; -} - -.terminal .xterm-color-144 { - color: #afaf87; -} - -.terminal .xterm-bg-color-144 { - background-color: #afaf87; -} - -.terminal .xterm-color-145 { - color: #afafaf; -} - -.terminal .xterm-bg-color-145 { - background-color: #afafaf; -} - -.terminal .xterm-color-146 { - color: #afafd7; -} - -.terminal .xterm-bg-color-146 { - background-color: #afafd7; -} - -.terminal .xterm-color-147 { - color: #afafff; -} - -.terminal .xterm-bg-color-147 { - background-color: #afafff; -} - -.terminal .xterm-color-148 { - color: #afd700; -} - -.terminal .xterm-bg-color-148 { - background-color: #afd700; -} - -.terminal .xterm-color-149 { - color: #afd75f; -} - -.terminal .xterm-bg-color-149 { - background-color: #afd75f; -} - -.terminal .xterm-color-150 { - color: #afd787; -} - -.terminal .xterm-bg-color-150 { - background-color: #afd787; -} - -.terminal .xterm-color-151 { - color: #afd7af; -} - -.terminal .xterm-bg-color-151 { - background-color: #afd7af; -} - -.terminal .xterm-color-152 { - color: #afd7d7; -} - -.terminal .xterm-bg-color-152 { - background-color: #afd7d7; -} - -.terminal .xterm-color-153 { - color: #afd7ff; -} - -.terminal .xterm-bg-color-153 { - background-color: #afd7ff; -} - -.terminal .xterm-color-154 { - color: #afff00; -} - -.terminal .xterm-bg-color-154 { - background-color: #afff00; -} - -.terminal .xterm-color-155 { - color: #afff5f; -} - -.terminal .xterm-bg-color-155 { - background-color: #afff5f; -} - -.terminal .xterm-color-156 { - color: #afff87; -} - -.terminal .xterm-bg-color-156 { - background-color: #afff87; -} - -.terminal .xterm-color-157 { - color: #afffaf; -} - -.terminal .xterm-bg-color-157 { - background-color: #afffaf; -} - -.terminal .xterm-color-158 { - color: #afffd7; -} - -.terminal .xterm-bg-color-158 { - background-color: #afffd7; -} - -.terminal .xterm-color-159 { - color: #afffff; -} - -.terminal .xterm-bg-color-159 { - background-color: #afffff; -} - -.terminal .xterm-color-160 { - color: #d70000; -} - -.terminal .xterm-bg-color-160 { - background-color: #d70000; -} - -.terminal .xterm-color-161 { - color: #d7005f; -} - -.terminal .xterm-bg-color-161 { - background-color: #d7005f; -} - -.terminal .xterm-color-162 { - color: #d70087; -} - -.terminal .xterm-bg-color-162 { - background-color: #d70087; -} - -.terminal .xterm-color-163 { - color: #d700af; -} - -.terminal .xterm-bg-color-163 { - background-color: #d700af; -} - -.terminal .xterm-color-164 { - color: #d700d7; -} - -.terminal .xterm-bg-color-164 { - background-color: #d700d7; -} - -.terminal .xterm-color-165 { - color: #d700ff; -} - -.terminal .xterm-bg-color-165 { - background-color: #d700ff; -} - -.terminal .xterm-color-166 { - color: #d75f00; -} - -.terminal .xterm-bg-color-166 { - background-color: #d75f00; -} - -.terminal .xterm-color-167 { - color: #d75f5f; -} - -.terminal .xterm-bg-color-167 { - background-color: #d75f5f; -} - -.terminal .xterm-color-168 { - color: #d75f87; -} - -.terminal .xterm-bg-color-168 { - background-color: #d75f87; -} - -.terminal .xterm-color-169 { - color: #d75faf; -} - -.terminal .xterm-bg-color-169 { - background-color: #d75faf; -} - -.terminal .xterm-color-170 { - color: #d75fd7; -} - -.terminal .xterm-bg-color-170 { - background-color: #d75fd7; -} - -.terminal .xterm-color-171 { - color: #d75fff; -} - -.terminal .xterm-bg-color-171 { - background-color: #d75fff; -} - -.terminal .xterm-color-172 { - color: #d78700; -} - -.terminal .xterm-bg-color-172 { - background-color: #d78700; -} - -.terminal .xterm-color-173 { - color: #d7875f; -} - -.terminal .xterm-bg-color-173 { - background-color: #d7875f; -} - -.terminal .xterm-color-174 { - color: #d78787; -} - -.terminal .xterm-bg-color-174 { - background-color: #d78787; -} - -.terminal .xterm-color-175 { - color: #d787af; -} - -.terminal .xterm-bg-color-175 { - background-color: #d787af; -} - -.terminal .xterm-color-176 { - color: #d787d7; -} - -.terminal .xterm-bg-color-176 { - background-color: #d787d7; -} - -.terminal .xterm-color-177 { - color: #d787ff; -} - -.terminal .xterm-bg-color-177 { - background-color: #d787ff; -} - -.terminal .xterm-color-178 { - color: #d7af00; -} - -.terminal .xterm-bg-color-178 { - background-color: #d7af00; -} - -.terminal .xterm-color-179 { - color: #d7af5f; -} - -.terminal .xterm-bg-color-179 { - background-color: #d7af5f; -} - -.terminal .xterm-color-180 { - color: #d7af87; -} - -.terminal .xterm-bg-color-180 { - background-color: #d7af87; -} - -.terminal .xterm-color-181 { - color: #d7afaf; -} - -.terminal .xterm-bg-color-181 { - background-color: #d7afaf; -} - -.terminal .xterm-color-182 { - color: #d7afd7; -} - -.terminal .xterm-bg-color-182 { - background-color: #d7afd7; -} - -.terminal .xterm-color-183 { - color: #d7afff; -} - -.terminal .xterm-bg-color-183 { - background-color: #d7afff; -} - -.terminal .xterm-color-184 { - color: #d7d700; -} - -.terminal .xterm-bg-color-184 { - background-color: #d7d700; -} - -.terminal .xterm-color-185 { - color: #d7d75f; -} - -.terminal .xterm-bg-color-185 { - background-color: #d7d75f; -} - -.terminal .xterm-color-186 { - color: #d7d787; -} - -.terminal .xterm-bg-color-186 { - background-color: #d7d787; -} - -.terminal .xterm-color-187 { - color: #d7d7af; -} - -.terminal .xterm-bg-color-187 { - background-color: #d7d7af; -} - -.terminal .xterm-color-188 { - color: #d7d7d7; -} - -.terminal .xterm-bg-color-188 { - background-color: #d7d7d7; -} - -.terminal .xterm-color-189 { - color: #d7d7ff; -} - -.terminal .xterm-bg-color-189 { - background-color: #d7d7ff; -} - -.terminal .xterm-color-190 { - color: #d7ff00; -} - -.terminal .xterm-bg-color-190 { - background-color: #d7ff00; -} - -.terminal .xterm-color-191 { - color: #d7ff5f; -} - -.terminal .xterm-bg-color-191 { - background-color: #d7ff5f; -} - -.terminal .xterm-color-192 { - color: #d7ff87; -} - -.terminal .xterm-bg-color-192 { - background-color: #d7ff87; -} - -.terminal .xterm-color-193 { - color: #d7ffaf; -} - -.terminal .xterm-bg-color-193 { - background-color: #d7ffaf; -} - -.terminal .xterm-color-194 { - color: #d7ffd7; -} - -.terminal .xterm-bg-color-194 { - background-color: #d7ffd7; -} - -.terminal .xterm-color-195 { - color: #d7ffff; -} - -.terminal .xterm-bg-color-195 { - background-color: #d7ffff; -} - -.terminal .xterm-color-196 { - color: #ff0000; -} - -.terminal .xterm-bg-color-196 { - background-color: #ff0000; -} - -.terminal .xterm-color-197 { - color: #ff005f; -} - -.terminal .xterm-bg-color-197 { - background-color: #ff005f; -} - -.terminal .xterm-color-198 { - color: #ff0087; -} - -.terminal .xterm-bg-color-198 { - background-color: #ff0087; -} - -.terminal .xterm-color-199 { - color: #ff00af; -} - -.terminal .xterm-bg-color-199 { - background-color: #ff00af; -} - -.terminal .xterm-color-200 { - color: #ff00d7; -} - -.terminal .xterm-bg-color-200 { - background-color: #ff00d7; -} - -.terminal .xterm-color-201 { - color: #ff00ff; -} - -.terminal .xterm-bg-color-201 { - background-color: #ff00ff; -} - -.terminal .xterm-color-202 { - color: #ff5f00; -} - -.terminal .xterm-bg-color-202 { - background-color: #ff5f00; -} - -.terminal .xterm-color-203 { - color: #ff5f5f; -} - -.terminal .xterm-bg-color-203 { - background-color: #ff5f5f; -} - -.terminal .xterm-color-204 { - color: #ff5f87; -} - -.terminal .xterm-bg-color-204 { - background-color: #ff5f87; -} - -.terminal .xterm-color-205 { - color: #ff5faf; -} - -.terminal .xterm-bg-color-205 { - background-color: #ff5faf; -} - -.terminal .xterm-color-206 { - color: #ff5fd7; -} - -.terminal .xterm-bg-color-206 { - background-color: #ff5fd7; -} - -.terminal .xterm-color-207 { - color: #ff5fff; -} - -.terminal .xterm-bg-color-207 { - background-color: #ff5fff; -} - -.terminal .xterm-color-208 { - color: #ff8700; -} - -.terminal .xterm-bg-color-208 { - background-color: #ff8700; -} - -.terminal .xterm-color-209 { - color: #ff875f; -} - -.terminal .xterm-bg-color-209 { - background-color: #ff875f; -} - -.terminal .xterm-color-210 { - color: #ff8787; -} - -.terminal .xterm-bg-color-210 { - background-color: #ff8787; -} - -.terminal .xterm-color-211 { - color: #ff87af; -} - -.terminal .xterm-bg-color-211 { - background-color: #ff87af; -} - -.terminal .xterm-color-212 { - color: #ff87d7; -} - -.terminal .xterm-bg-color-212 { - background-color: #ff87d7; -} - -.terminal .xterm-color-213 { - color: #ff87ff; -} - -.terminal .xterm-bg-color-213 { - background-color: #ff87ff; -} - -.terminal .xterm-color-214 { - color: #ffaf00; -} - -.terminal .xterm-bg-color-214 { - background-color: #ffaf00; -} - -.terminal .xterm-color-215 { - color: #ffaf5f; -} - -.terminal .xterm-bg-color-215 { - background-color: #ffaf5f; -} - -.terminal .xterm-color-216 { - color: #ffaf87; -} - -.terminal .xterm-bg-color-216 { - background-color: #ffaf87; -} - -.terminal .xterm-color-217 { - color: #ffafaf; -} - -.terminal .xterm-bg-color-217 { - background-color: #ffafaf; -} - -.terminal .xterm-color-218 { - color: #ffafd7; -} - -.terminal .xterm-bg-color-218 { - background-color: #ffafd7; -} - -.terminal .xterm-color-219 { - color: #ffafff; -} - -.terminal .xterm-bg-color-219 { - background-color: #ffafff; -} - -.terminal .xterm-color-220 { - color: #ffd700; -} - -.terminal .xterm-bg-color-220 { - background-color: #ffd700; -} - -.terminal .xterm-color-221 { - color: #ffd75f; -} - -.terminal .xterm-bg-color-221 { - background-color: #ffd75f; -} - -.terminal .xterm-color-222 { - color: #ffd787; -} - -.terminal .xterm-bg-color-222 { - background-color: #ffd787; -} - -.terminal .xterm-color-223 { - color: #ffd7af; -} - -.terminal .xterm-bg-color-223 { - background-color: #ffd7af; -} - -.terminal .xterm-color-224 { - color: #ffd7d7; -} - -.terminal .xterm-bg-color-224 { - background-color: #ffd7d7; -} - -.terminal .xterm-color-225 { - color: #ffd7ff; -} - -.terminal .xterm-bg-color-225 { - background-color: #ffd7ff; -} - -.terminal .xterm-color-226 { - color: #ffff00; -} - -.terminal .xterm-bg-color-226 { - background-color: #ffff00; -} - -.terminal .xterm-color-227 { - color: #ffff5f; -} - -.terminal .xterm-bg-color-227 { - background-color: #ffff5f; -} - -.terminal .xterm-color-228 { - color: #ffff87; -} - -.terminal .xterm-bg-color-228 { - background-color: #ffff87; -} - -.terminal .xterm-color-229 { - color: #ffffaf; -} - -.terminal .xterm-bg-color-229 { - background-color: #ffffaf; -} - -.terminal .xterm-color-230 { - color: #ffffd7; -} - -.terminal .xterm-bg-color-230 { - background-color: #ffffd7; -} - -.terminal .xterm-color-231 { - color: #ffffff; -} - -.terminal .xterm-bg-color-231 { - background-color: #ffffff; -} - -.terminal .xterm-color-232 { - color: #080808; -} - -.terminal .xterm-bg-color-232 { - background-color: #080808; -} - -.terminal .xterm-color-233 { - color: #121212; -} - -.terminal .xterm-bg-color-233 { - background-color: #121212; -} - -.terminal .xterm-color-234 { - color: #1c1c1c; -} - -.terminal .xterm-bg-color-234 { - background-color: #1c1c1c; -} - -.terminal .xterm-color-235 { - color: #262626; -} - -.terminal .xterm-bg-color-235 { - background-color: #262626; -} - -.terminal .xterm-color-236 { - color: #303030; -} - -.terminal .xterm-bg-color-236 { - background-color: #303030; -} - -.terminal .xterm-color-237 { - color: #3a3a3a; -} - -.terminal .xterm-bg-color-237 { - background-color: #3a3a3a; -} - -.terminal .xterm-color-238 { - color: #444444; -} - -.terminal .xterm-bg-color-238 { - background-color: #444444; -} - -.terminal .xterm-color-239 { - color: #4e4e4e; -} - -.terminal .xterm-bg-color-239 { - background-color: #4e4e4e; -} - -.terminal .xterm-color-240 { - color: #585858; -} - -.terminal .xterm-bg-color-240 { - background-color: #585858; -} - -.terminal .xterm-color-241 { - color: #626262; -} - -.terminal .xterm-bg-color-241 { - background-color: #626262; -} - -.terminal .xterm-color-242 { - color: #6c6c6c; -} - -.terminal .xterm-bg-color-242 { - background-color: #6c6c6c; -} - -.terminal .xterm-color-243 { - color: #767676; -} - -.terminal .xterm-bg-color-243 { - background-color: #767676; -} - -.terminal .xterm-color-244 { - color: #808080; -} - -.terminal .xterm-bg-color-244 { - background-color: #808080; -} - -.terminal .xterm-color-245 { - color: #8a8a8a; -} - -.terminal .xterm-bg-color-245 { - background-color: #8a8a8a; -} - -.terminal .xterm-color-246 { - color: #949494; -} - -.terminal .xterm-bg-color-246 { - background-color: #949494; -} - -.terminal .xterm-color-247 { - color: #9e9e9e; -} - -.terminal .xterm-bg-color-247 { - background-color: #9e9e9e; -} - -.terminal .xterm-color-248 { - color: #a8a8a8; -} - -.terminal .xterm-bg-color-248 { - background-color: #a8a8a8; -} - -.terminal .xterm-color-249 { - color: #b2b2b2; -} - -.terminal .xterm-bg-color-249 { - background-color: #b2b2b2; -} - -.terminal .xterm-color-250 { - color: #bcbcbc; -} - -.terminal .xterm-bg-color-250 { - background-color: #bcbcbc; -} - -.terminal .xterm-color-251 { - color: #c6c6c6; -} - -.terminal .xterm-bg-color-251 { - background-color: #c6c6c6; -} - -.terminal .xterm-color-252 { - color: #d0d0d0; -} - -.terminal .xterm-bg-color-252 { - background-color: #d0d0d0; -} - -.terminal .xterm-color-253 { - color: #dadada; -} - -.terminal .xterm-bg-color-253 { - background-color: #dadada; -} - -.terminal .xterm-color-254 { - color: #e4e4e4; -} - -.terminal .xterm-bg-color-254 { - background-color: #e4e4e4; -} - -.terminal .xterm-color-255 { - color: #eeeeee; -} - -.terminal .xterm-bg-color-255 { - background-color: #eeeeee; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; } diff --git a/apps/static/js/plugins/xterm/xterm.js b/apps/static/js/plugins/xterm/xterm.js index 4b6f223a8..db90c5a3c 100644 --- a/apps/static/js/plugins/xterm/xterm.js +++ b/apps/static/js/plugins/xterm/xterm.js @@ -1,21 +1,267 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Terminal = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o rows) { + this._rowContainer.removeChild(this._rowElements.pop()); + } + this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); + this._refreshRowsDimensions(); + }; + AccessibilityManager.prototype._createAccessibilityTreeNode = function () { + var element = document.createElement('div'); + element.setAttribute('role', 'listitem'); + element.tabIndex = -1; + this._refreshRowDimensions(element); + return element; + }; + AccessibilityManager.prototype._onTab = function (spaceCount) { + for (var i = 0; i < spaceCount; i++) { + this._onChar(' '); + } + }; + AccessibilityManager.prototype._onChar = function (char) { + var _this = this; + if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) { + if (this._charsToConsume.length > 0) { + var shiftedChar = this._charsToConsume.shift(); + if (shiftedChar !== char) { + this._announceCharacter(char); + } + } + else { + this._announceCharacter(char); + } + if (char === '\n') { + this._liveRegionLineCount++; + if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) { + this._liveRegion.textContent += Strings.tooMuchOutput; + } + } + if (Browser_1.isMac) { + if (this._liveRegion.textContent && this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { + setTimeout(function () { + _this._accessibilityTreeRoot.appendChild(_this._liveRegion); + }, 0); + } + } + } + }; + AccessibilityManager.prototype._clearLiveRegion = function () { + this._liveRegion.textContent = ''; + this._liveRegionLineCount = 0; + if (Browser_1.isMac) { + if (this._liveRegion.parentNode) { + this._accessibilityTreeRoot.removeChild(this._liveRegion); + } + } + }; + AccessibilityManager.prototype._onKey = function (keyChar) { + this._clearLiveRegion(); + this._charsToConsume.push(keyChar); + }; + AccessibilityManager.prototype._refreshRows = function (start, end) { + this._renderRowsDebouncer.refresh(start, end); + }; + AccessibilityManager.prototype._renderRows = function (start, end) { + var buffer = this._terminal.buffer; + var setSize = buffer.lines.length.toString(); + for (var i = start; i <= end; i++) { + var lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true); + var posInSet = (buffer.ydisp + i + 1).toString(); + var element = this._rowElements[i]; + element.textContent = lineData.length === 0 ? Strings.blankLine : lineData; + element.setAttribute('aria-posinset', posInSet); + element.setAttribute('aria-setsize', setSize); + } + }; + AccessibilityManager.prototype._refreshRowsDimensions = function () { + if (!this._terminal.renderer.dimensions.actualCellHeight) { + return; + } + for (var i = 0; i < this._terminal.rows; i++) { + this._refreshRowDimensions(this._rowElements[i]); + } + }; + AccessibilityManager.prototype._refreshRowDimensions = function (element) { + element.style.height = this._terminal.renderer.dimensions.actualCellHeight + "px"; + }; + AccessibilityManager.prototype._announceCharacter = function (char) { + if (char === ' ') { + this._liveRegion.innerHTML += ' '; + } + else { + this._liveRegion.textContent += char; + } + }; + return AccessibilityManager; +}(Lifecycle_2.Disposable)); +exports.AccessibilityManager = AccessibilityManager; + +},{"./Strings":13,"./common/Lifecycle":17,"./shared/utils/Browser":44,"./ui/Lifecycle":46,"./ui/RenderDebouncer":48}],2:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var CircularList_1 = require("./common/CircularList"); +var EventEmitter_1 = require("./EventEmitter"); +exports.DEFAULT_ATTR = (0 << 18) | (257 << 9) | (256 << 0); +exports.CHAR_DATA_ATTR_INDEX = 0; +exports.CHAR_DATA_CHAR_INDEX = 1; +exports.CHAR_DATA_WIDTH_INDEX = 2; +exports.CHAR_DATA_CODE_INDEX = 3; +exports.MAX_BUFFER_SIZE = 4294967295; var Buffer = (function () { - function Buffer(_terminal) { + function Buffer(_terminal, _hasScrollback) { this._terminal = _terminal; + this._hasScrollback = _hasScrollback; + this.markers = []; this.clear(); } - Object.defineProperty(Buffer.prototype, "lines", { + Object.defineProperty(Buffer.prototype, "hasScrollback", { get: function () { - return this._lines; + return this._hasScrollback && this.lines.maxLength > this._terminal.rows; }, enumerable: true, configurable: true }); + Object.defineProperty(Buffer.prototype, "isCursorInViewport", { + get: function () { + var absoluteY = this.ybase + this.y; + var relativeY = absoluteY - this.ydisp; + return (relativeY >= 0 && relativeY < this._terminal.rows); + }, + enumerable: true, + configurable: true + }); + Buffer.prototype._getCorrectBufferLength = function (rows) { + if (!this._hasScrollback) { + return rows; + } + var correctBufferLength = rows + this._terminal.options.scrollback; + return correctBufferLength > exports.MAX_BUFFER_SIZE ? exports.MAX_BUFFER_SIZE : correctBufferLength; + }; Buffer.prototype.fillViewportRows = function () { - if (this._lines.length === 0) { + if (this.lines.length === 0) { var i = this._terminal.rows; while (i--) { this.lines.push(this._terminal.blankLine()); @@ -27,76 +273,209 @@ var Buffer = (function () { this.ybase = 0; this.y = 0; this.x = 0; - this.scrollBottom = 0; + this.lines = new CircularList_1.CircularList(this._getCorrectBufferLength(this._terminal.rows)); this.scrollTop = 0; - this.tabs = {}; - this._lines = new CircularList_1.CircularList(this._terminal.scrollback); this.scrollBottom = this._terminal.rows - 1; + this.setupTabStops(); }; Buffer.prototype.resize = function (newCols, newRows) { - if (this._lines.length === 0) { - return; + var newMaxLength = this._getCorrectBufferLength(newRows); + if (newMaxLength > this.lines.maxLength) { + this.lines.maxLength = newMaxLength; } - if (this._terminal.cols < newCols) { - var ch = [this._terminal.defAttr, ' ', 1]; - for (var i = 0; i < this._lines.length; i++) { - if (this._lines.get(i) === undefined) { - this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols)); + if (this.lines.length > 0) { + if (this._terminal.cols < newCols) { + var ch = [exports.DEFAULT_ATTR, ' ', 1, 32]; + for (var i = 0; i < this.lines.length; i++) { + while (this.lines.get(i).length < newCols) { + this.lines.get(i).push(ch); + } } - while (this._lines.get(i).length < newCols) { - this._lines.get(i).push(ch); + } + var addToY = 0; + if (this._terminal.rows < newRows) { + for (var y = this._terminal.rows; y < newRows; y++) { + if (this.lines.length < newRows + this.ybase) { + if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) { + this.ybase--; + addToY++; + if (this.ydisp > 0) { + this.ydisp--; + } + } + else { + this.lines.push(this._terminal.blankLine(undefined, undefined, newCols)); + } + } + } + } + else { + for (var y = this._terminal.rows; y > newRows; y--) { + if (this.lines.length > newRows + this.ybase) { + if (this.lines.length > this.ybase + this.y + 1) { + this.lines.pop(); + } + else { + this.ybase++; + this.ydisp++; + } + } + } + } + if (newMaxLength < this.lines.maxLength) { + var amountToTrim = this.lines.length - newMaxLength; + if (amountToTrim > 0) { + this.lines.trimStart(amountToTrim); + this.ybase = Math.max(this.ybase - amountToTrim, 0); + this.ydisp = Math.max(this.ydisp - amountToTrim, 0); + } + this.lines.maxLength = newMaxLength; + } + this.x = Math.min(this.x, newCols - 1); + this.y = Math.min(this.y, newRows - 1); + if (addToY) { + this.y += addToY; + } + this.savedY = Math.min(this.savedY, newRows - 1); + this.savedX = Math.min(this.savedX, newCols - 1); + this.scrollTop = 0; + } + this.scrollBottom = newRows - 1; + }; + Buffer.prototype.translateBufferLineToString = function (lineIndex, trimRight, startCol, endCol) { + if (startCol === void 0) { startCol = 0; } + if (endCol === void 0) { endCol = null; } + var lineString = ''; + var line = this.lines.get(lineIndex); + if (!line) { + return ''; + } + var startIndex = startCol; + if (endCol === null) { + endCol = line.length; + } + var endIndex = endCol; + for (var i = 0; i < line.length; i++) { + var char = line[i]; + lineString += char[exports.CHAR_DATA_CHAR_INDEX]; + if (char[exports.CHAR_DATA_WIDTH_INDEX] === 0) { + if (startCol >= i) { + startIndex--; + } + if (endCol > i) { + endIndex--; + } + } + else { + if (char[exports.CHAR_DATA_CHAR_INDEX].length > 1) { + if (startCol > i) { + startIndex += char[exports.CHAR_DATA_CHAR_INDEX].length - 1; + } + if (endCol > i) { + endIndex += char[exports.CHAR_DATA_CHAR_INDEX].length - 1; + } } } } - var addToY = 0; - if (this._terminal.rows < newRows) { - for (var y = this._terminal.rows; y < newRows; y++) { - if (this._lines.length < newRows + this.ybase) { - if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) { - this.ybase--; - addToY++; - if (this.ydisp > 0) { - this.ydisp--; - } - } - else { - this._lines.push(this._terminal.blankLine(undefined, undefined, newCols)); - } - } + if (trimRight) { + var rightWhitespaceIndex = lineString.search(/\s+$/); + if (rightWhitespaceIndex !== -1) { + endIndex = Math.min(endIndex, rightWhitespaceIndex); + } + if (endIndex <= startIndex) { + return ''; + } + } + return lineString.substring(startIndex, endIndex); + }; + Buffer.prototype.getWrappedRangeForLine = function (y) { + var first = y; + var last = y; + while (first > 0 && this.lines.get(first).isWrapped) { + first--; + } + while (last + 1 < this.lines.length && this.lines.get(last + 1).isWrapped) { + last++; + } + return { first: first, last: last }; + }; + Buffer.prototype.setupTabStops = function (i) { + if (i != null) { + if (!this.tabs[i]) { + i = this.prevStop(i); } } else { - for (var y = this._terminal.rows; y > newRows; y--) { - if (this._lines.length > newRows + this.ybase) { - if (this._lines.length > this.ybase + this.y + 1) { - this._lines.pop(); - } - else { - this.ybase++; - this.ydisp++; - } - } + this.tabs = {}; + i = 0; + } + for (; i < this._terminal.cols; i += this._terminal.options.tabStopWidth) { + this.tabs[i] = true; + } + }; + Buffer.prototype.prevStop = function (x) { + if (x == null) { + x = this.x; + } + while (!this.tabs[--x] && x > 0) + ; + return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x; + }; + Buffer.prototype.nextStop = function (x) { + if (x == null) { + x = this.x; + } + while (!this.tabs[++x] && x < this._terminal.cols) + ; + return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x; + }; + Buffer.prototype.addMarker = function (y) { + var _this = this; + var marker = new Marker(y); + this.markers.push(marker); + marker.register(this.lines.addDisposableListener('trim', function (amount) { + marker.line -= amount; + if (marker.line < 0) { + marker.dispose(); } - } - if (this.y >= newRows) { - this.y = newRows - 1; - } - if (addToY) { - this.y += addToY; - } - if (this.x >= newCols) { - this.x = newCols - 1; - } - this.scrollTop = 0; - this.scrollBottom = newRows - 1; + })); + marker.register(marker.addDisposableListener('dispose', function () { return _this._removeMarker(marker); })); + return marker; + }; + Buffer.prototype._removeMarker = function (marker) { + this.markers.splice(this.markers.indexOf(marker), 1); }; return Buffer; }()); exports.Buffer = Buffer; +var Marker = (function (_super) { + __extends(Marker, _super); + function Marker(line) { + var _this = _super.call(this) || this; + _this.line = line; + _this._id = Marker._nextId++; + _this.isDisposed = false; + return _this; + } + Object.defineProperty(Marker.prototype, "id", { + get: function () { return this._id; }, + enumerable: true, + configurable: true + }); + Marker.prototype.dispose = function () { + if (this.isDisposed) { + return; + } + this.isDisposed = true; + this.emit('dispose'); + _super.prototype.dispose.call(this); + }; + Marker._nextId = 1; + return Marker; +}(EventEmitter_1.EventEmitter)); +exports.Marker = Marker; - - -},{"./utils/CircularList":18}],2:[function(require,module,exports){ +},{"./EventEmitter":7,"./common/CircularList":16}],3:[function(require,module,exports){ "use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || @@ -116,10 +495,11 @@ var BufferSet = (function (_super) { function BufferSet(_terminal) { var _this = _super.call(this) || this; _this._terminal = _terminal; - _this._normal = new Buffer_1.Buffer(_this._terminal); + _this._normal = new Buffer_1.Buffer(_this._terminal, true); _this._normal.fillViewportRows(); - _this._alt = new Buffer_1.Buffer(_this._terminal); + _this._alt = new Buffer_1.Buffer(_this._terminal, false); _this._activeBuffer = _this._normal; + _this.setupTabStops(); return _this; } Object.defineProperty(BufferSet.prototype, "alt", { @@ -144,26 +524,4415 @@ var BufferSet = (function (_super) { configurable: true }); BufferSet.prototype.activateNormalBuffer = function () { + if (this._activeBuffer === this._normal) { + return; + } this._alt.clear(); this._activeBuffer = this._normal; - this.emit('activate', this._normal); + this.emit('activate', { + activeBuffer: this._normal, + inactiveBuffer: this._alt + }); }; BufferSet.prototype.activateAltBuffer = function () { + if (this._activeBuffer === this._alt) { + return; + } this._alt.fillViewportRows(); this._activeBuffer = this._alt; - this.emit('activate', this._alt); + this.emit('activate', { + activeBuffer: this._alt, + inactiveBuffer: this._normal + }); }; BufferSet.prototype.resize = function (newCols, newRows) { this._normal.resize(newCols, newRows); this._alt.resize(newCols, newRows); }; + BufferSet.prototype.setupTabStops = function (i) { + this._normal.setupTabStops(i); + this._alt.setupTabStops(i); + }; return BufferSet; }(EventEmitter_1.EventEmitter)); exports.BufferSet = BufferSet; +},{"./Buffer":2,"./EventEmitter":7}],4:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.wcwidth = (function (opts) { + var COMBINING_BMP = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] + ]; + var COMBINING_HIGH = [ + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + function bisearch(ucs, data) { + var min = 0; + var max = data.length - 1; + var mid; + if (ucs < data[0][0] || ucs > data[max][1]) { + return false; + } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } + else if (ucs < data[mid][0]) { + max = mid - 1; + } + else { + return true; + } + } + return false; + } + function wcwidthBMP(ucs) { + if (ucs === 0) { + return opts.nul; + } + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) { + return opts.control; + } + if (bisearch(ucs, COMBINING_BMP)) { + return 0; + } + if (isWideBMP(ucs)) { + return 2; + } + return 1; + } + function isWideBMP(ucs) { + return (ucs >= 0x1100 && (ucs <= 0x115f || + ucs === 0x2329 || + ucs === 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || + (ucs >= 0xac00 && ucs <= 0xd7a3) || + (ucs >= 0xf900 && ucs <= 0xfaff) || + (ucs >= 0xfe10 && ucs <= 0xfe19) || + (ucs >= 0xfe30 && ucs <= 0xfe6f) || + (ucs >= 0xff00 && ucs <= 0xff60) || + (ucs >= 0xffe0 && ucs <= 0xffe6))); + } + function wcwidthHigh(ucs) { + if (bisearch(ucs, COMBINING_HIGH)) { + return 0; + } + if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { + return 2; + } + return 1; + } + var control = opts.control | 0; + var table = null; + function initTable() { + var CODEPOINTS = 65536; + var BITWIDTH = 2; + var ITEMSIZE = 32; + var CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE; + var CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH; + table = (typeof Uint32Array === 'undefined') + ? new Array(CONTAINERSIZE) + : new Uint32Array(CONTAINERSIZE); + for (var i = 0; i < CONTAINERSIZE; ++i) { + var num = 0; + var pos = CODEPOINTS_PER_ITEM; + while (pos--) { + num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos); + } + table[i] = num; + } + return table; + } + return function (num) { + num = num | 0; + if (num < 32) { + return control | 0; + } + if (num < 127) { + return 1; + } + var t = table || initTable(); + if (num < 65536) { + return t[num >> 4] >> ((num & 15) << 1) & 3; + } + return wcwidthHigh(num); + }; +})({ nul: 0, control: 0 }); +},{}],5:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var CompositionHelper = (function () { + function CompositionHelper(_textarea, _compositionView, _terminal) { + this._textarea = _textarea; + this._compositionView = _compositionView; + this._terminal = _terminal; + this._isComposing = false; + this._isSendingComposition = false; + this._compositionPosition = { start: null, end: null }; + } + CompositionHelper.prototype.compositionstart = function () { + this._isComposing = true; + this._compositionPosition.start = this._textarea.value.length; + this._compositionView.textContent = ''; + this._compositionView.classList.add('active'); + }; + CompositionHelper.prototype.compositionupdate = function (ev) { + var _this = this; + this._compositionView.textContent = ev.data; + this.updateCompositionElements(); + setTimeout(function () { + _this._compositionPosition.end = _this._textarea.value.length; + }, 0); + }; + CompositionHelper.prototype.compositionend = function () { + this._finalizeComposition(true); + }; + CompositionHelper.prototype.keydown = function (ev) { + if (this._isComposing || this._isSendingComposition) { + if (ev.keyCode === 229) { + return false; + } + else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) { + return false; + } + this._finalizeComposition(false); + } + if (ev.keyCode === 229) { + this._handleAnyTextareaChanges(); + return false; + } + return true; + }; + CompositionHelper.prototype._finalizeComposition = function (waitForPropogation) { + var _this = this; + this._compositionView.classList.remove('active'); + this._isComposing = false; + this._clearTextareaPosition(); + if (!waitForPropogation) { + this._isSendingComposition = false; + var input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end); + this._terminal.handler(input); + } + else { + var currentCompositionPosition_1 = { + start: this._compositionPosition.start, + end: this._compositionPosition.end + }; + this._isSendingComposition = true; + setTimeout(function () { + if (_this._isSendingComposition) { + _this._isSendingComposition = false; + var input = void 0; + if (_this._isComposing) { + input = _this._textarea.value.substring(currentCompositionPosition_1.start, currentCompositionPosition_1.end); + } + else { + input = _this._textarea.value.substring(currentCompositionPosition_1.start); + } + _this._terminal.handler(input); + } + }, 0); + } + }; + CompositionHelper.prototype._handleAnyTextareaChanges = function () { + var _this = this; + var oldValue = this._textarea.value; + setTimeout(function () { + if (!_this._isComposing) { + var newValue = _this._textarea.value; + var diff = newValue.replace(oldValue, ''); + if (diff.length > 0) { + _this._terminal.handler(diff); + } + } + }, 0); + }; + CompositionHelper.prototype.updateCompositionElements = function (dontRecurse) { + var _this = this; + if (!this._isComposing) { + return; + } + if (this._terminal.buffer.isCursorInViewport) { + var cellHeight = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight); + var cursorTop = this._terminal.buffer.y * cellHeight; + var cursorLeft = this._terminal.buffer.x * this._terminal.charMeasure.width; + this._compositionView.style.left = cursorLeft + 'px'; + this._compositionView.style.top = cursorTop + 'px'; + this._compositionView.style.height = cellHeight + 'px'; + this._compositionView.style.lineHeight = cellHeight + 'px'; + var compositionViewBounds = this._compositionView.getBoundingClientRect(); + this._textarea.style.left = cursorLeft + 'px'; + this._textarea.style.top = cursorTop + 'px'; + this._textarea.style.width = compositionViewBounds.width + 'px'; + this._textarea.style.height = compositionViewBounds.height + 'px'; + this._textarea.style.lineHeight = compositionViewBounds.height + 'px'; + } + if (!dontRecurse) { + setTimeout(function () { return _this.updateCompositionElements(true); }, 0); + } + }; + CompositionHelper.prototype._clearTextareaPosition = function () { + this._textarea.style.left = ''; + this._textarea.style.top = ''; + }; + return CompositionHelper; +}()); +exports.CompositionHelper = CompositionHelper; -},{"./Buffer":1,"./EventEmitter":6}],3:[function(require,module,exports){ +},{}],6:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("./common/Lifecycle"); +function r(low, high) { + var c = high - low; + var arr = new Array(c); + while (c--) { + arr[c] = --high; + } + return arr; +} +var TransitionTable = (function () { + function TransitionTable(length) { + this.table = (typeof Uint8Array === 'undefined') + ? new Array(length) + : new Uint8Array(length); + } + TransitionTable.prototype.add = function (code, state, action, next) { + this.table[state << 8 | code] = ((action | 0) << 4) | ((next === undefined) ? state : next); + }; + TransitionTable.prototype.addMany = function (codes, state, action, next) { + for (var i = 0; i < codes.length; i++) { + this.add(codes[i], state, action, next); + } + }; + return TransitionTable; +}()); +exports.TransitionTable = TransitionTable; +var PRINTABLES = r(0x20, 0x7f); +var EXECUTABLES = r(0x00, 0x18); +EXECUTABLES.push(0x19); +EXECUTABLES.concat(r(0x1c, 0x20)); +var DEFAULT_TRANSITION = 1 << 4 | 0; +exports.VT500_TRANSITION_TABLE = (function () { + var table = new TransitionTable(4095); + var states = r(0, 13 + 1); + var state; + for (state in states) { + for (var code = 0; code < 160; ++code) { + table.add(code, state, 1, 0); + } + } + table.addMany(PRINTABLES, 0, 2, 0); + for (state in states) { + table.addMany([0x18, 0x1a, 0x99, 0x9a], state, 3, 0); + table.addMany(r(0x80, 0x90), state, 3, 0); + table.addMany(r(0x90, 0x98), state, 3, 0); + table.add(0x9c, state, 0, 0); + table.add(0x1b, state, 11, 1); + table.add(0x9d, state, 4, 8); + table.addMany([0x98, 0x9e, 0x9f], state, 0, 7); + table.add(0x9b, state, 11, 3); + table.add(0x90, state, 11, 9); + } + table.addMany(EXECUTABLES, 0, 3, 0); + table.addMany(EXECUTABLES, 1, 3, 1); + table.add(0x7f, 1, 0, 1); + table.addMany(EXECUTABLES, 8, 0, 8); + table.addMany(EXECUTABLES, 3, 3, 3); + table.add(0x7f, 3, 0, 3); + table.addMany(EXECUTABLES, 4, 3, 4); + table.add(0x7f, 4, 0, 4); + table.addMany(EXECUTABLES, 6, 3, 6); + table.addMany(EXECUTABLES, 5, 3, 5); + table.add(0x7f, 5, 0, 5); + table.addMany(EXECUTABLES, 2, 3, 2); + table.add(0x7f, 2, 0, 2); + table.add(0x5d, 1, 4, 8); + table.addMany(PRINTABLES, 8, 5, 8); + table.add(0x7f, 8, 5, 8); + table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], 8, 6, 0); + table.addMany(r(0x1c, 0x20), 8, 0, 8); + table.addMany([0x58, 0x5e, 0x5f], 1, 0, 7); + table.addMany(PRINTABLES, 7, 0, 7); + table.addMany(EXECUTABLES, 7, 0, 7); + table.add(0x9c, 7, 0, 0); + table.add(0x5b, 1, 11, 3); + table.addMany(r(0x40, 0x7f), 3, 7, 0); + table.addMany(r(0x30, 0x3a), 3, 8, 4); + table.add(0x3b, 3, 8, 4); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], 3, 9, 4); + table.addMany(r(0x30, 0x3a), 4, 8, 4); + table.add(0x3b, 4, 8, 4); + table.addMany(r(0x40, 0x7f), 4, 7, 0); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 4, 0, 6); + table.addMany(r(0x20, 0x40), 6, 0, 6); + table.add(0x7f, 6, 0, 6); + table.addMany(r(0x40, 0x7f), 6, 0, 0); + table.add(0x3a, 3, 0, 6); + table.addMany(r(0x20, 0x30), 3, 9, 5); + table.addMany(r(0x20, 0x30), 5, 9, 5); + table.addMany(r(0x30, 0x40), 5, 0, 6); + table.addMany(r(0x40, 0x7f), 5, 7, 0); + table.addMany(r(0x20, 0x30), 4, 9, 5); + table.addMany(r(0x20, 0x30), 1, 9, 2); + table.addMany(r(0x20, 0x30), 2, 9, 2); + table.addMany(r(0x30, 0x7f), 2, 10, 0); + table.addMany(r(0x30, 0x50), 1, 10, 0); + table.addMany(r(0x51, 0x58), 1, 10, 0); + table.addMany([0x59, 0x5a, 0x5c], 1, 10, 0); + table.addMany(r(0x60, 0x7f), 1, 10, 0); + table.add(0x50, 1, 11, 9); + table.addMany(EXECUTABLES, 9, 0, 9); + table.add(0x7f, 9, 0, 9); + table.addMany(r(0x1c, 0x20), 9, 0, 9); + table.addMany(r(0x20, 0x30), 9, 9, 12); + table.add(0x3a, 9, 0, 11); + table.addMany(r(0x30, 0x3a), 9, 8, 10); + table.add(0x3b, 9, 8, 10); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], 9, 9, 10); + table.addMany(EXECUTABLES, 11, 0, 11); + table.addMany(r(0x20, 0x80), 11, 0, 11); + table.addMany(r(0x1c, 0x20), 11, 0, 11); + table.addMany(EXECUTABLES, 10, 0, 10); + table.add(0x7f, 10, 0, 10); + table.addMany(r(0x1c, 0x20), 10, 0, 10); + table.addMany(r(0x30, 0x3a), 10, 8, 10); + table.add(0x3b, 10, 8, 10); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 10, 0, 11); + table.addMany(r(0x20, 0x30), 10, 9, 12); + table.addMany(EXECUTABLES, 12, 0, 12); + table.add(0x7f, 12, 0, 12); + table.addMany(r(0x1c, 0x20), 12, 0, 12); + table.addMany(r(0x20, 0x30), 12, 9, 12); + table.addMany(r(0x30, 0x40), 12, 0, 11); + table.addMany(r(0x40, 0x7f), 12, 12, 13); + table.addMany(r(0x40, 0x7f), 10, 12, 13); + table.addMany(r(0x40, 0x7f), 9, 12, 13); + table.addMany(EXECUTABLES, 13, 13, 13); + table.addMany(PRINTABLES, 13, 13, 13); + table.add(0x7f, 13, 0, 13); + table.addMany([0x1b, 0x9c], 13, 14, 0); + return table; +})(); +var DcsDummy = (function () { + function DcsDummy() { + } + DcsDummy.prototype.hook = function (collect, params, flag) { }; + DcsDummy.prototype.put = function (data, start, end) { }; + DcsDummy.prototype.unhook = function () { }; + return DcsDummy; +}()); +var EscapeSequenceParser = (function (_super) { + __extends(EscapeSequenceParser, _super); + function EscapeSequenceParser(TRANSITIONS) { + if (TRANSITIONS === void 0) { TRANSITIONS = exports.VT500_TRANSITION_TABLE; } + var _this = _super.call(this) || this; + _this.TRANSITIONS = TRANSITIONS; + _this.initialState = 0; + _this.currentState = _this.initialState; + _this._osc = ''; + _this._params = [0]; + _this._collect = ''; + _this._printHandlerFb = function (data, start, end) { }; + _this._executeHandlerFb = function (code) { }; + _this._csiHandlerFb = function (collect, params, flag) { }; + _this._escHandlerFb = function (collect, flag) { }; + _this._oscHandlerFb = function (identifier, data) { }; + _this._dcsHandlerFb = new DcsDummy(); + _this._errorHandlerFb = function (state) { return state; }; + _this._printHandler = _this._printHandlerFb; + _this._executeHandlers = Object.create(null); + _this._csiHandlers = Object.create(null); + _this._escHandlers = Object.create(null); + _this._oscHandlers = Object.create(null); + _this._dcsHandlers = Object.create(null); + _this._activeDcsHandler = null; + _this._errorHandler = _this._errorHandlerFb; + return _this; + } + EscapeSequenceParser.prototype.dispose = function () { + this._printHandlerFb = null; + this._executeHandlerFb = null; + this._csiHandlerFb = null; + this._escHandlerFb = null; + this._oscHandlerFb = null; + this._dcsHandlerFb = null; + this._errorHandlerFb = null; + this._printHandler = null; + this._executeHandlers = null; + this._csiHandlers = null; + this._escHandlers = null; + this._oscHandlers = null; + this._dcsHandlers = null; + this._activeDcsHandler = null; + this._errorHandler = null; + }; + EscapeSequenceParser.prototype.setPrintHandler = function (callback) { + this._printHandler = callback; + }; + EscapeSequenceParser.prototype.clearPrintHandler = function () { + this._printHandler = this._printHandlerFb; + }; + EscapeSequenceParser.prototype.setExecuteHandler = function (flag, callback) { + this._executeHandlers[flag.charCodeAt(0)] = callback; + }; + EscapeSequenceParser.prototype.clearExecuteHandler = function (flag) { + if (this._executeHandlers[flag.charCodeAt(0)]) + delete this._executeHandlers[flag.charCodeAt(0)]; + }; + EscapeSequenceParser.prototype.setExecuteHandlerFallback = function (callback) { + this._executeHandlerFb = callback; + }; + EscapeSequenceParser.prototype.setCsiHandler = function (flag, callback) { + this._csiHandlers[flag.charCodeAt(0)] = callback; + }; + EscapeSequenceParser.prototype.clearCsiHandler = function (flag) { + if (this._csiHandlers[flag.charCodeAt(0)]) + delete this._csiHandlers[flag.charCodeAt(0)]; + }; + EscapeSequenceParser.prototype.setCsiHandlerFallback = function (callback) { + this._csiHandlerFb = callback; + }; + EscapeSequenceParser.prototype.setEscHandler = function (collectAndFlag, callback) { + this._escHandlers[collectAndFlag] = callback; + }; + EscapeSequenceParser.prototype.clearEscHandler = function (collectAndFlag) { + if (this._escHandlers[collectAndFlag]) + delete this._escHandlers[collectAndFlag]; + }; + EscapeSequenceParser.prototype.setEscHandlerFallback = function (callback) { + this._escHandlerFb = callback; + }; + EscapeSequenceParser.prototype.setOscHandler = function (ident, callback) { + this._oscHandlers[ident] = callback; + }; + EscapeSequenceParser.prototype.clearOscHandler = function (ident) { + if (this._oscHandlers[ident]) + delete this._oscHandlers[ident]; + }; + EscapeSequenceParser.prototype.setOscHandlerFallback = function (callback) { + this._oscHandlerFb = callback; + }; + EscapeSequenceParser.prototype.setDcsHandler = function (collectAndFlag, handler) { + this._dcsHandlers[collectAndFlag] = handler; + }; + EscapeSequenceParser.prototype.clearDcsHandler = function (collectAndFlag) { + if (this._dcsHandlers[collectAndFlag]) + delete this._dcsHandlers[collectAndFlag]; + }; + EscapeSequenceParser.prototype.setDcsHandlerFallback = function (handler) { + this._dcsHandlerFb = handler; + }; + EscapeSequenceParser.prototype.setErrorHandler = function (callback) { + this._errorHandler = callback; + }; + EscapeSequenceParser.prototype.clearErrorHandler = function () { + this._errorHandler = this._errorHandlerFb; + }; + EscapeSequenceParser.prototype.reset = function () { + this.currentState = this.initialState; + this._osc = ''; + this._params = [0]; + this._collect = ''; + this._activeDcsHandler = null; + }; + EscapeSequenceParser.prototype.parse = function (data) { + var code = 0; + var transition = 0; + var error = false; + var currentState = this.currentState; + var print = -1; + var dcs = -1; + var osc = this._osc; + var collect = this._collect; + var params = this._params; + var table = this.TRANSITIONS.table; + var dcsHandler = this._activeDcsHandler; + var callback = null; + var l = data.length; + for (var i = 0; i < l; ++i) { + code = data.charCodeAt(i); + if (currentState === 0 && code > 0x1f && code < 0x80) { + print = (~print) ? print : i; + do + code = data.charCodeAt(++i); + while (i < l && code > 0x1f && code < 0x80); + i--; + continue; + } + if (currentState === 4 && (code > 0x2f && code < 0x39)) { + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + continue; + } + transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION; + switch (transition >> 4) { + case 2: + print = (~print) ? print : i; + break; + case 3: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + callback = this._executeHandlers[code]; + if (callback) + callback(); + else + this._executeHandlerFb(code); + break; + case 0: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + else if (~dcs) { + dcsHandler.put(data, dcs, i); + dcs = -1; + } + break; + case 1: + if (code > 0x9f) { + switch (currentState) { + case 0: + print = (~print) ? print : i; + break; + case 8: + osc += String.fromCharCode(code); + transition |= 8; + break; + case 6: + transition |= 6; + break; + case 11: + transition |= 11; + break; + case 13: + dcs = (~dcs) ? dcs : i; + transition |= 13; + break; + default: + error = true; + } + } + else { + error = true; + } + if (error) { + var inject = this._errorHandler({ + position: i, + code: code, + currentState: currentState, + print: print, + dcs: dcs, + osc: osc, + collect: collect, + params: params, + abort: false + }); + if (inject.abort) + return; + error = false; + } + break; + case 7: + callback = this._csiHandlers[code]; + if (callback) + callback(params, collect); + else + this._csiHandlerFb(collect, params, code); + break; + case 8: + if (code === 0x3b) + params.push(0); + else + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + break; + case 9: + collect += String.fromCharCode(code); + break; + case 10: + callback = this._escHandlers[collect + String.fromCharCode(code)]; + if (callback) + callback(collect, code); + else + this._escHandlerFb(collect, code); + break; + case 11: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + case 12: + dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)]; + if (!dcsHandler) + dcsHandler = this._dcsHandlerFb; + dcsHandler.hook(collect, params, code); + break; + case 13: + dcs = (~dcs) ? dcs : i; + break; + case 14: + if (dcsHandler) { + if (~dcs) + dcsHandler.put(data, dcs, i); + dcsHandler.unhook(); + dcsHandler = null; + } + if (code === 0x1b) + transition |= 1; + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + case 4: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + osc = ''; + break; + case 5: + osc += data.charAt(i); + break; + case 6: + if (osc && code !== 0x18 && code !== 0x1a) { + var idx = osc.indexOf(';'); + if (idx === -1) { + this._oscHandlerFb(-1, osc); + } + else { + var identifier = parseInt(osc.substring(0, idx)); + var content = osc.substring(idx + 1); + callback = this._oscHandlers[identifier]; + if (callback) + callback(content); + else + this._oscHandlerFb(identifier, content); + } + } + if (code === 0x1b) + transition |= 1; + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + } + currentState = transition & 15; + } + if (currentState === 0 && ~print) { + this._printHandler(data, print, data.length); + } + else if (currentState === 13 && ~dcs && dcsHandler) { + dcsHandler.put(data, dcs, data.length); + } + this._osc = osc; + this._collect = collect; + this._params = params; + this._activeDcsHandler = dcsHandler; + this.currentState = currentState; + }; + return EscapeSequenceParser; +}(Lifecycle_1.Disposable)); +exports.EscapeSequenceParser = EscapeSequenceParser; + +},{"./common/Lifecycle":17}],7:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("./common/Lifecycle"); +var EventEmitter = (function (_super) { + __extends(EventEmitter, _super); + function EventEmitter() { + var _this = _super.call(this) || this; + _this._events = _this._events || {}; + return _this; + } + EventEmitter.prototype.on = function (type, listener) { + this._events[type] = this._events[type] || []; + this._events[type].push(listener); + }; + EventEmitter.prototype.addDisposableListener = function (type, handler) { + var _this = this; + this.on(type, handler); + return { + dispose: function () { + if (!handler) { + return; + } + _this.off(type, handler); + handler = null; + } + }; + }; + EventEmitter.prototype.off = function (type, listener) { + if (!this._events[type]) { + return; + } + var obj = this._events[type]; + var i = obj.length; + while (i--) { + if (obj[i] === listener) { + obj.splice(i, 1); + return; + } + } + }; + EventEmitter.prototype.removeAllListeners = function (type) { + if (this._events[type]) { + delete this._events[type]; + } + }; + EventEmitter.prototype.emit = function (type) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + if (!this._events[type]) { + return; + } + var obj = this._events[type]; + for (var i = 0; i < obj.length; i++) { + obj[i].apply(this, args); + } + }; + EventEmitter.prototype.listeners = function (type) { + return this._events[type] || []; + }; + EventEmitter.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._events = {}; + }; + return EventEmitter; +}(Lifecycle_1.Disposable)); +exports.EventEmitter = EventEmitter; + +},{"./common/Lifecycle":17}],8:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var EscapeSequences_1 = require("./common/data/EscapeSequences"); +var Charsets_1 = require("./core/data/Charsets"); +var Buffer_1 = require("./Buffer"); +var CharWidth_1 = require("./CharWidth"); +var EscapeSequenceParser_1 = require("./EscapeSequenceParser"); +var Lifecycle_1 = require("./common/Lifecycle"); +var GLEVEL = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 }; +var RequestTerminfo = (function () { + function RequestTerminfo(_terminal) { + this._terminal = _terminal; + } + RequestTerminfo.prototype.hook = function (collect, params, flag) { + this._data = ''; + }; + RequestTerminfo.prototype.put = function (data, start, end) { + this._data += data.substring(start, end); + }; + RequestTerminfo.prototype.unhook = function () { + this._terminal.send(EscapeSequences_1.C0.ESC + "P0+r" + this._data + EscapeSequences_1.C0.ESC + "\\"); + }; + return RequestTerminfo; +}()); +var DECRQSS = (function () { + function DECRQSS(_terminal) { + this._terminal = _terminal; + } + DECRQSS.prototype.hook = function (collect, params, flag) { + this._data = ''; + }; + DECRQSS.prototype.put = function (data, start, end) { + this._data += data.substring(start, end); + }; + DECRQSS.prototype.unhook = function () { + switch (this._data) { + case '"q': + return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r0\"q" + EscapeSequences_1.C0.ESC + "\\"); + case '"p': + return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r61\"p" + EscapeSequences_1.C0.ESC + "\\"); + case 'r': + var pt = '' + (this._terminal.buffer.scrollTop + 1) + + ';' + (this._terminal.buffer.scrollBottom + 1) + 'r'; + return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r" + pt + EscapeSequences_1.C0.ESC + "\\"); + case 'm': + return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r0m" + EscapeSequences_1.C0.ESC + "\\"); + case ' q': + var STYLES = { 'block': 2, 'underline': 4, 'bar': 6 }; + var style = STYLES[this._terminal.getOption('cursorStyle')]; + style -= this._terminal.getOption('cursorBlink'); + return this._terminal.send(EscapeSequences_1.C0.ESC + "P1$r" + style + " q" + EscapeSequences_1.C0.ESC + "\\"); + default: + this._terminal.error('Unknown DCS $q %s', this._data); + this._terminal.send(EscapeSequences_1.C0.ESC + "P0$r" + this._data + EscapeSequences_1.C0.ESC + "\\"); + } + }; + return DECRQSS; +}()); +var InputHandler = (function (_super) { + __extends(InputHandler, _super); + function InputHandler(_terminal, _parser) { + if (_parser === void 0) { _parser = new EscapeSequenceParser_1.EscapeSequenceParser(); } + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._parser = _parser; + _this.register(_this._parser); + _this._surrogateHigh = ''; + _this._parser.setCsiHandlerFallback(function (collect, params, flag) { + _this._terminal.error('Unknown CSI code: ', collect, params, String.fromCharCode(flag)); + }); + _this._parser.setEscHandlerFallback(function (collect, flag) { + _this._terminal.error('Unknown ESC code: ', collect, String.fromCharCode(flag)); + }); + _this._parser.setExecuteHandlerFallback(function (code) { + _this._terminal.error('Unknown EXECUTE code: ', code); + }); + _this._parser.setOscHandlerFallback(function (identifier, data) { + _this._terminal.error('Unknown OSC code: ', identifier, data); + }); + _this._parser.setPrintHandler(function (data, start, end) { return _this.print(data, start, end); }); + _this._parser.setCsiHandler('@', function (params, collect) { return _this.insertChars(params); }); + _this._parser.setCsiHandler('A', function (params, collect) { return _this.cursorUp(params); }); + _this._parser.setCsiHandler('B', function (params, collect) { return _this.cursorDown(params); }); + _this._parser.setCsiHandler('C', function (params, collect) { return _this.cursorForward(params); }); + _this._parser.setCsiHandler('D', function (params, collect) { return _this.cursorBackward(params); }); + _this._parser.setCsiHandler('E', function (params, collect) { return _this.cursorNextLine(params); }); + _this._parser.setCsiHandler('F', function (params, collect) { return _this.cursorPrecedingLine(params); }); + _this._parser.setCsiHandler('G', function (params, collect) { return _this.cursorCharAbsolute(params); }); + _this._parser.setCsiHandler('H', function (params, collect) { return _this.cursorPosition(params); }); + _this._parser.setCsiHandler('I', function (params, collect) { return _this.cursorForwardTab(params); }); + _this._parser.setCsiHandler('J', function (params, collect) { return _this.eraseInDisplay(params); }); + _this._parser.setCsiHandler('K', function (params, collect) { return _this.eraseInLine(params); }); + _this._parser.setCsiHandler('L', function (params, collect) { return _this.insertLines(params); }); + _this._parser.setCsiHandler('M', function (params, collect) { return _this.deleteLines(params); }); + _this._parser.setCsiHandler('P', function (params, collect) { return _this.deleteChars(params); }); + _this._parser.setCsiHandler('S', function (params, collect) { return _this.scrollUp(params); }); + _this._parser.setCsiHandler('T', function (params, collect) { return _this.scrollDown(params, collect); }); + _this._parser.setCsiHandler('X', function (params, collect) { return _this.eraseChars(params); }); + _this._parser.setCsiHandler('Z', function (params, collect) { return _this.cursorBackwardTab(params); }); + _this._parser.setCsiHandler('`', function (params, collect) { return _this.charPosAbsolute(params); }); + _this._parser.setCsiHandler('a', function (params, collect) { return _this.hPositionRelative(params); }); + _this._parser.setCsiHandler('b', function (params, collect) { return _this.repeatPrecedingCharacter(params); }); + _this._parser.setCsiHandler('c', function (params, collect) { return _this.sendDeviceAttributes(params, collect); }); + _this._parser.setCsiHandler('d', function (params, collect) { return _this.linePosAbsolute(params); }); + _this._parser.setCsiHandler('e', function (params, collect) { return _this.vPositionRelative(params); }); + _this._parser.setCsiHandler('f', function (params, collect) { return _this.hVPosition(params); }); + _this._parser.setCsiHandler('g', function (params, collect) { return _this.tabClear(params); }); + _this._parser.setCsiHandler('h', function (params, collect) { return _this.setMode(params, collect); }); + _this._parser.setCsiHandler('l', function (params, collect) { return _this.resetMode(params, collect); }); + _this._parser.setCsiHandler('m', function (params, collect) { return _this.charAttributes(params); }); + _this._parser.setCsiHandler('n', function (params, collect) { return _this.deviceStatus(params, collect); }); + _this._parser.setCsiHandler('p', function (params, collect) { return _this.softReset(params, collect); }); + _this._parser.setCsiHandler('q', function (params, collect) { return _this.setCursorStyle(params, collect); }); + _this._parser.setCsiHandler('r', function (params, collect) { return _this.setScrollRegion(params, collect); }); + _this._parser.setCsiHandler('s', function (params, collect) { return _this.saveCursor(params); }); + _this._parser.setCsiHandler('u', function (params, collect) { return _this.restoreCursor(params); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.BEL, function () { return _this.bell(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.LF, function () { return _this.lineFeed(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.VT, function () { return _this.lineFeed(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.FF, function () { return _this.lineFeed(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.CR, function () { return _this.carriageReturn(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.BS, function () { return _this.backspace(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.HT, function () { return _this.tab(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.SO, function () { return _this.shiftOut(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.SI, function () { return _this.shiftIn(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C1.IND, function () { return _this.index(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C1.NEL, function () { return _this.nextLine(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C1.HTS, function () { return _this.tabSet(); }); + _this._parser.setOscHandler(0, function (data) { return _this.setTitle(data); }); + _this._parser.setOscHandler(2, function (data) { return _this.setTitle(data); }); + _this._parser.setEscHandler('7', function () { return _this.saveCursor([]); }); + _this._parser.setEscHandler('8', function () { return _this.restoreCursor([]); }); + _this._parser.setEscHandler('D', function () { return _this.index(); }); + _this._parser.setEscHandler('E', function () { return _this.nextLine(); }); + _this._parser.setEscHandler('H', function () { return _this.tabSet(); }); + _this._parser.setEscHandler('M', function () { return _this.reverseIndex(); }); + _this._parser.setEscHandler('=', function () { return _this.keypadApplicationMode(); }); + _this._parser.setEscHandler('>', function () { return _this.keypadNumericMode(); }); + _this._parser.setEscHandler('c', function () { return _this.reset(); }); + _this._parser.setEscHandler('n', function () { return _this.setgLevel(2); }); + _this._parser.setEscHandler('o', function () { return _this.setgLevel(3); }); + _this._parser.setEscHandler('|', function () { return _this.setgLevel(3); }); + _this._parser.setEscHandler('}', function () { return _this.setgLevel(2); }); + _this._parser.setEscHandler('~', function () { return _this.setgLevel(1); }); + _this._parser.setEscHandler('%@', function () { return _this.selectDefaultCharset(); }); + _this._parser.setEscHandler('%G', function () { return _this.selectDefaultCharset(); }); + var _loop_1 = function (flag) { + this_1._parser.setEscHandler('(' + flag, function () { return _this.selectCharset('(' + flag); }); + this_1._parser.setEscHandler(')' + flag, function () { return _this.selectCharset(')' + flag); }); + this_1._parser.setEscHandler('*' + flag, function () { return _this.selectCharset('*' + flag); }); + this_1._parser.setEscHandler('+' + flag, function () { return _this.selectCharset('+' + flag); }); + this_1._parser.setEscHandler('-' + flag, function () { return _this.selectCharset('-' + flag); }); + this_1._parser.setEscHandler('.' + flag, function () { return _this.selectCharset('.' + flag); }); + this_1._parser.setEscHandler('/' + flag, function () { return _this.selectCharset('/' + flag); }); + }; + var this_1 = this; + for (var flag in Charsets_1.CHARSETS) { + _loop_1(flag); + } + _this._parser.setErrorHandler(function (state) { + _this._terminal.error('Parsing error: ', state); + return state; + }); + _this._parser.setDcsHandler('$q', new DECRQSS(_this._terminal)); + _this._parser.setDcsHandler('+q', new RequestTerminfo(_this._terminal)); + return _this; + } + InputHandler.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._terminal = null; + }; + InputHandler.prototype.parse = function (data) { + var buffer = this._terminal.buffer; + var cursorStartX = buffer.x; + var cursorStartY = buffer.y; + if (this._terminal.debug) { + this._terminal.log('data: ' + data); + } + if (this._surrogateHigh) { + data = this._surrogateHigh + data; + this._surrogateHigh = ''; + } + this._parser.parse(data); + buffer = this._terminal.buffer; + if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { + this._terminal.emit('cursormove'); + } + }; + InputHandler.prototype.print = function (data, start, end) { + var char; + var code; + var low; + var chWidth; + var buffer = this._terminal.buffer; + var charset = this._terminal.charset; + var screenReaderMode = this._terminal.options.screenReaderMode; + var cols = this._terminal.cols; + var wraparoundMode = this._terminal.wraparoundMode; + var insertMode = this._terminal.insertMode; + var curAttr = this._terminal.curAttr; + var bufferRow = buffer.lines.get(buffer.y + buffer.ybase); + this._terminal.updateRange(buffer.y); + for (var stringPosition = start; stringPosition < end; ++stringPosition) { + char = data.charAt(stringPosition); + code = data.charCodeAt(stringPosition); + if (0xD800 <= code && code <= 0xDBFF) { + low = data.charCodeAt(stringPosition + 1); + if (isNaN(low)) { + this._surrogateHigh = char; + continue; + } + code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + char += data.charAt(stringPosition + 1); + } + if (0xDC00 <= code && code <= 0xDFFF) { + continue; + } + chWidth = CharWidth_1.wcwidth(code); + if (charset) { + char = charset[char] || char; + code = char.charCodeAt(0); + } + if (screenReaderMode) { + this._terminal.emit('a11y.char', char); + } + if (!chWidth && buffer.x) { + if (bufferRow[buffer.x - 1]) { + if (!bufferRow[buffer.x - 1][Buffer_1.CHAR_DATA_WIDTH_INDEX]) { + if (bufferRow[buffer.x - 2]) { + bufferRow[buffer.x - 2][Buffer_1.CHAR_DATA_CHAR_INDEX] += char; + bufferRow[buffer.x - 2][Buffer_1.CHAR_DATA_CODE_INDEX] = code; + } + } + else { + bufferRow[buffer.x - 1][Buffer_1.CHAR_DATA_CHAR_INDEX] += char; + bufferRow[buffer.x - 1][Buffer_1.CHAR_DATA_CODE_INDEX] = code; + } + } + continue; + } + if (buffer.x + chWidth - 1 >= cols) { + if (wraparoundMode) { + buffer.x = 0; + buffer.y++; + if (buffer.y > buffer.scrollBottom) { + buffer.y--; + this._terminal.scroll(true); + } + else { + buffer.lines.get(buffer.y).isWrapped = true; + } + bufferRow = buffer.lines.get(buffer.y + buffer.ybase); + } + else { + if (chWidth === 2) { + continue; + } + } + } + if (insertMode) { + for (var moves = 0; moves < chWidth; ++moves) { + var removed = bufferRow.pop(); + if (removed[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0 + && bufferRow[this._terminal.cols - 2] + && bufferRow[this._terminal.cols - 2][Buffer_1.CHAR_DATA_WIDTH_INDEX] === 2) { + bufferRow[this._terminal.cols - 2] = [curAttr, ' ', 1, 32]; + } + bufferRow.splice(buffer.x, 0, [curAttr, ' ', 1, 32]); + } + } + bufferRow[buffer.x++] = [curAttr, char, chWidth, code]; + if (chWidth === 2) { + bufferRow[buffer.x++] = [curAttr, '', 0, undefined]; + } + } + this._terminal.updateRange(buffer.y); + }; + InputHandler.prototype.bell = function () { + this._terminal.bell(); + }; + InputHandler.prototype.lineFeed = function () { + var buffer = this._terminal.buffer; + if (this._terminal.convertEol) { + buffer.x = 0; + } + buffer.y++; + if (buffer.y > buffer.scrollBottom) { + buffer.y--; + this._terminal.scroll(); + } + if (buffer.x >= this._terminal.cols) { + buffer.x--; + } + this._terminal.emit('linefeed'); + }; + InputHandler.prototype.carriageReturn = function () { + this._terminal.buffer.x = 0; + }; + InputHandler.prototype.backspace = function () { + if (this._terminal.buffer.x > 0) { + this._terminal.buffer.x--; + } + }; + InputHandler.prototype.tab = function () { + var originalX = this._terminal.buffer.x; + this._terminal.buffer.x = this._terminal.buffer.nextStop(); + if (this._terminal.options.screenReaderMode) { + this._terminal.emit('a11y.tab', this._terminal.buffer.x - originalX); + } + }; + InputHandler.prototype.shiftOut = function () { + this._terminal.setgLevel(1); + }; + InputHandler.prototype.shiftIn = function () { + this._terminal.setgLevel(0); + }; + InputHandler.prototype.insertChars = function (params) { + var param = params[0]; + if (param < 1) + param = 1; + var buffer = this._terminal.buffer; + var row = buffer.y + buffer.ybase; + var j = buffer.x; + var ch = [this._terminal.eraseAttr(), ' ', 1, 32]; + while (param-- && j < this._terminal.cols) { + buffer.lines.get(row).splice(j++, 0, ch); + buffer.lines.get(row).pop(); + } + }; + InputHandler.prototype.cursorUp = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y -= param; + if (this._terminal.buffer.y < 0) { + this._terminal.buffer.y = 0; + } + }; + InputHandler.prototype.cursorDown = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y += param; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + }; + InputHandler.prototype.cursorForward = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x += param; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.cursorBackward = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + this._terminal.buffer.x -= param; + if (this._terminal.buffer.x < 0) { + this._terminal.buffer.x = 0; + } + }; + InputHandler.prototype.cursorNextLine = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y += param; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + this._terminal.buffer.x = 0; + }; + InputHandler.prototype.cursorPrecedingLine = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y -= param; + if (this._terminal.buffer.y < 0) { + this._terminal.buffer.y = 0; + } + this._terminal.buffer.x = 0; + }; + InputHandler.prototype.cursorCharAbsolute = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x = param - 1; + }; + InputHandler.prototype.cursorPosition = function (params) { + var col; + var row = params[0] - 1; + if (params.length >= 2) { + col = params[1] - 1; + } + else { + col = 0; + } + if (row < 0) { + row = 0; + } + else if (row >= this._terminal.rows) { + row = this._terminal.rows - 1; + } + if (col < 0) { + col = 0; + } + else if (col >= this._terminal.cols) { + col = this._terminal.cols - 1; + } + this._terminal.buffer.x = col; + this._terminal.buffer.y = row; + }; + InputHandler.prototype.cursorForwardTab = function (params) { + var param = params[0] || 1; + while (param--) { + this._terminal.buffer.x = this._terminal.buffer.nextStop(); + } + }; + InputHandler.prototype.eraseInDisplay = function (params) { + var j; + switch (params[0]) { + case 0: + this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); + j = this._terminal.buffer.y + 1; + for (; j < this._terminal.rows; j++) { + this._terminal.eraseLine(j); + } + break; + case 1: + this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); + j = this._terminal.buffer.y; + while (j--) { + this._terminal.eraseLine(j); + } + break; + case 2: + j = this._terminal.rows; + while (j--) + this._terminal.eraseLine(j); + break; + case 3: + var scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows; + if (scrollBackSize > 0) { + this._terminal.buffer.lines.trimStart(scrollBackSize); + this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0); + this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0); + this._terminal.emit('scroll', 0); + } + break; + } + }; + InputHandler.prototype.eraseInLine = function (params) { + switch (params[0]) { + case 0: + this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); + break; + case 1: + this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); + break; + case 2: + this._terminal.eraseLine(this._terminal.buffer.y); + break; + } + }; + InputHandler.prototype.insertLines = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var buffer = this._terminal.buffer; + var row = buffer.y + buffer.ybase; + var scrollBottomRowsOffset = this._terminal.rows - 1 - buffer.scrollBottom; + var scrollBottomAbsolute = this._terminal.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1; + while (param--) { + buffer.lines.splice(scrollBottomAbsolute - 1, 1); + buffer.lines.splice(row, 0, this._terminal.blankLine(true)); + } + this._terminal.updateRange(buffer.y); + this._terminal.updateRange(buffer.scrollBottom); + }; + InputHandler.prototype.deleteLines = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var buffer = this._terminal.buffer; + var row = buffer.y + buffer.ybase; + var j; + j = this._terminal.rows - 1 - buffer.scrollBottom; + j = this._terminal.rows - 1 + buffer.ybase - j; + while (param--) { + buffer.lines.splice(row, 1); + buffer.lines.splice(j, 0, this._terminal.blankLine(true)); + } + this._terminal.updateRange(buffer.y); + this._terminal.updateRange(buffer.scrollBottom); + }; + InputHandler.prototype.deleteChars = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var buffer = this._terminal.buffer; + var row = buffer.y + buffer.ybase; + var ch = [this._terminal.eraseAttr(), ' ', 1, 32]; + while (param--) { + buffer.lines.get(row).splice(buffer.x, 1); + buffer.lines.get(row).push(ch); + } + this._terminal.updateRange(buffer.y); + }; + InputHandler.prototype.scrollUp = function (params) { + var param = params[0] || 1; + var buffer = this._terminal.buffer; + while (param--) { + buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1); + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, this._terminal.blankLine()); + } + this._terminal.updateRange(buffer.scrollTop); + this._terminal.updateRange(buffer.scrollBottom); + }; + InputHandler.prototype.scrollDown = function (params, collect) { + if (params.length < 2 && !collect) { + var param = params[0] || 1; + var buffer = this._terminal.buffer; + while (param--) { + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1); + buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, this._terminal.blankLine()); + } + this._terminal.updateRange(buffer.scrollTop); + this._terminal.updateRange(buffer.scrollBottom); + } + }; + InputHandler.prototype.eraseChars = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var buffer = this._terminal.buffer; + var row = buffer.y + buffer.ybase; + var j = buffer.x; + var ch = [this._terminal.eraseAttr(), ' ', 1, 32]; + while (param-- && j < this._terminal.cols) { + buffer.lines.get(row)[j++] = ch; + } + }; + InputHandler.prototype.cursorBackwardTab = function (params) { + var param = params[0] || 1; + var buffer = this._terminal.buffer; + while (param--) { + buffer.x = buffer.prevStop(); + } + }; + InputHandler.prototype.charPosAbsolute = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x = param - 1; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.hPositionRelative = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x += param; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.repeatPrecedingCharacter = function (params) { + var param = params[0] || 1; + var buffer = this._terminal.buffer; + var line = buffer.lines.get(buffer.ybase + buffer.y); + var ch = line[buffer.x - 1] || [Buffer_1.DEFAULT_ATTR, ' ', 1, 32]; + while (param--) { + line[buffer.x++] = ch; + } + }; + InputHandler.prototype.sendDeviceAttributes = function (params, collect) { + if (params[0] > 0) { + return; + } + if (!collect) { + if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[?1;2c'); + } + else if (this._terminal.is('linux')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[?6c'); + } + } + else if (collect === '>') { + if (this._terminal.is('xterm')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[>0;276;0c'); + } + else if (this._terminal.is('rxvt-unicode')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[>85;95;0c'); + } + else if (this._terminal.is('linux')) { + this._terminal.send(params[0] + 'c'); + } + else if (this._terminal.is('screen')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[>83;40003;0c'); + } + } + }; + InputHandler.prototype.linePosAbsolute = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y = param - 1; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + }; + InputHandler.prototype.vPositionRelative = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y += param; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + }; + InputHandler.prototype.hVPosition = function (params) { + if (params[0] < 1) + params[0] = 1; + if (params[1] < 1) + params[1] = 1; + this._terminal.buffer.y = params[0] - 1; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + this._terminal.buffer.x = params[1] - 1; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.tabClear = function (params) { + var param = params[0]; + if (param <= 0) { + delete this._terminal.buffer.tabs[this._terminal.buffer.x]; + } + else if (param === 3) { + this._terminal.buffer.tabs = {}; + } + }; + InputHandler.prototype.setMode = function (params, collect) { + if (params.length > 1) { + for (var i = 0; i < params.length; i++) { + this.setMode([params[i]]); + } + return; + } + if (!collect) { + switch (params[0]) { + case 4: + this._terminal.insertMode = true; + break; + case 20: + break; + } + } + else if (collect === '?') { + switch (params[0]) { + case 1: + this._terminal.applicationCursor = true; + break; + case 2: + this._terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); + this._terminal.setgCharset(1, Charsets_1.DEFAULT_CHARSET); + this._terminal.setgCharset(2, Charsets_1.DEFAULT_CHARSET); + this._terminal.setgCharset(3, Charsets_1.DEFAULT_CHARSET); + break; + case 3: + this._terminal.savedCols = this._terminal.cols; + this._terminal.resize(132, this._terminal.rows); + break; + case 6: + this._terminal.originMode = true; + break; + case 7: + this._terminal.wraparoundMode = true; + break; + case 12: + break; + case 66: + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + this._terminal.viewport.syncScrollArea(); + break; + case 9: + case 1000: + case 1002: + case 1003: + this._terminal.x10Mouse = params[0] === 9; + this._terminal.vt200Mouse = params[0] === 1000; + this._terminal.normalMouse = params[0] > 1000; + this._terminal.mouseEvents = true; + this._terminal.element.classList.add('enable-mouse-events'); + this._terminal.selectionManager.disable(); + this._terminal.log('Binding to mouse events.'); + break; + case 1004: + this._terminal.sendFocus = true; + break; + case 1005: + this._terminal.utfMouse = true; + break; + case 1006: + this._terminal.sgrMouse = true; + break; + case 1015: + this._terminal.urxvtMouse = true; + break; + case 25: + this._terminal.cursorHidden = false; + break; + case 1049: + case 47: + case 1047: + this._terminal.buffers.activateAltBuffer(); + this._terminal.viewport.syncScrollArea(); + this._terminal.showCursor(); + break; + case 2004: + this._terminal.bracketedPasteMode = true; + break; + } + } + }; + InputHandler.prototype.resetMode = function (params, collect) { + if (params.length > 1) { + for (var i = 0; i < params.length; i++) { + this.resetMode([params[i]]); + } + return; + } + if (!collect) { + switch (params[0]) { + case 4: + this._terminal.insertMode = false; + break; + case 20: + break; + } + } + else if (collect === '?') { + switch (params[0]) { + case 1: + this._terminal.applicationCursor = false; + break; + case 3: + if (this._terminal.cols === 132 && this._terminal.savedCols) { + this._terminal.resize(this._terminal.savedCols, this._terminal.rows); + } + delete this._terminal.savedCols; + break; + case 6: + this._terminal.originMode = false; + break; + case 7: + this._terminal.wraparoundMode = false; + break; + case 12: + break; + case 66: + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + this._terminal.viewport.syncScrollArea(); + break; + case 9: + case 1000: + case 1002: + case 1003: + this._terminal.x10Mouse = false; + this._terminal.vt200Mouse = false; + this._terminal.normalMouse = false; + this._terminal.mouseEvents = false; + this._terminal.element.classList.remove('enable-mouse-events'); + this._terminal.selectionManager.enable(); + break; + case 1004: + this._terminal.sendFocus = false; + break; + case 1005: + this._terminal.utfMouse = false; + break; + case 1006: + this._terminal.sgrMouse = false; + break; + case 1015: + this._terminal.urxvtMouse = false; + break; + case 25: + this._terminal.cursorHidden = true; + break; + case 1049: + case 47: + case 1047: + this._terminal.buffers.activateNormalBuffer(); + this._terminal.refresh(0, this._terminal.rows - 1); + this._terminal.viewport.syncScrollArea(); + this._terminal.showCursor(); + break; + case 2004: + this._terminal.bracketedPasteMode = false; + break; + } + } + }; + InputHandler.prototype.charAttributes = function (params) { + if (params.length === 1 && params[0] === 0) { + this._terminal.curAttr = Buffer_1.DEFAULT_ATTR; + return; + } + var l = params.length; + var flags = this._terminal.curAttr >> 18; + var fg = (this._terminal.curAttr >> 9) & 0x1ff; + var bg = this._terminal.curAttr & 0x1ff; + var p; + for (var i = 0; i < l; i++) { + p = params[i]; + if (p >= 30 && p <= 37) { + fg = p - 30; + } + else if (p >= 40 && p <= 47) { + bg = p - 40; + } + else if (p >= 90 && p <= 97) { + p += 8; + fg = p - 90; + } + else if (p >= 100 && p <= 107) { + p += 8; + bg = p - 100; + } + else if (p === 0) { + flags = Buffer_1.DEFAULT_ATTR >> 18; + fg = (Buffer_1.DEFAULT_ATTR >> 9) & 0x1ff; + bg = Buffer_1.DEFAULT_ATTR & 0x1ff; + } + else if (p === 1) { + flags |= 1; + } + else if (p === 3) { + flags |= 64; + } + else if (p === 4) { + flags |= 2; + } + else if (p === 5) { + flags |= 4; + } + else if (p === 7) { + flags |= 8; + } + else if (p === 8) { + flags |= 16; + } + else if (p === 2) { + flags |= 32; + } + else if (p === 22) { + flags &= ~1; + flags &= ~32; + } + else if (p === 24) { + flags &= ~2; + } + else if (p === 25) { + flags &= ~4; + } + else if (p === 27) { + flags &= ~8; + } + else if (p === 28) { + flags &= ~16; + } + else if (p === 39) { + fg = (Buffer_1.DEFAULT_ATTR >> 9) & 0x1ff; + } + else if (p === 49) { + bg = Buffer_1.DEFAULT_ATTR & 0x1ff; + } + else if (p === 38) { + if (params[i + 1] === 2) { + i += 2; + fg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); + if (fg === -1) + fg = 0x1ff; + i += 2; + } + else if (params[i + 1] === 5) { + i += 2; + p = params[i] & 0xff; + fg = p; + } + } + else if (p === 48) { + if (params[i + 1] === 2) { + i += 2; + bg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); + if (bg === -1) + bg = 0x1ff; + i += 2; + } + else if (params[i + 1] === 5) { + i += 2; + p = params[i] & 0xff; + bg = p; + } + } + else if (p === 100) { + fg = (Buffer_1.DEFAULT_ATTR >> 9) & 0x1ff; + bg = Buffer_1.DEFAULT_ATTR & 0x1ff; + } + else { + this._terminal.error('Unknown SGR attribute: %d.', p); + } + } + this._terminal.curAttr = (flags << 18) | (fg << 9) | bg; + }; + InputHandler.prototype.deviceStatus = function (params, collect) { + if (!collect) { + switch (params[0]) { + case 5: + this._terminal.send(EscapeSequences_1.C0.ESC + '[0n'); + break; + case 6: + this._terminal.send(EscapeSequences_1.C0.ESC + '[' + + (this._terminal.buffer.y + 1) + + ';' + + (this._terminal.buffer.x + 1) + + 'R'); + break; + } + } + else if (collect === '?') { + switch (params[0]) { + case 6: + this._terminal.send(EscapeSequences_1.C0.ESC + '[?' + + (this._terminal.buffer.y + 1) + + ';' + + (this._terminal.buffer.x + 1) + + 'R'); + break; + case 15: + break; + case 25: + break; + case 26: + break; + case 53: + break; + } + } + }; + InputHandler.prototype.softReset = function (params, collect) { + if (collect === '!') { + this._terminal.cursorHidden = false; + this._terminal.insertMode = false; + this._terminal.originMode = false; + this._terminal.wraparoundMode = true; + this._terminal.applicationKeypad = false; + this._terminal.viewport.syncScrollArea(); + this._terminal.applicationCursor = false; + this._terminal.buffer.scrollTop = 0; + this._terminal.buffer.scrollBottom = this._terminal.rows - 1; + this._terminal.curAttr = Buffer_1.DEFAULT_ATTR; + this._terminal.buffer.x = this._terminal.buffer.y = 0; + this._terminal.charset = null; + this._terminal.glevel = 0; + this._terminal.charsets = [null]; + } + }; + InputHandler.prototype.setCursorStyle = function (params, collect) { + if (collect === ' ') { + var param = params[0] < 1 ? 1 : params[0]; + switch (param) { + case 1: + case 2: + this._terminal.setOption('cursorStyle', 'block'); + break; + case 3: + case 4: + this._terminal.setOption('cursorStyle', 'underline'); + break; + case 5: + case 6: + this._terminal.setOption('cursorStyle', 'bar'); + break; + } + var isBlinking = param % 2 === 1; + this._terminal.setOption('cursorBlink', isBlinking); + } + }; + InputHandler.prototype.setScrollRegion = function (params, collect) { + if (collect) + return; + this._terminal.buffer.scrollTop = (params[0] || 1) - 1; + this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1; + this._terminal.buffer.x = 0; + this._terminal.buffer.y = 0; + }; + InputHandler.prototype.saveCursor = function (params) { + this._terminal.buffer.savedX = this._terminal.buffer.x; + this._terminal.buffer.savedY = this._terminal.buffer.y; + this._terminal.savedCurAttr = this._terminal.curAttr; + }; + InputHandler.prototype.restoreCursor = function (params) { + this._terminal.buffer.x = this._terminal.buffer.savedX || 0; + this._terminal.buffer.y = this._terminal.buffer.savedY || 0; + this._terminal.curAttr = this._terminal.savedCurAttr || Buffer_1.DEFAULT_ATTR; + }; + InputHandler.prototype.setTitle = function (data) { + this._terminal.handleTitle(data); + }; + InputHandler.prototype.nextLine = function () { + this._terminal.buffer.x = 0; + this.index(); + }; + InputHandler.prototype.keypadApplicationMode = function () { + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + }; + InputHandler.prototype.keypadNumericMode = function () { + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + }; + InputHandler.prototype.selectDefaultCharset = function () { + this._terminal.setgLevel(0); + this._terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); + }; + InputHandler.prototype.selectCharset = function (collectAndFlag) { + if (collectAndFlag.length !== 2) + return this.selectDefaultCharset(); + if (collectAndFlag[0] === '/') + return; + this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], Charsets_1.CHARSETS[collectAndFlag[1]] || Charsets_1.DEFAULT_CHARSET); + }; + InputHandler.prototype.index = function () { + this._terminal.index(); + }; + InputHandler.prototype.tabSet = function () { + this._terminal.tabSet(); + }; + InputHandler.prototype.reverseIndex = function () { + this._terminal.reverseIndex(); + }; + InputHandler.prototype.reset = function () { + this._parser.reset(); + this._terminal.reset(); + }; + InputHandler.prototype.setgLevel = function (level) { + this._terminal.setgLevel(level); + }; + return InputHandler; +}(Lifecycle_1.Disposable)); +exports.InputHandler = InputHandler; + +},{"./Buffer":2,"./CharWidth":4,"./EscapeSequenceParser":6,"./common/Lifecycle":17,"./common/data/EscapeSequences":18,"./core/data/Charsets":19}],9:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var MouseZoneManager_1 = require("./ui/MouseZoneManager"); +var EventEmitter_1 = require("./EventEmitter"); +var Linkifier = (function (_super) { + __extends(Linkifier, _super); + function Linkifier(_terminal) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._linkMatchers = []; + _this._nextLinkMatcherId = 0; + _this._rowsToLinkify = { + start: null, + end: null + }; + return _this; + } + Linkifier.prototype.attachToDom = function (mouseZoneManager) { + this._mouseZoneManager = mouseZoneManager; + }; + Linkifier.prototype.linkifyRows = function (start, end) { + var _this = this; + if (!this._mouseZoneManager) { + return; + } + if (this._rowsToLinkify.start === null) { + this._rowsToLinkify.start = start; + this._rowsToLinkify.end = end; + } + else { + this._rowsToLinkify.start = Math.min(this._rowsToLinkify.start, start); + this._rowsToLinkify.end = Math.max(this._rowsToLinkify.end, end); + } + this._mouseZoneManager.clearAll(start, end); + if (this._rowsTimeoutId) { + clearTimeout(this._rowsTimeoutId); + } + this._rowsTimeoutId = setTimeout(function () { return _this._linkifyRows(); }, Linkifier.TIME_BEFORE_LINKIFY); + }; + Linkifier.prototype._linkifyRows = function () { + this._rowsTimeoutId = null; + for (var i = this._rowsToLinkify.start; i <= this._rowsToLinkify.end; i++) { + this._linkifyRow(i); + } + this._rowsToLinkify.start = null; + this._rowsToLinkify.end = null; + }; + Linkifier.prototype.registerLinkMatcher = function (regex, handler, options) { + if (options === void 0) { options = {}; } + if (!handler) { + throw new Error('handler must be defined'); + } + var matcher = { + id: this._nextLinkMatcherId++, + regex: regex, + handler: handler, + matchIndex: options.matchIndex, + validationCallback: options.validationCallback, + hoverTooltipCallback: options.tooltipCallback, + hoverLeaveCallback: options.leaveCallback, + willLinkActivate: options.willLinkActivate, + priority: options.priority || 0 + }; + this._addLinkMatcherToList(matcher); + return matcher.id; + }; + Linkifier.prototype._addLinkMatcherToList = function (matcher) { + if (this._linkMatchers.length === 0) { + this._linkMatchers.push(matcher); + return; + } + for (var i = this._linkMatchers.length - 1; i >= 0; i--) { + if (matcher.priority <= this._linkMatchers[i].priority) { + this._linkMatchers.splice(i + 1, 0, matcher); + return; + } + } + this._linkMatchers.splice(0, 0, matcher); + }; + Linkifier.prototype.deregisterLinkMatcher = function (matcherId) { + for (var i = 0; i < this._linkMatchers.length; i++) { + if (this._linkMatchers[i].id === matcherId) { + this._linkMatchers.splice(i, 1); + return true; + } + } + return false; + }; + Linkifier.prototype._linkifyRow = function (rowIndex) { + var absoluteRowIndex = this._terminal.buffer.ydisp + rowIndex; + if (absoluteRowIndex >= this._terminal.buffer.lines.length) { + return; + } + if (this._terminal.buffer.lines.get(absoluteRowIndex).isWrapped) { + if (rowIndex !== 0) { + return; + } + do { + rowIndex--; + absoluteRowIndex--; + } while (this._terminal.buffer.lines.get(absoluteRowIndex).isWrapped); + } + var text = this._terminal.buffer.translateBufferLineToString(absoluteRowIndex, false); + var currentIndex = absoluteRowIndex + 1; + while (currentIndex < this._terminal.buffer.lines.length && + this._terminal.buffer.lines.get(currentIndex).isWrapped) { + text += this._terminal.buffer.translateBufferLineToString(currentIndex++, false); + } + for (var i = 0; i < this._linkMatchers.length; i++) { + this._doLinkifyRow(rowIndex, text, this._linkMatchers[i]); + } + }; + Linkifier.prototype._doLinkifyRow = function (rowIndex, text, matcher, offset) { + var _this = this; + if (offset === void 0) { offset = 0; } + var match = text.match(matcher.regex); + if (!match || match.length === 0) { + return; + } + var uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; + var index = text.indexOf(uri); + if (matcher.validationCallback) { + matcher.validationCallback(uri, function (isValid) { + if (_this._rowsTimeoutId) { + return; + } + if (isValid) { + _this._addLink(offset + index, rowIndex, uri, matcher); + } + }); + } + else { + this._addLink(offset + index, rowIndex, uri, matcher); + } + var remainingStartIndex = index + uri.length; + var remainingText = text.substr(remainingStartIndex); + if (remainingText.length > 0) { + this._doLinkifyRow(rowIndex, remainingText, matcher, offset + remainingStartIndex); + } + }; + Linkifier.prototype._addLink = function (x, y, uri, matcher) { + var _this = this; + var x1 = x % this._terminal.cols; + var y1 = y + Math.floor(x / this._terminal.cols); + var x2 = (x1 + uri.length) % this._terminal.cols; + var y2 = y1 + Math.floor((x1 + uri.length) / this._terminal.cols); + if (x2 === 0) { + x2 = this._terminal.cols; + y2--; + } + this._mouseZoneManager.add(new MouseZoneManager_1.MouseZone(x1 + 1, y1 + 1, x2 + 1, y2 + 1, function (e) { + if (matcher.handler) { + return matcher.handler(e, uri); + } + window.open(uri, '_blank'); + }, function (e) { + _this.emit("linkhover", _this._createLinkHoverEvent(x1, y1, x2, y2)); + _this._terminal.element.classList.add('xterm-cursor-pointer'); + }, function (e) { + _this.emit("linktooltip", _this._createLinkHoverEvent(x1, y1, x2, y2)); + if (matcher.hoverTooltipCallback) { + matcher.hoverTooltipCallback(e, uri); + } + }, function () { + _this.emit("linkleave", _this._createLinkHoverEvent(x1, y1, x2, y2)); + _this._terminal.element.classList.remove('xterm-cursor-pointer'); + if (matcher.hoverLeaveCallback) { + matcher.hoverLeaveCallback(); + } + }, function (e) { + if (matcher.willLinkActivate) { + return matcher.willLinkActivate(e, uri); + } + return true; + })); + }; + Linkifier.prototype._createLinkHoverEvent = function (x1, y1, x2, y2) { + return { x1: x1, y1: y1, x2: x2, y2: y2, cols: this._terminal.cols }; + }; + Linkifier.TIME_BEFORE_LINKIFY = 200; + return Linkifier; +}(EventEmitter_1.EventEmitter)); +exports.Linkifier = Linkifier; + +},{"./EventEmitter":7,"./ui/MouseZoneManager":47}],10:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var MouseHelper_1 = require("./utils/MouseHelper"); +var Browser = require("./shared/utils/Browser"); +var EventEmitter_1 = require("./EventEmitter"); +var SelectionModel_1 = require("./SelectionModel"); +var Buffer_1 = require("./Buffer"); +var AltClickHandler_1 = require("./handlers/AltClickHandler"); +var DRAG_SCROLL_MAX_THRESHOLD = 50; +var DRAG_SCROLL_MAX_SPEED = 15; +var DRAG_SCROLL_INTERVAL = 50; +var ALT_CLICK_MOVE_CURSOR_TIME = 500; +var WORD_SEPARATORS = ' ()[]{}\'"'; +var NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); +var ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); +var SelectionManager = (function (_super) { + __extends(SelectionManager, _super); + function SelectionManager(_terminal, _charMeasure) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._charMeasure = _charMeasure; + _this._enabled = true; + _this._initListeners(); + _this.enable(); + _this._model = new SelectionModel_1.SelectionModel(_terminal); + _this._activeSelectionMode = 0; + return _this; + } + SelectionManager.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._removeMouseDownListeners(); + }; + Object.defineProperty(SelectionManager.prototype, "_buffer", { + get: function () { + return this._terminal.buffers.active; + }, + enumerable: true, + configurable: true + }); + SelectionManager.prototype._initListeners = function () { + var _this = this; + this._mouseMoveListener = function (event) { return _this._onMouseMove(event); }; + this._mouseUpListener = function (event) { return _this._onMouseUp(event); }; + this._trimListener = function (amount) { return _this._onTrim(amount); }; + this.initBuffersListeners(); + }; + SelectionManager.prototype.initBuffersListeners = function () { + var _this = this; + this._terminal.buffer.lines.on('trim', this._trimListener); + this._terminal.buffers.on('activate', function (e) { return _this._onBufferActivate(e); }); + }; + SelectionManager.prototype.disable = function () { + this.clearSelection(); + this._enabled = false; + }; + SelectionManager.prototype.enable = function () { + this._enabled = true; + }; + Object.defineProperty(SelectionManager.prototype, "selectionStart", { + get: function () { return this._model.finalSelectionStart; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "selectionEnd", { + get: function () { return this._model.finalSelectionEnd; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "hasSelection", { + get: function () { + var start = this._model.finalSelectionStart; + var end = this._model.finalSelectionEnd; + if (!start || !end) { + return false; + } + return start[0] !== end[0] || start[1] !== end[1]; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "selectionText", { + get: function () { + var start = this._model.finalSelectionStart; + var end = this._model.finalSelectionEnd; + if (!start || !end) { + return ''; + } + var result = []; + if (this._activeSelectionMode === 3) { + if (start[0] === end[0]) { + return ''; + } + for (var i = start[1]; i <= end[1]; i++) { + var lineText = this._buffer.translateBufferLineToString(i, true, start[0], end[0]); + result.push(lineText); + } + } + else { + var startRowEndCol = start[1] === end[1] ? end[0] : null; + result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol)); + for (var i = start[1] + 1; i <= end[1] - 1; i++) { + var bufferLine = this._buffer.lines.get(i); + var lineText = this._buffer.translateBufferLineToString(i, true); + if (bufferLine.isWrapped) { + result[result.length - 1] += lineText; + } + else { + result.push(lineText); + } + } + if (start[1] !== end[1]) { + var bufferLine = this._buffer.lines.get(end[1]); + var lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]); + if (bufferLine.isWrapped) { + result[result.length - 1] += lineText; + } + else { + result.push(lineText); + } + } + } + var formattedResult = result.map(function (line) { + return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' '); + }).join(Browser.isMSWindows ? '\r\n' : '\n'); + return formattedResult; + }, + enumerable: true, + configurable: true + }); + SelectionManager.prototype.clearSelection = function () { + this._model.clearSelection(); + this._removeMouseDownListeners(); + this.refresh(); + }; + SelectionManager.prototype.refresh = function (isNewSelection) { + var _this = this; + if (!this._refreshAnimationFrame) { + this._refreshAnimationFrame = window.requestAnimationFrame(function () { return _this._refresh(); }); + } + if (Browser.isLinux && isNewSelection) { + var selectionText = this.selectionText; + if (selectionText.length) { + this.emit('newselection', this.selectionText); + } + } + }; + SelectionManager.prototype._refresh = function () { + this._refreshAnimationFrame = null; + this.emit('refresh', { + start: this._model.finalSelectionStart, + end: this._model.finalSelectionEnd, + columnSelectMode: this._activeSelectionMode === 3 + }); + }; + SelectionManager.prototype.isClickInSelection = function (event) { + var coords = this._getMouseBufferCoords(event); + var start = this._model.finalSelectionStart; + var end = this._model.finalSelectionEnd; + if (!start || !end) { + return false; + } + return (coords[1] > start[1] && coords[1] < end[1]) || + (start[1] === end[1] && coords[1] === start[1] && coords[0] > start[0] && coords[0] < end[0]) || + (start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]); + }; + SelectionManager.prototype.selectWordAtCursor = function (event) { + var coords = this._getMouseBufferCoords(event); + if (coords) { + this._selectWordAt(coords, false); + this._model.selectionEnd = null; + this.refresh(true); + } + }; + SelectionManager.prototype.selectAll = function () { + this._model.isSelectAllActive = true; + this.refresh(); + this._terminal.emit('selection'); + }; + SelectionManager.prototype.selectLines = function (start, end) { + this._model.clearSelection(); + start = Math.max(start, 0); + end = Math.min(end, this._terminal.buffer.lines.length - 1); + this._model.selectionStart = [0, start]; + this._model.selectionEnd = [this._terminal.cols, end]; + this.refresh(); + this._terminal.emit('selection'); + }; + SelectionManager.prototype._onTrim = function (amount) { + var needsRefresh = this._model.onTrim(amount); + if (needsRefresh) { + this.refresh(); + } + }; + SelectionManager.prototype._getMouseBufferCoords = function (event) { + var coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true); + if (!coords) { + return null; + } + coords[0]--; + coords[1]--; + coords[1] += this._terminal.buffer.ydisp; + return coords; + }; + SelectionManager.prototype._getMouseEventScrollAmount = function (event) { + var offset = MouseHelper_1.MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; + var terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight); + if (offset >= 0 && offset <= terminalHeight) { + return 0; + } + if (offset > terminalHeight) { + offset -= terminalHeight; + } + offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD); + offset /= DRAG_SCROLL_MAX_THRESHOLD; + return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1)); + }; + SelectionManager.prototype.shouldForceSelection = function (event) { + if (Browser.isMac) { + return event.altKey && this._terminal.options.macOptionClickForcesSelection; + } + return event.shiftKey; + }; + SelectionManager.prototype.onMouseDown = function (event) { + this._mouseDownTimeStamp = event.timeStamp; + if (event.button === 2 && this.hasSelection) { + return; + } + if (event.button !== 0) { + return; + } + if (!this._enabled) { + if (!this.shouldForceSelection(event)) { + return; + } + event.stopPropagation(); + } + event.preventDefault(); + this._dragScrollAmount = 0; + if (this._enabled && event.shiftKey) { + this._onIncrementalClick(event); + } + else { + if (event.detail === 1) { + this._onSingleClick(event); + } + else if (event.detail === 2) { + this._onDoubleClick(event); + } + else if (event.detail === 3) { + this._onTripleClick(event); + } + } + this._addMouseDownListeners(); + this.refresh(true); + }; + SelectionManager.prototype._addMouseDownListeners = function () { + var _this = this; + this._terminal.element.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.ownerDocument.addEventListener('mouseup', this._mouseUpListener); + this._dragScrollIntervalTimer = setInterval(function () { return _this._dragScroll(); }, DRAG_SCROLL_INTERVAL); + }; + SelectionManager.prototype._removeMouseDownListeners = function () { + this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); + clearInterval(this._dragScrollIntervalTimer); + this._dragScrollIntervalTimer = null; + }; + SelectionManager.prototype._onIncrementalClick = function (event) { + if (this._model.selectionStart) { + this._model.selectionEnd = this._getMouseBufferCoords(event); + } + }; + SelectionManager.prototype._onSingleClick = function (event) { + this._model.selectionStartLength = 0; + this._model.isSelectAllActive = false; + this._activeSelectionMode = this.shouldColumnSelect(event) ? 3 : 0; + this._model.selectionStart = this._getMouseBufferCoords(event); + if (!this._model.selectionStart) { + return; + } + this._model.selectionEnd = null; + var line = this._buffer.lines.get(this._model.selectionStart[1]); + if (!line) { + return; + } + if (line.length >= this._model.selectionStart[0]) { + return; + } + var char = line[this._model.selectionStart[0]]; + if (char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + this._model.selectionStart[0]++; + } + }; + SelectionManager.prototype._onDoubleClick = function (event) { + var coords = this._getMouseBufferCoords(event); + if (coords) { + this._activeSelectionMode = 1; + this._selectWordAt(coords, true); + } + }; + SelectionManager.prototype._onTripleClick = function (event) { + var coords = this._getMouseBufferCoords(event); + if (coords) { + this._activeSelectionMode = 2; + this._selectLineAt(coords[1]); + } + }; + SelectionManager.prototype.shouldColumnSelect = function (event) { + return event.altKey && !(Browser.isMac && this._terminal.options.macOptionClickForcesSelection); + }; + SelectionManager.prototype._onMouseMove = function (event) { + event.stopImmediatePropagation(); + var previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null; + this._model.selectionEnd = this._getMouseBufferCoords(event); + if (!this._model.selectionEnd) { + this.refresh(true); + return; + } + if (this._activeSelectionMode === 2) { + if (this._model.selectionEnd[1] < this._model.selectionStart[1]) { + this._model.selectionEnd[0] = 0; + } + else { + this._model.selectionEnd[0] = this._terminal.cols; + } + } + else if (this._activeSelectionMode === 1) { + this._selectToWordAt(this._model.selectionEnd); + } + this._dragScrollAmount = this._getMouseEventScrollAmount(event); + if (this._dragScrollAmount > 0) { + this._model.selectionEnd[0] = this._terminal.cols; + } + else if (this._dragScrollAmount < 0) { + this._model.selectionEnd[0] = 0; + } + if (this._model.selectionEnd[1] < this._buffer.lines.length) { + var char = this._buffer.lines.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]]; + if (char && char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + this._model.selectionEnd[0]++; + } + } + if (!previousSelectionEnd || + previousSelectionEnd[0] !== this._model.selectionEnd[0] || + previousSelectionEnd[1] !== this._model.selectionEnd[1]) { + this.refresh(true); + } + }; + SelectionManager.prototype._dragScroll = function () { + if (this._dragScrollAmount) { + this._terminal.scrollLines(this._dragScrollAmount, false); + if (this._dragScrollAmount > 0) { + this._model.selectionEnd = [this._terminal.cols - 1, Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1)]; + } + else { + this._model.selectionEnd = [0, this._terminal.buffer.ydisp]; + } + this.refresh(); + } + }; + SelectionManager.prototype._onMouseUp = function (event) { + var timeElapsed = event.timeStamp - this._mouseDownTimeStamp; + this._removeMouseDownListeners(); + if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) { + (new AltClickHandler_1.AltClickHandler(event, this._terminal)).move(); + } + else if (this.hasSelection) { + this._terminal.emit('selection'); + } + }; + SelectionManager.prototype._onBufferActivate = function (e) { + this.clearSelection(); + e.inactiveBuffer.lines.off('trim', this._trimListener); + e.activeBuffer.lines.on('trim', this._trimListener); + }; + SelectionManager.prototype._convertViewportColToCharacterIndex = function (bufferLine, coords) { + var charIndex = coords[0]; + for (var i = 0; coords[0] >= i; i++) { + var char = bufferLine[i]; + if (char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + charIndex--; + } + else if (char[Buffer_1.CHAR_DATA_CHAR_INDEX].length > 1 && coords[0] !== i) { + charIndex += char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + } + } + return charIndex; + }; + SelectionManager.prototype.setSelection = function (col, row, length) { + this._model.clearSelection(); + this._removeMouseDownListeners(); + this._model.selectionStart = [col, row]; + this._model.selectionStartLength = length; + this.refresh(); + }; + SelectionManager.prototype._getWordAt = function (coords, allowWhitespaceOnlySelection) { + if (coords[0] >= this._terminal.cols) { + return null; + } + var bufferLine = this._buffer.lines.get(coords[1]); + if (!bufferLine) { + return null; + } + var line = this._buffer.translateBufferLineToString(coords[1], false); + var startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords); + var endIndex = startIndex; + var charOffset = coords[0] - startIndex; + var leftWideCharCount = 0; + var rightWideCharCount = 0; + var leftLongCharOffset = 0; + var rightLongCharOffset = 0; + if (line.charAt(startIndex) === ' ') { + while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') { + startIndex--; + } + while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') { + endIndex++; + } + } + else { + var startCol = coords[0]; + var endCol = coords[0]; + if (bufferLine[startCol][Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + leftWideCharCount++; + startCol--; + } + if (bufferLine[endCol][Buffer_1.CHAR_DATA_WIDTH_INDEX] === 2) { + rightWideCharCount++; + endCol++; + } + if (bufferLine[endCol][Buffer_1.CHAR_DATA_CHAR_INDEX].length > 1) { + rightLongCharOffset += bufferLine[endCol][Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + endIndex += bufferLine[endCol][Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + } + while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine[startCol - 1])) { + var char = bufferLine[startCol - 1]; + if (char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + leftWideCharCount++; + startCol--; + } + else if (char[Buffer_1.CHAR_DATA_CHAR_INDEX].length > 1) { + leftLongCharOffset += char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + startIndex -= char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + } + startIndex--; + startCol--; + } + while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine[endCol + 1])) { + var char = bufferLine[endCol + 1]; + if (char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 2) { + rightWideCharCount++; + endCol++; + } + else if (char[Buffer_1.CHAR_DATA_CHAR_INDEX].length > 1) { + rightLongCharOffset += char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + endIndex += char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + } + endIndex++; + endCol++; + } + } + endIndex++; + var start = startIndex + + charOffset + - leftWideCharCount + + leftLongCharOffset; + var length = Math.min(this._terminal.cols, endIndex + - startIndex + + leftWideCharCount + + rightWideCharCount + - leftLongCharOffset + - rightLongCharOffset); + if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') { + return null; + } + return { start: start, length: length }; + }; + SelectionManager.prototype._selectWordAt = function (coords, allowWhitespaceOnlySelection) { + var wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection); + if (wordPosition) { + this._model.selectionStart = [wordPosition.start, coords[1]]; + this._model.selectionStartLength = wordPosition.length; + } + }; + SelectionManager.prototype._selectToWordAt = function (coords) { + var wordPosition = this._getWordAt(coords, true); + if (wordPosition) { + this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; + } + }; + SelectionManager.prototype._isCharWordSeparator = function (charData) { + if (charData[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + return false; + } + return WORD_SEPARATORS.indexOf(charData[Buffer_1.CHAR_DATA_CHAR_INDEX]) >= 0; + }; + SelectionManager.prototype._selectLineAt = function (line) { + var wrappedRange = this._buffer.getWrappedRangeForLine(line); + this._model.selectionStart = [0, wrappedRange.first]; + this._model.selectionEnd = [this._terminal.cols, wrappedRange.last]; + this._model.selectionStartLength = 0; + }; + return SelectionManager; +}(EventEmitter_1.EventEmitter)); +exports.SelectionManager = SelectionManager; + +},{"./Buffer":2,"./EventEmitter":7,"./SelectionModel":11,"./handlers/AltClickHandler":21,"./shared/utils/Browser":44,"./utils/MouseHelper":51}],11:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SelectionModel = (function () { + function SelectionModel(_terminal) { + this._terminal = _terminal; + this.clearSelection(); + } + SelectionModel.prototype.clearSelection = function () { + this.selectionStart = null; + this.selectionEnd = null; + this.isSelectAllActive = false; + this.selectionStartLength = 0; + }; + Object.defineProperty(SelectionModel.prototype, "finalSelectionStart", { + get: function () { + if (this.isSelectAllActive) { + return [0, 0]; + } + if (!this.selectionEnd || !this.selectionStart) { + return this.selectionStart; + } + return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionModel.prototype, "finalSelectionEnd", { + get: function () { + if (this.isSelectAllActive) { + return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1]; + } + if (!this.selectionStart) { + return null; + } + if (!this.selectionEnd || this.areSelectionValuesReversed()) { + return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]]; + } + if (this.selectionStartLength) { + if (this.selectionEnd[1] === this.selectionStart[1]) { + return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]]; + } + } + return this.selectionEnd; + }, + enumerable: true, + configurable: true + }); + SelectionModel.prototype.areSelectionValuesReversed = function () { + var start = this.selectionStart; + var end = this.selectionEnd; + if (!start || !end) { + return false; + } + return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]); + }; + SelectionModel.prototype.onTrim = function (amount) { + if (this.selectionStart) { + this.selectionStart[1] -= amount; + } + if (this.selectionEnd) { + this.selectionEnd[1] -= amount; + } + if (this.selectionEnd && this.selectionEnd[1] < 0) { + this.clearSelection(); + return true; + } + if (this.selectionStart && this.selectionStart[1] < 0) { + this.selectionStart[1] = 0; + } + return false; + }; + return SelectionModel; +}()); +exports.SelectionModel = SelectionModel; + +},{}],12:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg=='; +var SoundManager = (function () { + function SoundManager(_terminal) { + this._terminal = _terminal; + } + SoundManager.prototype.playBellSound = function () { + var audioContextCtor = window.AudioContext || window.webkitAudioContext; + if (!this._audioContext && audioContextCtor) { + this._audioContext = new audioContextCtor(); + } + if (this._audioContext) { + var bellAudioSource_1 = this._audioContext.createBufferSource(); + var context_1 = this._audioContext; + this._audioContext.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), function (buffer) { + bellAudioSource_1.buffer = buffer; + bellAudioSource_1.connect(context_1.destination); + bellAudioSource_1.start(0); + }); + } + else { + console.warn('Sorry, but the Web Audio API is not supported by your browser. Please, consider upgrading to the latest version'); + } + }; + SoundManager.prototype._base64ToArrayBuffer = function (base64) { + var binaryString = window.atob(base64); + var len = binaryString.length; + var bytes = new Uint8Array(len); + for (var i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes.buffer; + }; + SoundManager.prototype._removeMimeType = function (dataURI) { + var splitUri = dataURI.split(','); + return splitUri[1]; + }; + return SoundManager; +}()); +exports.SoundManager = SoundManager; + +},{}],13:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.blankLine = 'Blank line'; +exports.promptLabel = 'Terminal input'; +exports.tooMuchOutput = 'Too much output to announce, navigate to rows manually to read'; + +},{}],14:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BufferSet_1 = require("./BufferSet"); +var Buffer_1 = require("./Buffer"); +var CompositionHelper_1 = require("./CompositionHelper"); +var EventEmitter_1 = require("./EventEmitter"); +var Viewport_1 = require("./Viewport"); +var Clipboard_1 = require("./handlers/Clipboard"); +var EscapeSequences_1 = require("./common/data/EscapeSequences"); +var InputHandler_1 = require("./InputHandler"); +var Renderer_1 = require("./renderer/Renderer"); +var Linkifier_1 = require("./Linkifier"); +var SelectionManager_1 = require("./SelectionManager"); +var CharMeasure_1 = require("./ui/CharMeasure"); +var Browser = require("./shared/utils/Browser"); +var Lifecycle_1 = require("./ui/Lifecycle"); +var Strings = require("./Strings"); +var MouseHelper_1 = require("./utils/MouseHelper"); +var Clone_1 = require("./utils/Clone"); +var SoundManager_1 = require("./SoundManager"); +var ColorManager_1 = require("./renderer/ColorManager"); +var MouseZoneManager_1 = require("./ui/MouseZoneManager"); +var AccessibilityManager_1 = require("./AccessibilityManager"); +var ScreenDprMonitor_1 = require("./ui/ScreenDprMonitor"); +var CharAtlasCache_1 = require("./renderer/atlas/CharAtlasCache"); +var DomRenderer_1 = require("./renderer/dom/DomRenderer"); +var Keyboard_1 = require("./core/input/Keyboard"); +var document = (typeof window !== 'undefined') ? window.document : null; +var WRITE_BUFFER_PAUSE_THRESHOLD = 5; +var WRITE_BATCH_SIZE = 300; +var CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows', 'rendererType']; +var DEFAULT_OPTIONS = { + cols: 80, + rows: 24, + convertEol: false, + termName: 'xterm', + cursorBlink: false, + cursorStyle: 'block', + bellSound: SoundManager_1.DEFAULT_BELL_SOUND, + bellStyle: 'none', + drawBoldTextInBrightColors: true, + enableBold: true, + experimentalCharAtlas: 'static', + fontFamily: 'courier-new, courier, monospace', + fontSize: 15, + fontWeight: 'normal', + fontWeightBold: 'bold', + lineHeight: 1.0, + letterSpacing: 0, + scrollback: 1000, + screenKeys: false, + screenReaderMode: false, + debug: false, + macOptionIsMeta: false, + macOptionClickForcesSelection: false, + cancelEvents: false, + disableStdin: false, + useFlowControl: false, + allowTransparency: false, + tabStopWidth: 8, + theme: null, + rightClickSelectsWord: Browser.isMac, + rendererType: 'canvas' +}; +var Terminal = (function (_super) { + __extends(Terminal, _super); + function Terminal(options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this) || this; + _this.browser = Browser; + _this.options = Clone_1.clone(options); + _this._setup(); + return _this; + } + Terminal.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._customKeyEventHandler = null; + CharAtlasCache_1.removeTerminalFromCache(this); + this.handler = function () { }; + this.write = function () { }; + if (this.element && this.element.parentNode) { + this.element.parentNode.removeChild(this.element); + } + }; + Terminal.prototype.destroy = function () { + this.dispose(); + }; + Terminal.prototype._setup = function () { + var _this = this; + Object.keys(DEFAULT_OPTIONS).forEach(function (key) { + if (_this.options[key] == null) { + _this.options[key] = DEFAULT_OPTIONS[key]; + } + }); + this._parent = document ? document.body : null; + this.cols = this.options.cols; + this.rows = this.options.rows; + if (this.options.handler) { + this.on('data', this.options.handler); + } + this.cursorState = 0; + this.cursorHidden = false; + this._sendDataQueue = ''; + this._customKeyEventHandler = null; + this.applicationKeypad = false; + this.applicationCursor = false; + this.originMode = false; + this.insertMode = false; + this.wraparoundMode = true; + this.bracketedPasteMode = false; + this.charset = null; + this.gcharset = null; + this.glevel = 0; + this.charsets = [null]; + this.curAttr = Buffer_1.DEFAULT_ATTR; + this.params = []; + this.currentParam = 0; + this.writeBuffer = []; + this._writeInProgress = false; + this._xoffSentToCatchUp = false; + this._userScrolling = false; + this._inputHandler = new InputHandler_1.InputHandler(this); + this.register(this._inputHandler); + this.renderer = this.renderer || null; + this.selectionManager = this.selectionManager || null; + this.linkifier = this.linkifier || new Linkifier_1.Linkifier(this); + this._mouseZoneManager = this._mouseZoneManager || null; + this.soundManager = this.soundManager || new SoundManager_1.SoundManager(this); + this.buffers = new BufferSet_1.BufferSet(this); + if (this.selectionManager) { + this.selectionManager.clearSelection(); + this.selectionManager.initBuffersListeners(); + } + }; + Object.defineProperty(Terminal.prototype, "buffer", { + get: function () { + return this.buffers.active; + }, + enumerable: true, + configurable: true + }); + Terminal.prototype.eraseAttr = function () { + return (Buffer_1.DEFAULT_ATTR & ~0x1ff) | (this.curAttr & 0x1ff); + }; + Terminal.prototype.focus = function () { + if (this.textarea) { + this.textarea.focus(); + } + }; + Object.defineProperty(Terminal.prototype, "isFocused", { + get: function () { + return document.activeElement === this.textarea; + }, + enumerable: true, + configurable: true + }); + Terminal.prototype.getOption = function (key) { + if (!(key in DEFAULT_OPTIONS)) { + throw new Error('No option with key "' + key + '"'); + } + return this.options[key]; + }; + Terminal.prototype.setOption = function (key, value) { + if (!(key in DEFAULT_OPTIONS)) { + throw new Error('No option with key "' + key + '"'); + } + if (CONSTRUCTOR_ONLY_OPTIONS.indexOf(key) !== -1) { + console.error("Option \"" + key + "\" can only be set in the constructor"); + } + switch (key) { + case 'bellStyle': + if (!value) { + value = 'none'; + } + break; + case 'cursorStyle': + if (!value) { + value = 'block'; + } + break; + case 'fontWeight': + if (!value) { + value = 'normal'; + } + break; + case 'fontWeightBold': + if (!value) { + value = 'bold'; + } + break; + case 'lineHeight': + if (value < 1) { + console.warn(key + " cannot be less than 1, value: " + value); + return; + } + case 'tabStopWidth': + if (value < 1) { + console.warn(key + " cannot be less than 1, value: " + value); + return; + } + break; + case 'theme': + if (this.renderer) { + this._setTheme(value); + return; + } + break; + case 'scrollback': + value = Math.min(value, Buffer_1.MAX_BUFFER_SIZE); + if (value < 0) { + console.warn(key + " cannot be less than 0, value: " + value); + return; + } + if (this.options[key] !== value) { + var newBufferLength = this.rows + value; + if (this.buffer.lines.length > newBufferLength) { + var amountToTrim = this.buffer.lines.length - newBufferLength; + var needsRefresh = (this.buffer.ydisp - amountToTrim < 0); + this.buffer.lines.trimStart(amountToTrim); + this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0); + this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0); + if (needsRefresh) { + this.refresh(0, this.rows - 1); + } + } + } + break; + } + this.options[key] = value; + switch (key) { + case 'fontFamily': + case 'fontSize': + if (this.renderer) { + this.renderer.clear(); + this.charMeasure.measure(this.options); + } + break; + case 'drawBoldTextInBrightColors': + case 'experimentalCharAtlas': + case 'enableBold': + case 'letterSpacing': + case 'lineHeight': + case 'fontWeight': + case 'fontWeightBold': + if (this.renderer) { + this.renderer.clear(); + this.renderer.onResize(this.cols, this.rows); + this.refresh(0, this.rows - 1); + } + case 'scrollback': + this.buffers.resize(this.cols, this.rows); + if (this.viewport) { + this.viewport.syncScrollArea(); + } + break; + case 'screenReaderMode': + if (value) { + if (!this._accessibilityManager) { + this._accessibilityManager = new AccessibilityManager_1.AccessibilityManager(this); + } + } + else { + if (this._accessibilityManager) { + this._accessibilityManager.dispose(); + this._accessibilityManager = null; + } + } + break; + case 'tabStopWidth': + this.buffers.setupTabStops(); + break; + } + if (this.renderer) { + this.renderer.onOptionsChanged(); + } + }; + Terminal.prototype._onTextAreaFocus = function () { + if (this.sendFocus) { + this.send(EscapeSequences_1.C0.ESC + '[I'); + } + this.element.classList.add('focus'); + this.showCursor(); + this.emit('focus'); + }; + Terminal.prototype.blur = function () { + return this.textarea.blur(); + }; + Terminal.prototype._onTextAreaBlur = function () { + this.textarea.value = ''; + this.refresh(this.buffer.y, this.buffer.y); + if (this.sendFocus) { + this.send(EscapeSequences_1.C0.ESC + '[O'); + } + this.element.classList.remove('focus'); + this.emit('blur'); + }; + Terminal.prototype._initGlobal = function () { + var _this = this; + this._bindKeys(); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'copy', function (event) { + if (!_this.hasSelection()) { + return; + } + Clipboard_1.copyHandler(event, _this, _this.selectionManager); + })); + var pasteHandlerWrapper = function (event) { return Clipboard_1.pasteHandler(event, _this); }; + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper)); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper)); + if (Browser.isFirefox) { + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'mousedown', function (event) { + if (event.button === 2) { + Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager, _this.options.rightClickSelectsWord); + } + })); + } + else { + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'contextmenu', function (event) { + Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager, _this.options.rightClickSelectsWord); + })); + } + if (Browser.isLinux) { + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'auxclick', function (event) { + if (event.button === 1) { + Clipboard_1.moveTextAreaUnderMouseCursor(event, _this.textarea); + } + })); + } + }; + Terminal.prototype._bindKeys = function () { + var _this = this; + var self = this; + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'keydown', function (ev) { + if (document.activeElement !== this) { + return; + } + self._keyDown(ev); + }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'keypress', function (ev) { + if (document.activeElement !== this) { + return; + } + self._keyPress(ev); + }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'keyup', function (ev) { + if (!wasModifierKeyOnlyEvent(ev)) { + _this.focus(); + } + self._keyUp(ev); + }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'keydown', function (ev) { return _this._keyDown(ev); }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'keypress', function (ev) { return _this._keyPress(ev); }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'compositionstart', function () { return _this._compositionHelper.compositionstart(); })); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'compositionupdate', function (e) { return _this._compositionHelper.compositionupdate(e); })); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'compositionend', function () { return _this._compositionHelper.compositionend(); })); + this.register(this.addDisposableListener('refresh', function () { return _this._compositionHelper.updateCompositionElements(); })); + this.register(this.addDisposableListener('refresh', function (data) { return _this._queueLinkification(data.start, data.end); })); + }; + Terminal.prototype.open = function (parent) { + var _this = this; + this._parent = parent || this._parent; + if (!this._parent) { + throw new Error('Terminal requires a parent element.'); + } + this._context = this._parent.ownerDocument.defaultView; + this._document = this._parent.ownerDocument; + this._screenDprMonitor = new ScreenDprMonitor_1.ScreenDprMonitor(); + this._screenDprMonitor.setListener(function () { return _this.emit('dprchange', window.devicePixelRatio); }); + this.register(this._screenDprMonitor); + this.element = this._document.createElement('div'); + this.element.dir = 'ltr'; + this.element.classList.add('terminal'); + this.element.classList.add('xterm'); + this.element.setAttribute('tabindex', '0'); + this._parent.appendChild(this.element); + var fragment = document.createDocumentFragment(); + this._viewportElement = document.createElement('div'); + this._viewportElement.classList.add('xterm-viewport'); + fragment.appendChild(this._viewportElement); + this._viewportScrollArea = document.createElement('div'); + this._viewportScrollArea.classList.add('xterm-scroll-area'); + this._viewportElement.appendChild(this._viewportScrollArea); + this.screenElement = document.createElement('div'); + this.screenElement.classList.add('xterm-screen'); + this._helperContainer = document.createElement('div'); + this._helperContainer.classList.add('xterm-helpers'); + this.screenElement.appendChild(this._helperContainer); + fragment.appendChild(this.screenElement); + this._mouseZoneManager = new MouseZoneManager_1.MouseZoneManager(this); + this.register(this._mouseZoneManager); + this.register(this.addDisposableListener('scroll', function () { return _this._mouseZoneManager.clearAll(); })); + this.linkifier.attachToDom(this._mouseZoneManager); + this.textarea = document.createElement('textarea'); + this.textarea.classList.add('xterm-helper-textarea'); + this.textarea.setAttribute('aria-label', Strings.promptLabel); + this.textarea.setAttribute('aria-multiline', 'false'); + this.textarea.setAttribute('autocorrect', 'off'); + this.textarea.setAttribute('autocapitalize', 'off'); + this.textarea.setAttribute('spellcheck', 'false'); + this.textarea.tabIndex = 0; + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'focus', function () { return _this._onTextAreaFocus(); })); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'blur', function () { return _this._onTextAreaBlur(); })); + this._helperContainer.appendChild(this.textarea); + this._compositionView = document.createElement('div'); + this._compositionView.classList.add('composition-view'); + this._compositionHelper = new CompositionHelper_1.CompositionHelper(this.textarea, this._compositionView, this); + this._helperContainer.appendChild(this._compositionView); + this.charMeasure = new CharMeasure_1.CharMeasure(document, this._helperContainer); + this.element.appendChild(fragment); + switch (this.options.rendererType) { + case 'canvas': + this.renderer = new Renderer_1.Renderer(this, this.options.theme); + break; + case 'dom': + this.renderer = new DomRenderer_1.DomRenderer(this, this.options.theme); + break; + default: throw new Error("Unrecognized rendererType \"" + this.options.rendererType + "\""); + } + this.register(this.renderer); + this.options.theme = null; + this.viewport = new Viewport_1.Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure); + this.viewport.onThemeChanged(this.renderer.colorManager.colors); + this.register(this.viewport); + this.register(this.addDisposableListener('cursormove', function () { return _this.renderer.onCursorMove(); })); + this.register(this.addDisposableListener('resize', function () { return _this.renderer.onResize(_this.cols, _this.rows); })); + this.register(this.addDisposableListener('blur', function () { return _this.renderer.onBlur(); })); + this.register(this.addDisposableListener('focus', function () { return _this.renderer.onFocus(); })); + this.register(this.addDisposableListener('dprchange', function () { return _this.renderer.onWindowResize(window.devicePixelRatio); })); + this.register(Lifecycle_1.addDisposableDomListener(window, 'resize', function () { return _this.renderer.onWindowResize(window.devicePixelRatio); })); + this.register(this.charMeasure.addDisposableListener('charsizechanged', function () { return _this.renderer.onCharSizeChanged(); })); + this.register(this.renderer.addDisposableListener('resize', function (dimensions) { return _this.viewport.syncScrollArea(); })); + this.selectionManager = new SelectionManager_1.SelectionManager(this, this.charMeasure); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'mousedown', function (e) { return _this.selectionManager.onMouseDown(e); })); + this.register(this.selectionManager.addDisposableListener('refresh', function (data) { return _this.renderer.onSelectionChanged(data.start, data.end, data.columnSelectMode); })); + this.register(this.selectionManager.addDisposableListener('newselection', function (text) { + _this.textarea.value = text; + _this.textarea.focus(); + _this.textarea.select(); + })); + this.register(this.addDisposableListener('scroll', function () { + _this.viewport.syncScrollArea(); + _this.selectionManager.refresh(); + })); + this.register(Lifecycle_1.addDisposableDomListener(this._viewportElement, 'scroll', function () { return _this.selectionManager.refresh(); })); + this.mouseHelper = new MouseHelper_1.MouseHelper(this.renderer); + if (this.options.screenReaderMode) { + this._accessibilityManager = new AccessibilityManager_1.AccessibilityManager(this); + } + this.charMeasure.measure(this.options); + this.refresh(0, this.rows - 1); + this._initGlobal(); + this.bindMouse(); + }; + Terminal.prototype._setTheme = function (theme) { + var colors = this.renderer.setTheme(theme); + if (this.viewport) { + this.viewport.onThemeChanged(colors); + } + }; + Terminal.prototype.bindMouse = function () { + var _this = this; + var el = this.element; + var self = this; + var pressed = 32; + function sendButton(ev) { + var button; + var pos; + button = getButton(ev); + pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + if (!pos) + return; + sendEvent(button, pos); + switch (ev.overrideType || ev.type) { + case 'mousedown': + pressed = button; + break; + case 'mouseup': + pressed = 32; + break; + case 'wheel': + break; + } + } + function sendMove(ev) { + var button = pressed; + var pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + if (!pos) + return; + button += 32; + sendEvent(button, pos); + } + function encode(data, ch) { + if (!self.utfMouse) { + if (ch === 255) { + data.push(0); + return; + } + if (ch > 127) + ch = 127; + data.push(ch); + } + else { + if (ch === 2047) { + data.push(0); + return; + } + if (ch < 127) { + data.push(ch); + } + else { + if (ch > 2047) + ch = 2047; + data.push(0xC0 | (ch >> 6)); + data.push(0x80 | (ch & 0x3F)); + } + } + } + function sendEvent(button, pos) { + if (self._vt300Mouse) { + button &= 3; + pos.x -= 32; + pos.y -= 32; + var data_1 = EscapeSequences_1.C0.ESC + '[24'; + if (button === 0) + data_1 += '1'; + else if (button === 1) + data_1 += '3'; + else if (button === 2) + data_1 += '5'; + else if (button === 3) + return; + else + data_1 += '0'; + data_1 += '~[' + pos.x + ',' + pos.y + ']\r'; + self.send(data_1); + return; + } + if (self._decLocator) { + button &= 3; + pos.x -= 32; + pos.y -= 32; + if (button === 0) + button = 2; + else if (button === 1) + button = 4; + else if (button === 2) + button = 6; + else if (button === 3) + button = 3; + self.send(EscapeSequences_1.C0.ESC + '[' + + button + + ';' + + (button === 3 ? 4 : 0) + + ';' + + pos.y + + ';' + + pos.x + + ';' + + pos.page || 0 + + '&w'); + return; + } + if (self.urxvtMouse) { + pos.x -= 32; + pos.y -= 32; + pos.x++; + pos.y++; + self.send(EscapeSequences_1.C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); + return; + } + if (self.sgrMouse) { + pos.x -= 32; + pos.y -= 32; + self.send(EscapeSequences_1.C0.ESC + '[<' + + (((button & 3) === 3 ? button & ~3 : button) - 32) + + ';' + + pos.x + + ';' + + pos.y + + ((button & 3) === 3 ? 'm' : 'M')); + return; + } + var data = []; + encode(data, button); + encode(data, pos.x); + encode(data, pos.y); + self.send(EscapeSequences_1.C0.ESC + '[M' + String.fromCharCode.apply(String, data)); + } + function getButton(ev) { + var button; + var shift; + var meta; + var ctrl; + var mod; + switch (ev.overrideType || ev.type) { + case 'mousedown': + button = ev.button != null + ? +ev.button + : ev.which != null + ? ev.which - 1 + : null; + if (Browser.isMSIE) { + button = button === 1 ? 0 : button === 4 ? 1 : button; + } + break; + case 'mouseup': + button = 3; + break; + case 'DOMMouseScroll': + button = ev.detail < 0 + ? 64 + : 65; + break; + case 'wheel': + button = ev.wheelDeltaY > 0 + ? 64 + : 65; + break; + } + shift = ev.shiftKey ? 4 : 0; + meta = ev.metaKey ? 8 : 0; + ctrl = ev.ctrlKey ? 16 : 0; + mod = shift | meta | ctrl; + if (self.vt200Mouse) { + mod &= ctrl; + } + else if (!self.normalMouse) { + mod = 0; + } + button = (32 + (mod << 2)) + button; + return button; + } + this.register(Lifecycle_1.addDisposableDomListener(el, 'mousedown', function (ev) { + ev.preventDefault(); + _this.focus(); + if (!_this.mouseEvents || _this.selectionManager.shouldForceSelection(ev)) { + return; + } + sendButton(ev); + if (_this.vt200Mouse) { + ev.overrideType = 'mouseup'; + sendButton(ev); + return _this.cancel(ev); + } + var moveHandler; + if (_this.normalMouse) { + moveHandler = function (event) { + if (!_this.normalMouse) { + return; + } + sendMove(event); + }; + _this._document.addEventListener('mousemove', moveHandler); + } + var handler = function (ev) { + if (_this.normalMouse && !_this.x10Mouse) { + sendButton(ev); + } + if (moveHandler) { + _this._document.removeEventListener('mousemove', moveHandler); + moveHandler = null; + } + _this._document.removeEventListener('mouseup', handler); + return _this.cancel(ev); + }; + _this._document.addEventListener('mouseup', handler); + return _this.cancel(ev); + })); + this.register(Lifecycle_1.addDisposableDomListener(el, 'wheel', function (ev) { + if (!_this.mouseEvents) { + if (!_this.buffer.hasScrollback) { + var amount = _this.viewport.getLinesScrolled(ev); + if (amount === 0) { + return; + } + var sequence = EscapeSequences_1.C0.ESC + (_this.applicationCursor ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B'); + var data = ''; + for (var i = 0; i < Math.abs(amount); i++) { + data += sequence; + } + _this.send(data); + } + return; + } + if (_this.x10Mouse || _this._vt300Mouse || _this._decLocator) + return; + sendButton(ev); + ev.preventDefault(); + })); + this.register(Lifecycle_1.addDisposableDomListener(el, 'wheel', function (ev) { + if (_this.mouseEvents) + return; + _this.viewport.onWheel(ev); + return _this.cancel(ev); + })); + this.register(Lifecycle_1.addDisposableDomListener(el, 'touchstart', function (ev) { + if (_this.mouseEvents) + return; + _this.viewport.onTouchStart(ev); + return _this.cancel(ev); + })); + this.register(Lifecycle_1.addDisposableDomListener(el, 'touchmove', function (ev) { + if (_this.mouseEvents) + return; + _this.viewport.onTouchMove(ev); + return _this.cancel(ev); + })); + }; + Terminal.prototype.refresh = function (start, end) { + if (this.renderer) { + this.renderer.refreshRows(start, end); + } + }; + Terminal.prototype._queueLinkification = function (start, end) { + if (this.linkifier) { + this.linkifier.linkifyRows(start, end); + } + }; + Terminal.prototype.updateCursorStyle = function (ev) { + if (this.selectionManager && this.selectionManager.shouldColumnSelect(ev)) { + this.element.classList.add('xterm-cursor-crosshair'); + } + else { + this.element.classList.remove('xterm-cursor-crosshair'); + } + }; + Terminal.prototype.showCursor = function () { + if (!this.cursorState) { + this.cursorState = 1; + this.refresh(this.buffer.y, this.buffer.y); + } + }; + Terminal.prototype.scroll = function (isWrapped) { + var newLine = this.blankLine(undefined, isWrapped); + var topRow = this.buffer.ybase + this.buffer.scrollTop; + var bottomRow = this.buffer.ybase + this.buffer.scrollBottom; + if (this.buffer.scrollTop === 0) { + var willBufferBeTrimmed = this.buffer.lines.length === this.buffer.lines.maxLength; + if (bottomRow === this.buffer.lines.length - 1) { + this.buffer.lines.push(newLine); + } + else { + this.buffer.lines.splice(bottomRow + 1, 0, newLine); + } + if (!willBufferBeTrimmed) { + this.buffer.ybase++; + if (!this._userScrolling) { + this.buffer.ydisp++; + } + } + else { + if (this._userScrolling) { + this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); + } + } + } + else { + var scrollRegionHeight = bottomRow - topRow + 1; + this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); + this.buffer.lines.set(bottomRow, newLine); + } + if (!this._userScrolling) { + this.buffer.ydisp = this.buffer.ybase; + } + this.updateRange(this.buffer.scrollTop); + this.updateRange(this.buffer.scrollBottom); + this.emit('scroll', this.buffer.ydisp); + }; + Terminal.prototype.scrollLines = function (disp, suppressScrollEvent) { + if (disp < 0) { + if (this.buffer.ydisp === 0) { + return; + } + this._userScrolling = true; + } + else if (disp + this.buffer.ydisp >= this.buffer.ybase) { + this._userScrolling = false; + } + var oldYdisp = this.buffer.ydisp; + this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0); + if (oldYdisp === this.buffer.ydisp) { + return; + } + if (!suppressScrollEvent) { + this.emit('scroll', this.buffer.ydisp); + } + this.refresh(0, this.rows - 1); + }; + Terminal.prototype.scrollPages = function (pageCount) { + this.scrollLines(pageCount * (this.rows - 1)); + }; + Terminal.prototype.scrollToTop = function () { + this.scrollLines(-this.buffer.ydisp); + }; + Terminal.prototype.scrollToBottom = function () { + this.scrollLines(this.buffer.ybase - this.buffer.ydisp); + }; + Terminal.prototype.scrollToLine = function (line) { + var scrollAmount = line - this.buffer.ydisp; + if (scrollAmount !== 0) { + this.scrollLines(scrollAmount); + } + }; + Terminal.prototype.write = function (data) { + var _this = this; + if (!data) { + return; + } + this.writeBuffer.push(data); + if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { + this.send(EscapeSequences_1.C0.DC3); + this._xoffSentToCatchUp = true; + } + if (!this._writeInProgress && this.writeBuffer.length > 0) { + this._writeInProgress = true; + setTimeout(function () { + _this._innerWrite(); + }); + } + }; + Terminal.prototype._innerWrite = function () { + var _this = this; + var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); + while (writeBatch.length > 0) { + var data = writeBatch.shift(); + if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { + this.send(EscapeSequences_1.C0.DC1); + this._xoffSentToCatchUp = false; + } + this._refreshStart = this.buffer.y; + this._refreshEnd = this.buffer.y; + this._inputHandler.parse(data); + this.updateRange(this.buffer.y); + this.refresh(this._refreshStart, this._refreshEnd); + } + if (this.writeBuffer.length > 0) { + setTimeout(function () { return _this._innerWrite(); }, 0); + } + else { + this._writeInProgress = false; + } + }; + Terminal.prototype.writeln = function (data) { + this.write(data + '\r\n'); + }; + Terminal.prototype.attachCustomKeyEventHandler = function (customKeyEventHandler) { + this._customKeyEventHandler = customKeyEventHandler; + }; + Terminal.prototype.registerLinkMatcher = function (regex, handler, options) { + var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); + this.refresh(0, this.rows - 1); + return matcherId; + }; + Terminal.prototype.deregisterLinkMatcher = function (matcherId) { + if (this.linkifier.deregisterLinkMatcher(matcherId)) { + this.refresh(0, this.rows - 1); + } + }; + Object.defineProperty(Terminal.prototype, "markers", { + get: function () { + return this.buffer.markers; + }, + enumerable: true, + configurable: true + }); + Terminal.prototype.addMarker = function (cursorYOffset) { + if (this.buffer !== this.buffers.normal) { + return; + } + return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); + }; + Terminal.prototype.hasSelection = function () { + return this.selectionManager ? this.selectionManager.hasSelection : false; + }; + Terminal.prototype.getSelection = function () { + return this.selectionManager ? this.selectionManager.selectionText : ''; + }; + Terminal.prototype.clearSelection = function () { + if (this.selectionManager) { + this.selectionManager.clearSelection(); + } + }; + Terminal.prototype.selectAll = function () { + if (this.selectionManager) { + this.selectionManager.selectAll(); + } + }; + Terminal.prototype.selectLines = function (start, end) { + if (this.selectionManager) { + this.selectionManager.selectLines(start, end); + } + }; + Terminal.prototype._keyDown = function (event) { + if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) { + return false; + } + if (!this._compositionHelper.keydown(event)) { + if (this.buffer.ybase !== this.buffer.ydisp) { + this.scrollToBottom(); + } + return false; + } + var result = Keyboard_1.evaluateKeyboardEvent(event, this.applicationCursor, this.browser.isMac, this.options.macOptionIsMeta); + this.updateCursorStyle(event); + if (result.type === 3 || result.type === 2) { + var scrollCount = this.rows - 1; + this.scrollLines(result.type === 2 ? -scrollCount : scrollCount); + return this.cancel(event, true); + } + if (result.type === 1) { + this.selectAll(); + } + if (this._isThirdLevelShift(this.browser, event)) { + return true; + } + if (result.cancel) { + this.cancel(event, true); + } + if (!result.key) { + return true; + } + this.emit('keydown', event); + this.emit('key', result.key, event); + this.showCursor(); + this.handler(result.key); + return this.cancel(event, true); + }; + Terminal.prototype._isThirdLevelShift = function (browser, ev) { + var thirdLevelKey = (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) || + (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); + if (ev.type === 'keypress') { + return thirdLevelKey; + } + return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); + }; + Terminal.prototype.setgLevel = function (g) { + this.glevel = g; + this.charset = this.charsets[g]; + }; + Terminal.prototype.setgCharset = function (g, charset) { + this.charsets[g] = charset; + if (this.glevel === g) { + this.charset = charset; + } + }; + Terminal.prototype._keyUp = function (ev) { + this.updateCursorStyle(ev); + }; + Terminal.prototype._keyPress = function (ev) { + var key; + if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) { + return false; + } + this.cancel(ev); + if (ev.charCode) { + key = ev.charCode; + } + else if (ev.which == null) { + key = ev.keyCode; + } + else if (ev.which !== 0 && ev.charCode !== 0) { + key = ev.which; + } + else { + return false; + } + if (!key || ((ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev))) { + return false; + } + key = String.fromCharCode(key); + this.emit('keypress', key, ev); + this.emit('key', key, ev); + this.showCursor(); + this.handler(key); + return true; + }; + Terminal.prototype.send = function (data) { + var _this = this; + if (!this._sendDataQueue) { + setTimeout(function () { + _this.handler(_this._sendDataQueue); + _this._sendDataQueue = ''; + }, 1); + } + this._sendDataQueue += data; + }; + Terminal.prototype.bell = function () { + var _this = this; + this.emit('bell'); + if (this._soundBell()) { + this.soundManager.playBellSound(); + } + if (this._visualBell()) { + this.element.classList.add('visual-bell-active'); + clearTimeout(this._visualBellTimer); + this._visualBellTimer = window.setTimeout(function () { + _this.element.classList.remove('visual-bell-active'); + }, 200); + } + }; + Terminal.prototype.log = function (text, data) { + if (!this.options.debug) + return; + if (!this._context.console || !this._context.console.log) + return; + this._context.console.log(text, data); + }; + Terminal.prototype.error = function (text, data) { + if (!this.options.debug) + return; + if (!this._context.console || !this._context.console.error) + return; + this._context.console.error(text, data); + }; + Terminal.prototype.resize = function (x, y) { + if (isNaN(x) || isNaN(y)) { + return; + } + if (x === this.cols && y === this.rows) { + if (this.charMeasure && (!this.charMeasure.width || !this.charMeasure.height)) { + this.charMeasure.measure(this.options); + } + return; + } + if (x < 1) + x = 1; + if (y < 1) + y = 1; + this.buffers.resize(x, y); + this.cols = x; + this.rows = y; + this.buffers.setupTabStops(this.cols); + if (this.charMeasure) { + this.charMeasure.measure(this.options); + } + this.refresh(0, this.rows - 1); + this.emit('resize', { cols: x, rows: y }); + }; + Terminal.prototype.updateRange = function (y) { + if (y < this._refreshStart) + this._refreshStart = y; + if (y > this._refreshEnd) + this._refreshEnd = y; + }; + Terminal.prototype.maxRange = function () { + this._refreshStart = 0; + this._refreshEnd = this.rows - 1; + }; + Terminal.prototype.eraseRight = function (x, y) { + var line = this.buffer.lines.get(this.buffer.ybase + y); + if (!line) { + return; + } + var ch = [this.eraseAttr(), ' ', 1, 32]; + for (; x < this.cols; x++) { + line[x] = ch; + } + this.updateRange(y); + }; + Terminal.prototype.eraseLeft = function (x, y) { + var line = this.buffer.lines.get(this.buffer.ybase + y); + if (!line) { + return; + } + var ch = [this.eraseAttr(), ' ', 1, 32]; + x++; + while (x--) { + line[x] = ch; + } + this.updateRange(y); + }; + Terminal.prototype.clear = function () { + if (this.buffer.ybase === 0 && this.buffer.y === 0) { + return; + } + this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)); + this.buffer.lines.length = 1; + this.buffer.ydisp = 0; + this.buffer.ybase = 0; + this.buffer.y = 0; + for (var i = 1; i < this.rows; i++) { + this.buffer.lines.push(this.blankLine()); + } + this.refresh(0, this.rows - 1); + this.emit('scroll', this.buffer.ydisp); + }; + Terminal.prototype.eraseLine = function (y) { + this.eraseRight(0, y); + }; + Terminal.prototype.blankLine = function (cur, isWrapped, cols) { + var attr = cur ? this.eraseAttr() : Buffer_1.DEFAULT_ATTR; + var ch = [attr, ' ', 1, 32]; + var line = []; + if (isWrapped) { + line.isWrapped = isWrapped; + } + cols = cols || this.cols; + for (var i = 0; i < cols; i++) { + line[i] = ch; + } + return line; + }; + Terminal.prototype.ch = function (cur) { + if (cur) { + return [this.eraseAttr(), ' ', 1, 32]; + } + return [Buffer_1.DEFAULT_ATTR, ' ', 1, 32]; + }; + Terminal.prototype.is = function (term) { + return (this.options.termName + '').indexOf(term) === 0; + }; + Terminal.prototype.handler = function (data) { + if (this.options.disableStdin) { + return; + } + if (this.selectionManager && this.selectionManager.hasSelection) { + this.selectionManager.clearSelection(); + } + if (this.buffer.ybase !== this.buffer.ydisp) { + this.scrollToBottom(); + } + this.emit('data', data); + }; + Terminal.prototype.handleTitle = function (title) { + this.emit('title', title); + }; + Terminal.prototype.index = function () { + this.buffer.y++; + if (this.buffer.y > this.buffer.scrollBottom) { + this.buffer.y--; + this.scroll(); + } + if (this.buffer.x >= this.cols) { + this.buffer.x--; + } + }; + Terminal.prototype.reverseIndex = function () { + if (this.buffer.y === this.buffer.scrollTop) { + var scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop; + this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1); + this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true)); + this.updateRange(this.buffer.scrollTop); + this.updateRange(this.buffer.scrollBottom); + } + else { + this.buffer.y--; + } + }; + Terminal.prototype.reset = function () { + this.options.rows = this.rows; + this.options.cols = this.cols; + var customKeyEventHandler = this._customKeyEventHandler; + var inputHandler = this._inputHandler; + var cursorState = this.cursorState; + this._setup(); + this._customKeyEventHandler = customKeyEventHandler; + this._inputHandler = inputHandler; + this.cursorState = cursorState; + this.refresh(0, this.rows - 1); + if (this.viewport) { + this.viewport.syncScrollArea(); + } + }; + Terminal.prototype.tabSet = function () { + this.buffer.tabs[this.buffer.x] = true; + }; + Terminal.prototype.cancel = function (ev, force) { + if (!this.options.cancelEvents && !force) { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + return false; + }; + Terminal.prototype.matchColor = function (r1, g1, b1) { + var hash = (r1 << 16) | (g1 << 8) | b1; + if (matchColorCache[hash] != null) { + return matchColorCache[hash]; + } + var ldiff = Infinity; + var li = -1; + var i = 0; + var c; + var r2; + var g2; + var b2; + var diff; + for (; i < ColorManager_1.DEFAULT_ANSI_COLORS.length; i++) { + c = ColorManager_1.DEFAULT_ANSI_COLORS[i].rgba; + r2 = c >>> 24; + g2 = c >>> 16 & 0xFF; + b2 = c >>> 8 & 0xFF; + diff = matchColorDistance(r1, g1, b1, r2, g2, b2); + if (diff === 0) { + li = i; + break; + } + if (diff < ldiff) { + ldiff = diff; + li = i; + } + } + return matchColorCache[hash] = li; + }; + Terminal.prototype._visualBell = function () { + return false; + }; + Terminal.prototype._soundBell = function () { + return this.options.bellStyle === 'sound'; + }; + return Terminal; +}(EventEmitter_1.EventEmitter)); +exports.Terminal = Terminal; +function wasModifierKeyOnlyEvent(ev) { + return ev.keyCode === 16 || + ev.keyCode === 17 || + ev.keyCode === 18; +} +var matchColorCache = {}; +function matchColorDistance(r1, g1, b1, r2, g2, b2) { + return Math.pow(30 * (r1 - r2), 2) + + Math.pow(59 * (g1 - g2), 2) + + Math.pow(11 * (b1 - b2), 2); +} + +},{"./AccessibilityManager":1,"./Buffer":2,"./BufferSet":3,"./CompositionHelper":5,"./EventEmitter":7,"./InputHandler":8,"./Linkifier":9,"./SelectionManager":10,"./SoundManager":12,"./Strings":13,"./Viewport":15,"./common/data/EscapeSequences":18,"./core/input/Keyboard":20,"./handlers/Clipboard":22,"./renderer/ColorManager":25,"./renderer/Renderer":29,"./renderer/atlas/CharAtlasCache":33,"./renderer/dom/DomRenderer":40,"./shared/utils/Browser":44,"./ui/CharMeasure":45,"./ui/Lifecycle":46,"./ui/MouseZoneManager":47,"./ui/ScreenDprMonitor":49,"./utils/Clone":50,"./utils/MouseHelper":51}],15:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("./common/Lifecycle"); +var Lifecycle_2 = require("./ui/Lifecycle"); +var FALLBACK_SCROLL_BAR_WIDTH = 15; +var Viewport = (function (_super) { + __extends(Viewport, _super); + function Viewport(_terminal, _viewportElement, _scrollArea, _charMeasure) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._viewportElement = _viewportElement; + _this._scrollArea = _scrollArea; + _this._charMeasure = _charMeasure; + _this.scrollBarWidth = 0; + _this._currentRowHeight = 0; + _this._lastRecordedBufferLength = 0; + _this._lastRecordedViewportHeight = 0; + _this._lastRecordedBufferHeight = 0; + _this._wheelPartialScroll = 0; + _this.scrollBarWidth = (_this._viewportElement.offsetWidth - _this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; + _this.register(Lifecycle_2.addDisposableDomListener(_this._viewportElement, 'scroll', _this._onScroll.bind(_this))); + setTimeout(function () { return _this.syncScrollArea(); }, 0); + return _this; + } + Viewport.prototype.onThemeChanged = function (colors) { + this._viewportElement.style.backgroundColor = colors.background.css; + }; + Viewport.prototype._refresh = function () { + if (this._charMeasure.height > 0) { + this._currentRowHeight = this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio; + this._lastRecordedViewportHeight = this._viewportElement.offsetHeight; + var newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._terminal.renderer.dimensions.canvasHeight); + if (this._lastRecordedBufferHeight !== newBufferHeight) { + this._lastRecordedBufferHeight = newBufferHeight; + this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px'; + } + } + }; + Viewport.prototype.syncScrollArea = function () { + if (this._lastRecordedBufferLength !== this._terminal.buffer.lines.length) { + this._lastRecordedBufferLength = this._terminal.buffer.lines.length; + this._refresh(); + } + else if (this._lastRecordedViewportHeight !== this._terminal.renderer.dimensions.canvasHeight) { + this._refresh(); + } + else { + if (this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) { + this._refresh(); + } + } + var scrollTop = this._terminal.buffer.ydisp * this._currentRowHeight; + if (this._viewportElement.scrollTop !== scrollTop) { + this._viewportElement.scrollTop = scrollTop; + } + }; + Viewport.prototype._onScroll = function (ev) { + if (!this._viewportElement.offsetParent) { + return; + } + var newRow = Math.round(this._viewportElement.scrollTop / this._currentRowHeight); + var diff = newRow - this._terminal.buffer.ydisp; + this._terminal.scrollLines(diff, true); + }; + Viewport.prototype.onWheel = function (ev) { + var amount = this._getPixelsScrolled(ev); + if (amount === 0) { + return; + } + this._viewportElement.scrollTop += amount; + ev.preventDefault(); + }; + Viewport.prototype._getPixelsScrolled = function (ev) { + if (ev.deltaY === 0) { + return 0; + } + var amount = ev.deltaY; + if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { + amount *= this._currentRowHeight; + } + else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + amount *= this._currentRowHeight * this._terminal.rows; + } + return amount; + }; + Viewport.prototype.getLinesScrolled = function (ev) { + if (ev.deltaY === 0) { + return 0; + } + var amount = ev.deltaY; + if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) { + amount /= this._currentRowHeight + 0.0; + this._wheelPartialScroll += amount; + amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1); + this._wheelPartialScroll %= 1; + } + else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + amount *= this._terminal.rows; + } + return amount; + }; + Viewport.prototype.onTouchStart = function (ev) { + this._lastTouchY = ev.touches[0].pageY; + }; + Viewport.prototype.onTouchMove = function (ev) { + var deltaY = this._lastTouchY - ev.touches[0].pageY; + this._lastTouchY = ev.touches[0].pageY; + if (deltaY === 0) { + return; + } + this._viewportElement.scrollTop += deltaY; + ev.preventDefault(); + }; + return Viewport; +}(Lifecycle_1.Disposable)); +exports.Viewport = Viewport; + +},{"./common/Lifecycle":17,"./ui/Lifecycle":46}],16:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter_1 = require("../EventEmitter"); +var CircularList = (function (_super) { + __extends(CircularList, _super); + function CircularList(_maxLength) { + var _this = _super.call(this) || this; + _this._maxLength = _maxLength; + _this._array = new Array(_this._maxLength); + _this._startIndex = 0; + _this._length = 0; + return _this; + } + Object.defineProperty(CircularList.prototype, "maxLength", { + get: function () { + return this._maxLength; + }, + set: function (newMaxLength) { + if (this._maxLength === newMaxLength) { + return; + } + var newArray = new Array(newMaxLength); + for (var i = 0; i < Math.min(newMaxLength, this.length); i++) { + newArray[i] = this._array[this._getCyclicIndex(i)]; + } + this._array = newArray; + this._maxLength = newMaxLength; + this._startIndex = 0; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CircularList.prototype, "length", { + get: function () { + return this._length; + }, + set: function (newLength) { + if (newLength > this._length) { + for (var i = this._length; i < newLength; i++) { + this._array[i] = undefined; + } + } + this._length = newLength; + }, + enumerable: true, + configurable: true + }); + CircularList.prototype.get = function (index) { + return this._array[this._getCyclicIndex(index)]; + }; + CircularList.prototype.set = function (index, value) { + this._array[this._getCyclicIndex(index)] = value; + }; + CircularList.prototype.push = function (value) { + this._array[this._getCyclicIndex(this._length)] = value; + if (this._length === this._maxLength) { + this._startIndex++; + if (this._startIndex === this._maxLength) { + this._startIndex = 0; + } + this.emit('trim', 1); + } + else { + this._length++; + } + }; + CircularList.prototype.pop = function () { + return this._array[this._getCyclicIndex(this._length-- - 1)]; + }; + CircularList.prototype.splice = function (start, deleteCount) { + var items = []; + for (var _i = 2; _i < arguments.length; _i++) { + items[_i - 2] = arguments[_i]; + } + if (deleteCount) { + for (var i = start; i < this._length - deleteCount; i++) { + this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; + } + this._length -= deleteCount; + } + if (items && items.length) { + for (var i = this._length - 1; i >= start; i--) { + this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)]; + } + for (var i = 0; i < items.length; i++) { + this._array[this._getCyclicIndex(start + i)] = items[i]; + } + if (this._length + items.length > this.maxLength) { + var countToTrim = (this._length + items.length) - this.maxLength; + this._startIndex += countToTrim; + this._length = this.maxLength; + this.emit('trim', countToTrim); + } + else { + this._length += items.length; + } + } + }; + CircularList.prototype.trimStart = function (count) { + if (count > this._length) { + count = this._length; + } + this._startIndex += count; + this._length -= count; + this.emit('trim', count); + }; + CircularList.prototype.shiftElements = function (start, count, offset) { + if (count <= 0) { + return; + } + if (start < 0 || start >= this._length) { + throw new Error('start argument out of range'); + } + if (start + offset < 0) { + throw new Error('Cannot shift elements in list beyond index 0'); + } + if (offset > 0) { + for (var i = count - 1; i >= 0; i--) { + this.set(start + i + offset, this.get(start + i)); + } + var expandListBy = (start + count + offset) - this._length; + if (expandListBy > 0) { + this._length += expandListBy; + while (this._length > this.maxLength) { + this._length--; + this._startIndex++; + this.emit('trim', 1); + } + } + } + else { + for (var i = 0; i < count; i++) { + this.set(start + i + offset, this.get(start + i)); + } + } + }; + CircularList.prototype._getCyclicIndex = function (index) { + return (this._startIndex + index) % this.maxLength; + }; + return CircularList; +}(EventEmitter_1.EventEmitter)); +exports.CircularList = CircularList; + +},{"../EventEmitter":7}],17:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Disposable = (function () { + function Disposable() { + this._disposables = []; + } + Disposable.prototype.dispose = function () { + this._disposables.forEach(function (d) { return d.dispose(); }); + this._disposables.length = 0; + }; + Disposable.prototype.register = function (d) { + this._disposables.push(d); + }; + return Disposable; +}()); +exports.Disposable = Disposable; + +},{}],18:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var C0; +(function (C0) { + C0.NUL = '\x00'; + C0.SOH = '\x01'; + C0.STX = '\x02'; + C0.ETX = '\x03'; + C0.EOT = '\x04'; + C0.ENQ = '\x05'; + C0.ACK = '\x06'; + C0.BEL = '\x07'; + C0.BS = '\x08'; + C0.HT = '\x09'; + C0.LF = '\x0a'; + C0.VT = '\x0b'; + C0.FF = '\x0c'; + C0.CR = '\x0d'; + C0.SO = '\x0e'; + C0.SI = '\x0f'; + C0.DLE = '\x10'; + C0.DC1 = '\x11'; + C0.DC2 = '\x12'; + C0.DC3 = '\x13'; + C0.DC4 = '\x14'; + C0.NAK = '\x15'; + C0.SYN = '\x16'; + C0.ETB = '\x17'; + C0.CAN = '\x18'; + C0.EM = '\x19'; + C0.SUB = '\x1a'; + C0.ESC = '\x1b'; + C0.FS = '\x1c'; + C0.GS = '\x1d'; + C0.RS = '\x1e'; + C0.US = '\x1f'; + C0.SP = '\x20'; + C0.DEL = '\x7f'; +})(C0 = exports.C0 || (exports.C0 = {})); +var C1; +(function (C1) { + C1.PAD = '\x80'; + C1.HOP = '\x81'; + C1.BPH = '\x82'; + C1.NBH = '\x83'; + C1.IND = '\x84'; + C1.NEL = '\x85'; + C1.SSA = '\x86'; + C1.ESA = '\x87'; + C1.HTS = '\x88'; + C1.HTJ = '\x89'; + C1.VTS = '\x8a'; + C1.PLD = '\x8b'; + C1.PLU = '\x8c'; + C1.RI = '\x8d'; + C1.SS2 = '\x8e'; + C1.SS3 = '\x8f'; + C1.DCS = '\x90'; + C1.PU1 = '\x91'; + C1.PU2 = '\x92'; + C1.STS = '\x93'; + C1.CCH = '\x94'; + C1.MW = '\x95'; + C1.SPA = '\x96'; + C1.EPA = '\x97'; + C1.SOS = '\x98'; + C1.SGCI = '\x99'; + C1.SCI = '\x9a'; + C1.CSI = '\x9b'; + C1.ST = '\x9c'; + C1.OSC = '\x9d'; + C1.PM = '\x9e'; + C1.APC = '\x9f'; +})(C1 = exports.C1 || (exports.C1 = {})); + +},{}],19:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CHARSETS = {}; @@ -324,4175 +5093,84 @@ exports.CHARSETS['='] = { '~': 'û' }; - - -},{}],4:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var CompositionHelper = (function () { - function CompositionHelper(textarea, compositionView, terminal) { - this.textarea = textarea; - this.compositionView = compositionView; - this.terminal = terminal; - this.isComposing = false; - this.isSendingComposition = false; - this.compositionPosition = { start: null, end: null }; - } - CompositionHelper.prototype.compositionstart = function () { - this.isComposing = true; - this.compositionPosition.start = this.textarea.value.length; - this.compositionView.textContent = ''; - this.compositionView.classList.add('active'); - }; - CompositionHelper.prototype.compositionupdate = function (ev) { - var _this = this; - this.compositionView.textContent = ev.data; - this.updateCompositionElements(); - setTimeout(function () { - _this.compositionPosition.end = _this.textarea.value.length; - }, 0); - }; - CompositionHelper.prototype.compositionend = function () { - this.finalizeComposition(true); - }; - CompositionHelper.prototype.keydown = function (ev) { - if (this.isComposing || this.isSendingComposition) { - if (ev.keyCode === 229) { - return false; - } - else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) { - return false; - } - else { - this.finalizeComposition(false); - } - } - if (ev.keyCode === 229) { - this.handleAnyTextareaChanges(); - return false; - } - return true; - }; - CompositionHelper.prototype.finalizeComposition = function (waitForPropogation) { - var _this = this; - this.compositionView.classList.remove('active'); - this.isComposing = false; - this.clearTextareaPosition(); - if (!waitForPropogation) { - this.isSendingComposition = false; - var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end); - this.terminal.handler(input); - } - else { - var currentCompositionPosition_1 = { - start: this.compositionPosition.start, - end: this.compositionPosition.end, - }; - this.isSendingComposition = true; - setTimeout(function () { - if (_this.isSendingComposition) { - _this.isSendingComposition = false; - var input = void 0; - if (_this.isComposing) { - input = _this.textarea.value.substring(currentCompositionPosition_1.start, currentCompositionPosition_1.end); - } - else { - input = _this.textarea.value.substring(currentCompositionPosition_1.start); - } - _this.terminal.handler(input); - } - }, 0); - } - }; - CompositionHelper.prototype.handleAnyTextareaChanges = function () { - var _this = this; - var oldValue = this.textarea.value; - setTimeout(function () { - if (!_this.isComposing) { - var newValue = _this.textarea.value; - var diff = newValue.replace(oldValue, ''); - if (diff.length > 0) { - _this.terminal.handler(diff); - } - } - }, 0); - }; - CompositionHelper.prototype.updateCompositionElements = function (dontRecurse) { - var _this = this; - if (!this.isComposing) { - return; - } - var cursor = this.terminal.element.querySelector('.terminal-cursor'); - if (cursor) { - var xtermRows = this.terminal.element.querySelector('.xterm-rows'); - var cursorTop = xtermRows.offsetTop + cursor.offsetTop; - this.compositionView.style.left = cursor.offsetLeft + 'px'; - this.compositionView.style.top = cursorTop + 'px'; - this.compositionView.style.height = cursor.offsetHeight + 'px'; - this.compositionView.style.lineHeight = cursor.offsetHeight + 'px'; - var compositionViewBounds = this.compositionView.getBoundingClientRect(); - this.textarea.style.left = cursor.offsetLeft + 'px'; - this.textarea.style.top = cursorTop + 'px'; - this.textarea.style.width = compositionViewBounds.width + 'px'; - this.textarea.style.height = compositionViewBounds.height + 'px'; - this.textarea.style.lineHeight = compositionViewBounds.height + 'px'; - } - if (!dontRecurse) { - setTimeout(function () { return _this.updateCompositionElements(true); }, 0); - } - }; - ; - CompositionHelper.prototype.clearTextareaPosition = function () { - this.textarea.style.left = ''; - this.textarea.style.top = ''; - }; - ; - return CompositionHelper; -}()); -exports.CompositionHelper = CompositionHelper; - - - -},{}],5:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var C0; -(function (C0) { - C0.NUL = '\x00'; - C0.SOH = '\x01'; - C0.STX = '\x02'; - C0.ETX = '\x03'; - C0.EOT = '\x04'; - C0.ENQ = '\x05'; - C0.ACK = '\x06'; - C0.BEL = '\x07'; - C0.BS = '\x08'; - C0.HT = '\x09'; - C0.LF = '\x0a'; - C0.VT = '\x0b'; - C0.FF = '\x0c'; - C0.CR = '\x0d'; - C0.SO = '\x0e'; - C0.SI = '\x0f'; - C0.DLE = '\x10'; - C0.DC1 = '\x11'; - C0.DC2 = '\x12'; - C0.DC3 = '\x13'; - C0.DC4 = '\x14'; - C0.NAK = '\x15'; - C0.SYN = '\x16'; - C0.ETB = '\x17'; - C0.CAN = '\x18'; - C0.EM = '\x19'; - C0.SUB = '\x1a'; - C0.ESC = '\x1b'; - C0.FS = '\x1c'; - C0.GS = '\x1d'; - C0.RS = '\x1e'; - C0.US = '\x1f'; - C0.SP = '\x20'; - C0.DEL = '\x7f'; -})(C0 = exports.C0 || (exports.C0 = {})); -; - - - -},{}],6:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -; -var EventEmitter = (function () { - function EventEmitter() { - this._events = this._events || {}; - } - EventEmitter.prototype.on = function (type, listener) { - this._events[type] = this._events[type] || []; - this._events[type].push(listener); - }; - EventEmitter.prototype.off = function (type, listener) { - if (!this._events[type]) { - return; - } - var obj = this._events[type]; - var i = obj.length; - while (i--) { - if (obj[i] === listener || obj[i].listener === listener) { - obj.splice(i, 1); - return; - } - } - }; - EventEmitter.prototype.removeAllListeners = function (type) { - if (this._events[type]) { - delete this._events[type]; - } - }; - EventEmitter.prototype.once = function (type, listener) { - function on() { - var args = Array.prototype.slice.call(arguments); - this.off(type, on); - return listener.apply(this, args); - } - on.listener = listener; - return this.on(type, on); - }; - EventEmitter.prototype.emit = function (type) { - var args = []; - for (var _i = 1; _i < arguments.length; _i++) { - args[_i - 1] = arguments[_i]; - } - if (!this._events[type]) { - return; - } - var obj = this._events[type]; - for (var i = 0; i < obj.length; i++) { - obj[i].apply(this, args); - } - }; - EventEmitter.prototype.listeners = function (type) { - return this._events[type] || []; - }; - return EventEmitter; -}()); -exports.EventEmitter = EventEmitter; - - - -},{}],7:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var EscapeSequences_1 = require("./EscapeSequences"); -var Charsets_1 = require("./Charsets"); -var InputHandler = (function () { - function InputHandler(_terminal) { - this._terminal = _terminal; - } - InputHandler.prototype.addChar = function (char, code) { - if (char >= ' ') { - var ch_width = exports.wcwidth(code); - if (this._terminal.charset && this._terminal.charset[char]) { - char = this._terminal.charset[char]; - } - var row = this._terminal.buffer.y + this._terminal.buffer.ybase; - if (!ch_width && this._terminal.buffer.x) { - if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) { - if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) { - if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char; - } - else { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char; - } - this._terminal.updateRange(this._terminal.buffer.y); - } - return; - } - if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) { - if (this._terminal.wraparoundMode) { - this._terminal.buffer.x = 0; - this._terminal.buffer.y++; - if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { - this._terminal.buffer.y--; - this._terminal.scroll(true); - } - else { - this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true; - } - } - else { - if (ch_width === 2) - return; - } - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - if (this._terminal.insertMode) { - for (var moves = 0; moves < ch_width; ++moves) { - var removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop(); - if (removed[2] === 0 - && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] - && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; - } - this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1]); - } - } - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width]; - this._terminal.buffer.x++; - this._terminal.updateRange(this._terminal.buffer.y); - if (ch_width === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0]; - this._terminal.buffer.x++; - } - } - }; - InputHandler.prototype.bell = function () { - var _this = this; - if (!this._terminal.visualBell) { - return; - } - this._terminal.element.style.borderColor = 'white'; - setTimeout(function () { return _this._terminal.element.style.borderColor = ''; }, 10); - if (this._terminal.popOnBell) { - this._terminal.focus(); - } - }; - InputHandler.prototype.lineFeed = function () { - if (this._terminal.convertEol) { - this._terminal.buffer.x = 0; - } - this._terminal.buffer.y++; - if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { - this._terminal.buffer.y--; - this._terminal.scroll(); - } - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } - this._terminal.emit('lineFeed'); - }; - InputHandler.prototype.carriageReturn = function () { - this._terminal.buffer.x = 0; - }; - InputHandler.prototype.backspace = function () { - if (this._terminal.buffer.x > 0) { - this._terminal.buffer.x--; - } - }; - InputHandler.prototype.tab = function () { - this._terminal.buffer.x = this._terminal.nextStop(); - }; - InputHandler.prototype.shiftOut = function () { - this._terminal.setgLevel(1); - }; - InputHandler.prototype.shiftIn = function () { - this._terminal.setgLevel(0); - }; - InputHandler.prototype.insertChars = function (params) { - var param, row, j, ch; - param = params[0]; - if (param < 1) - param = 1; - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.buffer.x; - ch = [this._terminal.eraseAttr(), ' ', 1]; - while (param-- && j < this._terminal.cols) { - this._terminal.buffer.lines.get(row).splice(j++, 0, ch); - this._terminal.buffer.lines.get(row).pop(); - } - }; - InputHandler.prototype.cursorUp = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y -= param; - if (this._terminal.buffer.y < 0) { - this._terminal.buffer.y = 0; - } - }; - InputHandler.prototype.cursorDown = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y += param; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } - }; - InputHandler.prototype.cursorForward = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.x += param; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } - }; - InputHandler.prototype.cursorBackward = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } - this._terminal.buffer.x -= param; - if (this._terminal.buffer.x < 0) { - this._terminal.buffer.x = 0; - } - }; - InputHandler.prototype.cursorNextLine = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y += param; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - this._terminal.buffer.x = 0; - }; - InputHandler.prototype.cursorPrecedingLine = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y -= param; - if (this._terminal.buffer.y < 0) { - this._terminal.buffer.y = 0; - } - this._terminal.buffer.x = 0; - }; - InputHandler.prototype.cursorCharAbsolute = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.x = param - 1; - }; - InputHandler.prototype.cursorPosition = function (params) { - var row, col; - row = params[0] - 1; - if (params.length >= 2) { - col = params[1] - 1; - } - else { - col = 0; - } - if (row < 0) { - row = 0; - } - else if (row >= this._terminal.rows) { - row = this._terminal.rows - 1; - } - if (col < 0) { - col = 0; - } - else if (col >= this._terminal.cols) { - col = this._terminal.cols - 1; - } - this._terminal.buffer.x = col; - this._terminal.buffer.y = row; - }; - InputHandler.prototype.cursorForwardTab = function (params) { - var param = params[0] || 1; - while (param--) { - this._terminal.buffer.x = this._terminal.nextStop(); - } - }; - InputHandler.prototype.eraseInDisplay = function (params) { - var j; - switch (params[0]) { - case 0: - this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); - j = this._terminal.buffer.y + 1; - for (; j < this._terminal.rows; j++) { - this._terminal.eraseLine(j); - } - break; - case 1: - this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); - j = this._terminal.buffer.y; - while (j--) { - this._terminal.eraseLine(j); - } - break; - case 2: - j = this._terminal.rows; - while (j--) - this._terminal.eraseLine(j); - break; - case 3: - var scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows; - if (scrollBackSize > 0) { - this._terminal.buffer.lines.trimStart(scrollBackSize); - this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0); - this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0); - this._terminal.emit('scroll', 0); - } - break; - } - }; - InputHandler.prototype.eraseInLine = function (params) { - switch (params[0]) { - case 0: - this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); - break; - case 1: - this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); - break; - case 2: - this._terminal.eraseLine(this._terminal.buffer.y); - break; - } - }; - InputHandler.prototype.insertLines = function (params) { - var param, row, j; - param = params[0]; - if (param < 1) { - param = 1; - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; - j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1; - while (param--) { - if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) { - this._terminal.buffer.lines.trimStart(1); - this._terminal.buffer.ybase--; - this._terminal.buffer.ydisp--; - row--; - j--; - } - this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true)); - this._terminal.buffer.lines.splice(j, 1); - } - this._terminal.updateRange(this._terminal.buffer.y); - this._terminal.updateRange(this._terminal.buffer.scrollBottom); - }; - InputHandler.prototype.deleteLines = function (params) { - var param, row, j; - param = params[0]; - if (param < 1) { - param = 1; - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; - j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j; - while (param--) { - if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) { - this._terminal.buffer.lines.trimStart(1); - this._terminal.buffer.ybase -= 1; - this._terminal.buffer.ydisp -= 1; - } - this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true)); - this._terminal.buffer.lines.splice(row, 1); - } - this._terminal.updateRange(this._terminal.buffer.y); - this._terminal.updateRange(this._terminal.buffer.scrollBottom); - }; - InputHandler.prototype.deleteChars = function (params) { - var param, row, ch; - param = params[0]; - if (param < 1) { - param = 1; - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - ch = [this._terminal.eraseAttr(), ' ', 1]; - while (param--) { - this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1); - this._terminal.buffer.lines.get(row).push(ch); - } - }; - InputHandler.prototype.scrollUp = function (params) { - var param = params[0] || 1; - while (param--) { - this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1); - this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine()); - } - this._terminal.updateRange(this._terminal.buffer.scrollTop); - this._terminal.updateRange(this._terminal.buffer.scrollBottom); - }; - InputHandler.prototype.scrollDown = function (params) { - var param = params[0] || 1; - while (param--) { - this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1); - this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine()); - } - this._terminal.updateRange(this._terminal.buffer.scrollTop); - this._terminal.updateRange(this._terminal.buffer.scrollBottom); - }; - InputHandler.prototype.eraseChars = function (params) { - var param, row, j, ch; - param = params[0]; - if (param < 1) { - param = 1; - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.buffer.x; - ch = [this._terminal.eraseAttr(), ' ', 1]; - while (param-- && j < this._terminal.cols) { - this._terminal.buffer.lines.get(row)[j++] = ch; - } - }; - InputHandler.prototype.cursorBackwardTab = function (params) { - var param = params[0] || 1; - while (param--) { - this._terminal.buffer.x = this._terminal.prevStop(); - } - }; - InputHandler.prototype.charPosAbsolute = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.x = param - 1; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } - }; - InputHandler.prototype.HPositionRelative = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.x += param; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } - }; - InputHandler.prototype.repeatPrecedingCharacter = function (params) { - var param = params[0] || 1, line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y), ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1]; - while (param--) { - line[this._terminal.buffer.x++] = ch; - } - }; - InputHandler.prototype.sendDeviceAttributes = function (params) { - if (params[0] > 0) { - return; - } - if (!this._terminal.prefix) { - if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[?1;2c'); - } - else if (this._terminal.is('linux')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[?6c'); - } - } - else if (this._terminal.prefix === '>') { - if (this._terminal.is('xterm')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[>0;276;0c'); - } - else if (this._terminal.is('rxvt-unicode')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[>85;95;0c'); - } - else if (this._terminal.is('linux')) { - this._terminal.send(params[0] + 'c'); - } - else if (this._terminal.is('screen')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[>83;40003;0c'); - } - } - }; - InputHandler.prototype.linePosAbsolute = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y = param - 1; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - }; - InputHandler.prototype.VPositionRelative = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y += param; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } - }; - InputHandler.prototype.HVPosition = function (params) { - if (params[0] < 1) - params[0] = 1; - if (params[1] < 1) - params[1] = 1; - this._terminal.buffer.y = params[0] - 1; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - this._terminal.buffer.x = params[1] - 1; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } - }; - InputHandler.prototype.tabClear = function (params) { - var param = params[0]; - if (param <= 0) { - delete this._terminal.buffer.tabs[this._terminal.buffer.x]; - } - else if (param === 3) { - this._terminal.buffer.tabs = {}; - } - }; - InputHandler.prototype.setMode = function (params) { - if (params.length > 1) { - for (var i = 0; i < params.length; i++) { - this.setMode([params[i]]); - } - return; - } - if (!this._terminal.prefix) { - switch (params[0]) { - case 4: - this._terminal.insertMode = true; - break; - case 20: - break; - } - } - else if (this._terminal.prefix === '?') { - switch (params[0]) { - case 1: - this._terminal.applicationCursor = true; - break; - case 2: - this._terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); - this._terminal.setgCharset(1, Charsets_1.DEFAULT_CHARSET); - this._terminal.setgCharset(2, Charsets_1.DEFAULT_CHARSET); - this._terminal.setgCharset(3, Charsets_1.DEFAULT_CHARSET); - break; - case 3: - this._terminal.savedCols = this._terminal.cols; - this._terminal.resize(132, this._terminal.rows); - break; - case 6: - this._terminal.originMode = true; - break; - case 7: - this._terminal.wraparoundMode = true; - break; - case 12: - break; - case 66: - this._terminal.log('Serial port requested application keypad.'); - this._terminal.applicationKeypad = true; - this._terminal.viewport.syncScrollArea(); - break; - case 9: - case 1000: - case 1002: - case 1003: - this._terminal.x10Mouse = params[0] === 9; - this._terminal.vt200Mouse = params[0] === 1000; - this._terminal.normalMouse = params[0] > 1000; - this._terminal.mouseEvents = true; - this._terminal.element.classList.add('enable-mouse-events'); - this._terminal.selectionManager.disable(); - this._terminal.log('Binding to mouse events.'); - break; - case 1004: - this._terminal.sendFocus = true; - break; - case 1005: - this._terminal.utfMouse = true; - break; - case 1006: - this._terminal.sgrMouse = true; - break; - case 1015: - this._terminal.urxvtMouse = true; - break; - case 25: - this._terminal.cursorHidden = false; - break; - case 1049: - case 47: - case 1047: - this._terminal.buffers.activateAltBuffer(); - this._terminal.viewport.syncScrollArea(); - this._terminal.showCursor(); - break; - } - } - }; - InputHandler.prototype.resetMode = function (params) { - if (params.length > 1) { - for (var i = 0; i < params.length; i++) { - this.resetMode([params[i]]); - } - return; - } - if (!this._terminal.prefix) { - switch (params[0]) { - case 4: - this._terminal.insertMode = false; - break; - case 20: - break; - } - } - else if (this._terminal.prefix === '?') { - switch (params[0]) { - case 1: - this._terminal.applicationCursor = false; - break; - case 3: - if (this._terminal.cols === 132 && this._terminal.savedCols) { - this._terminal.resize(this._terminal.savedCols, this._terminal.rows); - } - delete this._terminal.savedCols; - break; - case 6: - this._terminal.originMode = false; - break; - case 7: - this._terminal.wraparoundMode = false; - break; - case 12: - break; - case 66: - this._terminal.log('Switching back to normal keypad.'); - this._terminal.applicationKeypad = false; - this._terminal.viewport.syncScrollArea(); - break; - case 9: - case 1000: - case 1002: - case 1003: - this._terminal.x10Mouse = false; - this._terminal.vt200Mouse = false; - this._terminal.normalMouse = false; - this._terminal.mouseEvents = false; - this._terminal.element.classList.remove('enable-mouse-events'); - this._terminal.selectionManager.enable(); - break; - case 1004: - this._terminal.sendFocus = false; - break; - case 1005: - this._terminal.utfMouse = false; - break; - case 1006: - this._terminal.sgrMouse = false; - break; - case 1015: - this._terminal.urxvtMouse = false; - break; - case 25: - this._terminal.cursorHidden = true; - break; - case 1049: - case 47: - case 1047: - this._terminal.buffers.activateNormalBuffer(); - this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines); - this._terminal.refresh(0, this._terminal.rows - 1); - this._terminal.viewport.syncScrollArea(); - this._terminal.showCursor(); - break; - } - } - }; - InputHandler.prototype.charAttributes = function (params) { - if (params.length === 1 && params[0] === 0) { - this._terminal.curAttr = this._terminal.defAttr; - return; - } - var l = params.length, i = 0, flags = this._terminal.curAttr >> 18, fg = (this._terminal.curAttr >> 9) & 0x1ff, bg = this._terminal.curAttr & 0x1ff, p; - for (; i < l; i++) { - p = params[i]; - if (p >= 30 && p <= 37) { - fg = p - 30; - } - else if (p >= 40 && p <= 47) { - bg = p - 40; - } - else if (p >= 90 && p <= 97) { - p += 8; - fg = p - 90; - } - else if (p >= 100 && p <= 107) { - p += 8; - bg = p - 100; - } - else if (p === 0) { - flags = this._terminal.defAttr >> 18; - fg = (this._terminal.defAttr >> 9) & 0x1ff; - bg = this._terminal.defAttr & 0x1ff; - } - else if (p === 1) { - flags |= 1; - } - else if (p === 4) { - flags |= 2; - } - else if (p === 5) { - flags |= 4; - } - else if (p === 7) { - flags |= 8; - } - else if (p === 8) { - flags |= 16; - } - else if (p === 22) { - flags &= ~1; - } - else if (p === 24) { - flags &= ~2; - } - else if (p === 25) { - flags &= ~4; - } - else if (p === 27) { - flags &= ~8; - } - else if (p === 28) { - flags &= ~16; - } - else if (p === 39) { - fg = (this._terminal.defAttr >> 9) & 0x1ff; - } - else if (p === 49) { - bg = this._terminal.defAttr & 0x1ff; - } - else if (p === 38) { - if (params[i + 1] === 2) { - i += 2; - fg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); - if (fg === -1) - fg = 0x1ff; - i += 2; - } - else if (params[i + 1] === 5) { - i += 2; - p = params[i] & 0xff; - fg = p; - } - } - else if (p === 48) { - if (params[i + 1] === 2) { - i += 2; - bg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); - if (bg === -1) - bg = 0x1ff; - i += 2; - } - else if (params[i + 1] === 5) { - i += 2; - p = params[i] & 0xff; - bg = p; - } - } - else if (p === 100) { - fg = (this._terminal.defAttr >> 9) & 0x1ff; - bg = this._terminal.defAttr & 0x1ff; - } - else { - this._terminal.error('Unknown SGR attribute: %d.', p); - } - } - this._terminal.curAttr = (flags << 18) | (fg << 9) | bg; - }; - InputHandler.prototype.deviceStatus = function (params) { - if (!this._terminal.prefix) { - switch (params[0]) { - case 5: - this._terminal.send(EscapeSequences_1.C0.ESC + '[0n'); - break; - case 6: - this._terminal.send(EscapeSequences_1.C0.ESC + '[' - + (this._terminal.buffer.y + 1) - + ';' - + (this._terminal.buffer.x + 1) - + 'R'); - break; - } - } - else if (this._terminal.prefix === '?') { - switch (params[0]) { - case 6: - this._terminal.send(EscapeSequences_1.C0.ESC + '[?' - + (this._terminal.buffer.y + 1) - + ';' - + (this._terminal.buffer.x + 1) - + 'R'); - break; - case 15: - break; - case 25: - break; - case 26: - break; - case 53: - break; - } - } - }; - InputHandler.prototype.softReset = function (params) { - this._terminal.cursorHidden = false; - this._terminal.insertMode = false; - this._terminal.originMode = false; - this._terminal.wraparoundMode = true; - this._terminal.applicationKeypad = false; - this._terminal.viewport.syncScrollArea(); - this._terminal.applicationCursor = false; - this._terminal.buffer.scrollTop = 0; - this._terminal.buffer.scrollBottom = this._terminal.rows - 1; - this._terminal.curAttr = this._terminal.defAttr; - this._terminal.buffer.x = this._terminal.buffer.y = 0; - this._terminal.charset = null; - this._terminal.glevel = 0; - this._terminal.charsets = [null]; - }; - InputHandler.prototype.setCursorStyle = function (params) { - var param = params[0] < 1 ? 1 : params[0]; - switch (param) { - case 1: - case 2: - this._terminal.setOption('cursorStyle', 'block'); - break; - case 3: - case 4: - this._terminal.setOption('cursorStyle', 'underline'); - break; - case 5: - case 6: - this._terminal.setOption('cursorStyle', 'bar'); - break; - } - var isBlinking = param % 2 === 1; - this._terminal.setOption('cursorBlink', isBlinking); - }; - InputHandler.prototype.setScrollRegion = function (params) { - if (this._terminal.prefix) - return; - this._terminal.buffer.scrollTop = (params[0] || 1) - 1; - this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1; - this._terminal.buffer.x = 0; - this._terminal.buffer.y = 0; - }; - InputHandler.prototype.saveCursor = function (params) { - this._terminal.buffer.savedX = this._terminal.buffer.x; - this._terminal.buffer.savedY = this._terminal.buffer.y; - }; - InputHandler.prototype.restoreCursor = function (params) { - this._terminal.buffer.x = this._terminal.buffer.savedX || 0; - this._terminal.buffer.y = this._terminal.buffer.savedY || 0; - }; - return InputHandler; -}()); -exports.InputHandler = InputHandler; -exports.wcwidth = (function (opts) { - var COMBINING_BMP = [ - [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], - [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], - [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], - [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], - [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], - [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], - [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], - [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], - [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], - [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], - [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], - [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], - [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], - [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], - [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], - [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], - [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], - [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], - [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], - [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], - [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], - [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], - [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], - [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], - [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], - [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], - [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], - [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], - [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], - [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], - [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], - [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], - [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], - [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], - [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], - [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], - [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB], - ]; - var COMBINING_HIGH = [ - [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], - [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] - ]; - function bisearch(ucs, data) { - var min = 0; - var max = data.length - 1; - var mid; - if (ucs < data[0][0] || ucs > data[max][1]) - return false; - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) - min = mid + 1; - else if (ucs < data[mid][0]) - max = mid - 1; - else - return true; - } - return false; - } - function wcwidthBMP(ucs) { - if (ucs === 0) - return opts.nul; - if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) - return opts.control; - if (bisearch(ucs, COMBINING_BMP)) - return 0; - if (isWideBMP(ucs)) { - return 2; - } - return 1; - } - function isWideBMP(ucs) { - return (ucs >= 0x1100 && (ucs <= 0x115f || - ucs === 0x2329 || - ucs === 0x232a || - (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || - (ucs >= 0xac00 && ucs <= 0xd7a3) || - (ucs >= 0xf900 && ucs <= 0xfaff) || - (ucs >= 0xfe10 && ucs <= 0xfe19) || - (ucs >= 0xfe30 && ucs <= 0xfe6f) || - (ucs >= 0xff00 && ucs <= 0xff60) || - (ucs >= 0xffe0 && ucs <= 0xffe6))); - } - function wcwidthHigh(ucs) { - if (bisearch(ucs, COMBINING_HIGH)) - return 0; - if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { - return 2; - } - return 1; - } - var control = opts.control | 0; - var table = null; - function init_table() { - var CODEPOINTS = 65536; - var BITWIDTH = 2; - var ITEMSIZE = 32; - var CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE; - var CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH; - table = (typeof Uint32Array === 'undefined') - ? new Array(CONTAINERSIZE) - : new Uint32Array(CONTAINERSIZE); - for (var i = 0; i < CONTAINERSIZE; ++i) { - var num = 0; - var pos = CODEPOINTS_PER_ITEM; - while (pos--) - num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos); - table[i] = num; - } - return table; - } - return function (num) { - num = num | 0; - if (num < 32) - return control | 0; - if (num < 127) - return 1; - var t = table || init_table(); - if (num < 65536) - return t[num >> 4] >> ((num & 15) << 1) & 3; - return wcwidthHigh(num); - }; -})({ nul: 0, control: 0 }); - - - -},{"./Charsets":3,"./EscapeSequences":5}],8:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var INVALID_LINK_CLASS = 'xterm-invalid-link'; -var protocolClause = '(https?:\\/\\/)'; -var domainCharacterSet = '[\\da-z\\.-]+'; -var negatedDomainCharacterSet = '[^\\da-z\\.-]+'; -var domainBodyClause = '(' + domainCharacterSet + ')'; -var tldClause = '([a-z\\.]{2,6})'; -var ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; -var localHostClause = '(localhost)'; -var portClause = '(:\\d{1,5})'; -var hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -var pathClause = '(\\/[\\/\\w\\.\\-%~]*)*'; -var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; -var queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; -var hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; -var negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+'; -var bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause; -var start = '(?:^|' + negatedDomainCharacterSet + ')('; -var end = ')($|' + negatedPathCharacterSet + ')'; -var strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); -var HYPERTEXT_LINK_MATCHER_ID = 0; -var Linkifier = (function () { - function Linkifier() { - this._nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; - this._rowTimeoutIds = []; - this._linkMatchers = []; - this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 }); - } - Linkifier.prototype.attachToDom = function (document, rows) { - this._document = document; - this._rows = rows; - }; - Linkifier.prototype.linkifyRow = function (rowIndex) { - if (!this._document) { - return; - } - var timeoutId = this._rowTimeoutIds[rowIndex]; - if (timeoutId) { - clearTimeout(timeoutId); - } - this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY); - }; - Linkifier.prototype.setHypertextLinkHandler = function (handler) { - this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler; - }; - Linkifier.prototype.setHypertextValidationCallback = function (callback) { - this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback; - }; - Linkifier.prototype.registerLinkMatcher = function (regex, handler, options) { - if (options === void 0) { options = {}; } - if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) { - throw new Error('handler must be defined'); - } - var matcher = { - id: this._nextLinkMatcherId++, - regex: regex, - handler: handler, - matchIndex: options.matchIndex, - validationCallback: options.validationCallback, - priority: options.priority || 0 - }; - this._addLinkMatcherToList(matcher); - return matcher.id; - }; - Linkifier.prototype._addLinkMatcherToList = function (matcher) { - if (this._linkMatchers.length === 0) { - this._linkMatchers.push(matcher); - return; - } - for (var i = this._linkMatchers.length - 1; i >= 0; i--) { - if (matcher.priority <= this._linkMatchers[i].priority) { - this._linkMatchers.splice(i + 1, 0, matcher); - return; - } - } - this._linkMatchers.splice(0, 0, matcher); - }; - Linkifier.prototype.deregisterLinkMatcher = function (matcherId) { - for (var i = 1; i < this._linkMatchers.length; i++) { - if (this._linkMatchers[i].id === matcherId) { - this._linkMatchers.splice(i, 1); - return true; - } - } - return false; - }; - Linkifier.prototype._linkifyRow = function (rowIndex) { - var row = this._rows[rowIndex]; - if (!row) { - return; - } - var text = row.textContent; - for (var i = 0; i < this._linkMatchers.length; i++) { - var matcher = this._linkMatchers[i]; - var linkElements = this._doLinkifyRow(row, matcher); - if (linkElements.length > 0) { - if (matcher.validationCallback) { - var _loop_1 = function (j) { - var element = linkElements[j]; - matcher.validationCallback(element.textContent, element, function (isValid) { - if (!isValid) { - element.classList.add(INVALID_LINK_CLASS); - } - }); - }; - for (var j = 0; j < linkElements.length; j++) { - _loop_1(j); - } - } - return; - } - } - }; - Linkifier.prototype._doLinkifyRow = function (row, matcher) { - var result = []; - var isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID; - var nodes = row.childNodes; - var match = row.textContent.match(matcher.regex); - if (!match || match.length === 0) { - return result; - } - var uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; - var rowStartIndex = match.index + uri.length; - for (var i = 0; i < nodes.length; i++) { - var node = nodes[i]; - var searchIndex = node.textContent.indexOf(uri); - if (searchIndex >= 0) { - var linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher); - if (node.textContent.length === uri.length) { - if (node.nodeType === 3) { - this._replaceNode(node, linkElement); - } - else { - var element = node; - if (element.nodeName === 'A') { - return result; - } - element.innerHTML = ''; - element.appendChild(linkElement); - } - } - else if (node.childNodes.length > 1) { - for (var j = 0; j < node.childNodes.length; j++) { - var childNode = node.childNodes[j]; - var childSearchIndex = childNode.textContent.indexOf(uri); - if (childSearchIndex !== -1) { - this._replaceNodeSubstringWithNode(childNode, linkElement, uri, childSearchIndex); - break; - } - } - } - else { - var nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex); - i += nodesAdded; - } - result.push(linkElement); - match = row.textContent.substring(rowStartIndex).match(matcher.regex); - if (!match || match.length === 0) { - return result; - } - uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; - rowStartIndex += match.index + uri.length; - } - } - return result; - }; - Linkifier.prototype._createAnchorElement = function (uri, handler, isHypertextLinkHandler) { - var element = this._document.createElement('a'); - element.textContent = uri; - element.draggable = false; - if (isHypertextLinkHandler) { - element.href = uri; - element.target = '_blank'; - element.addEventListener('click', function (event) { - if (handler) { - return handler(event, uri); - } - }); - } - else { - element.addEventListener('click', function (event) { - if (element.classList.contains(INVALID_LINK_CLASS)) { - return; - } - return handler(event, uri); - }); - } - return element; - }; - Linkifier.prototype._replaceNode = function (oldNode) { - var newNodes = []; - for (var _i = 1; _i < arguments.length; _i++) { - newNodes[_i - 1] = arguments[_i]; - } - var parent = oldNode.parentNode; - for (var i = 0; i < newNodes.length; i++) { - parent.insertBefore(newNodes[i], oldNode); - } - parent.removeChild(oldNode); - }; - Linkifier.prototype._replaceNodeSubstringWithNode = function (targetNode, newNode, substring, substringIndex) { - if (targetNode.childNodes.length === 1) { - targetNode = targetNode.childNodes[0]; - } - if (targetNode.nodeType !== 3) { - throw new Error('targetNode must be a text node or only contain a single text node'); - } - var fullText = targetNode.textContent; - if (substringIndex === 0) { - var rightText_1 = fullText.substring(substring.length); - var rightTextNode_1 = this._document.createTextNode(rightText_1); - this._replaceNode(targetNode, newNode, rightTextNode_1); - return 0; - } - if (substringIndex === targetNode.textContent.length - substring.length) { - var leftText_1 = fullText.substring(0, substringIndex); - var leftTextNode_1 = this._document.createTextNode(leftText_1); - this._replaceNode(targetNode, leftTextNode_1, newNode); - return 0; - } - var leftText = fullText.substring(0, substringIndex); - var leftTextNode = this._document.createTextNode(leftText); - var rightText = fullText.substring(substringIndex + substring.length); - var rightTextNode = this._document.createTextNode(rightText); - this._replaceNode(targetNode, leftTextNode, newNode, rightTextNode); - return 1; - }; - return Linkifier; -}()); -Linkifier.TIME_BEFORE_LINKIFY = 200; -exports.Linkifier = Linkifier; - - - -},{}],9:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var EscapeSequences_1 = require("./EscapeSequences"); -var Charsets_1 = require("./Charsets"); -var normalStateHandler = {}; -normalStateHandler[EscapeSequences_1.C0.BEL] = function (parser, handler) { return handler.bell(); }; -normalStateHandler[EscapeSequences_1.C0.LF] = function (parser, handler) { return handler.lineFeed(); }; -normalStateHandler[EscapeSequences_1.C0.VT] = normalStateHandler[EscapeSequences_1.C0.LF]; -normalStateHandler[EscapeSequences_1.C0.FF] = normalStateHandler[EscapeSequences_1.C0.LF]; -normalStateHandler[EscapeSequences_1.C0.CR] = function (parser, handler) { return handler.carriageReturn(); }; -normalStateHandler[EscapeSequences_1.C0.BS] = function (parser, handler) { return handler.backspace(); }; -normalStateHandler[EscapeSequences_1.C0.HT] = function (parser, handler) { return handler.tab(); }; -normalStateHandler[EscapeSequences_1.C0.SO] = function (parser, handler) { return handler.shiftOut(); }; -normalStateHandler[EscapeSequences_1.C0.SI] = function (parser, handler) { return handler.shiftIn(); }; -normalStateHandler[EscapeSequences_1.C0.ESC] = function (parser, handler) { return parser.setState(ParserState.ESCAPED); }; -var escapedStateHandler = {}; -escapedStateHandler['['] = function (parser, terminal) { - terminal.params = []; - terminal.currentParam = 0; - parser.setState(ParserState.CSI_PARAM); -}; -escapedStateHandler[']'] = function (parser, terminal) { - terminal.params = []; - terminal.currentParam = 0; - parser.setState(ParserState.OSC); -}; -escapedStateHandler['P'] = function (parser, terminal) { - terminal.params = []; - terminal.currentParam = 0; - parser.setState(ParserState.DCS); -}; -escapedStateHandler['_'] = function (parser, terminal) { - parser.setState(ParserState.IGNORE); -}; -escapedStateHandler['^'] = function (parser, terminal) { - parser.setState(ParserState.IGNORE); -}; -escapedStateHandler['c'] = function (parser, terminal) { - terminal.reset(); -}; -escapedStateHandler['E'] = function (parser, terminal) { - terminal.buffer.x = 0; - terminal.index(); - parser.setState(ParserState.NORMAL); -}; -escapedStateHandler['D'] = function (parser, terminal) { - terminal.index(); - parser.setState(ParserState.NORMAL); -}; -escapedStateHandler['M'] = function (parser, terminal) { - terminal.reverseIndex(); - parser.setState(ParserState.NORMAL); -}; -escapedStateHandler['%'] = function (parser, terminal) { - terminal.setgLevel(0); - terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); - parser.setState(ParserState.NORMAL); - parser.skipNextChar(); -}; -escapedStateHandler[EscapeSequences_1.C0.CAN] = function (parser) { return parser.setState(ParserState.NORMAL); }; -var csiParamStateHandler = {}; -csiParamStateHandler['?'] = function (parser) { return parser.setPrefix('?'); }; -csiParamStateHandler['>'] = function (parser) { return parser.setPrefix('>'); }; -csiParamStateHandler['!'] = function (parser) { return parser.setPrefix('!'); }; -csiParamStateHandler['0'] = function (parser) { return parser.setParam(parser.getParam() * 10); }; -csiParamStateHandler['1'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 1); }; -csiParamStateHandler['2'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 2); }; -csiParamStateHandler['3'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 3); }; -csiParamStateHandler['4'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 4); }; -csiParamStateHandler['5'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 5); }; -csiParamStateHandler['6'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 6); }; -csiParamStateHandler['7'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 7); }; -csiParamStateHandler['8'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 8); }; -csiParamStateHandler['9'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 9); }; -csiParamStateHandler['$'] = function (parser) { return parser.setPostfix('$'); }; -csiParamStateHandler['"'] = function (parser) { return parser.setPostfix('"'); }; -csiParamStateHandler[' '] = function (parser) { return parser.setPostfix(' '); }; -csiParamStateHandler['\''] = function (parser) { return parser.setPostfix('\''); }; -csiParamStateHandler[';'] = function (parser) { return parser.finalizeParam(); }; -csiParamStateHandler[EscapeSequences_1.C0.CAN] = function (parser) { return parser.setState(ParserState.NORMAL); }; -var csiStateHandler = {}; -csiStateHandler['@'] = function (handler, params, prefix) { return handler.insertChars(params); }; -csiStateHandler['A'] = function (handler, params, prefix) { return handler.cursorUp(params); }; -csiStateHandler['B'] = function (handler, params, prefix) { return handler.cursorDown(params); }; -csiStateHandler['C'] = function (handler, params, prefix) { return handler.cursorForward(params); }; -csiStateHandler['D'] = function (handler, params, prefix) { return handler.cursorBackward(params); }; -csiStateHandler['E'] = function (handler, params, prefix) { return handler.cursorNextLine(params); }; -csiStateHandler['F'] = function (handler, params, prefix) { return handler.cursorPrecedingLine(params); }; -csiStateHandler['G'] = function (handler, params, prefix) { return handler.cursorCharAbsolute(params); }; -csiStateHandler['H'] = function (handler, params, prefix) { return handler.cursorPosition(params); }; -csiStateHandler['I'] = function (handler, params, prefix) { return handler.cursorForwardTab(params); }; -csiStateHandler['J'] = function (handler, params, prefix) { return handler.eraseInDisplay(params); }; -csiStateHandler['K'] = function (handler, params, prefix) { return handler.eraseInLine(params); }; -csiStateHandler['L'] = function (handler, params, prefix) { return handler.insertLines(params); }; -csiStateHandler['M'] = function (handler, params, prefix) { return handler.deleteLines(params); }; -csiStateHandler['P'] = function (handler, params, prefix) { return handler.deleteChars(params); }; -csiStateHandler['S'] = function (handler, params, prefix) { return handler.scrollUp(params); }; -csiStateHandler['T'] = function (handler, params, prefix) { - if (params.length < 2 && !prefix) { - handler.scrollDown(params); - } -}; -csiStateHandler['X'] = function (handler, params, prefix) { return handler.eraseChars(params); }; -csiStateHandler['Z'] = function (handler, params, prefix) { return handler.cursorBackwardTab(params); }; -csiStateHandler['`'] = function (handler, params, prefix) { return handler.charPosAbsolute(params); }; -csiStateHandler['a'] = function (handler, params, prefix) { return handler.HPositionRelative(params); }; -csiStateHandler['b'] = function (handler, params, prefix) { return handler.repeatPrecedingCharacter(params); }; -csiStateHandler['c'] = function (handler, params, prefix) { return handler.sendDeviceAttributes(params); }; -csiStateHandler['d'] = function (handler, params, prefix) { return handler.linePosAbsolute(params); }; -csiStateHandler['e'] = function (handler, params, prefix) { return handler.VPositionRelative(params); }; -csiStateHandler['f'] = function (handler, params, prefix) { return handler.HVPosition(params); }; -csiStateHandler['g'] = function (handler, params, prefix) { return handler.tabClear(params); }; -csiStateHandler['h'] = function (handler, params, prefix) { return handler.setMode(params); }; -csiStateHandler['l'] = function (handler, params, prefix) { return handler.resetMode(params); }; -csiStateHandler['m'] = function (handler, params, prefix) { return handler.charAttributes(params); }; -csiStateHandler['n'] = function (handler, params, prefix) { return handler.deviceStatus(params); }; -csiStateHandler['p'] = function (handler, params, prefix) { - switch (prefix) { - case '!': - handler.softReset(params); - break; - } -}; -csiStateHandler['q'] = function (handler, params, prefix, postfix) { - if (postfix === ' ') { - handler.setCursorStyle(params); - } -}; -csiStateHandler['r'] = function (handler, params) { return handler.setScrollRegion(params); }; -csiStateHandler['s'] = function (handler, params) { return handler.saveCursor(params); }; -csiStateHandler['u'] = function (handler, params) { return handler.restoreCursor(params); }; -csiStateHandler[EscapeSequences_1.C0.CAN] = function (handler, params, prefix, postfix, parser) { return parser.setState(ParserState.NORMAL); }; -var ParserState; -(function (ParserState) { - ParserState[ParserState["NORMAL"] = 0] = "NORMAL"; - ParserState[ParserState["ESCAPED"] = 1] = "ESCAPED"; - ParserState[ParserState["CSI_PARAM"] = 2] = "CSI_PARAM"; - ParserState[ParserState["CSI"] = 3] = "CSI"; - ParserState[ParserState["OSC"] = 4] = "OSC"; - ParserState[ParserState["CHARSET"] = 5] = "CHARSET"; - ParserState[ParserState["DCS"] = 6] = "DCS"; - ParserState[ParserState["IGNORE"] = 7] = "IGNORE"; -})(ParserState || (ParserState = {})); -var Parser = (function () { - function Parser(_inputHandler, _terminal) { - this._inputHandler = _inputHandler; - this._terminal = _terminal; - this._state = ParserState.NORMAL; - } - Parser.prototype.parse = function (data) { - var l = data.length, j, cs, ch, code, low; - if (this._terminal.debug) { - this._terminal.log('data: ' + data); - } - this._position = 0; - if (this._terminal.surrogate_high) { - data = this._terminal.surrogate_high + data; - this._terminal.surrogate_high = ''; - } - for (; this._position < l; this._position++) { - ch = data[this._position]; - code = data.charCodeAt(this._position); - if (0xD800 <= code && code <= 0xDBFF) { - low = data.charCodeAt(this._position + 1); - if (isNaN(low)) { - this._terminal.surrogate_high = ch; - continue; - } - code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; - ch += data.charAt(this._position + 1); - } - if (0xDC00 <= code && code <= 0xDFFF) - continue; - switch (this._state) { - case ParserState.NORMAL: - if (ch in normalStateHandler) { - normalStateHandler[ch](this, this._inputHandler); - } - else { - this._inputHandler.addChar(ch, code); - } - break; - case ParserState.ESCAPED: - if (ch in escapedStateHandler) { - escapedStateHandler[ch](this, this._terminal); - break; - } - switch (ch) { - case '(': - case ')': - case '*': - case '+': - case '-': - case '.': - switch (ch) { - case '(': - this._terminal.gcharset = 0; - break; - case ')': - this._terminal.gcharset = 1; - break; - case '*': - this._terminal.gcharset = 2; - break; - case '+': - this._terminal.gcharset = 3; - break; - case '-': - this._terminal.gcharset = 1; - break; - case '.': - this._terminal.gcharset = 2; - break; - } - this._state = ParserState.CHARSET; - break; - case '/': - this._terminal.gcharset = 3; - this._state = ParserState.CHARSET; - this._position--; - break; - case 'N': - break; - case 'O': - break; - case 'n': - this._terminal.setgLevel(2); - break; - case 'o': - this._terminal.setgLevel(3); - break; - case '|': - this._terminal.setgLevel(3); - break; - case '}': - this._terminal.setgLevel(2); - break; - case '~': - this._terminal.setgLevel(1); - break; - case '7': - this._inputHandler.saveCursor(); - this._state = ParserState.NORMAL; - break; - case '8': - this._inputHandler.restoreCursor(); - this._state = ParserState.NORMAL; - break; - case '#': - this._state = ParserState.NORMAL; - this._position++; - break; - case 'H': - this._terminal.tabSet(); - this._state = ParserState.NORMAL; - break; - case '=': - this._terminal.log('Serial port requested application keypad.'); - this._terminal.applicationKeypad = true; - this._terminal.viewport.syncScrollArea(); - this._state = ParserState.NORMAL; - break; - case '>': - this._terminal.log('Switching back to normal keypad.'); - this._terminal.applicationKeypad = false; - this._terminal.viewport.syncScrollArea(); - this._state = ParserState.NORMAL; - break; - default: - this._state = ParserState.NORMAL; - this._terminal.error('Unknown ESC control: %s.', ch); - break; - } - break; - case ParserState.CHARSET: - if (ch in Charsets_1.CHARSETS) { - cs = Charsets_1.CHARSETS[ch]; - if (ch === '/') { - this.skipNextChar(); - } - } - else { - cs = Charsets_1.DEFAULT_CHARSET; - } - this._terminal.setgCharset(this._terminal.gcharset, cs); - this._terminal.gcharset = null; - this._state = ParserState.NORMAL; - break; - case ParserState.OSC: - if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) { - if (ch === EscapeSequences_1.C0.ESC) - this._position++; - this._terminal.params.push(this._terminal.currentParam); - switch (this._terminal.params[0]) { - case 0: - case 1: - case 2: - if (this._terminal.params[1]) { - this._terminal.title = this._terminal.params[1]; - this._terminal.handleTitle(this._terminal.title); - } - break; - case 3: - break; - case 4: - case 5: - break; - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - break; - case 46: - break; - case 50: - break; - case 51: - break; - case 52: - break; - case 104: - case 105: - case 110: - case 111: - case 112: - case 113: - case 114: - case 115: - case 116: - case 117: - case 118: - break; - } - this._terminal.params = []; - this._terminal.currentParam = 0; - this._state = ParserState.NORMAL; - } - else { - if (!this._terminal.params.length) { - if (ch >= '0' && ch <= '9') { - this._terminal.currentParam = - this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48; - } - else if (ch === ';') { - this._terminal.params.push(this._terminal.currentParam); - this._terminal.currentParam = ''; - } - } - else { - this._terminal.currentParam += ch; - } - } - break; - case ParserState.CSI_PARAM: - if (ch in csiParamStateHandler) { - csiParamStateHandler[ch](this); - break; - } - this.finalizeParam(); - this._state = ParserState.CSI; - case ParserState.CSI: - if (ch in csiStateHandler) { - if (this._terminal.debug) { - this._terminal.log("CSI " + (this._terminal.prefix ? this._terminal.prefix : '') + " " + (this._terminal.params ? this._terminal.params.join(';') : '') + " " + (this._terminal.postfix ? this._terminal.postfix : '') + " " + ch); - } - csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this); - } - else { - this._terminal.error('Unknown CSI code: %s.', ch); - } - this._state = ParserState.NORMAL; - this._terminal.prefix = ''; - this._terminal.postfix = ''; - break; - case ParserState.DCS: - if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) { - if (ch === EscapeSequences_1.C0.ESC) - this._position++; - var pt = void 0; - var valid = void 0; - switch (this._terminal.prefix) { - case '': - break; - case '$q': - pt = this._terminal.currentParam; - valid = false; - switch (pt) { - case '"q': - pt = '0"q'; - break; - case '"p': - pt = '61"p'; - break; - case 'r': - pt = '' - + (this._terminal.buffer.scrollTop + 1) - + ';' - + (this._terminal.buffer.scrollBottom + 1) - + 'r'; - break; - case 'm': - pt = '0m'; - break; - default: - this._terminal.error('Unknown DCS Pt: %s.', pt); - pt = ''; - break; - } - this._terminal.send(EscapeSequences_1.C0.ESC + 'P' + +valid + '$r' + pt + EscapeSequences_1.C0.ESC + '\\'); - break; - case '+p': - break; - case '+q': - pt = this._terminal.currentParam; - valid = false; - this._terminal.send(EscapeSequences_1.C0.ESC + 'P' + +valid + '+r' + pt + EscapeSequences_1.C0.ESC + '\\'); - break; - default: - this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix); - break; - } - this._terminal.currentParam = 0; - this._terminal.prefix = ''; - this._state = ParserState.NORMAL; - } - else if (!this._terminal.currentParam) { - if (!this._terminal.prefix && ch !== '$' && ch !== '+') { - this._terminal.currentParam = ch; - } - else if (this._terminal.prefix.length === 2) { - this._terminal.currentParam = ch; - } - else { - this._terminal.prefix += ch; - } - } - else { - this._terminal.currentParam += ch; - } - break; - case ParserState.IGNORE: - if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) { - if (ch === EscapeSequences_1.C0.ESC) - this._position++; - this._state = ParserState.NORMAL; - } - break; - } - } - return this._state; - }; - Parser.prototype.setState = function (state) { - this._state = state; - }; - Parser.prototype.setPrefix = function (prefix) { - this._terminal.prefix = prefix; - }; - Parser.prototype.setPostfix = function (postfix) { - this._terminal.postfix = postfix; - }; - Parser.prototype.setParam = function (param) { - this._terminal.currentParam = param; - }; - Parser.prototype.getParam = function () { - return this._terminal.currentParam; - }; - Parser.prototype.finalizeParam = function () { - this._terminal.params.push(this._terminal.currentParam); - this._terminal.currentParam = 0; - }; - Parser.prototype.skipNextChar = function () { - this._position++; - }; - return Parser; -}()); -exports.Parser = Parser; - - - -},{"./Charsets":3,"./EscapeSequences":5}],10:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var DomElementObjectPool_1 = require("./utils/DomElementObjectPool"); -var MAX_REFRESH_FRAME_SKIP = 5; -var FLAGS; -(function (FLAGS) { - FLAGS[FLAGS["BOLD"] = 1] = "BOLD"; - FLAGS[FLAGS["UNDERLINE"] = 2] = "UNDERLINE"; - FLAGS[FLAGS["BLINK"] = 4] = "BLINK"; - FLAGS[FLAGS["INVERSE"] = 8] = "INVERSE"; - FLAGS[FLAGS["INVISIBLE"] = 16] = "INVISIBLE"; -})(FLAGS || (FLAGS = {})); -; -var brokenBold = null; -var Renderer = (function () { - function Renderer(_terminal) { - this._terminal = _terminal; - this._refreshRowsQueue = []; - this._refreshFramesSkipped = 0; - this._refreshAnimationFrame = null; - this._spanElementObjectPool = new DomElementObjectPool_1.DomElementObjectPool('span'); - if (brokenBold === null) { - brokenBold = checkBoldBroken(this._terminal.element); - } - this._spanElementObjectPool = new DomElementObjectPool_1.DomElementObjectPool('span'); - } - Renderer.prototype.queueRefresh = function (start, end) { - this._refreshRowsQueue.push({ start: start, end: end }); - if (!this._refreshAnimationFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); - } - }; - Renderer.prototype._refreshLoop = function () { - var skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP; - if (skipFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); - return; - } - this._refreshFramesSkipped = 0; - var start; - var end; - if (this._refreshRowsQueue.length > 4) { - start = 0; - end = this._terminal.rows - 1; - } - else { - start = this._refreshRowsQueue[0].start; - end = this._refreshRowsQueue[0].end; - for (var i = 1; i < this._refreshRowsQueue.length; i++) { - if (this._refreshRowsQueue[i].start < start) { - start = this._refreshRowsQueue[i].start; - } - if (this._refreshRowsQueue[i].end > end) { - end = this._refreshRowsQueue[i].end; - } - } - } - this._refreshRowsQueue = []; - this._refreshAnimationFrame = null; - this._refresh(start, end); - }; - Renderer.prototype._refresh = function (start, end) { - var parent; - if (end - start >= this._terminal.rows / 2) { - parent = this._terminal.element.parentNode; - if (parent) { - this._terminal.element.removeChild(this._terminal.rowContainer); - } - } - var width = this._terminal.cols; - var y = start; - if (end >= this._terminal.rows) { - this._terminal.log('`end` is too large. Most likely a bad CSR.'); - end = this._terminal.rows - 1; - } - for (; y <= end; y++) { - var row = y + this._terminal.buffer.ydisp; - var line = this._terminal.buffer.lines.get(row); - var x = void 0; - if (this._terminal.buffer.y === y - (this._terminal.buffer.ybase - this._terminal.buffer.ydisp) && - this._terminal.cursorState && - !this._terminal.cursorHidden) { - x = this._terminal.buffer.x; - } - else { - x = -1; - } - var attr = this._terminal.defAttr; - var documentFragment = document.createDocumentFragment(); - var innerHTML = ''; - var currentElement = void 0; - while (this._terminal.children[y].children.length) { - var child = this._terminal.children[y].children[0]; - this._terminal.children[y].removeChild(child); - this._spanElementObjectPool.release(child); - } - for (var i = 0; i < width; i++) { - var data = line[i][0]; - var ch = line[i][1]; - var ch_width = line[i][2]; - var isCursor = i === x; - if (!ch_width) { - continue; - } - if (data !== attr || isCursor) { - if (attr !== this._terminal.defAttr && !isCursor) { - if (innerHTML) { - currentElement.innerHTML = innerHTML; - innerHTML = ''; - } - documentFragment.appendChild(currentElement); - currentElement = null; - } - if (data !== this._terminal.defAttr || isCursor) { - if (innerHTML && !currentElement) { - currentElement = this._spanElementObjectPool.acquire(); - } - if (currentElement) { - if (innerHTML) { - currentElement.innerHTML = innerHTML; - innerHTML = ''; - } - documentFragment.appendChild(currentElement); - } - currentElement = this._spanElementObjectPool.acquire(); - var bg = data & 0x1ff; - var fg = (data >> 9) & 0x1ff; - var flags = data >> 18; - if (isCursor) { - currentElement.classList.add('reverse-video'); - currentElement.classList.add('terminal-cursor'); - } - if (flags & FLAGS.BOLD) { - if (!brokenBold) { - currentElement.classList.add('xterm-bold'); - } - if (fg < 8) { - fg += 8; - } - } - if (flags & FLAGS.UNDERLINE) { - currentElement.classList.add('xterm-underline'); - } - if (flags & FLAGS.BLINK) { - currentElement.classList.add('xterm-blink'); - } - if (flags & FLAGS.INVERSE) { - var temp = bg; - bg = fg; - fg = temp; - if ((flags & 1) && fg < 8) { - fg += 8; - } - } - if (flags & FLAGS.INVISIBLE && !isCursor) { - currentElement.classList.add('xterm-hidden'); - } - if (flags & FLAGS.INVERSE) { - if (bg === 257) { - bg = 15; - } - if (fg === 256) { - fg = 0; - } - } - if (bg < 256) { - currentElement.classList.add("xterm-bg-color-" + bg); - } - if (fg < 256) { - currentElement.classList.add("xterm-color-" + fg); - } - } - } - if (ch_width === 2) { - innerHTML += "" + ch + ""; - } - else if (ch.charCodeAt(0) > 255) { - innerHTML += "" + ch + ""; - } - else { - switch (ch) { - case '&': - innerHTML += '&'; - break; - case '<': - innerHTML += '<'; - break; - case '>': - innerHTML += '>'; - break; - default: - if (ch <= ' ') { - innerHTML += ' '; - } - else { - innerHTML += ch; - } - break; - } - } - attr = isCursor ? -1 : data; - } - if (innerHTML && !currentElement) { - currentElement = this._spanElementObjectPool.acquire(); - } - if (currentElement) { - if (innerHTML) { - currentElement.innerHTML = innerHTML; - innerHTML = ''; - } - documentFragment.appendChild(currentElement); - currentElement = null; - } - this._terminal.children[y].appendChild(documentFragment); - } - if (parent) { - this._terminal.element.appendChild(this._terminal.rowContainer); - } - this._terminal.emit('refresh', { element: this._terminal.element, start: start, end: end }); - }; - ; - Renderer.prototype.refreshSelection = function (start, end) { - while (this._terminal.selectionContainer.children.length) { - this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]); - } - if (!start || !end) { - return; - } - var viewportStartRow = start[1] - this._terminal.buffer.ydisp; - var viewportEndRow = end[1] - this._terminal.buffer.ydisp; - var viewportCappedStartRow = Math.max(viewportStartRow, 0); - var viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); - if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { - return; - } - var documentFragment = document.createDocumentFragment(); - var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; - var endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); - var middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; - documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); - if (viewportCappedStartRow !== viewportCappedEndRow) { - var endCol_1 = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol_1)); - } - this._terminal.selectionContainer.appendChild(documentFragment); - }; - Renderer.prototype._createSelectionElement = function (row, colStart, colEnd, rowCount) { - if (rowCount === void 0) { rowCount = 1; } - var element = document.createElement('div'); - element.style.height = rowCount * this._terminal.charMeasure.height + "px"; - element.style.top = row * this._terminal.charMeasure.height + "px"; - element.style.left = colStart * this._terminal.charMeasure.width + "px"; - element.style.width = this._terminal.charMeasure.width * (colEnd - colStart) + "px"; - return element; - }; - return Renderer; -}()); -exports.Renderer = Renderer; -function checkBoldBroken(terminal) { - var document = terminal.ownerDocument; - var el = document.createElement('span'); - el.innerHTML = 'hello world'; - terminal.appendChild(el); - var w1 = el.offsetWidth; - var h1 = el.offsetHeight; - el.style.fontWeight = 'bold'; - var w2 = el.offsetWidth; - var h2 = el.offsetHeight; - terminal.removeChild(el); - return w1 !== w2 || h1 !== h2; -} - - - -},{"./utils/DomElementObjectPool":19}],11:[function(require,module,exports){ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var Mouse = require("./utils/Mouse"); -var Browser = require("./utils/Browser"); -var EventEmitter_1 = require("./EventEmitter"); -var SelectionModel_1 = require("./SelectionModel"); -var BufferLine_1 = require("./utils/BufferLine"); -var DRAG_SCROLL_MAX_THRESHOLD = 50; -var DRAG_SCROLL_MAX_SPEED = 15; -var DRAG_SCROLL_INTERVAL = 50; -var WORD_SEPARATORS = ' ()[]{}\'"'; -var LINE_DATA_CHAR_INDEX = 1; -var LINE_DATA_WIDTH_INDEX = 2; -var NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); -var ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); -var SelectionMode; -(function (SelectionMode) { - SelectionMode[SelectionMode["NORMAL"] = 0] = "NORMAL"; - SelectionMode[SelectionMode["WORD"] = 1] = "WORD"; - SelectionMode[SelectionMode["LINE"] = 2] = "LINE"; -})(SelectionMode || (SelectionMode = {})); -var SelectionManager = (function (_super) { - __extends(SelectionManager, _super); - function SelectionManager(_terminal, _buffer, _rowContainer, _charMeasure) { - var _this = _super.call(this) || this; - _this._terminal = _terminal; - _this._buffer = _buffer; - _this._rowContainer = _rowContainer; - _this._charMeasure = _charMeasure; - _this._enabled = true; - _this._initListeners(); - _this.enable(); - _this._model = new SelectionModel_1.SelectionModel(_terminal); - _this._activeSelectionMode = SelectionMode.NORMAL; - return _this; - } - SelectionManager.prototype._initListeners = function () { - var _this = this; - this._mouseMoveListener = function (event) { return _this._onMouseMove(event); }; - this._mouseUpListener = function (event) { return _this._onMouseUp(event); }; - this._rowContainer.addEventListener('mousedown', function (event) { return _this._onMouseDown(event); }); - this._buffer.on('trim', function (amount) { return _this._onTrim(amount); }); - }; - SelectionManager.prototype.disable = function () { - this.clearSelection(); - this._enabled = false; - }; - SelectionManager.prototype.enable = function () { - this._enabled = true; - }; - SelectionManager.prototype.setBuffer = function (buffer) { - this._buffer = buffer; - this.clearSelection(); - }; - Object.defineProperty(SelectionManager.prototype, "selectionStart", { - get: function () { return this._model.finalSelectionStart; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SelectionManager.prototype, "selectionEnd", { - get: function () { return this._model.finalSelectionEnd; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SelectionManager.prototype, "hasSelection", { - get: function () { - var start = this._model.finalSelectionStart; - var end = this._model.finalSelectionEnd; - if (!start || !end) { - return false; - } - return start[0] !== end[0] || start[1] !== end[1]; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SelectionManager.prototype, "selectionText", { - get: function () { - var start = this._model.finalSelectionStart; - var end = this._model.finalSelectionEnd; - if (!start || !end) { - return ''; - } - var startRowEndCol = start[1] === end[1] ? end[0] : null; - var result = []; - result.push(BufferLine_1.translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol)); - for (var i = start[1] + 1; i <= end[1] - 1; i++) { - var bufferLine = this._buffer.get(i); - var lineText = BufferLine_1.translateBufferLineToString(bufferLine, true); - if (bufferLine.isWrapped) { - result[result.length - 1] += lineText; - } - else { - result.push(lineText); - } - } - if (start[1] !== end[1]) { - var bufferLine = this._buffer.get(end[1]); - var lineText = BufferLine_1.translateBufferLineToString(bufferLine, true, 0, end[0]); - if (bufferLine.isWrapped) { - result[result.length - 1] += lineText; - } - else { - result.push(lineText); - } - } - var formattedResult = result.map(function (line) { - return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' '); - }).join(Browser.isMSWindows ? '\r\n' : '\n'); - return formattedResult; - }, - enumerable: true, - configurable: true - }); - SelectionManager.prototype.clearSelection = function () { - this._model.clearSelection(); - this._removeMouseDownListeners(); - this.refresh(); - }; - SelectionManager.prototype.refresh = function (isNewSelection) { - var _this = this; - if (!this._refreshAnimationFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(function () { return _this._refresh(); }); - } - if (Browser.isLinux && isNewSelection) { - var selectionText = this.selectionText; - if (selectionText.length) { - this.emit('newselection', this.selectionText); - } - } - }; - SelectionManager.prototype._refresh = function () { - this._refreshAnimationFrame = null; - this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd }); - }; - SelectionManager.prototype.selectAll = function () { - this._model.isSelectAllActive = true; - this.refresh(); - }; - SelectionManager.prototype._onTrim = function (amount) { - var needsRefresh = this._model.onTrim(amount); - if (needsRefresh) { - this.refresh(); - } - }; - SelectionManager.prototype._getMouseBufferCoords = function (event) { - var coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true); - if (!coords) { - return null; - } - coords[0]--; - coords[1]--; - coords[1] += this._terminal.buffer.ydisp; - return coords; - }; - SelectionManager.prototype._getMouseEventScrollAmount = function (event) { - var offset = Mouse.getCoordsRelativeToElement(event, this._rowContainer)[1]; - var terminalHeight = this._terminal.rows * this._charMeasure.height; - if (offset >= 0 && offset <= terminalHeight) { - return 0; - } - if (offset > terminalHeight) { - offset -= terminalHeight; - } - offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD); - offset /= DRAG_SCROLL_MAX_THRESHOLD; - return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1)); - }; - SelectionManager.prototype._onMouseDown = function (event) { - if (event.button === 2 && this.hasSelection) { - event.stopPropagation(); - return; - } - if (event.button !== 0) { - return; - } - if (!this._enabled) { - var shouldForceSelection = Browser.isMac && event.altKey; - if (!shouldForceSelection) { - return; - } - event.stopPropagation(); - } - event.preventDefault(); - this._dragScrollAmount = 0; - if (this._enabled && event.shiftKey) { - this._onIncrementalClick(event); - } - else { - if (event.detail === 1) { - this._onSingleClick(event); - } - else if (event.detail === 2) { - this._onDoubleClick(event); - } - else if (event.detail === 3) { - this._onTripleClick(event); - } - } - this._addMouseDownListeners(); - this.refresh(true); - }; - SelectionManager.prototype._addMouseDownListeners = function () { - var _this = this; - this._rowContainer.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); - this._rowContainer.ownerDocument.addEventListener('mouseup', this._mouseUpListener); - this._dragScrollIntervalTimer = setInterval(function () { return _this._dragScroll(); }, DRAG_SCROLL_INTERVAL); - }; - SelectionManager.prototype._removeMouseDownListeners = function () { - this._rowContainer.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); - this._rowContainer.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); - clearInterval(this._dragScrollIntervalTimer); - this._dragScrollIntervalTimer = null; - }; - SelectionManager.prototype._onIncrementalClick = function (event) { - if (this._model.selectionStart) { - this._model.selectionEnd = this._getMouseBufferCoords(event); - } - }; - SelectionManager.prototype._onSingleClick = function (event) { - this._model.selectionStartLength = 0; - this._model.isSelectAllActive = false; - this._activeSelectionMode = SelectionMode.NORMAL; - this._model.selectionStart = this._getMouseBufferCoords(event); - if (!this._model.selectionStart) { - return; - } - this._model.selectionEnd = null; - var line = this._buffer.get(this._model.selectionStart[1]); - if (!line) { - return; - } - var char = line[this._model.selectionStart[0]]; - if (char[LINE_DATA_WIDTH_INDEX] === 0) { - this._model.selectionStart[0]++; - } - }; - SelectionManager.prototype._onDoubleClick = function (event) { - var coords = this._getMouseBufferCoords(event); - if (coords) { - this._activeSelectionMode = SelectionMode.WORD; - this._selectWordAt(coords); - } - }; - SelectionManager.prototype._onTripleClick = function (event) { - var coords = this._getMouseBufferCoords(event); - if (coords) { - this._activeSelectionMode = SelectionMode.LINE; - this._selectLineAt(coords[1]); - } - }; - SelectionManager.prototype._onMouseMove = function (event) { - var previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null; - this._model.selectionEnd = this._getMouseBufferCoords(event); - if (!this._model.selectionEnd) { - this.refresh(true); - return; - } - if (this._activeSelectionMode === SelectionMode.LINE) { - if (this._model.selectionEnd[1] < this._model.selectionStart[1]) { - this._model.selectionEnd[0] = 0; - } - else { - this._model.selectionEnd[0] = this._terminal.cols; - } - } - else if (this._activeSelectionMode === SelectionMode.WORD) { - this._selectToWordAt(this._model.selectionEnd); - } - this._dragScrollAmount = this._getMouseEventScrollAmount(event); - if (this._dragScrollAmount > 0) { - this._model.selectionEnd[0] = this._terminal.cols - 1; - } - else if (this._dragScrollAmount < 0) { - this._model.selectionEnd[0] = 0; - } - if (this._model.selectionEnd[1] < this._buffer.length) { - var char = this._buffer.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]]; - if (char && char[2] === 0) { - this._model.selectionEnd[0]++; - } - } - if (!previousSelectionEnd || - previousSelectionEnd[0] !== this._model.selectionEnd[0] || - previousSelectionEnd[1] !== this._model.selectionEnd[1]) { - this.refresh(true); - } - }; - SelectionManager.prototype._dragScroll = function () { - if (this._dragScrollAmount) { - this._terminal.scrollDisp(this._dragScrollAmount, false); - if (this._dragScrollAmount > 0) { - this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows]; - } - else { - this._model.selectionEnd = [0, this._terminal.buffer.ydisp]; - } - this.refresh(); - } - }; - SelectionManager.prototype._onMouseUp = function (event) { - this._removeMouseDownListeners(); - }; - SelectionManager.prototype._convertViewportColToCharacterIndex = function (bufferLine, coords) { - var charIndex = coords[0]; - for (var i = 0; coords[0] >= i; i++) { - var char = bufferLine[i]; - if (char[LINE_DATA_WIDTH_INDEX] === 0) { - charIndex--; - } - } - return charIndex; - }; - SelectionManager.prototype.setSelection = function (col, row, length) { - this._model.clearSelection(); - this._removeMouseDownListeners(); - this._model.selectionStart = [col, row]; - this._model.selectionStartLength = length; - this.refresh(); - }; - SelectionManager.prototype._getWordAt = function (coords) { - var bufferLine = this._buffer.get(coords[1]); - if (!bufferLine) { - return null; - } - var line = BufferLine_1.translateBufferLineToString(bufferLine, false); - var endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords); - var startIndex = endIndex; - var charOffset = coords[0] - startIndex; - var leftWideCharCount = 0; - var rightWideCharCount = 0; - if (line.charAt(startIndex) === ' ') { - while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') { - startIndex--; - } - while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') { - endIndex++; - } - } - else { - var startCol = coords[0]; - var endCol = coords[0]; - if (bufferLine[startCol][LINE_DATA_WIDTH_INDEX] === 0) { - leftWideCharCount++; - startCol--; - } - if (bufferLine[endCol][LINE_DATA_WIDTH_INDEX] === 2) { - rightWideCharCount++; - endCol++; - } - while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) { - if (bufferLine[startCol - 1][LINE_DATA_WIDTH_INDEX] === 0) { - leftWideCharCount++; - startCol--; - } - startIndex--; - startCol--; - } - while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) { - if (bufferLine[endCol + 1][LINE_DATA_WIDTH_INDEX] === 2) { - rightWideCharCount++; - endCol++; - } - endIndex++; - endCol++; - } - } - var start = startIndex + charOffset - leftWideCharCount; - var length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1, this._terminal.cols); - return { start: start, length: length }; - }; - SelectionManager.prototype._selectWordAt = function (coords) { - var wordPosition = this._getWordAt(coords); - if (wordPosition) { - this._model.selectionStart = [wordPosition.start, coords[1]]; - this._model.selectionStartLength = wordPosition.length; - } - }; - SelectionManager.prototype._selectToWordAt = function (coords) { - var wordPosition = this._getWordAt(coords); - if (wordPosition) { - this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; - } - }; - SelectionManager.prototype._isCharWordSeparator = function (char) { - return WORD_SEPARATORS.indexOf(char) >= 0; - }; - SelectionManager.prototype._selectLineAt = function (line) { - this._model.selectionStart = [0, line]; - this._model.selectionStartLength = this._terminal.cols; - }; - return SelectionManager; -}(EventEmitter_1.EventEmitter)); -exports.SelectionManager = SelectionManager; - - - -},{"./EventEmitter":6,"./SelectionModel":12,"./utils/Browser":15,"./utils/BufferLine":16,"./utils/Mouse":21}],12:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var SelectionModel = (function () { - function SelectionModel(_terminal) { - this._terminal = _terminal; - this.clearSelection(); - } - SelectionModel.prototype.clearSelection = function () { - this.selectionStart = null; - this.selectionEnd = null; - this.isSelectAllActive = false; - this.selectionStartLength = 0; - }; - Object.defineProperty(SelectionModel.prototype, "finalSelectionStart", { - get: function () { - if (this.isSelectAllActive) { - return [0, 0]; - } - if (!this.selectionEnd || !this.selectionStart) { - return this.selectionStart; - } - return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SelectionModel.prototype, "finalSelectionEnd", { - get: function () { - if (this.isSelectAllActive) { - return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1]; - } - if (!this.selectionStart) { - return null; - } - if (!this.selectionEnd || this.areSelectionValuesReversed()) { - return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]]; - } - if (this.selectionStartLength) { - if (this.selectionEnd[1] === this.selectionStart[1]) { - return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]]; - } - } - return this.selectionEnd; - }, - enumerable: true, - configurable: true - }); - SelectionModel.prototype.areSelectionValuesReversed = function () { - var start = this.selectionStart; - var end = this.selectionEnd; - return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]); - }; - SelectionModel.prototype.onTrim = function (amount) { - if (this.selectionStart) { - this.selectionStart[1] -= amount; - } - if (this.selectionEnd) { - this.selectionEnd[1] -= amount; - } - if (this.selectionEnd && this.selectionEnd[1] < 0) { - this.clearSelection(); - return true; - } - if (this.selectionStart && this.selectionStart[1] < 0) { - this.selectionStart[1] = 0; - } - return false; - }; - return SelectionModel; -}()); -exports.SelectionModel = SelectionModel; - - - -},{}],13:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var Viewport = (function () { - function Viewport(terminal, viewportElement, scrollArea, charMeasure) { - var _this = this; - this.terminal = terminal; - this.viewportElement = viewportElement; - this.scrollArea = scrollArea; - this.charMeasure = charMeasure; - this.currentRowHeight = 0; - this.lastRecordedBufferLength = 0; - this.lastRecordedViewportHeight = 0; - this.terminal.on('scroll', this.syncScrollArea.bind(this)); - this.terminal.on('resize', this.syncScrollArea.bind(this)); - this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); - setTimeout(function () { return _this.syncScrollArea(); }, 0); - } - Viewport.prototype.refresh = function () { - if (this.charMeasure.height > 0) { - var rowHeightChanged = this.charMeasure.height !== this.currentRowHeight; - if (rowHeightChanged) { - this.currentRowHeight = this.charMeasure.height; - this.viewportElement.style.lineHeight = this.charMeasure.height + 'px'; - this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px'; - } - var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; - if (rowHeightChanged || viewportHeightChanged) { - this.lastRecordedViewportHeight = this.terminal.rows; - this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px'; - this.terminal.selectionContainer.style.height = this.viewportElement.style.height; - } - this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px'; - } - }; - Viewport.prototype.syncScrollArea = function () { - if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) { - this.lastRecordedBufferLength = this.terminal.buffer.lines.length; - this.refresh(); - } - else if (this.lastRecordedViewportHeight !== this.terminal.rows) { - this.refresh(); - } - else { - if (this.charMeasure.height !== this.currentRowHeight) { - this.refresh(); - } - } - var scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight; - if (this.viewportElement.scrollTop !== scrollTop) { - this.viewportElement.scrollTop = scrollTop; - } - }; - Viewport.prototype.onScroll = function (ev) { - var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); - var diff = newRow - this.terminal.buffer.ydisp; - this.terminal.scrollDisp(diff, true); - }; - Viewport.prototype.onWheel = function (ev) { - if (ev.deltaY === 0) { - return; - } - var multiplier = 1; - if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { - multiplier = this.currentRowHeight; - } - else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { - multiplier = this.currentRowHeight * this.terminal.rows; - } - this.viewportElement.scrollTop += ev.deltaY * multiplier; - ev.preventDefault(); - }; - ; - Viewport.prototype.onTouchStart = function (ev) { - this.lastTouchY = ev.touches[0].pageY; - }; - ; - Viewport.prototype.onTouchMove = function (ev) { - var deltaY = this.lastTouchY - ev.touches[0].pageY; - this.lastTouchY = ev.touches[0].pageY; - if (deltaY === 0) { - return; - } - this.viewportElement.scrollTop += deltaY; - ev.preventDefault(); - }; - ; - return Viewport; -}()); -exports.Viewport = Viewport; - - - -},{}],14:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -function prepareTextForTerminal(text, isMSWindows) { - if (isMSWindows) { - return text.replace(/\r?\n/g, '\r'); - } - return text; -} -exports.prepareTextForTerminal = prepareTextForTerminal; -function copyHandler(ev, term, selectionManager) { - if (term.browser.isMSIE) { - window.clipboardData.setData('Text', selectionManager.selectionText); - } - else { - ev.clipboardData.setData('text/plain', selectionManager.selectionText); - } - ev.preventDefault(); -} -exports.copyHandler = copyHandler; -function pasteHandler(ev, term) { - ev.stopPropagation(); - var text; - var dispatchPaste = function (text) { - text = prepareTextForTerminal(text, term.browser.isMSWindows); - term.handler(text); - term.textarea.value = ''; - term.emit('paste', text); - return term.cancel(ev); - }; - if (term.browser.isMSIE) { - if (window.clipboardData) { - text = window.clipboardData.getData('Text'); - dispatchPaste(text); - } - } - else { - if (ev.clipboardData) { - text = ev.clipboardData.getData('text/plain'); - dispatchPaste(text); - } - } -} -exports.pasteHandler = pasteHandler; -function moveTextAreaUnderMouseCursor(ev, textarea) { - textarea.style.position = 'fixed'; - textarea.style.width = '20px'; - textarea.style.height = '20px'; - textarea.style.left = (ev.clientX - 10) + 'px'; - textarea.style.top = (ev.clientY - 10) + 'px'; - textarea.style.zIndex = '1000'; - textarea.focus(); - setTimeout(function () { - textarea.style.position = null; - textarea.style.width = null; - textarea.style.height = null; - textarea.style.left = null; - textarea.style.top = null; - textarea.style.zIndex = null; - }, 4); -} -exports.moveTextAreaUnderMouseCursor = moveTextAreaUnderMouseCursor; -function rightClickHandler(ev, textarea, selectionManager) { - moveTextAreaUnderMouseCursor(ev, textarea); - textarea.value = selectionManager.selectionText; - textarea.select(); -} -exports.rightClickHandler = rightClickHandler; - - - -},{}],15:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var Generic_1 = require("./Generic"); -var isNode = (typeof navigator === 'undefined') ? true : false; -var userAgent = (isNode) ? 'node' : navigator.userAgent; -var platform = (isNode) ? 'node' : navigator.platform; -exports.isFirefox = !!~userAgent.indexOf('Firefox'); -exports.isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident'); -exports.isMac = Generic_1.contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform); -exports.isIpad = platform === 'iPad'; -exports.isIphone = platform === 'iPhone'; -exports.isMSWindows = Generic_1.contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform); -exports.isLinux = platform.indexOf('Linux') >= 0; - - - -},{"./Generic":20}],16:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var LINE_DATA_CHAR_INDEX = 1; -var LINE_DATA_WIDTH_INDEX = 2; -function translateBufferLineToString(line, trimRight, startCol, endCol) { - if (startCol === void 0) { startCol = 0; } - if (endCol === void 0) { endCol = null; } - var lineString = ''; - var widthAdjustedStartCol = startCol; - var widthAdjustedEndCol = endCol; - for (var i = 0; i < line.length; i++) { - var char = line[i]; - lineString += char[LINE_DATA_CHAR_INDEX]; - if (char[LINE_DATA_WIDTH_INDEX] === 0) { - if (startCol >= i) { - widthAdjustedStartCol--; - } - if (endCol >= i) { - widthAdjustedEndCol--; - } - } - } - var finalEndCol = widthAdjustedEndCol || line.length; - if (trimRight) { - var rightWhitespaceIndex = lineString.search(/\s+$/); - if (rightWhitespaceIndex !== -1) { - finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex); - } - if (finalEndCol <= widthAdjustedStartCol) { - return ''; - } - } - return lineString.substring(widthAdjustedStartCol, finalEndCol); -} -exports.translateBufferLineToString = translateBufferLineToString; - - - -},{}],17:[function(require,module,exports){ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var EventEmitter_js_1 = require("../EventEmitter.js"); -var CharMeasure = (function (_super) { - __extends(CharMeasure, _super); - function CharMeasure(document, parentElement) { - var _this = _super.call(this) || this; - _this._document = document; - _this._parentElement = parentElement; - return _this; - } - Object.defineProperty(CharMeasure.prototype, "width", { - get: function () { - return this._width; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(CharMeasure.prototype, "height", { - get: function () { - return this._height; - }, - enumerable: true, - configurable: true - }); - CharMeasure.prototype.measure = function () { - var _this = this; - if (!this._measureElement) { - this._measureElement = this._document.createElement('span'); - this._measureElement.style.position = 'absolute'; - this._measureElement.style.top = '0'; - this._measureElement.style.left = '-9999em'; - this._measureElement.textContent = 'W'; - this._measureElement.setAttribute('aria-hidden', 'true'); - this._parentElement.appendChild(this._measureElement); - setTimeout(function () { return _this._doMeasure(); }, 0); - } - else { - this._doMeasure(); - } - }; - CharMeasure.prototype._doMeasure = function () { - var geometry = this._measureElement.getBoundingClientRect(); - if (geometry.width === 0 || geometry.height === 0) { - return; - } - if (this._width !== geometry.width || this._height !== geometry.height) { - this._width = geometry.width; - this._height = geometry.height; - this.emit('charsizechanged'); - } - }; - return CharMeasure; -}(EventEmitter_js_1.EventEmitter)); -exports.CharMeasure = CharMeasure; - - - -},{"../EventEmitter.js":6}],18:[function(require,module,exports){ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var EventEmitter_1 = require("../EventEmitter"); -var CircularList = (function (_super) { - __extends(CircularList, _super); - function CircularList(maxLength) { - var _this = _super.call(this) || this; - _this._array = new Array(maxLength); - _this._startIndex = 0; - _this._length = 0; - return _this; - } - Object.defineProperty(CircularList.prototype, "maxLength", { - get: function () { - return this._array.length; - }, - set: function (newMaxLength) { - var newArray = new Array(newMaxLength); - for (var i = 0; i < Math.min(newMaxLength, this.length); i++) { - newArray[i] = this._array[this._getCyclicIndex(i)]; - } - this._array = newArray; - this._startIndex = 0; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(CircularList.prototype, "length", { - get: function () { - return this._length; - }, - set: function (newLength) { - if (newLength > this._length) { - for (var i = this._length; i < newLength; i++) { - this._array[i] = undefined; - } - } - this._length = newLength; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(CircularList.prototype, "forEach", { - get: function () { - var _this = this; - return function (callbackfn) { - var i = 0; - var length = _this.length; - for (var i_1 = 0; i_1 < length; i_1++) { - callbackfn(_this.get(i_1), i_1); - } - }; - }, - enumerable: true, - configurable: true - }); - CircularList.prototype.get = function (index) { - return this._array[this._getCyclicIndex(index)]; - }; - CircularList.prototype.set = function (index, value) { - this._array[this._getCyclicIndex(index)] = value; - }; - CircularList.prototype.push = function (value) { - this._array[this._getCyclicIndex(this._length)] = value; - if (this._length === this.maxLength) { - this._startIndex++; - if (this._startIndex === this.maxLength) { - this._startIndex = 0; - } - this.emit('trim', 1); - } - else { - this._length++; - } - }; - CircularList.prototype.pop = function () { - return this._array[this._getCyclicIndex(this._length-- - 1)]; - }; - CircularList.prototype.splice = function (start, deleteCount) { - var items = []; - for (var _i = 2; _i < arguments.length; _i++) { - items[_i - 2] = arguments[_i]; - } - if (deleteCount) { - for (var i = start; i < this._length - deleteCount; i++) { - this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; - } - this._length -= deleteCount; - } - if (items && items.length) { - for (var i = this._length - 1; i >= start; i--) { - this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)]; - } - for (var i = 0; i < items.length; i++) { - this._array[this._getCyclicIndex(start + i)] = items[i]; - } - if (this._length + items.length > this.maxLength) { - var countToTrim = (this._length + items.length) - this.maxLength; - this._startIndex += countToTrim; - this._length = this.maxLength; - this.emit('trim', countToTrim); - } - else { - this._length += items.length; - } - } - }; - CircularList.prototype.trimStart = function (count) { - if (count > this._length) { - count = this._length; - } - this._startIndex += count; - this._length -= count; - this.emit('trim', count); - }; - CircularList.prototype.shiftElements = function (start, count, offset) { - if (count <= 0) { - return; - } - if (start < 0 || start >= this._length) { - throw new Error('start argument out of range'); - } - if (start + offset < 0) { - throw new Error('Cannot shift elements in list beyond index 0'); - } - if (offset > 0) { - for (var i = count - 1; i >= 0; i--) { - this.set(start + i + offset, this.get(start + i)); - } - var expandListBy = (start + count + offset) - this._length; - if (expandListBy > 0) { - this._length += expandListBy; - while (this._length > this.maxLength) { - this._length--; - this._startIndex++; - this.emit('trim', 1); - } - } - } - else { - for (var i = 0; i < count; i++) { - this.set(start + i + offset, this.get(start + i)); - } - } - }; - CircularList.prototype._getCyclicIndex = function (index) { - return (this._startIndex + index) % this.maxLength; - }; - return CircularList; -}(EventEmitter_1.EventEmitter)); -exports.CircularList = CircularList; - - - -},{"../EventEmitter":6}],19:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var DomElementObjectPool = (function () { - function DomElementObjectPool(type) { - this.type = type; - this._type = type; - this._pool = []; - this._inUse = {}; - } - DomElementObjectPool.prototype.acquire = function () { - var element; - if (this._pool.length === 0) { - element = this._createNew(); - } - else { - element = this._pool.pop(); - } - this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element; - return element; - }; - DomElementObjectPool.prototype.release = function (element) { - if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) { - throw new Error('Could not release an element not yet acquired'); - } - delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]; - this._cleanElement(element); - this._pool.push(element); - }; - DomElementObjectPool.prototype._createNew = function () { - var element = document.createElement(this._type); - var id = DomElementObjectPool._objectCount++; - element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10)); - return element; - }; - DomElementObjectPool.prototype._cleanElement = function (element) { - element.className = ''; - element.innerHTML = ''; - }; - return DomElementObjectPool; -}()); -DomElementObjectPool.OBJECT_ID_ATTRIBUTE = 'data-obj-id'; -DomElementObjectPool._objectCount = 0; -exports.DomElementObjectPool = DomElementObjectPool; - - - },{}],20:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -function contains(arr, el) { - return arr.indexOf(el) >= 0; -} -exports.contains = contains; -; - - - -},{}],21:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -function getCoordsRelativeToElement(event, element) { - if (event.pageX == null) { - return null; - } - var x = event.pageX; - var y = event.pageY; - while (element && element !== self.document.documentElement) { - x -= element.offsetLeft; - y -= element.offsetTop; - element = 'offsetParent' in element ? element.offsetParent : element.parentElement; - } - return [x, y]; -} -exports.getCoordsRelativeToElement = getCoordsRelativeToElement; -function getCoords(event, rowContainer, charMeasure, colCount, rowCount, isSelection) { - if (!charMeasure.width || !charMeasure.height) { - return null; - } - var coords = getCoordsRelativeToElement(event, rowContainer); - if (!coords) { - return null; - } - coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width); - coords[1] = Math.ceil(coords[1] / charMeasure.height); - coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1); - coords[1] = Math.min(Math.max(coords[1], 1), rowCount + 1); - return coords; -} -exports.getCoords = getCoords; -function getRawByteCoords(event, rowContainer, charMeasure, colCount, rowCount) { - var coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount); - var x = coords[0]; - var y = coords[1]; - x += 32; - y += 32; - return { x: x, y: y }; -} -exports.getRawByteCoords = getRawByteCoords; - - - -},{}],22:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var BufferSet_1 = require("./BufferSet"); -var CompositionHelper_1 = require("./CompositionHelper"); -var EventEmitter_1 = require("./EventEmitter"); -var Viewport_1 = require("./Viewport"); -var Clipboard_1 = require("./handlers/Clipboard"); -var EscapeSequences_1 = require("./EscapeSequences"); -var InputHandler_1 = require("./InputHandler"); -var Parser_1 = require("./Parser"); -var Renderer_1 = require("./Renderer"); -var Linkifier_1 = require("./Linkifier"); -var SelectionManager_1 = require("./SelectionManager"); -var CharMeasure_1 = require("./utils/CharMeasure"); -var Browser = require("./utils/Browser"); -var Mouse_1 = require("./utils/Mouse"); -var BufferLine_1 = require("./utils/BufferLine"); -var document = (typeof window != 'undefined') ? window.document : null; -var WRITE_BUFFER_PAUSE_THRESHOLD = 5; -var WRITE_BATCH_SIZE = 300; -var CURSOR_BLINK_INTERVAL = 600; -function Terminal(options) { - var self = this; - if (!(this instanceof Terminal)) { - return new Terminal(arguments[0], arguments[1], arguments[2]); - } - self.browser = Browser; - self.cancel = Terminal.cancel; - EventEmitter_1.EventEmitter.call(this); - if (typeof options === 'number') { - options = { - cols: arguments[0], - rows: arguments[1], - handler: arguments[2] - }; - } - options = options || {}; - Object.keys(Terminal.defaults).forEach(function (key) { - if (options[key] == null) { - options[key] = Terminal.options[key]; - if (Terminal[key] !== Terminal.defaults[key]) { - options[key] = Terminal[key]; - } - } - self[key] = options[key]; - }); - if (options.colors.length === 8) { - options.colors = options.colors.concat(Terminal._colors.slice(8)); - } - else if (options.colors.length === 16) { - options.colors = options.colors.concat(Terminal._colors.slice(16)); - } - else if (options.colors.length === 10) { - options.colors = options.colors.slice(0, -2).concat(Terminal._colors.slice(8, -2), options.colors.slice(-2)); - } - else if (options.colors.length === 18) { - options.colors = options.colors.concat(Terminal._colors.slice(16, -2), options.colors.slice(-2)); - } - this.colors = options.colors; - this.options = options; - this.parent = options.body || options.parent || (document ? document.getElementsByTagName('body')[0] : null); - this.cols = options.cols || options.geometry[0]; - this.rows = options.rows || options.geometry[1]; - this.geometry = [this.cols, this.rows]; - if (options.handler) { - this.on('data', options.handler); - } - this.cursorState = 0; - this.cursorHidden = false; - this.convertEol; - this.queue = ''; - this.customKeyEventHandler = null; - this.cursorBlinkInterval = null; - this.applicationKeypad = false; - this.applicationCursor = false; - this.originMode = false; - this.insertMode = false; - this.wraparoundMode = true; - this.charset = null; - this.gcharset = null; - this.glevel = 0; - this.charsets = [null]; - this.decLocator; - this.x10Mouse; - this.vt200Mouse; - this.vt300Mouse; - this.normalMouse; - this.mouseEvents; - this.sendFocus; - this.utfMouse; - this.sgrMouse; - this.urxvtMouse; - this.element; - this.children; - this.refreshStart; - this.refreshEnd; - this.savedX; - this.savedY; - this.savedCols; - this.readable = true; - this.writable = true; - this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); - this.curAttr = this.defAttr; - this.params = []; - this.currentParam = 0; - this.prefix = ''; - this.postfix = ''; - this.inputHandler = new InputHandler_1.InputHandler(this); - this.parser = new Parser_1.Parser(this.inputHandler, this); - this.renderer = this.renderer || null; - this.selectionManager = this.selectionManager || null; - this.linkifier = this.linkifier || new Linkifier_1.Linkifier(); - this.writeBuffer = []; - this.writeInProgress = false; - this.xoffSentToCatchUp = false; - this.writeStopped = false; - this.surrogate_high = ''; - this.buffers = new BufferSet_1.BufferSet(this); - this.buffer = this.buffers.active; - this.buffers.on('activate', function (buffer) { - this._terminal.buffer = buffer; - }); - if (this.selectionManager) { - this.selectionManager.setBuffer(this.buffer.lines); - } - this.setupStops(); - this.userScrolling = false; -} -inherits(Terminal, EventEmitter_1.EventEmitter); -Terminal.prototype.eraseAttr = function () { - return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); +var EscapeSequences_1 = require("../../common/data/EscapeSequences"); +var KEYCODE_KEY_MAPPINGS = { + 48: ['0', ')'], + 49: ['1', '!'], + 50: ['2', '@'], + 51: ['3', '#'], + 52: ['4', '$'], + 53: ['5', '%'], + 54: ['6', '^'], + 55: ['7', '&'], + 56: ['8', '*'], + 57: ['9', '('], + 186: [';', ':'], + 187: ['=', '+'], + 188: [',', '<'], + 189: ['-', '_'], + 190: ['.', '>'], + 191: ['/', '?'], + 192: ['`', '~'], + 219: ['[', '{'], + 220: ['\\', '|'], + 221: [']', '}'], + 222: ['\'', '"'] }; -Terminal.tangoColors = [ - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' -]; -Terminal.colors = (function () { - var colors = Terminal.tangoColors.slice(), r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff], i; - i = 0; - for (; i < 216; i++) { - out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); - } - i = 0; - for (; i < 24; i++) { - r = 8 + i * 10; - out(r, r, r); - } - function out(r, g, b) { - colors.push('#' + hex(r) + hex(g) + hex(b)); - } - function hex(c) { - c = c.toString(16); - return c.length < 2 ? '0' + c : c; - } - return colors; -})(); -Terminal._colors = Terminal.colors.slice(); -Terminal.vcolors = (function () { - var out = [], colors = Terminal.colors, i = 0, color; - for (; i < 256; i++) { - color = parseInt(colors[i].substring(1), 16); - out.push([ - (color >> 16) & 0xff, - (color >> 8) & 0xff, - color & 0xff - ]); - } - return out; -})(); -Terminal.defaults = { - colors: Terminal.colors, - theme: 'default', - convertEol: false, - termName: 'xterm', - geometry: [80, 24], - cursorBlink: false, - cursorStyle: 'block', - visualBell: false, - popOnBell: false, - scrollback: 1000, - screenKeys: false, - debug: false, - cancelEvents: false, - disableStdin: false, - useFlowControl: false, - tabStopWidth: 8 -}; -Terminal.options = {}; -Terminal.focus = null; -each(keys(Terminal.defaults), function (key) { - Terminal[key] = Terminal.defaults[key]; - Terminal.options[key] = Terminal.defaults[key]; -}); -Terminal.prototype.focus = function () { - return this.textarea.focus(); -}; -Terminal.prototype.getOption = function (key) { - if (!(key in Terminal.defaults)) { - throw new Error('No option with key "' + key + '"'); - } - if (typeof this.options[key] !== 'undefined') { - return this.options[key]; - } - return this[key]; -}; -Terminal.prototype.setOption = function (key, value) { - if (!(key in Terminal.defaults)) { - throw new Error('No option with key "' + key + '"'); - } - switch (key) { - case 'scrollback': - if (value < this.rows) { - var msg = 'Setting the scrollback value less than the number of rows '; - msg += "(" + this.rows + ") is not allowed."; - console.warn(msg); - return false; - } - if (this.options[key] !== value) { - if (this.buffer.lines.length > value) { - var amountToTrim = this.buffer.lines.length - value; - var needsRefresh = (this.buffer.ydisp - amountToTrim < 0); - this.buffer.lines.trimStart(amountToTrim); - this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0); - this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0); - if (needsRefresh) { - this.refresh(0, this.rows - 1); - } - } - this.buffer.lines.maxLength = value; - this.viewport.syncScrollArea(); - } - break; - } - this[key] = value; - this.options[key] = value; - switch (key) { - case 'cursorBlink': - this.setCursorBlinking(value); - break; - case 'cursorStyle': - this.element.classList.toggle("xterm-cursor-style-block", value === 'block'); - this.element.classList.toggle("xterm-cursor-style-underline", value === 'underline'); - this.element.classList.toggle("xterm-cursor-style-bar", value === 'bar'); - break; - case 'tabStopWidth': - this.setupStops(); - break; - } -}; -Terminal.prototype.restartCursorBlinking = function () { - this.setCursorBlinking(this.options.cursorBlink); -}; -Terminal.prototype.setCursorBlinking = function (enabled) { - this.element.classList.toggle('xterm-cursor-blink', enabled); - this.clearCursorBlinkingInterval(); - if (enabled) { - var self = this; - this.cursorBlinkInterval = setInterval(function () { - self.element.classList.toggle('xterm-cursor-blink-on'); - }, CURSOR_BLINK_INTERVAL); - } -}; -Terminal.prototype.clearCursorBlinkingInterval = function () { - this.element.classList.remove('xterm-cursor-blink-on'); - if (this.cursorBlinkInterval) { - clearInterval(this.cursorBlinkInterval); - this.cursorBlinkInterval = null; - } -}; -Terminal.bindFocus = function (term) { - on(term.textarea, 'focus', function (ev) { - if (term.sendFocus) { - term.send(EscapeSequences_1.C0.ESC + '[I'); - } - term.element.classList.add('focus'); - term.showCursor(); - term.restartCursorBlinking.apply(term); - Terminal.focus = term; - term.emit('focus', { terminal: term }); - }); -}; -Terminal.prototype.blur = function () { - return this.textarea.blur(); -}; -Terminal.bindBlur = function (term) { - on(term.textarea, 'blur', function (ev) { - term.refresh(term.buffer.y, term.buffer.y); - if (term.sendFocus) { - term.send(EscapeSequences_1.C0.ESC + '[O'); - } - term.element.classList.remove('focus'); - term.clearCursorBlinkingInterval.apply(term); - Terminal.focus = null; - term.emit('blur', { terminal: term }); - }); -}; -Terminal.prototype.initGlobal = function () { - var _this = this; - var term = this; - Terminal.bindKeys(this); - Terminal.bindFocus(this); - Terminal.bindBlur(this); - on(this.element, 'copy', function (event) { - if (!term.hasSelection()) { - return; - } - Clipboard_1.copyHandler(event, term, _this.selectionManager); - }); - var pasteHandlerWrapper = function (event) { return Clipboard_1.pasteHandler(event, term); }; - on(this.textarea, 'paste', pasteHandlerWrapper); - on(this.element, 'paste', pasteHandlerWrapper); - if (term.browser.isFirefox) { - on(this.element, 'mousedown', function (event) { - if (event.button == 2) { - Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager); - } - }); - } - else { - on(this.element, 'contextmenu', function (event) { - Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager); - }); - } - if (term.browser.isLinux) { - on(this.element, 'auxclick', function (event) { - if (event.button === 1) { - Clipboard_1.moveTextAreaUnderMouseCursor(event, _this.textarea, _this.selectionManager); - } - }); - } -}; -Terminal.bindKeys = function (term) { - on(term.element, 'keydown', function (ev) { - if (document.activeElement != this) { - return; - } - term.keyDown(ev); - }, true); - on(term.element, 'keypress', function (ev) { - if (document.activeElement != this) { - return; - } - term.keyPress(ev); - }, true); - on(term.element, 'keyup', function (ev) { - if (!wasMondifierKeyOnlyEvent(ev)) { - term.focus(term); - } - }, true); - on(term.textarea, 'keydown', function (ev) { - term.keyDown(ev); - }, true); - on(term.textarea, 'keypress', function (ev) { - term.keyPress(ev); - this.value = ''; - }, true); - on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper)); - on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper)); - on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper)); - term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper)); - term.on('refresh', function (data) { - term.queueLinkification(data.start, data.end); - }); -}; -Terminal.prototype.insertRow = function (row) { - if (typeof row != 'object') { - row = document.createElement('div'); - } - this.rowContainer.appendChild(row); - this.children.push(row); - return row; -}; -Terminal.prototype.open = function (parent, focus) { - var _this = this; - var self = this, i = 0, div; - this.parent = parent || this.parent; - if (!this.parent) { - throw new Error('Terminal requires a parent element.'); - } - this.context = this.parent.ownerDocument.defaultView; - this.document = this.parent.ownerDocument; - this.body = this.document.getElementsByTagName('body')[0]; - this.element = this.document.createElement('div'); - this.element.classList.add('terminal'); - this.element.classList.add('xterm'); - this.element.classList.add('xterm-theme-' + this.theme); - this.element.classList.add("xterm-cursor-style-" + this.options.cursorStyle); - this.setCursorBlinking(this.options.cursorBlink); - this.element.setAttribute('tabindex', 0); - this.viewportElement = document.createElement('div'); - this.viewportElement.classList.add('xterm-viewport'); - this.element.appendChild(this.viewportElement); - this.viewportScrollArea = document.createElement('div'); - this.viewportScrollArea.classList.add('xterm-scroll-area'); - this.viewportElement.appendChild(this.viewportScrollArea); - this.selectionContainer = document.createElement('div'); - this.selectionContainer.classList.add('xterm-selection'); - this.element.appendChild(this.selectionContainer); - this.rowContainer = document.createElement('div'); - this.rowContainer.classList.add('xterm-rows'); - this.element.appendChild(this.rowContainer); - this.children = []; - this.linkifier.attachToDom(document, this.children); - this.helperContainer = document.createElement('div'); - this.helperContainer.classList.add('xterm-helpers'); - this.element.appendChild(this.helperContainer); - this.textarea = document.createElement('textarea'); - this.textarea.classList.add('xterm-helper-textarea'); - this.textarea.setAttribute('autocorrect', 'off'); - this.textarea.setAttribute('autocapitalize', 'off'); - this.textarea.setAttribute('spellcheck', 'false'); - this.textarea.tabIndex = 0; - this.textarea.addEventListener('focus', function () { - self.emit('focus', { terminal: self }); - }); - this.textarea.addEventListener('blur', function () { - self.emit('blur', { terminal: self }); - }); - this.helperContainer.appendChild(this.textarea); - this.compositionView = document.createElement('div'); - this.compositionView.classList.add('composition-view'); - this.compositionHelper = new CompositionHelper_1.CompositionHelper(this.textarea, this.compositionView, this); - this.helperContainer.appendChild(this.compositionView); - this.charSizeStyleElement = document.createElement('style'); - this.helperContainer.appendChild(this.charSizeStyleElement); - for (; i < this.rows; i++) { - this.insertRow(); - } - this.parent.appendChild(this.element); - this.charMeasure = new CharMeasure_1.CharMeasure(document, this.helperContainer); - this.charMeasure.on('charsizechanged', function () { - self.updateCharSizeStyles(); - }); - this.charMeasure.measure(); - this.viewport = new Viewport_1.Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); - this.renderer = new Renderer_1.Renderer(this); - this.selectionManager = new SelectionManager_1.SelectionManager(this, this.buffer.lines, this.rowContainer, this.charMeasure); - this.selectionManager.on('refresh', function (data) { - _this.renderer.refreshSelection(data.start, data.end); - }); - this.selectionManager.on('newselection', function (text) { - _this.textarea.value = text; - _this.textarea.focus(); - _this.textarea.select(); - }); - this.on('scroll', function () { return _this.selectionManager.refresh(); }); - this.viewportElement.addEventListener('scroll', function () { return _this.selectionManager.refresh(); }); - this.refresh(0, this.rows - 1); - this.initGlobal(); - if (typeof focus == 'undefined') { - var message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n'; - message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 '; - message += 'it will default to `false`.'; - console.warn(message); - focus = true; - } - if (focus) { - this.focus(); - } - this.bindMouse(); - this.emit('open'); -}; -Terminal.loadAddon = function (addon, callback) { - if (typeof exports === 'object' && typeof module === 'object') { - return require('./addons/' + addon + '/' + addon); - } - else if (typeof define == 'function') { - return require(['./addons/' + addon + '/' + addon], callback); - } - else { - console.error('Cannot load a module without a CommonJS or RequireJS environment.'); - return false; - } -}; -Terminal.prototype.updateCharSizeStyles = function () { - this.charSizeStyleElement.textContent = - ".xterm-wide-char{width:" + this.charMeasure.width * 2 + "px;}" + - (".xterm-normal-char{width:" + this.charMeasure.width + "px;}") + - (".xterm-rows > div{height:" + this.charMeasure.height + "px;}"); -}; -Terminal.prototype.bindMouse = function () { - var el = this.element, self = this, pressed = 32; - function sendButton(ev) { - var button, pos; - button = getButton(ev); - pos = Mouse_1.getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); - if (!pos) - return; - sendEvent(button, pos); - switch (ev.overrideType || ev.type) { - case 'mousedown': - pressed = button; - break; - case 'mouseup': - pressed = 32; - break; - case 'wheel': - break; - } - } - function sendMove(ev) { - var button = pressed, pos; - pos = Mouse_1.getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); - if (!pos) - return; - button += 32; - sendEvent(button, pos); - } - function encode(data, ch) { - if (!self.utfMouse) { - if (ch === 255) - return data.push(0); - if (ch > 127) - ch = 127; - data.push(ch); - } - else { - if (ch === 2047) - return data.push(0); - if (ch < 127) { - data.push(ch); - } - else { - if (ch > 2047) - ch = 2047; - data.push(0xC0 | (ch >> 6)); - data.push(0x80 | (ch & 0x3F)); - } - } - } - function sendEvent(button, pos) { - if (self.vt300Mouse) { - button &= 3; - pos.x -= 32; - pos.y -= 32; - var data = EscapeSequences_1.C0.ESC + '[24'; - if (button === 0) - data += '1'; - else if (button === 1) - data += '3'; - else if (button === 2) - data += '5'; - else if (button === 3) - return; - else - data += '0'; - data += '~[' + pos.x + ',' + pos.y + ']\r'; - self.send(data); - return; - } - if (self.decLocator) { - button &= 3; - pos.x -= 32; - pos.y -= 32; - if (button === 0) - button = 2; - else if (button === 1) - button = 4; - else if (button === 2) - button = 6; - else if (button === 3) - button = 3; - self.send(EscapeSequences_1.C0.ESC + '[' - + button - + ';' - + (button === 3 ? 4 : 0) - + ';' - + pos.y - + ';' - + pos.x - + ';' - + (pos.page || 0) - + '&w'); - return; - } - if (self.urxvtMouse) { - pos.x -= 32; - pos.y -= 32; - pos.x++; - pos.y++; - self.send(EscapeSequences_1.C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); - return; - } - if (self.sgrMouse) { - pos.x -= 32; - pos.y -= 32; - self.send(EscapeSequences_1.C0.ESC + '[<' - + (((button & 3) === 3 ? button & ~3 : button) - 32) - + ';' - + pos.x - + ';' - + pos.y - + ((button & 3) === 3 ? 'm' : 'M')); - return; - } - var data = []; - encode(data, button); - encode(data, pos.x); - encode(data, pos.y); - self.send(EscapeSequences_1.C0.ESC + '[M' + String.fromCharCode.apply(String, data)); - } - function getButton(ev) { - var button, shift, meta, ctrl, mod; - switch (ev.overrideType || ev.type) { - case 'mousedown': - button = ev.button != null - ? +ev.button - : ev.which != null - ? ev.which - 1 - : null; - if (self.browser.isMSIE) { - button = button === 1 ? 0 : button === 4 ? 1 : button; - } - break; - case 'mouseup': - button = 3; - break; - case 'DOMMouseScroll': - button = ev.detail < 0 - ? 64 - : 65; - break; - case 'wheel': - button = ev.wheelDeltaY > 0 - ? 64 - : 65; - break; - } - shift = ev.shiftKey ? 4 : 0; - meta = ev.metaKey ? 8 : 0; - ctrl = ev.ctrlKey ? 16 : 0; - mod = shift | meta | ctrl; - if (self.vt200Mouse) { - mod &= ctrl; - } - else if (!self.normalMouse) { - mod = 0; - } - button = (32 + (mod << 2)) + button; - return button; - } - on(el, 'mousedown', function (ev) { - ev.preventDefault(); - self.focus(); - if (!self.mouseEvents) - return; - sendButton(ev); - if (self.vt200Mouse) { - ev.overrideType = 'mouseup'; - sendButton(ev); - return self.cancel(ev); - } - if (self.normalMouse) - on(self.document, 'mousemove', sendMove); - if (!self.x10Mouse) { - on(self.document, 'mouseup', function up(ev) { - sendButton(ev); - if (self.normalMouse) - off(self.document, 'mousemove', sendMove); - off(self.document, 'mouseup', up); - return self.cancel(ev); - }); - } - return self.cancel(ev); - }); - on(el, 'wheel', function (ev) { - if (!self.mouseEvents) - return; - if (self.x10Mouse - || self.vt300Mouse - || self.decLocator) - return; - sendButton(ev); - return self.cancel(ev); - }); - on(el, 'wheel', function (ev) { - if (self.mouseEvents) - return; - self.viewport.onWheel(ev); - return self.cancel(ev); - }); - on(el, 'touchstart', function (ev) { - if (self.mouseEvents) - return; - self.viewport.onTouchStart(ev); - return self.cancel(ev); - }); - on(el, 'touchmove', function (ev) { - if (self.mouseEvents) - return; - self.viewport.onTouchMove(ev); - return self.cancel(ev); - }); -}; -Terminal.prototype.destroy = function () { - this.readable = false; - this.writable = false; - this._events = {}; - this.handler = function () { }; - this.write = function () { }; - if (this.element && this.element.parentNode) { - this.element.parentNode.removeChild(this.element); - } -}; -Terminal.prototype.refresh = function (start, end) { - if (this.renderer) { - this.renderer.queueRefresh(start, end); - } -}; -Terminal.prototype.queueLinkification = function (start, end) { - if (this.linkifier) { - for (var i = start; i <= end; i++) { - this.linkifier.linkifyRow(i); - } - } -}; -Terminal.prototype.showCursor = function () { - if (!this.cursorState) { - this.cursorState = 1; - this.refresh(this.buffer.y, this.buffer.y); - } -}; -Terminal.prototype.scroll = function (isWrapped) { - var row; - if (this.buffer.lines.length === this.buffer.lines.maxLength) { - this.buffer.lines.trimStart(1); - this.buffer.ybase--; - if (this.buffer.ydisp !== 0) { - this.buffer.ydisp--; - } - } - this.buffer.ybase++; - if (!this.userScrolling) { - this.buffer.ydisp = this.buffer.ybase; - } - row = this.buffer.ybase + this.rows - 1; - row -= this.rows - 1 - this.buffer.scrollBottom; - if (row === this.buffer.lines.length) { - this.buffer.lines.push(this.blankLine(undefined, isWrapped)); - } - else { - this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped)); - } - if (this.buffer.scrollTop !== 0) { - if (this.buffer.ybase !== 0) { - this.buffer.ybase--; - if (!this.userScrolling) { - this.buffer.ydisp = this.buffer.ybase; - } - } - this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1); - } - this.updateRange(this.buffer.scrollTop); - this.updateRange(this.buffer.scrollBottom); - this.emit('scroll', this.buffer.ydisp); -}; -Terminal.prototype.scrollDisp = function (disp, suppressScrollEvent) { - if (disp < 0) { - if (this.buffer.ydisp === 0) { - return; - } - this.userScrolling = true; - } - else if (disp + this.buffer.ydisp >= this.buffer.ybase) { - this.userScrolling = false; - } - var oldYdisp = this.buffer.ydisp; - this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0); - if (oldYdisp === this.buffer.ydisp) { - return; - } - if (!suppressScrollEvent) { - this.emit('scroll', this.buffer.ydisp); - } - this.refresh(0, this.rows - 1); -}; -Terminal.prototype.scrollPages = function (pageCount) { - this.scrollDisp(pageCount * (this.rows - 1)); -}; -Terminal.prototype.scrollToTop = function () { - this.scrollDisp(-this.buffer.ydisp); -}; -Terminal.prototype.scrollToBottom = function () { - this.scrollDisp(this.buffer.ybase - this.buffer.ydisp); -}; -Terminal.prototype.write = function (data) { - this.writeBuffer.push(data); - if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { - this.send(EscapeSequences_1.C0.DC3); - this.xoffSentToCatchUp = true; - } - if (!this.writeInProgress && this.writeBuffer.length > 0) { - this.writeInProgress = true; - var self = this; - setTimeout(function () { - self.innerWrite(); - }); - } -}; -Terminal.prototype.innerWrite = function () { - var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); - while (writeBatch.length > 0) { - var data = writeBatch.shift(); - var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; - if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { - this.send(EscapeSequences_1.C0.DC1); - this.xoffSentToCatchUp = false; - } - this.refreshStart = this.buffer.y; - this.refreshEnd = this.buffer.y; - var state = this.parser.parse(data); - this.parser.setState(state); - this.updateRange(this.buffer.y); - this.refresh(this.refreshStart, this.refreshEnd); - } - if (this.writeBuffer.length > 0) { - var self = this; - setTimeout(function () { - self.innerWrite(); - }, 0); - } - else { - this.writeInProgress = false; - } -}; -Terminal.prototype.writeln = function (data) { - this.write(data + '\r\n'); -}; -Terminal.prototype.attachCustomKeydownHandler = function (customKeydownHandler) { - var message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.'; - console.warn(message); - this.attachCustomKeyEventHandler(customKeydownHandler); -}; -Terminal.prototype.attachCustomKeyEventHandler = function (customKeyEventHandler) { - this.customKeyEventHandler = customKeyEventHandler; -}; -Terminal.prototype.setHypertextLinkHandler = function (handler) { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); - } - this.linkifier.setHypertextLinkHandler(handler); - this.refresh(0, this.rows - 1); -}; -Terminal.prototype.setHypertextValidationCallback = function (callback) { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); - } - this.linkifier.setHypertextValidationCallback(callback); - this.refresh(0, this.rows - 1); -}; -Terminal.prototype.registerLinkMatcher = function (regex, handler, options) { - if (this.linkifier) { - var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); - this.refresh(0, this.rows - 1); - return matcherId; - } -}; -Terminal.prototype.deregisterLinkMatcher = function (matcherId) { - if (this.linkifier) { - if (this.linkifier.deregisterLinkMatcher(matcherId)) { - this.refresh(0, this.rows - 1); - } - } -}; -Terminal.prototype.hasSelection = function () { - return this.selectionManager ? this.selectionManager.hasSelection : false; -}; -Terminal.prototype.getSelection = function () { - return this.selectionManager ? this.selectionManager.selectionText : ''; -}; -Terminal.prototype.clearSelection = function () { - if (this.selectionManager) { - this.selectionManager.clearSelection(); - } -}; -Terminal.prototype.selectAll = function () { - if (this.selectionManager) { - this.selectionManager.selectAll(); - } -}; -Terminal.prototype.keyDown = function (ev) { - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { - return false; - } - this.restartCursorBlinking(); - if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) { - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); - } - return false; - } - var self = this; - var result = this.evaluateKeyEscapeSequence(ev); - if (result.key === EscapeSequences_1.C0.DC3) { - this.writeStopped = true; - } - else if (result.key === EscapeSequences_1.C0.DC1) { - this.writeStopped = false; - } - if (result.scrollDisp) { - this.scrollDisp(result.scrollDisp); - return this.cancel(ev, true); - } - if (isThirdLevelShift(this, ev)) { - return true; - } - if (result.cancel) { - this.cancel(ev, true); - } - if (!result.key) { - return true; - } - this.emit('keydown', ev); - this.emit('key', result.key, ev); - this.showCursor(); - this.handler(result.key); - return this.cancel(ev, true); -}; -Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { +function evaluateKeyboardEvent(ev, applicationCursorMode, isMac, macOptionIsMeta) { var result = { + type: 0, cancel: false, - key: undefined, - scrollDisp: undefined + key: undefined }; - var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3; + var modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0); switch (ev.keyCode) { + case 0: + if (ev.key === 'UIKeyInputUpArrow') { + if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OA'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[A'; + } + } + else if (ev.key === 'UIKeyInputLeftArrow') { + if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OD'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[D'; + } + } + else if (ev.key === 'UIKeyInputRightArrow') { + if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OC'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[C'; + } + } + else if (ev.key === 'UIKeyInputDownArrow') { + if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OB'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[B'; + } + } + break; case 8: if (ev.shiftKey) { result.key = EscapeSequences_1.C0.BS; break; } + else if (ev.altKey) { + result.key = EscapeSequences_1.C0.ESC + EscapeSequences_1.C0.DEL; + break; + } result.key = EscapeSequences_1.C0.DEL; break; case 9: @@ -4514,11 +5192,11 @@ Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { case 37: if (modifiers) { result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'D'; - if (result.key == EscapeSequences_1.C0.ESC + '[1;3D') { - result.key = (this.browser.isMac) ? EscapeSequences_1.C0.ESC + 'b' : EscapeSequences_1.C0.ESC + '[1;5D'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3D') { + result.key = isMac ? EscapeSequences_1.C0.ESC + 'b' : EscapeSequences_1.C0.ESC + '[1;5D'; } } - else if (this.applicationCursor) { + else if (applicationCursorMode) { result.key = EscapeSequences_1.C0.ESC + 'OD'; } else { @@ -4528,11 +5206,11 @@ Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { case 39: if (modifiers) { result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'C'; - if (result.key == EscapeSequences_1.C0.ESC + '[1;3C') { - result.key = (this.browser.isMac) ? EscapeSequences_1.C0.ESC + 'f' : EscapeSequences_1.C0.ESC + '[1;5C'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3C') { + result.key = isMac ? EscapeSequences_1.C0.ESC + 'f' : EscapeSequences_1.C0.ESC + '[1;5C'; } } - else if (this.applicationCursor) { + else if (applicationCursorMode) { result.key = EscapeSequences_1.C0.ESC + 'OC'; } else { @@ -4542,11 +5220,11 @@ Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { case 38: if (modifiers) { result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'A'; - if (result.key == EscapeSequences_1.C0.ESC + '[1;3A') { + if (result.key === EscapeSequences_1.C0.ESC + '[1;3A') { result.key = EscapeSequences_1.C0.ESC + '[1;5A'; } } - else if (this.applicationCursor) { + else if (applicationCursorMode) { result.key = EscapeSequences_1.C0.ESC + 'OA'; } else { @@ -4556,11 +5234,11 @@ Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { case 40: if (modifiers) { result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'B'; - if (result.key == EscapeSequences_1.C0.ESC + '[1;3B') { + if (result.key === EscapeSequences_1.C0.ESC + '[1;3B') { result.key = EscapeSequences_1.C0.ESC + '[1;5B'; } } - else if (this.applicationCursor) { + else if (applicationCursorMode) { result.key = EscapeSequences_1.C0.ESC + 'OB'; } else { @@ -4581,24 +5259,30 @@ Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { } break; case 36: - if (modifiers) + if (modifiers) { result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'H'; - else if (this.applicationCursor) + } + else if (applicationCursorMode) { result.key = EscapeSequences_1.C0.ESC + 'OH'; - else + } + else { result.key = EscapeSequences_1.C0.ESC + '[H'; + } break; case 35: - if (modifiers) + if (modifiers) { result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'F'; - else if (this.applicationCursor) + } + else if (applicationCursorMode) { result.key = EscapeSequences_1.C0.ESC + 'OF'; - else + } + else { result.key = EscapeSequences_1.C0.ESC + '[F'; + } break; case 33: if (ev.shiftKey) { - result.scrollDisp = -(this.rows - 1); + result.type = 2; } else { result.key = EscapeSequences_1.C0.ESC + '[5~'; @@ -4606,7 +5290,7 @@ Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { break; case 34: if (ev.shiftKey) { - result.scrollDisp = this.rows - 1; + result.type = 3; } else { result.key = EscapeSequences_1.C0.ESC + '[6~'; @@ -4732,401 +5416,2859 @@ Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { result.key = String.fromCharCode(29); } } - else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) { - if (ev.keyCode >= 65 && ev.keyCode <= 90) { - result.key = EscapeSequences_1.C0.ESC + String.fromCharCode(ev.keyCode + 32); + else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) { + var keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode]; + var key = keyMapping && keyMapping[!ev.shiftKey ? 0 : 1]; + if (key) { + result.key = EscapeSequences_1.C0.ESC + key; } - else if (ev.keyCode === 192) { - result.key = EscapeSequences_1.C0.ESC + '`'; - } - else if (ev.keyCode >= 48 && ev.keyCode <= 57) { - result.key = EscapeSequences_1.C0.ESC + (ev.keyCode - 48); + else if (ev.keyCode >= 65 && ev.keyCode <= 90) { + var keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32; + result.key = EscapeSequences_1.C0.ESC + String.fromCharCode(keyCode); } } - else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { + else if (isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { if (ev.keyCode === 65) { - this.selectAll(); + result.type = 1; } } break; } return result; -}; -Terminal.prototype.setgLevel = function (g) { - this.glevel = g; - this.charset = this.charsets[g]; -}; -Terminal.prototype.setgCharset = function (g, charset) { - this.charsets[g] = charset; - if (this.glevel === g) { - this.charset = charset; - } -}; -Terminal.prototype.keyPress = function (ev) { - var key; - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { - return false; - } - this.cancel(ev); - if (ev.charCode) { - key = ev.charCode; - } - else if (ev.which == null) { - key = ev.keyCode; - } - else if (ev.which !== 0 && ev.charCode !== 0) { - key = ev.which; - } - else { - return false; - } - if (!key || ((ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev))) { - return false; - } - key = String.fromCharCode(key); - this.emit('keypress', key, ev); - this.emit('key', key, ev); - this.showCursor(); - this.handler(key); - return true; -}; -Terminal.prototype.send = function (data) { - var self = this; - if (!this.queue) { - setTimeout(function () { - self.handler(self.queue); - self.queue = ''; - }, 1); - } - this.queue += data; -}; -Terminal.prototype.bell = function () { - if (!this.visualBell) - return; - var self = this; - this.element.style.borderColor = 'white'; - setTimeout(function () { - self.element.style.borderColor = ''; - }, 10); - if (this.popOnBell) - this.focus(); -}; -Terminal.prototype.log = function () { - if (!this.debug) - return; - if (!this.context.console || !this.context.console.log) - return; - var args = Array.prototype.slice.call(arguments); - this.context.console.log.apply(this.context.console, args); -}; -Terminal.prototype.error = function () { - if (!this.debug) - return; - if (!this.context.console || !this.context.console.error) - return; - var args = Array.prototype.slice.call(arguments); - this.context.console.error.apply(this.context.console, args); -}; -Terminal.prototype.resize = function (x, y) { - if (isNaN(x) || isNaN(y)) { - return; - } - if (y > this.getOption('scrollback')) { - this.setOption('scrollback', y); - } - var line, el, i, j, ch, addToY; - if (x === this.cols && y === this.rows) { - if (!this.charMeasure.width || !this.charMeasure.height) { - this.charMeasure.measure(); - } - return; - } - if (x < 1) - x = 1; - if (y < 1) - y = 1; - this.buffers.resize(x, y); - while (this.children.length < y) { - this.insertRow(); - } - while (this.children.length > y) { - el = this.children.shift(); - if (!el) - continue; - el.parentNode.removeChild(el); - } - this.cols = x; - this.rows = y; - this.setupStops(this.cols); - this.charMeasure.measure(); - this.refresh(0, this.rows - 1); - this.geometry = [this.cols, this.rows]; - this.emit('resize', { terminal: this, cols: x, rows: y }); -}; -Terminal.prototype.updateRange = function (y) { - if (y < this.refreshStart) - this.refreshStart = y; - if (y > this.refreshEnd) - this.refreshEnd = y; -}; -Terminal.prototype.maxRange = function () { - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; -}; -Terminal.prototype.setupStops = function (i) { - if (i != null) { - if (!this.buffer.tabs[i]) { - i = this.prevStop(i); - } - } - else { - this.buffer.tabs = {}; - i = 0; - } - for (; i < this.cols; i += this.getOption('tabStopWidth')) { - this.buffer.tabs[i] = true; - } -}; -Terminal.prototype.prevStop = function (x) { - if (x == null) - x = this.buffer.x; - while (!this.buffer.tabs[--x] && x > 0) - ; - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; -}; -Terminal.prototype.nextStop = function (x) { - if (x == null) - x = this.buffer.x; - while (!this.buffer.tabs[++x] && x < this.cols) - ; - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; -}; -Terminal.prototype.eraseRight = function (x, y) { - var line = this.buffer.lines.get(this.buffer.ybase + y); - if (!line) { - return; - } - var ch = [this.eraseAttr(), ' ', 1]; - for (; x < this.cols; x++) { - line[x] = ch; - } - this.updateRange(y); -}; -Terminal.prototype.eraseLeft = function (x, y) { - var line = this.buffer.lines.get(this.buffer.ybase + y); - if (!line) { - return; - } - var ch = [this.eraseAttr(), ' ', 1]; - x++; - while (x--) { - line[x] = ch; - } - this.updateRange(y); -}; -Terminal.prototype.clear = function () { - if (this.buffer.ybase === 0 && this.buffer.y === 0) { - return; - } - this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)); - this.buffer.lines.length = 1; - this.buffer.ydisp = 0; - this.buffer.ybase = 0; - this.buffer.y = 0; - for (var i = 1; i < this.rows; i++) { - this.buffer.lines.push(this.blankLine()); - } - this.refresh(0, this.rows - 1); - this.emit('scroll', this.buffer.ydisp); -}; -Terminal.prototype.eraseLine = function (y) { - this.eraseRight(0, y); -}; -Terminal.prototype.blankLine = function (cur, isWrapped, cols) { - var attr = cur - ? this.eraseAttr() - : this.defAttr; - var ch = [attr, ' ', 1], line = [], i = 0; - if (isWrapped) { - line.isWrapped = isWrapped; - } - cols = cols || this.cols; - for (; i < cols; i++) { - line[i] = ch; - } - return line; -}; -Terminal.prototype.ch = function (cur) { - return cur - ? [this.eraseAttr(), ' ', 1] - : [this.defAttr, ' ', 1]; -}; -Terminal.prototype.is = function (term) { - var name = this.termName; - return (name + '').indexOf(term) === 0; -}; -Terminal.prototype.handler = function (data) { - if (this.options.disableStdin) { - return; - } - if (this.selectionManager && this.selectionManager.hasSelection) { - this.selectionManager.clearSelection(); - } - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); - } - this.emit('data', data); -}; -Terminal.prototype.handleTitle = function (title) { - this.emit('title', title); -}; -Terminal.prototype.index = function () { - this.buffer.y++; - if (this.buffer.y > this.buffer.scrollBottom) { - this.buffer.y--; - this.scroll(); - } - if (this.buffer.x >= this.cols) { - this.buffer.x--; - } -}; -Terminal.prototype.reverseIndex = function () { - var j; - if (this.buffer.y === this.buffer.scrollTop) { - this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1); - this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true)); - this.updateRange(this.buffer.scrollTop); - this.updateRange(this.buffer.scrollBottom); - } - else { - this.buffer.y--; - } -}; -Terminal.prototype.reset = function () { - this.options.rows = this.rows; - this.options.cols = this.cols; - var customKeyEventHandler = this.customKeyEventHandler; - var cursorBlinkInterval = this.cursorBlinkInterval; - var inputHandler = this.inputHandler; - Terminal.call(this, this.options); - this.customKeyEventHandler = customKeyEventHandler; - this.cursorBlinkInterval = cursorBlinkInterval; - this.inputHandler = inputHandler; - this.refresh(0, this.rows - 1); - this.viewport.syncScrollArea(); -}; -Terminal.prototype.tabSet = function () { - this.buffer.tabs[this.buffer.x] = true; -}; -function on(el, type, handler, capture) { - if (!Array.isArray(el)) { - el = [el]; - } - el.forEach(function (element) { - element.addEventListener(type, handler, capture || false); - }); } -function off(el, type, handler, capture) { - el.removeEventListener(type, handler, capture || false); +exports.evaluateKeyboardEvent = evaluateKeyboardEvent; + +},{"../../common/data/EscapeSequences":18}],21:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var EscapeSequences_1 = require("../common/data/EscapeSequences"); +var AltClickHandler = (function () { + function AltClickHandler(_mouseEvent, _terminal) { + this._mouseEvent = _mouseEvent; + this._terminal = _terminal; + this._lines = this._terminal.buffer.lines; + this._startCol = this._terminal.buffer.x; + this._startRow = this._terminal.buffer.y; + var coordinates = this._terminal.mouseHelper.getCoords(this._mouseEvent, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, false); + if (coordinates) { + _a = coordinates.map(function (coordinate) { + return coordinate - 1; + }), this._endCol = _a[0], this._endRow = _a[1]; + } + var _a; + } + AltClickHandler.prototype.move = function () { + if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) { + this._terminal.send(this._arrowSequences()); + } + }; + AltClickHandler.prototype._arrowSequences = function () { + if (!this._terminal.buffer.hasScrollback) { + return this._resetStartingRow() + this._moveToRequestedRow() + this._moveToRequestedCol(); + } + return this._moveHorizontallyOnly(); + }; + AltClickHandler.prototype._resetStartingRow = function () { + if (this._moveToRequestedRow().length === 0) { + return ''; + } + return repeat(this._bufferLine(this._startCol, this._startRow, this._startCol, this._startRow - this._wrappedRowsForRow(this._startRow), false).length, this._sequence("D")); + }; + AltClickHandler.prototype._moveToRequestedRow = function () { + var startRow = this._startRow - this._wrappedRowsForRow(this._startRow); + var endRow = this._endRow - this._wrappedRowsForRow(this._endRow); + var rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(); + return repeat(rowsToMove, this._sequence(this._verticalDirection())); + }; + AltClickHandler.prototype._moveToRequestedCol = function () { + var startRow; + if (this._moveToRequestedRow().length > 0) { + startRow = this._endRow - this._wrappedRowsForRow(this._endRow); + } + else { + startRow = this._startRow; + } + var endRow = this._endRow; + var direction = this._horizontalDirection(); + return repeat(this._bufferLine(this._startCol, startRow, this._endCol, endRow, direction === "C").length, this._sequence(direction)); + }; + AltClickHandler.prototype._moveHorizontallyOnly = function () { + var direction = this._horizontalDirection(); + return repeat(Math.abs(this._startCol - this._endCol), this._sequence(direction)); + }; + AltClickHandler.prototype._wrappedRowsCount = function () { + var wrappedRows = 0; + var startRow = this._startRow - this._wrappedRowsForRow(this._startRow); + var endRow = this._endRow - this._wrappedRowsForRow(this._endRow); + for (var i = 0; i < Math.abs(startRow - endRow); i++) { + var direction = this._verticalDirection() === "A" ? -1 : 1; + if (this._lines.get(startRow + (direction * i)).isWrapped) { + wrappedRows++; + } + } + return wrappedRows; + }; + AltClickHandler.prototype._wrappedRowsForRow = function (currentRow) { + var rowCount = 0; + var lineWraps = this._lines.get(currentRow).isWrapped; + while (lineWraps && currentRow >= 0 && currentRow < this._terminal.rows) { + rowCount++; + currentRow--; + lineWraps = this._lines.get(currentRow).isWrapped; + } + return rowCount; + }; + AltClickHandler.prototype._horizontalDirection = function () { + var startRow; + if (this._moveToRequestedRow().length > 0) { + startRow = this._endRow - this._wrappedRowsForRow(this._endRow); + } + else { + startRow = this._startRow; + } + if ((this._startCol < this._endCol && + startRow <= this._endRow) || + (this._startCol >= this._endCol && + startRow < this._endRow)) { + return "C"; + } + return "D"; + }; + AltClickHandler.prototype._verticalDirection = function () { + if (this._startRow > this._endRow) { + return "A"; + } + return "B"; + }; + AltClickHandler.prototype._bufferLine = function (startCol, startRow, endCol, endRow, forward) { + var currentCol = startCol; + var currentRow = startRow; + var bufferStr = ''; + while (currentCol !== endCol || currentRow !== endRow) { + currentCol += forward ? 1 : -1; + if (forward && currentCol > this._terminal.cols - 1) { + bufferStr += this._terminal.buffer.translateBufferLineToString(currentRow, false, startCol, currentCol); + currentCol = 0; + startCol = 0; + currentRow++; + } + else if (!forward && currentCol < 0) { + bufferStr += this._terminal.buffer.translateBufferLineToString(currentRow, false, 0, startCol + 1); + currentCol = this._terminal.cols - 1; + startCol = currentCol; + currentRow--; + } + } + return bufferStr + this._terminal.buffer.translateBufferLineToString(currentRow, false, startCol, currentCol); + }; + AltClickHandler.prototype._sequence = function (direction) { + var mod = this._terminal.applicationCursor ? 'O' : '['; + return EscapeSequences_1.C0.ESC + mod + direction; + }; + return AltClickHandler; +}()); +exports.AltClickHandler = AltClickHandler; +function repeat(count, str) { + count = Math.floor(count); + var rpt = ''; + for (var i = 0; i < count; i++) { + rpt += str; + } + return rpt; } -function cancel(ev, force) { - if (!this.cancelEvents && !force) { - return; + +},{"../common/data/EscapeSequences":18}],22:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function prepareTextForTerminal(text) { + return text.replace(/\r?\n/g, '\r'); +} +exports.prepareTextForTerminal = prepareTextForTerminal; +function bracketTextForPaste(text, bracketedPasteMode) { + if (bracketedPasteMode) { + return '\x1b[200~' + text + '\x1b[201~'; + } + return text; +} +exports.bracketTextForPaste = bracketTextForPaste; +function copyHandler(ev, term, selectionManager) { + if (term.browser.isMSIE) { + window.clipboardData.setData('Text', selectionManager.selectionText); + } + else { + ev.clipboardData.setData('text/plain', selectionManager.selectionText); } ev.preventDefault(); +} +exports.copyHandler = copyHandler; +function pasteHandler(ev, term) { ev.stopPropagation(); - return false; -} -function inherits(child, parent) { - function f() { - this.constructor = child; + var text; + var dispatchPaste = function (text) { + text = prepareTextForTerminal(text); + text = bracketTextForPaste(text, term.bracketedPasteMode); + term.handler(text); + term.textarea.value = ''; + term.emit('paste', text); + term.cancel(ev); + }; + if (term.browser.isMSIE) { + if (window.clipboardData) { + text = window.clipboardData.getData('Text'); + dispatchPaste(text); + } } - f.prototype = parent.prototype; - child.prototype = new f; -} -function indexOf(obj, el) { - var i = obj.length; - while (i--) { - if (obj[i] === el) - return i; + else { + if (ev.clipboardData) { + text = ev.clipboardData.getData('text/plain'); + dispatchPaste(text); + } } - return -1; } -function isThirdLevelShift(term, ev) { - var thirdLevelKey = (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || - (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); - if (ev.type == 'keypress') { - return thirdLevelKey; - } - return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); +exports.pasteHandler = pasteHandler; +function moveTextAreaUnderMouseCursor(ev, textarea) { + textarea.style.position = 'fixed'; + textarea.style.width = '20px'; + textarea.style.height = '20px'; + textarea.style.left = (ev.clientX - 10) + 'px'; + textarea.style.top = (ev.clientY - 10) + 'px'; + textarea.style.zIndex = '1000'; + textarea.focus(); + setTimeout(function () { + textarea.style.position = null; + textarea.style.width = null; + textarea.style.height = null; + textarea.style.left = null; + textarea.style.top = null; + textarea.style.zIndex = null; + }, 200); } -Terminal.prototype.matchColor = matchColor; -function matchColor(r1, g1, b1) { - var hash = (r1 << 16) | (g1 << 8) | b1; - if (matchColor._cache[hash] != null) { - return matchColor._cache[hash]; +exports.moveTextAreaUnderMouseCursor = moveTextAreaUnderMouseCursor; +function rightClickHandler(ev, textarea, selectionManager, shouldSelectWord) { + moveTextAreaUnderMouseCursor(ev, textarea); + if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) { + selectionManager.selectWordAtCursor(ev); } - var ldiff = Infinity, li = -1, i = 0, c, r2, g2, b2, diff; - for (; i < Terminal.vcolors.length; i++) { - c = Terminal.vcolors[i]; - r2 = c[0]; - g2 = c[1]; - b2 = c[2]; - diff = matchColor.distance(r1, g1, b1, r2, g2, b2); - if (diff === 0) { - li = i; + textarea.value = selectionManager.selectionText; + textarea.select(); +} +exports.rightClickHandler = rightClickHandler; + +},{}],23:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Terminal_1 = require("../Terminal"); +var Strings = require("../Strings"); +var Terminal = (function () { + function Terminal(options) { + this._core = new Terminal_1.Terminal(options); + } + Object.defineProperty(Terminal.prototype, "element", { + get: function () { return this._core.element; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "textarea", { + get: function () { return this._core.textarea; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "rows", { + get: function () { return this._core.rows; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "cols", { + get: function () { return this._core.cols; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "markers", { + get: function () { return this._core.markers; }, + enumerable: true, + configurable: true + }); + Terminal.prototype.blur = function () { + this._core.blur(); + }; + Terminal.prototype.focus = function () { + this._core.focus(); + }; + Terminal.prototype.on = function (type, listener) { + this._core.on(type, listener); + }; + Terminal.prototype.off = function (type, listener) { + this._core.off(type, listener); + }; + Terminal.prototype.emit = function (type, data) { + this._core.emit(type, data); + }; + Terminal.prototype.addDisposableListener = function (type, handler) { + return this._core.addDisposableListener(type, handler); + }; + Terminal.prototype.resize = function (columns, rows) { + this._core.resize(columns, rows); + }; + Terminal.prototype.writeln = function (data) { + this._core.writeln(data); + }; + Terminal.prototype.open = function (parent) { + this._core.open(parent); + }; + Terminal.prototype.attachCustomKeyEventHandler = function (customKeyEventHandler) { + this._core.attachCustomKeyEventHandler(customKeyEventHandler); + }; + Terminal.prototype.registerLinkMatcher = function (regex, handler, options) { + return this._core.registerLinkMatcher(regex, handler, options); + }; + Terminal.prototype.deregisterLinkMatcher = function (matcherId) { + this._core.deregisterLinkMatcher(matcherId); + }; + Terminal.prototype.addMarker = function (cursorYOffset) { + return this._core.addMarker(cursorYOffset); + }; + Terminal.prototype.hasSelection = function () { + return this._core.hasSelection(); + }; + Terminal.prototype.getSelection = function () { + return this._core.getSelection(); + }; + Terminal.prototype.clearSelection = function () { + this._core.clearSelection(); + }; + Terminal.prototype.selectAll = function () { + this._core.selectAll(); + }; + Terminal.prototype.selectLines = function (start, end) { + this._core.selectLines(start, end); + }; + Terminal.prototype.dispose = function () { + this._core.dispose(); + }; + Terminal.prototype.destroy = function () { + this._core.destroy(); + }; + Terminal.prototype.scrollLines = function (amount) { + this._core.scrollLines(amount); + }; + Terminal.prototype.scrollPages = function (pageCount) { + this._core.scrollPages(pageCount); + }; + Terminal.prototype.scrollToTop = function () { + this._core.scrollToTop(); + }; + Terminal.prototype.scrollToBottom = function () { + this._core.scrollToBottom(); + }; + Terminal.prototype.scrollToLine = function (line) { + this._core.scrollToLine(line); + }; + Terminal.prototype.clear = function () { + this._core.clear(); + }; + Terminal.prototype.write = function (data) { + this._core.write(data); + }; + Terminal.prototype.getOption = function (key) { + return this._core.getOption(key); + }; + Terminal.prototype.setOption = function (key, value) { + this._core.setOption(key, value); + }; + Terminal.prototype.refresh = function (start, end) { + this._core.refresh(start, end); + }; + Terminal.prototype.reset = function () { + this._core.reset(); + }; + Terminal.applyAddon = function (addon) { + addon.apply(Terminal); + }; + Object.defineProperty(Terminal, "strings", { + get: function () { + return Strings; + }, + enumerable: true, + configurable: true + }); + return Terminal; +}()); +exports.Terminal = Terminal; + +},{"../Strings":13,"../Terminal":14}],24:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("./atlas/Types"); +var CharAtlasCache_1 = require("./atlas/CharAtlasCache"); +var Buffer_1 = require("../Buffer"); +var BaseRenderLayer = (function () { + function BaseRenderLayer(_container, id, zIndex, _alpha, _colors) { + this._container = _container; + this._alpha = _alpha; + this._colors = _colors; + this._scaledCharWidth = 0; + this._scaledCharHeight = 0; + this._scaledCellWidth = 0; + this._scaledCellHeight = 0; + this._scaledCharLeft = 0; + this._scaledCharTop = 0; + this._canvas = document.createElement('canvas'); + this._canvas.classList.add("xterm-" + id + "-layer"); + this._canvas.style.zIndex = zIndex.toString(); + this._initCanvas(); + this._container.appendChild(this._canvas); + } + BaseRenderLayer.prototype._initCanvas = function () { + this._ctx = this._canvas.getContext('2d', { alpha: this._alpha }); + if (!this._alpha) { + this.clearAll(); + } + }; + BaseRenderLayer.prototype.onOptionsChanged = function (terminal) { }; + BaseRenderLayer.prototype.onBlur = function (terminal) { }; + BaseRenderLayer.prototype.onFocus = function (terminal) { }; + BaseRenderLayer.prototype.onCursorMove = function (terminal) { }; + BaseRenderLayer.prototype.onGridChanged = function (terminal, startRow, endRow) { }; + BaseRenderLayer.prototype.onSelectionChanged = function (terminal, start, end, columnSelectMode) { + if (columnSelectMode === void 0) { columnSelectMode = false; } + }; + BaseRenderLayer.prototype.onThemeChanged = function (terminal, colorSet) { + this._refreshCharAtlas(terminal, colorSet); + }; + BaseRenderLayer.prototype.setTransparency = function (terminal, alpha) { + if (alpha === this._alpha) { + return; + } + var oldCanvas = this._canvas; + this._alpha = alpha; + this._canvas = this._canvas.cloneNode(); + this._initCanvas(); + this._container.replaceChild(this._canvas, oldCanvas); + this._refreshCharAtlas(terminal, this._colors); + this.onGridChanged(terminal, 0, terminal.rows - 1); + }; + BaseRenderLayer.prototype._refreshCharAtlas = function (terminal, colorSet) { + if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { + return; + } + this._charAtlas = CharAtlasCache_1.acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight); + this._charAtlas.warmUp(); + }; + BaseRenderLayer.prototype.resize = function (terminal, dim) { + this._scaledCellWidth = dim.scaledCellWidth; + this._scaledCellHeight = dim.scaledCellHeight; + this._scaledCharWidth = dim.scaledCharWidth; + this._scaledCharHeight = dim.scaledCharHeight; + this._scaledCharLeft = dim.scaledCharLeft; + this._scaledCharTop = dim.scaledCharTop; + this._canvas.width = dim.scaledCanvasWidth; + this._canvas.height = dim.scaledCanvasHeight; + this._canvas.style.width = dim.canvasWidth + "px"; + this._canvas.style.height = dim.canvasHeight + "px"; + if (!this._alpha) { + this.clearAll(); + } + this._refreshCharAtlas(terminal, this._colors); + }; + BaseRenderLayer.prototype.fillCells = function (x, y, width, height) { + this._ctx.fillRect(x * this._scaledCellWidth, y * this._scaledCellHeight, width * this._scaledCellWidth, height * this._scaledCellHeight); + }; + BaseRenderLayer.prototype.fillBottomLineAtCells = function (x, y, width) { + if (width === void 0) { width = 1; } + this._ctx.fillRect(x * this._scaledCellWidth, (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1, width * this._scaledCellWidth, window.devicePixelRatio); + }; + BaseRenderLayer.prototype.fillLeftLineAtCell = function (x, y) { + this._ctx.fillRect(x * this._scaledCellWidth, y * this._scaledCellHeight, window.devicePixelRatio, this._scaledCellHeight); + }; + BaseRenderLayer.prototype.strokeRectAtCell = function (x, y, width, height) { + this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.strokeRect(x * this._scaledCellWidth + window.devicePixelRatio / 2, y * this._scaledCellHeight + (window.devicePixelRatio / 2), width * this._scaledCellWidth - window.devicePixelRatio, (height * this._scaledCellHeight) - window.devicePixelRatio); + }; + BaseRenderLayer.prototype.clearAll = function () { + if (this._alpha) { + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } + else { + this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + } + }; + BaseRenderLayer.prototype.clearCells = function (x, y, width, height) { + if (this._alpha) { + this._ctx.clearRect(x * this._scaledCellWidth, y * this._scaledCellHeight, width * this._scaledCellWidth, height * this._scaledCellHeight); + } + else { + this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillRect(x * this._scaledCellWidth, y * this._scaledCellHeight, width * this._scaledCellWidth, height * this._scaledCellHeight); + } + }; + BaseRenderLayer.prototype.fillCharTrueColor = function (terminal, charData, x, y) { + this._ctx.font = this._getFont(terminal, false, false); + this._ctx.textBaseline = 'top'; + this._clipRow(terminal, y); + this._ctx.fillText(charData[Buffer_1.CHAR_DATA_CHAR_INDEX], x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); + }; + BaseRenderLayer.prototype.drawChar = function (terminal, char, code, width, x, y, fg, bg, bold, dim, italic) { + var drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8; + fg += drawInBrightColor ? 8 : 0; + var atlasDidDraw = this._charAtlas && this._charAtlas.draw(this._ctx, { char: char, code: code, bg: bg, fg: fg, bold: bold && terminal.options.enableBold, dim: dim, italic: italic }, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); + if (!atlasDidDraw) { + this._drawUncachedChar(terminal, char, width, fg, x, y, bold && terminal.options.enableBold, dim, italic); + } + }; + BaseRenderLayer.prototype._drawUncachedChar = function (terminal, char, width, fg, x, y, bold, dim, italic) { + this._ctx.save(); + this._ctx.font = this._getFont(terminal, bold, italic); + this._ctx.textBaseline = 'top'; + if (fg === Types_1.INVERTED_DEFAULT_COLOR) { + this._ctx.fillStyle = this._colors.background.css; + } + else if (fg < 256) { + this._ctx.fillStyle = this._colors.ansi[fg].css; + } + else { + this._ctx.fillStyle = this._colors.foreground.css; + } + this._clipRow(terminal, y); + if (dim) { + this._ctx.globalAlpha = Types_1.DIM_OPACITY; + } + this._ctx.fillText(char, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); + this._ctx.restore(); + }; + BaseRenderLayer.prototype._clipRow = function (terminal, y) { + this._ctx.beginPath(); + this._ctx.rect(0, y * this._scaledCellHeight, terminal.cols * this._scaledCellWidth, this._scaledCellHeight); + this._ctx.clip(); + }; + BaseRenderLayer.prototype._getFont = function (terminal, isBold, isItalic) { + var fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight; + var fontStyle = isItalic ? 'italic' : ''; + return fontStyle + " " + fontWeight + " " + terminal.options.fontSize * window.devicePixelRatio + "px " + terminal.options.fontFamily; + }; + return BaseRenderLayer; +}()); +exports.BaseRenderLayer = BaseRenderLayer; + +},{"../Buffer":2,"./atlas/CharAtlasCache":33,"./atlas/Types":39}],25:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var DEFAULT_FOREGROUND = fromHex('#ffffff'); +var DEFAULT_BACKGROUND = fromHex('#000000'); +var DEFAULT_CURSOR = fromHex('#ffffff'); +var DEFAULT_CURSOR_ACCENT = fromHex('#000000'); +var DEFAULT_SELECTION = { + css: 'rgba(255, 255, 255, 0.3)', + rgba: 0xFFFFFF77 +}; +exports.DEFAULT_ANSI_COLORS = (function () { + var colors = [ + fromHex('#2e3436'), + fromHex('#cc0000'), + fromHex('#4e9a06'), + fromHex('#c4a000'), + fromHex('#3465a4'), + fromHex('#75507b'), + fromHex('#06989a'), + fromHex('#d3d7cf'), + fromHex('#555753'), + fromHex('#ef2929'), + fromHex('#8ae234'), + fromHex('#fce94f'), + fromHex('#729fcf'), + fromHex('#ad7fa8'), + fromHex('#34e2e2'), + fromHex('#eeeeec') + ]; + var v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + for (var i = 0; i < 216; i++) { + var r = v[(i / 36) % 6 | 0]; + var g = v[(i / 6) % 6 | 0]; + var b = v[i % 6]; + colors.push({ + css: "#" + toPaddedHex(r) + toPaddedHex(g) + toPaddedHex(b), + rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0 + }); + } + for (var i = 0; i < 24; i++) { + var c = 8 + i * 10; + var ch = toPaddedHex(c); + colors.push({ + css: "#" + ch + ch + ch, + rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0 + }); + } + return colors; +})(); +function fromHex(css) { + return { + css: css, + rgba: parseInt(css.slice(1), 16) << 8 | 0xFF + }; +} +function toPaddedHex(c) { + var s = c.toString(16); + return s.length < 2 ? '0' + s : s; +} +var ColorManager = (function () { + function ColorManager(document, allowTransparency) { + this.allowTransparency = allowTransparency; + var canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + this._ctx = canvas.getContext('2d'); + this._ctx.globalCompositeOperation = 'copy'; + this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1); + this.colors = { + foreground: DEFAULT_FOREGROUND, + background: DEFAULT_BACKGROUND, + cursor: DEFAULT_CURSOR, + cursorAccent: DEFAULT_CURSOR_ACCENT, + selection: DEFAULT_SELECTION, + ansi: exports.DEFAULT_ANSI_COLORS.slice() + }; + } + ColorManager.prototype.setTheme = function (theme) { + this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND); + this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); + this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); + this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); + this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + this.colors.ansi[0] = this._parseColor(theme.black, exports.DEFAULT_ANSI_COLORS[0]); + this.colors.ansi[1] = this._parseColor(theme.red, exports.DEFAULT_ANSI_COLORS[1]); + this.colors.ansi[2] = this._parseColor(theme.green, exports.DEFAULT_ANSI_COLORS[2]); + this.colors.ansi[3] = this._parseColor(theme.yellow, exports.DEFAULT_ANSI_COLORS[3]); + this.colors.ansi[4] = this._parseColor(theme.blue, exports.DEFAULT_ANSI_COLORS[4]); + this.colors.ansi[5] = this._parseColor(theme.magenta, exports.DEFAULT_ANSI_COLORS[5]); + this.colors.ansi[6] = this._parseColor(theme.cyan, exports.DEFAULT_ANSI_COLORS[6]); + this.colors.ansi[7] = this._parseColor(theme.white, exports.DEFAULT_ANSI_COLORS[7]); + this.colors.ansi[8] = this._parseColor(theme.brightBlack, exports.DEFAULT_ANSI_COLORS[8]); + this.colors.ansi[9] = this._parseColor(theme.brightRed, exports.DEFAULT_ANSI_COLORS[9]); + this.colors.ansi[10] = this._parseColor(theme.brightGreen, exports.DEFAULT_ANSI_COLORS[10]); + this.colors.ansi[11] = this._parseColor(theme.brightYellow, exports.DEFAULT_ANSI_COLORS[11]); + this.colors.ansi[12] = this._parseColor(theme.brightBlue, exports.DEFAULT_ANSI_COLORS[12]); + this.colors.ansi[13] = this._parseColor(theme.brightMagenta, exports.DEFAULT_ANSI_COLORS[13]); + this.colors.ansi[14] = this._parseColor(theme.brightCyan, exports.DEFAULT_ANSI_COLORS[14]); + this.colors.ansi[15] = this._parseColor(theme.brightWhite, exports.DEFAULT_ANSI_COLORS[15]); + }; + ColorManager.prototype._parseColor = function (css, fallback, allowTransparency) { + if (allowTransparency === void 0) { allowTransparency = this.allowTransparency; } + if (!css) { + return fallback; + } + this._ctx.fillStyle = this._litmusColor; + this._ctx.fillStyle = css; + if (typeof this._ctx.fillStyle !== 'string') { + console.warn("Color: " + css + " is invalid using fallback " + fallback.css); + return fallback; + } + this._ctx.fillRect(0, 0, 1, 1); + var data = this._ctx.getImageData(0, 0, 1, 1).data; + if (!allowTransparency && data[3] !== 0xFF) { + console.warn("Color: " + css + " is using transparency, but allowTransparency is false. " + + ("Using fallback " + fallback.css + ".")); + return fallback; + } + return { + css: css, + rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0 + }; + }; + return ColorManager; +}()); +exports.ColorManager = ColorManager; + +},{}],26:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Buffer_1 = require("../Buffer"); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var BLINK_INTERVAL = 600; +var CursorRenderLayer = (function (_super) { + __extends(CursorRenderLayer, _super); + function CursorRenderLayer(container, zIndex, colors) { + var _this = _super.call(this, container, 'cursor', zIndex, true, colors) || this; + _this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + _this._cursorRenderers = { + 'bar': _this._renderBarCursor.bind(_this), + 'block': _this._renderBlockCursor.bind(_this), + 'underline': _this._renderUnderlineCursor.bind(_this) + }; + return _this; + } + CursorRenderLayer.prototype.resize = function (terminal, dim) { + _super.prototype.resize.call(this, terminal, dim); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + }; + CursorRenderLayer.prototype.reset = function (terminal) { + this._clearCursor(); + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + this.onOptionsChanged(terminal); + } + }; + CursorRenderLayer.prototype.onBlur = function (terminal) { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.pause(); + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + }; + CursorRenderLayer.prototype.onFocus = function (terminal) { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.resume(terminal); + } + else { + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + }; + CursorRenderLayer.prototype.onOptionsChanged = function (terminal) { + var _this = this; + if (terminal.options.cursorBlink) { + if (!this._cursorBlinkStateManager) { + this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, function () { + _this._render(terminal, true); + }); + } + } + else { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + }; + CursorRenderLayer.prototype.onCursorMove = function (terminal) { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + }; + CursorRenderLayer.prototype.onGridChanged = function (terminal, startRow, endRow) { + if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { + this._render(terminal, false); + } + else { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + }; + CursorRenderLayer.prototype._render = function (terminal, triggeredByAnimationFrame) { + if (!terminal.cursorState || terminal.cursorHidden) { + this._clearCursor(); + return; + } + var cursorY = terminal.buffer.ybase + terminal.buffer.y; + var viewportRelativeCursorY = cursorY - terminal.buffer.ydisp; + if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) { + this._clearCursor(); + return; + } + var charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; + if (!charData) { + return; + } + if (!terminal.isFocused) { + this._clearCursor(); + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); + this._ctx.restore(); + this._state.x = terminal.buffer.x; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.options.cursorStyle; + this._state.width = charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]; + return; + } + if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + this._clearCursor(); + return; + } + if (this._state) { + if (this._state.x === terminal.buffer.x && + this._state.y === viewportRelativeCursorY && + this._state.isFocused === terminal.isFocused && + this._state.style === terminal.options.cursorStyle && + this._state.width === charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]) { + return; + } + this._clearCursor(); + } + this._ctx.save(); + this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); + this._ctx.restore(); + this._state.x = terminal.buffer.x; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.options.cursorStyle; + this._state.width = charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]; + }; + CursorRenderLayer.prototype._clearCursor = function () { + if (this._state) { + this.clearCells(this._state.x, this._state.y, this._state.width, 1); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + } + }; + CursorRenderLayer.prototype._renderBarCursor = function (terminal, x, y, charData) { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillLeftLineAtCell(x, y); + this._ctx.restore(); + }; + CursorRenderLayer.prototype._renderBlockCursor = function (terminal, x, y, charData) { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillCells(x, y, charData[Buffer_1.CHAR_DATA_WIDTH_INDEX], 1); + this._ctx.fillStyle = this._colors.cursorAccent.css; + this.fillCharTrueColor(terminal, charData, x, y); + this._ctx.restore(); + }; + CursorRenderLayer.prototype._renderUnderlineCursor = function (terminal, x, y, charData) { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillBottomLineAtCells(x, y); + this._ctx.restore(); + }; + CursorRenderLayer.prototype._renderBlurCursor = function (terminal, x, y, charData) { + this._ctx.save(); + this._ctx.strokeStyle = this._colors.cursor.css; + this.strokeRectAtCell(x, y, charData[Buffer_1.CHAR_DATA_WIDTH_INDEX], 1); + this._ctx.restore(); + }; + return CursorRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.CursorRenderLayer = CursorRenderLayer; +var CursorBlinkStateManager = (function () { + function CursorBlinkStateManager(terminal, _renderCallback) { + this._renderCallback = _renderCallback; + this.isCursorVisible = true; + if (terminal.isFocused) { + this._restartInterval(); + } + } + Object.defineProperty(CursorBlinkStateManager.prototype, "isPaused", { + get: function () { return !(this._blinkStartTimeout || this._blinkInterval); }, + enumerable: true, + configurable: true + }); + CursorBlinkStateManager.prototype.dispose = function () { + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + }; + CursorBlinkStateManager.prototype.restartBlinkAnimation = function (terminal) { + var _this = this; + if (this.isPaused) { + return; + } + this._animationTimeRestarted = Date.now(); + this.isCursorVisible = true; + if (!this._animationFrame) { + this._animationFrame = window.requestAnimationFrame(function () { + _this._renderCallback(); + _this._animationFrame = null; + }); + } + }; + CursorBlinkStateManager.prototype._restartInterval = function (timeToStart) { + var _this = this; + if (timeToStart === void 0) { timeToStart = BLINK_INTERVAL; } + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + } + this._blinkStartTimeout = setTimeout(function () { + if (_this._animationTimeRestarted) { + var time = BLINK_INTERVAL - (Date.now() - _this._animationTimeRestarted); + _this._animationTimeRestarted = null; + if (time > 0) { + _this._restartInterval(time); + return; + } + } + _this.isCursorVisible = false; + _this._animationFrame = window.requestAnimationFrame(function () { + _this._renderCallback(); + _this._animationFrame = null; + }); + _this._blinkInterval = setInterval(function () { + if (_this._animationTimeRestarted) { + var time = BLINK_INTERVAL - (Date.now() - _this._animationTimeRestarted); + _this._animationTimeRestarted = null; + _this._restartInterval(time); + return; + } + _this.isCursorVisible = !_this.isCursorVisible; + _this._animationFrame = window.requestAnimationFrame(function () { + _this._renderCallback(); + _this._animationFrame = null; + }); + }, BLINK_INTERVAL); + }, timeToStart); + }; + CursorBlinkStateManager.prototype.pause = function () { + this.isCursorVisible = true; + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + }; + CursorBlinkStateManager.prototype.resume = function (terminal) { + this._animationTimeRestarted = null; + this._restartInterval(); + this.restartBlinkAnimation(terminal); + }; + return CursorBlinkStateManager; +}()); + +},{"../Buffer":2,"./BaseRenderLayer":24}],27:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var GridCache = (function () { + function GridCache() { + this.cache = []; + } + GridCache.prototype.resize = function (width, height) { + for (var x = 0; x < width; x++) { + if (this.cache.length <= x) { + this.cache.push([]); + } + for (var y = this.cache[x].length; y < height; y++) { + this.cache[x].push(null); + } + this.cache[x].length = height; + } + this.cache.length = width; + }; + GridCache.prototype.clear = function () { + for (var x = 0; x < this.cache.length; x++) { + for (var y = 0; y < this.cache[x].length; y++) { + this.cache[x][y] = null; + } + } + }; + return GridCache; +}()); +exports.GridCache = GridCache; + +},{}],28:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var LinkRenderLayer = (function (_super) { + __extends(LinkRenderLayer, _super); + function LinkRenderLayer(container, zIndex, colors, terminal) { + var _this = _super.call(this, container, 'link', zIndex, true, colors) || this; + _this._state = null; + terminal.linkifier.on("linkhover", function (e) { return _this._onLinkHover(e); }); + terminal.linkifier.on("linkleave", function (e) { return _this._onLinkLeave(e); }); + return _this; + } + LinkRenderLayer.prototype.resize = function (terminal, dim) { + _super.prototype.resize.call(this, terminal, dim); + this._state = null; + }; + LinkRenderLayer.prototype.reset = function (terminal) { + this._clearCurrentLink(); + }; + LinkRenderLayer.prototype._clearCurrentLink = function () { + if (this._state) { + this.clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1); + var middleRowCount = this._state.y2 - this._state.y1 - 1; + if (middleRowCount > 0) { + this.clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount); + } + this.clearCells(0, this._state.y2, this._state.x2, 1); + this._state = null; + } + }; + LinkRenderLayer.prototype._onLinkHover = function (e) { + this._ctx.fillStyle = this._colors.foreground.css; + if (e.y1 === e.y2) { + this.fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1); + } + else { + this.fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1); + for (var y = e.y1 + 1; y < e.y2; y++) { + this.fillBottomLineAtCells(0, y, e.cols); + } + this.fillBottomLineAtCells(0, e.y2, e.x2); + } + this._state = e; + }; + LinkRenderLayer.prototype._onLinkLeave = function (e) { + this._clearCurrentLink(); + }; + return LinkRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.LinkRenderLayer = LinkRenderLayer; + +},{"./BaseRenderLayer":24}],29:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var TextRenderLayer_1 = require("./TextRenderLayer"); +var SelectionRenderLayer_1 = require("./SelectionRenderLayer"); +var CursorRenderLayer_1 = require("./CursorRenderLayer"); +var ColorManager_1 = require("./ColorManager"); +var LinkRenderLayer_1 = require("./LinkRenderLayer"); +var EventEmitter_1 = require("../EventEmitter"); +var RenderDebouncer_1 = require("../ui/RenderDebouncer"); +var ScreenDprMonitor_1 = require("../ui/ScreenDprMonitor"); +var Renderer = (function (_super) { + __extends(Renderer, _super); + function Renderer(_terminal, theme) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._isPaused = false; + _this._needsFullRefresh = false; + var allowTransparency = _this._terminal.options.allowTransparency; + _this.colorManager = new ColorManager_1.ColorManager(document, allowTransparency); + if (theme) { + _this.colorManager.setTheme(theme); + } + _this._renderLayers = [ + new TextRenderLayer_1.TextRenderLayer(_this._terminal.screenElement, 0, _this.colorManager.colors, allowTransparency), + new SelectionRenderLayer_1.SelectionRenderLayer(_this._terminal.screenElement, 1, _this.colorManager.colors), + new LinkRenderLayer_1.LinkRenderLayer(_this._terminal.screenElement, 2, _this.colorManager.colors, _this._terminal), + new CursorRenderLayer_1.CursorRenderLayer(_this._terminal.screenElement, 3, _this.colorManager.colors) + ]; + _this.dimensions = { + scaledCharWidth: null, + scaledCharHeight: null, + scaledCellWidth: null, + scaledCellHeight: null, + scaledCharLeft: null, + scaledCharTop: null, + scaledCanvasWidth: null, + scaledCanvasHeight: null, + canvasWidth: null, + canvasHeight: null, + actualCellWidth: null, + actualCellHeight: null + }; + _this._devicePixelRatio = window.devicePixelRatio; + _this._updateDimensions(); + _this.onOptionsChanged(); + _this._renderDebouncer = new RenderDebouncer_1.RenderDebouncer(_this._terminal, _this._renderRows.bind(_this)); + _this._screenDprMonitor = new ScreenDprMonitor_1.ScreenDprMonitor(); + _this._screenDprMonitor.setListener(function () { return _this.onWindowResize(window.devicePixelRatio); }); + _this.register(_this._screenDprMonitor); + if ('IntersectionObserver' in window) { + var observer_1 = new IntersectionObserver(function (e) { return _this.onIntersectionChange(e[0]); }, { threshold: 0 }); + observer_1.observe(_this._terminal.element); + _this.register({ dispose: function () { return observer_1.disconnect(); } }); + } + return _this; + } + Renderer.prototype.onIntersectionChange = function (entry) { + this._isPaused = entry.intersectionRatio === 0; + if (!this._isPaused && this._needsFullRefresh) { + this._terminal.refresh(0, this._terminal.rows - 1); + } + }; + Renderer.prototype.onWindowResize = function (devicePixelRatio) { + if (this._devicePixelRatio !== devicePixelRatio) { + this._devicePixelRatio = devicePixelRatio; + this.onResize(this._terminal.cols, this._terminal.rows); + } + }; + Renderer.prototype.setTheme = function (theme) { + var _this = this; + this.colorManager.setTheme(theme); + this._renderLayers.forEach(function (l) { + l.onThemeChanged(_this._terminal, _this.colorManager.colors); + l.reset(_this._terminal); + }); + if (this._isPaused) { + this._needsFullRefresh = true; + } + else { + this._terminal.refresh(0, this._terminal.rows - 1); + } + return this.colorManager.colors; + }; + Renderer.prototype.onResize = function (cols, rows) { + var _this = this; + this._updateDimensions(); + this._renderLayers.forEach(function (l) { return l.resize(_this._terminal, _this.dimensions); }); + if (this._isPaused) { + this._needsFullRefresh = true; + } + else { + this._terminal.refresh(0, this._terminal.rows - 1); + } + this._terminal.screenElement.style.width = this.dimensions.canvasWidth + "px"; + this._terminal.screenElement.style.height = this.dimensions.canvasHeight + "px"; + this.emit('resize', { + width: this.dimensions.canvasWidth, + height: this.dimensions.canvasHeight + }); + }; + Renderer.prototype.onCharSizeChanged = function () { + this.onResize(this._terminal.cols, this._terminal.rows); + }; + Renderer.prototype.onBlur = function () { + var _this = this; + this._runOperation(function (l) { return l.onBlur(_this._terminal); }); + }; + Renderer.prototype.onFocus = function () { + var _this = this; + this._runOperation(function (l) { return l.onFocus(_this._terminal); }); + }; + Renderer.prototype.onSelectionChanged = function (start, end, columnSelectMode) { + var _this = this; + if (columnSelectMode === void 0) { columnSelectMode = false; } + this._runOperation(function (l) { return l.onSelectionChanged(_this._terminal, start, end, columnSelectMode); }); + }; + Renderer.prototype.onCursorMove = function () { + var _this = this; + this._runOperation(function (l) { return l.onCursorMove(_this._terminal); }); + }; + Renderer.prototype.onOptionsChanged = function () { + var _this = this; + this.colorManager.allowTransparency = this._terminal.options.allowTransparency; + this._runOperation(function (l) { return l.onOptionsChanged(_this._terminal); }); + }; + Renderer.prototype.clear = function () { + var _this = this; + this._runOperation(function (l) { return l.reset(_this._terminal); }); + }; + Renderer.prototype._runOperation = function (operation) { + if (this._isPaused) { + this._needsFullRefresh = true; + } + else { + this._renderLayers.forEach(function (l) { return operation(l); }); + } + }; + Renderer.prototype.refreshRows = function (start, end) { + if (this._isPaused) { + this._needsFullRefresh = true; + return; + } + this._renderDebouncer.refresh(start, end); + }; + Renderer.prototype._renderRows = function (start, end) { + var _this = this; + this._renderLayers.forEach(function (l) { return l.onGridChanged(_this._terminal, start, end); }); + this._terminal.emit('refresh', { start: start, end: end }); + }; + Renderer.prototype._updateDimensions = function () { + if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { + return; + } + this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); + this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2); + this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); + this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; + this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; + }; + return Renderer; +}(EventEmitter_1.EventEmitter)); +exports.Renderer = Renderer; + +},{"../EventEmitter":7,"../ui/RenderDebouncer":48,"../ui/ScreenDprMonitor":49,"./ColorManager":25,"./CursorRenderLayer":26,"./LinkRenderLayer":28,"./SelectionRenderLayer":30,"./TextRenderLayer":31}],30:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var SelectionRenderLayer = (function (_super) { + __extends(SelectionRenderLayer, _super); + function SelectionRenderLayer(container, zIndex, colors) { + var _this = _super.call(this, container, 'selection', zIndex, true, colors) || this; + _this._clearState(); + return _this; + } + SelectionRenderLayer.prototype._clearState = function () { + this._state = { + start: null, + end: null, + columnSelectMode: null, + ydisp: null + }; + }; + SelectionRenderLayer.prototype.resize = function (terminal, dim) { + _super.prototype.resize.call(this, terminal, dim); + this._clearState(); + }; + SelectionRenderLayer.prototype.reset = function (terminal) { + if (this._state.start && this._state.end) { + this._clearState(); + this.clearAll(); + } + }; + SelectionRenderLayer.prototype.onSelectionChanged = function (terminal, start, end, columnSelectMode) { + if (!this._didStateChange(start, end, columnSelectMode, terminal.buffer.ydisp)) { + return; + } + this.clearAll(); + if (!start || !end) { + return; + } + var viewportStartRow = start[1] - terminal.buffer.ydisp; + var viewportEndRow = end[1] - terminal.buffer.ydisp; + var viewportCappedStartRow = Math.max(viewportStartRow, 0); + var viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + return; + } + this._ctx.fillStyle = this._colors.selection.css; + if (columnSelectMode) { + var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; + var width = end[0] - startCol; + var height = viewportCappedEndRow - viewportCappedStartRow + 1; + this.fillCells(startCol, viewportCappedStartRow, width, height); + } + else { + var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; + var startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; + this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); + var middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0); + this.fillCells(0, viewportCappedStartRow + 1, terminal.cols, middleRowsCount); + if (viewportCappedStartRow !== viewportCappedEndRow) { + var endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols; + this.fillCells(0, viewportCappedEndRow, endCol, 1); + } + } + this._state.start = [start[0], start[1]]; + this._state.end = [end[0], end[1]]; + this._state.columnSelectMode = columnSelectMode; + this._state.ydisp = terminal.buffer.ydisp; + }; + SelectionRenderLayer.prototype._didStateChange = function (start, end, columnSelectMode, ydisp) { + return !this._areCoordinatesEqual(start, this._state.start) || + !this._areCoordinatesEqual(end, this._state.end) || + columnSelectMode !== this._state.columnSelectMode || + ydisp !== this._state.ydisp; + }; + SelectionRenderLayer.prototype._areCoordinatesEqual = function (coord1, coord2) { + if (!coord1 || !coord2) { + return false; + } + return coord1[0] === coord2[0] && coord1[1] === coord2[1]; + }; + return SelectionRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.SelectionRenderLayer = SelectionRenderLayer; + +},{"./BaseRenderLayer":24}],31:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Buffer_1 = require("../Buffer"); +var Types_1 = require("./atlas/Types"); +var GridCache_1 = require("./GridCache"); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var TextRenderLayer = (function (_super) { + __extends(TextRenderLayer, _super); + function TextRenderLayer(container, zIndex, colors, alpha) { + var _this = _super.call(this, container, 'text', zIndex, alpha, colors) || this; + _this._characterOverlapCache = {}; + _this._state = new GridCache_1.GridCache(); + return _this; + } + TextRenderLayer.prototype.resize = function (terminal, dim) { + _super.prototype.resize.call(this, terminal, dim); + var terminalFont = this._getFont(terminal, false, false); + if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) { + this._characterWidth = dim.scaledCharWidth; + this._characterFont = terminalFont; + this._characterOverlapCache = {}; + } + this._state.clear(); + this._state.resize(terminal.cols, terminal.rows); + }; + TextRenderLayer.prototype.reset = function (terminal) { + this._state.clear(); + this.clearAll(); + }; + TextRenderLayer.prototype._forEachCell = function (terminal, firstRow, lastRow, callback) { + for (var y = firstRow; y <= lastRow; y++) { + var row = y + terminal.buffer.ydisp; + var line = terminal.buffer.lines.get(row); + for (var x = 0; x < terminal.cols; x++) { + var charData = line[x]; + var code = charData[Buffer_1.CHAR_DATA_CODE_INDEX]; + var char = charData[Buffer_1.CHAR_DATA_CHAR_INDEX]; + var attr = charData[Buffer_1.CHAR_DATA_ATTR_INDEX]; + var width = charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]; + if (width === 0) { + continue; + } + if (this._isOverlapping(charData)) { + if (x < line.length - 1 && line[x + 1][Buffer_1.CHAR_DATA_CODE_INDEX] === 32) { + width = 2; + } + } + var flags = attr >> 18; + var bg = attr & 0x1ff; + var fg = (attr >> 9) & 0x1ff; + if (flags & 8) { + var temp = bg; + bg = fg; + fg = temp; + if (fg === 256) { + fg = Types_1.INVERTED_DEFAULT_COLOR; + } + if (bg === 257) { + bg = Types_1.INVERTED_DEFAULT_COLOR; + } + } + callback(code, char, width, x, y, fg, bg, flags); + } + } + }; + TextRenderLayer.prototype._drawBackground = function (terminal, firstRow, lastRow) { + var _this = this; + var ctx = this._ctx; + var cols = terminal.cols; + var startX = 0; + var startY = 0; + var prevFillStyle = null; + ctx.save(); + this._forEachCell(terminal, firstRow, lastRow, function (code, char, width, x, y, fg, bg, flags) { + var nextFillStyle = null; + if (bg === Types_1.INVERTED_DEFAULT_COLOR) { + nextFillStyle = _this._colors.foreground.css; + } + else if (bg < 256) { + nextFillStyle = _this._colors.ansi[bg].css; + } + if (prevFillStyle === null) { + startX = x; + startY = y; + } + if (y !== startY) { + ctx.fillStyle = prevFillStyle; + _this.fillCells(startX, startY, cols - startX, 1); + startX = x; + startY = y; + } + else if (prevFillStyle !== nextFillStyle) { + ctx.fillStyle = prevFillStyle; + _this.fillCells(startX, startY, x - startX, 1); + startX = x; + startY = y; + } + prevFillStyle = nextFillStyle; + }); + if (prevFillStyle !== null) { + ctx.fillStyle = prevFillStyle; + this.fillCells(startX, startY, cols - startX, 1); + } + ctx.restore(); + }; + TextRenderLayer.prototype._drawForeground = function (terminal, firstRow, lastRow) { + var _this = this; + this._forEachCell(terminal, firstRow, lastRow, function (code, char, width, x, y, fg, bg, flags) { + if (flags & 16) { + return; + } + if (flags & 2) { + _this._ctx.save(); + if (fg === Types_1.INVERTED_DEFAULT_COLOR) { + _this._ctx.fillStyle = _this._colors.background.css; + } + else if (fg < 256) { + _this._ctx.fillStyle = _this._colors.ansi[fg].css; + } + else { + _this._ctx.fillStyle = _this._colors.foreground.css; + } + _this.fillBottomLineAtCells(x, y); + _this._ctx.restore(); + } + _this.drawChar(terminal, char, code, width, x, y, fg, bg, !!(flags & 1), !!(flags & 32), !!(flags & 64)); + }); + }; + TextRenderLayer.prototype.onGridChanged = function (terminal, firstRow, lastRow) { + if (this._state.cache.length === 0) { + return; + } + if (this._charAtlas) { + this._charAtlas.beginFrame(); + } + this.clearCells(0, firstRow, terminal.cols, lastRow - firstRow + 1); + this._drawBackground(terminal, firstRow, lastRow); + this._drawForeground(terminal, firstRow, lastRow); + }; + TextRenderLayer.prototype.onOptionsChanged = function (terminal) { + this.setTransparency(terminal, terminal.options.allowTransparency); + }; + TextRenderLayer.prototype._isOverlapping = function (charData) { + if (charData[Buffer_1.CHAR_DATA_WIDTH_INDEX] !== 1) { + return false; + } + var code = charData[Buffer_1.CHAR_DATA_CODE_INDEX]; + if (code < 256) { + return false; + } + var char = charData[Buffer_1.CHAR_DATA_CHAR_INDEX]; + if (this._characterOverlapCache.hasOwnProperty(char)) { + return this._characterOverlapCache[char]; + } + this._ctx.save(); + this._ctx.font = this._characterFont; + var overlaps = Math.floor(this._ctx.measureText(char).width) > this._characterWidth; + this._ctx.restore(); + this._characterOverlapCache[char] = overlaps; + return overlaps; + }; + return TextRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.TextRenderLayer = TextRenderLayer; + +},{"../Buffer":2,"./BaseRenderLayer":24,"./GridCache":27,"./atlas/Types":39}],32:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseCharAtlas = (function () { + function BaseCharAtlas() { + this._didWarmUp = false; + } + BaseCharAtlas.prototype.warmUp = function () { + if (!this._didWarmUp) { + this._doWarmUp(); + this._didWarmUp = true; + } + }; + BaseCharAtlas.prototype._doWarmUp = function () { }; + BaseCharAtlas.prototype.beginFrame = function () { }; + return BaseCharAtlas; +}()); +exports.default = BaseCharAtlas; + +},{}],33:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var CharAtlasUtils_1 = require("./CharAtlasUtils"); +var DynamicCharAtlas_1 = require("./DynamicCharAtlas"); +var NoneCharAtlas_1 = require("./NoneCharAtlas"); +var StaticCharAtlas_1 = require("./StaticCharAtlas"); +var charAtlasImplementations = { + 'none': NoneCharAtlas_1.default, + 'static': StaticCharAtlas_1.default, + 'dynamic': DynamicCharAtlas_1.default +}; +var charAtlasCache = []; +function acquireCharAtlas(terminal, colors, scaledCharWidth, scaledCharHeight) { + var newConfig = CharAtlasUtils_1.generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); + for (var i = 0; i < charAtlasCache.length; i++) { + var entry = charAtlasCache[i]; + var ownedByIndex = entry.ownedBy.indexOf(terminal); + if (ownedByIndex >= 0) { + if (CharAtlasUtils_1.configEquals(entry.config, newConfig)) { + return entry.atlas; + } + if (entry.ownedBy.length === 1) { + charAtlasCache.splice(i, 1); + } + else { + entry.ownedBy.splice(ownedByIndex, 1); + } break; } - if (diff < ldiff) { - ldiff = diff; - li = i; + } + for (var i = 0; i < charAtlasCache.length; i++) { + var entry = charAtlasCache[i]; + if (CharAtlasUtils_1.configEquals(entry.config, newConfig)) { + entry.ownedBy.push(terminal); + return entry.atlas; } } - return matchColor._cache[hash] = li; + var newEntry = { + atlas: new charAtlasImplementations[terminal.options.experimentalCharAtlas](document, newConfig), + config: newConfig, + ownedBy: [terminal] + }; + charAtlasCache.push(newEntry); + return newEntry.atlas; } -matchColor._cache = {}; -matchColor.distance = function (r1, g1, b1, r2, g2, b2) { - return Math.pow(30 * (r1 - r2), 2) - + Math.pow(59 * (g1 - g2), 2) - + Math.pow(11 * (b1 - b2), 2); +exports.acquireCharAtlas = acquireCharAtlas; +function removeTerminalFromCache(terminal) { + for (var i = 0; i < charAtlasCache.length; i++) { + var index = charAtlasCache[i].ownedBy.indexOf(terminal); + if (index !== -1) { + if (charAtlasCache[i].ownedBy.length === 1) { + charAtlasCache.splice(i, 1); + } + else { + charAtlasCache[i].ownedBy.splice(index, 1); + } + break; + } + } +} +exports.removeTerminalFromCache = removeTerminalFromCache; + +},{"./CharAtlasUtils":34,"./DynamicCharAtlas":35,"./NoneCharAtlas":37,"./StaticCharAtlas":38}],34:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors) { + var clonedColors = { + foreground: colors.foreground, + background: colors.background, + cursor: null, + cursorAccent: null, + selection: null, + ansi: colors.ansi.slice(0, 16) + }; + return { + type: terminal.options.experimentalCharAtlas, + devicePixelRatio: window.devicePixelRatio, + scaledCharWidth: scaledCharWidth, + scaledCharHeight: scaledCharHeight, + fontFamily: terminal.options.fontFamily, + fontSize: terminal.options.fontSize, + fontWeight: terminal.options.fontWeight, + fontWeightBold: terminal.options.fontWeightBold, + allowTransparency: terminal.options.allowTransparency, + colors: clonedColors + }; +} +exports.generateConfig = generateConfig; +function configEquals(a, b) { + for (var i = 0; i < a.colors.ansi.length; i++) { + if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) { + return false; + } + } + return a.type === b.type && + a.devicePixelRatio === b.devicePixelRatio && + a.fontFamily === b.fontFamily && + a.fontSize === b.fontSize && + a.fontWeight === b.fontWeight && + a.fontWeightBold === b.fontWeightBold && + a.allowTransparency === b.allowTransparency && + a.scaledCharWidth === b.scaledCharWidth && + a.scaledCharHeight === b.scaledCharHeight && + a.colors.foreground === b.colors.foreground && + a.colors.background === b.colors.background; +} +exports.configEquals = configEquals; + +},{}],35:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("./Types"); +var BaseCharAtlas_1 = require("./BaseCharAtlas"); +var ColorManager_1 = require("../ColorManager"); +var CharAtlasGenerator_1 = require("../../shared/atlas/CharAtlasGenerator"); +var LRUMap_1 = require("./LRUMap"); +var TEXTURE_WIDTH = 1024; +var TEXTURE_HEIGHT = 1024; +var TRANSPARENT_COLOR = { + css: 'rgba(0, 0, 0, 0)', + rgba: 0 }; -function each(obj, iter, con) { - if (obj.forEach) - return obj.forEach(iter, con); - for (var i = 0; i < obj.length; i++) { - iter.call(con, obj[i], i, obj); +var FRAME_CACHE_DRAW_LIMIT = 100; +function getGlyphCacheKey(glyph) { + var styleFlags = (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); + return glyph.bg + "_" + glyph.fg + "_" + styleFlags + glyph.char; +} +var DynamicCharAtlas = (function (_super) { + __extends(DynamicCharAtlas, _super); + function DynamicCharAtlas(document, _config) { + var _this = _super.call(this) || this; + _this._config = _config; + _this._drawToCacheCount = 0; + _this._cacheCanvas = document.createElement('canvas'); + _this._cacheCanvas.width = TEXTURE_WIDTH; + _this._cacheCanvas.height = TEXTURE_HEIGHT; + _this._cacheCtx = _this._cacheCanvas.getContext('2d', { alpha: true }); + var tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = _this._config.scaledCharWidth; + tmpCanvas.height = _this._config.scaledCharHeight; + _this._tmpCtx = tmpCanvas.getContext('2d', { alpha: _this._config.allowTransparency }); + _this._width = Math.floor(TEXTURE_WIDTH / _this._config.scaledCharWidth); + _this._height = Math.floor(TEXTURE_HEIGHT / _this._config.scaledCharHeight); + var capacity = _this._width * _this._height; + _this._cacheMap = new LRUMap_1.default(capacity); + _this._cacheMap.prealloc(capacity); + return _this; } -} -function wasMondifierKeyOnlyEvent(ev) { - return ev.keyCode === 16 || - ev.keyCode === 17 || - ev.keyCode === 18; -} -function keys(obj) { - if (Object.keys) - return Object.keys(obj); - var key, keys = []; - for (key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - keys.push(key); + DynamicCharAtlas.prototype.beginFrame = function () { + this._drawToCacheCount = 0; + }; + DynamicCharAtlas.prototype.draw = function (ctx, glyph, x, y) { + var glyphKey = getGlyphCacheKey(glyph); + var cacheValue = this._cacheMap.get(glyphKey); + if (cacheValue != null) { + this._drawFromCache(ctx, cacheValue, x, y); + return true; + } + else if (this._canCache(glyph) && this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) { + var index = void 0; + if (this._cacheMap.size < this._cacheMap.capacity) { + index = this._cacheMap.size; + } + else { + index = this._cacheMap.peek().index; + } + var cacheValue_1 = this._drawToCache(glyph, index); + this._cacheMap.set(glyphKey, cacheValue_1); + this._drawFromCache(ctx, cacheValue_1, x, y); + return true; + } + return false; + }; + DynamicCharAtlas.prototype._canCache = function (glyph) { + return glyph.code < 256; + }; + DynamicCharAtlas.prototype._toCoordinates = function (index) { + return [ + (index % this._width) * this._config.scaledCharWidth, + Math.floor(index / this._width) * this._config.scaledCharHeight + ]; + }; + DynamicCharAtlas.prototype._drawFromCache = function (ctx, cacheValue, x, y) { + if (cacheValue.isEmpty) { + return; + } + var _a = this._toCoordinates(cacheValue.index), cacheX = _a[0], cacheY = _a[1]; + ctx.drawImage(this._cacheCanvas, cacheX, cacheY, this._config.scaledCharWidth, this._config.scaledCharHeight, x, y, this._config.scaledCharWidth, this._config.scaledCharHeight); + }; + DynamicCharAtlas.prototype._getColorFromAnsiIndex = function (idx) { + if (idx < this._config.colors.ansi.length) { + return this._config.colors.ansi[idx]; + } + return ColorManager_1.DEFAULT_ANSI_COLORS[idx]; + }; + DynamicCharAtlas.prototype._getBackgroundColor = function (glyph) { + if (this._config.allowTransparency) { + return TRANSPARENT_COLOR; + } + else if (glyph.bg === Types_1.INVERTED_DEFAULT_COLOR) { + return this._config.colors.foreground; + } + else if (glyph.bg < 256) { + return this._getColorFromAnsiIndex(glyph.bg); + } + return this._config.colors.background; + }; + DynamicCharAtlas.prototype._getForegroundColor = function (glyph) { + if (glyph.fg === Types_1.INVERTED_DEFAULT_COLOR) { + return this._config.colors.background; + } + else if (glyph.fg < 256) { + return this._getColorFromAnsiIndex(glyph.fg); + } + return this._config.colors.foreground; + }; + DynamicCharAtlas.prototype._drawToCache = function (glyph, index) { + this._drawToCacheCount++; + this._tmpCtx.save(); + var backgroundColor = this._getBackgroundColor(glyph); + this._tmpCtx.globalCompositeOperation = 'copy'; + this._tmpCtx.fillStyle = backgroundColor.css; + this._tmpCtx.fillRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight); + this._tmpCtx.globalCompositeOperation = 'source-over'; + var fontWeight = glyph.bold ? this._config.fontWeightBold : this._config.fontWeight; + var fontStyle = glyph.italic ? 'italic' : ''; + this._tmpCtx.font = + fontStyle + " " + fontWeight + " " + this._config.fontSize * this._config.devicePixelRatio + "px " + this._config.fontFamily; + this._tmpCtx.textBaseline = 'top'; + this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css; + if (glyph.dim) { + this._tmpCtx.globalAlpha = Types_1.DIM_OPACITY; + } + this._tmpCtx.fillText(glyph.char, 0, 0); + this._tmpCtx.restore(); + var imageData = this._tmpCtx.getImageData(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight); + var isEmpty = false; + if (!this._config.allowTransparency) { + isEmpty = CharAtlasGenerator_1.clearColor(imageData, backgroundColor); + } + var _a = this._toCoordinates(index), x = _a[0], y = _a[1]; + this._cacheCtx.putImageData(imageData, x, y); + return { + index: index, + isEmpty: isEmpty + }; + }; + return DynamicCharAtlas; +}(BaseCharAtlas_1.default)); +exports.default = DynamicCharAtlas; + +},{"../../shared/atlas/CharAtlasGenerator":42,"../ColorManager":25,"./BaseCharAtlas":32,"./LRUMap":36,"./Types":39}],36:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var LRUMap = (function () { + function LRUMap(capacity) { + this.capacity = capacity; + this._map = {}; + this._head = null; + this._tail = null; + this._nodePool = []; + this.size = 0; + } + LRUMap.prototype._unlinkNode = function (node) { + var prev = node.prev; + var next = node.next; + if (node === this._head) { + this._head = next; + } + if (node === this._tail) { + this._tail = prev; + } + if (prev !== null) { + prev.next = next; + } + if (next !== null) { + next.prev = prev; + } + }; + LRUMap.prototype._appendNode = function (node) { + var tail = this._tail; + if (tail !== null) { + tail.next = node; + } + node.prev = tail; + node.next = null; + this._tail = node; + if (this._head === null) { + this._head = node; + } + }; + LRUMap.prototype.prealloc = function (count) { + var nodePool = this._nodePool; + for (var i = 0; i < count; i++) { + nodePool.push({ + prev: null, + next: null, + key: null, + value: null + }); + } + }; + LRUMap.prototype.get = function (key) { + var node = this._map[key]; + if (node !== undefined) { + this._unlinkNode(node); + this._appendNode(node); + return node.value; + } + return null; + }; + LRUMap.prototype.peek = function () { + var head = this._head; + return head === null ? null : head.value; + }; + LRUMap.prototype.set = function (key, value) { + var node = this._map[key]; + if (node !== undefined) { + node = this._map[key]; + this._unlinkNode(node); + node.value = value; + } + else if (this.size >= this.capacity) { + node = this._head; + this._unlinkNode(node); + delete this._map[node.key]; + node.key = key; + node.value = value; + this._map[key] = node; + } + else { + var nodePool = this._nodePool; + if (nodePool.length > 0) { + node = nodePool.pop(); + node.key = key; + node.value = value; + } + else { + node = { + prev: null, + next: null, + key: key, + value: value + }; + } + this._map[key] = node; + this.size++; + } + this._appendNode(node); + }; + return LRUMap; +}()); +exports.default = LRUMap; + +},{}],37:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseCharAtlas_1 = require("./BaseCharAtlas"); +var NoneCharAtlas = (function (_super) { + __extends(NoneCharAtlas, _super); + function NoneCharAtlas(document, config) { + return _super.call(this) || this; + } + NoneCharAtlas.prototype.draw = function (ctx, glyph, x, y) { + return false; + }; + return NoneCharAtlas; +}(BaseCharAtlas_1.default)); +exports.default = NoneCharAtlas; + +},{"./BaseCharAtlas":32}],38:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("./Types"); +var Types_2 = require("../../shared/atlas/Types"); +var CharAtlasGenerator_1 = require("../../shared/atlas/CharAtlasGenerator"); +var BaseCharAtlas_1 = require("./BaseCharAtlas"); +var StaticCharAtlas = (function (_super) { + __extends(StaticCharAtlas, _super); + function StaticCharAtlas(_document, _config) { + var _this = _super.call(this) || this; + _this._document = _document; + _this._config = _config; + _this._canvasFactory = function (width, height) { + var canvas = _this._document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; + }; + return _this; + } + StaticCharAtlas.prototype._doWarmUp = function () { + var _this = this; + var result = CharAtlasGenerator_1.generateStaticCharAtlasTexture(window, this._canvasFactory, this._config); + if (result instanceof HTMLCanvasElement) { + this._texture = result; + } + else { + result.then(function (texture) { + _this._texture = texture; + }); + } + }; + StaticCharAtlas.prototype._isCached = function (glyph, colorIndex) { + var isAscii = glyph.code < 256; + var isBasicColor = glyph.fg < 16; + var isDefaultColor = glyph.fg >= 256; + var isDefaultBackground = glyph.bg >= 256; + return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic; + }; + StaticCharAtlas.prototype.draw = function (ctx, glyph, x, y) { + if (this._texture == null) { + return false; + } + var colorIndex = 0; + if (glyph.fg < 256) { + colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0); + } + else { + if (glyph.bold) { + colorIndex = 1; + } + } + if (!this._isCached(glyph, colorIndex)) { + return false; + } + ctx.save(); + var charAtlasCellWidth = this._config.scaledCharWidth + Types_2.CHAR_ATLAS_CELL_SPACING; + var charAtlasCellHeight = this._config.scaledCharHeight + Types_2.CHAR_ATLAS_CELL_SPACING; + if (glyph.dim) { + ctx.globalAlpha = Types_1.DIM_OPACITY; + } + ctx.drawImage(this._texture, glyph.code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, charAtlasCellWidth, this._config.scaledCharHeight, x, y, charAtlasCellWidth, this._config.scaledCharHeight); + ctx.restore(); + return true; + }; + return StaticCharAtlas; +}(BaseCharAtlas_1.default)); +exports.default = StaticCharAtlas; + +},{"../../shared/atlas/CharAtlasGenerator":42,"../../shared/atlas/Types":43,"./BaseCharAtlas":32,"./Types":39}],39:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.INVERTED_DEFAULT_COLOR = -1; +exports.DIM_OPACITY = 0.5; + +},{}],40:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter_1 = require("../../EventEmitter"); +var ColorManager_1 = require("../ColorManager"); +var RenderDebouncer_1 = require("../../ui/RenderDebouncer"); +var DomRendererRowFactory_1 = require("./DomRendererRowFactory"); +var TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; +var ROW_CONTAINER_CLASS = 'xterm-rows'; +var FG_CLASS_PREFIX = 'xterm-fg-'; +var BG_CLASS_PREFIX = 'xterm-bg-'; +var FOCUS_CLASS = 'xterm-focus'; +var SELECTION_CLASS = 'xterm-selection'; +var nextTerminalId = 1; +var DomRenderer = (function (_super) { + __extends(DomRenderer, _super); + function DomRenderer(_terminal, theme) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._terminalClass = nextTerminalId++; + _this._rowElements = []; + var allowTransparency = _this._terminal.options.allowTransparency; + _this.colorManager = new ColorManager_1.ColorManager(document, allowTransparency); + _this.setTheme(theme); + _this._rowContainer = document.createElement('div'); + _this._rowContainer.classList.add(ROW_CONTAINER_CLASS); + _this._rowContainer.style.lineHeight = 'normal'; + _this._rowContainer.setAttribute('aria-hidden', 'true'); + _this._refreshRowElements(_this._terminal.rows, _this._terminal.cols); + _this._selectionContainer = document.createElement('div'); + _this._selectionContainer.classList.add(SELECTION_CLASS); + _this._selectionContainer.setAttribute('aria-hidden', 'true'); + _this.dimensions = { + scaledCharWidth: null, + scaledCharHeight: null, + scaledCellWidth: null, + scaledCellHeight: null, + scaledCharLeft: null, + scaledCharTop: null, + scaledCanvasWidth: null, + scaledCanvasHeight: null, + canvasWidth: null, + canvasHeight: null, + actualCellWidth: null, + actualCellHeight: null + }; + _this._updateDimensions(); + _this._renderDebouncer = new RenderDebouncer_1.RenderDebouncer(_this._terminal, _this._renderRows.bind(_this)); + _this._rowFactory = new DomRendererRowFactory_1.DomRendererRowFactory(document); + _this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + _this._terminalClass); + _this._terminal.screenElement.appendChild(_this._rowContainer); + _this._terminal.screenElement.appendChild(_this._selectionContainer); + return _this; + } + DomRenderer.prototype._updateDimensions = function () { + var _this = this; + this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio; + this.dimensions.scaledCharHeight = this._terminal.charMeasure.height * window.devicePixelRatio; + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth; + this.dimensions.scaledCellHeight = this.dimensions.scaledCharHeight; + this.dimensions.scaledCharLeft = 0; + this.dimensions.scaledCharTop = 0; + this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols; + this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows; + this.dimensions.canvasWidth = this._terminal.charMeasure.width * this._terminal.cols; + this.dimensions.canvasHeight = this._terminal.charMeasure.height * this._terminal.rows; + this.dimensions.actualCellWidth = this._terminal.charMeasure.width; + this.dimensions.actualCellHeight = this._terminal.charMeasure.height; + this._rowElements.forEach(function (element) { + element.style.width = _this.dimensions.canvasWidth + "px"; + element.style.height = _this._terminal.charMeasure.height + "px"; + }); + if (!this._dimensionsStyleElement) { + this._dimensionsStyleElement = document.createElement('style'); + this._terminal.screenElement.appendChild(this._dimensionsStyleElement); + } + var styles = this._terminalSelector + " ." + ROW_CONTAINER_CLASS + " span {" + + " display: inline-block;" + + " height: 100%;" + + " vertical-align: top;" + + (" width: " + this._terminal.charMeasure.width + "px") + + "}"; + this._dimensionsStyleElement.innerHTML = styles; + this._selectionContainer.style.height = this._terminal._viewportElement.style.height; + this._rowContainer.style.width = this.dimensions.canvasWidth + "px"; + this._rowContainer.style.height = this.dimensions.canvasHeight + "px"; + }; + DomRenderer.prototype.setTheme = function (theme) { + var _this = this; + if (theme) { + this.colorManager.setTheme(theme); + } + if (!this._themeStyleElement) { + this._themeStyleElement = document.createElement('style'); + this._terminal.screenElement.appendChild(this._themeStyleElement); + } + var styles = this._terminalSelector + " ." + ROW_CONTAINER_CLASS + " {" + + (" color: " + this.colorManager.colors.foreground.css + ";") + + (" background-color: " + this.colorManager.colors.background.css + ";") + + (" font-family: " + this._terminal.getOption('fontFamily') + ";") + + (" font-size: " + this._terminal.getOption('fontSize') + "px;") + + "}"; + styles += + this._terminalSelector + " span:not(." + DomRendererRowFactory_1.BOLD_CLASS + ") {" + + (" font-weight: " + this._terminal.options.fontWeight + ";") + + "}" + + (this._terminalSelector + " span." + DomRendererRowFactory_1.BOLD_CLASS + " {") + + (" font-weight: " + this._terminal.options.fontWeightBold + ";") + + "}" + + (this._terminalSelector + " span." + DomRendererRowFactory_1.ITALIC_CLASS + " {") + + " font-style: italic;" + + "}"; + styles += + this._terminalSelector + " ." + ROW_CONTAINER_CLASS + "." + FOCUS_CLASS + " ." + DomRendererRowFactory_1.CURSOR_CLASS + " {" + + (" background-color: " + this.colorManager.colors.cursor.css + ";") + + (" color: " + this.colorManager.colors.cursorAccent.css + ";") + + "}" + + (this._terminalSelector + " ." + ROW_CONTAINER_CLASS + ":not(." + FOCUS_CLASS + ") ." + DomRendererRowFactory_1.CURSOR_CLASS + " {") + + " outline: 1px solid #fff;" + + " outline-offset: -1px;" + + "}"; + styles += + this._terminalSelector + " ." + SELECTION_CLASS + " {" + + " position: absolute;" + + " top: 0;" + + " left: 0;" + + " z-index: 1;" + + " pointer-events: none;" + + "}" + + (this._terminalSelector + " ." + SELECTION_CLASS + " div {") + + " position: absolute;" + + (" background-color: " + this.colorManager.colors.selection.css + ";") + + "}"; + this.colorManager.colors.ansi.forEach(function (c, i) { + styles += + _this._terminalSelector + " ." + FG_CLASS_PREFIX + i + " { color: " + c.css + "; }" + + (_this._terminalSelector + " ." + BG_CLASS_PREFIX + i + " { background-color: " + c.css + "; }"); + }); + this._themeStyleElement.innerHTML = styles; + return this.colorManager.colors; + }; + DomRenderer.prototype.onWindowResize = function (devicePixelRatio) { + this._updateDimensions(); + }; + DomRenderer.prototype._refreshRowElements = function (cols, rows) { + for (var i = this._rowElements.length; i <= rows; i++) { + var row = document.createElement('div'); + this._rowContainer.appendChild(row); + this._rowElements.push(row); + } + while (this._rowElements.length > rows) { + this._rowContainer.removeChild(this._rowElements.pop()); + } + }; + DomRenderer.prototype.onResize = function (cols, rows) { + this._refreshRowElements(cols, rows); + this._updateDimensions(); + }; + DomRenderer.prototype.onCharSizeChanged = function () { + this._updateDimensions(); + }; + DomRenderer.prototype.onBlur = function () { + this._rowContainer.classList.remove(FOCUS_CLASS); + }; + DomRenderer.prototype.onFocus = function () { + this._rowContainer.classList.add(FOCUS_CLASS); + }; + DomRenderer.prototype.onSelectionChanged = function (start, end, columnSelectMode) { + while (this._selectionContainer.children.length) { + this._selectionContainer.removeChild(this._selectionContainer.children[0]); + } + if (!start || !end) { + return; + } + var viewportStartRow = start[1] - this._terminal.buffer.ydisp; + var viewportEndRow = end[1] - this._terminal.buffer.ydisp; + var viewportCappedStartRow = Math.max(viewportStartRow, 0); + var viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); + if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { + return; + } + var documentFragment = document.createDocumentFragment(); + var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; + if (columnSelectMode) { + documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, end[0], viewportCappedEndRow - viewportStartRow + 1)); + } + else { + var endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; + documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); + var middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; + documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); + if (viewportCappedStartRow !== viewportCappedEndRow) { + var endCol_1 = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; + documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol_1)); + } + } + this._selectionContainer.appendChild(documentFragment); + }; + DomRenderer.prototype._createSelectionElement = function (row, colStart, colEnd, rowCount) { + if (rowCount === void 0) { rowCount = 1; } + var element = document.createElement('div'); + element.style.height = rowCount * this._terminal.charMeasure.height + "px"; + element.style.top = row * this._terminal.charMeasure.height + "px"; + element.style.left = colStart * this._terminal.charMeasure.width + "px"; + element.style.width = this._terminal.charMeasure.width * (colEnd - colStart) + "px"; + return element; + }; + DomRenderer.prototype.onCursorMove = function () { + }; + DomRenderer.prototype.onOptionsChanged = function () { + this._updateDimensions(); + this.setTheme(undefined); + this._terminal.refresh(0, this._terminal.rows - 1); + }; + DomRenderer.prototype.clear = function () { + this._rowElements.forEach(function (e) { return e.innerHTML = ''; }); + }; + DomRenderer.prototype.refreshRows = function (start, end) { + this._renderDebouncer.refresh(start, end); + }; + DomRenderer.prototype._renderRows = function (start, end) { + var terminal = this._terminal; + var cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y; + var cursorX = this._terminal.buffer.x; + for (var y = start; y <= end; y++) { + var rowElement = this._rowElements[y]; + rowElement.innerHTML = ''; + var row = y + terminal.buffer.ydisp; + var lineData = terminal.buffer.lines.get(row); + rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorX, terminal.charMeasure.width, terminal.cols)); + } + this._terminal.emit('refresh', { start: start, end: end }); + }; + Object.defineProperty(DomRenderer.prototype, "_terminalSelector", { + get: function () { + return "." + TERMINAL_CLASS_PREFIX + this._terminalClass; + }, + enumerable: true, + configurable: true + }); + return DomRenderer; +}(EventEmitter_1.EventEmitter)); +exports.DomRenderer = DomRenderer; + +},{"../../EventEmitter":7,"../../ui/RenderDebouncer":48,"../ColorManager":25,"./DomRendererRowFactory":41}],41:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Buffer_1 = require("../../Buffer"); +exports.BOLD_CLASS = 'xterm-bold'; +exports.ITALIC_CLASS = 'xterm-italic'; +exports.CURSOR_CLASS = 'xterm-cursor'; +var DomRendererRowFactory = (function () { + function DomRendererRowFactory(_document) { + this._document = _document; + } + DomRendererRowFactory.prototype.createRow = function (lineData, isCursorRow, cursorX, cellWidth, cols) { + var fragment = this._document.createDocumentFragment(); + var colCount = 0; + for (var x = 0; x < lineData.length; x++) { + if (colCount >= cols) { + continue; + } + var charData = lineData[x]; + var char = charData[Buffer_1.CHAR_DATA_CHAR_INDEX]; + var attr = charData[Buffer_1.CHAR_DATA_ATTR_INDEX]; + var width = charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]; + if (width === 0) { + continue; + } + var charElement = this._document.createElement('span'); + if (width > 1) { + charElement.style.width = cellWidth * width + "px"; + } + var flags = attr >> 18; + var bg = attr & 0x1ff; + var fg = (attr >> 9) & 0x1ff; + if (isCursorRow && x === cursorX) { + charElement.classList.add(exports.CURSOR_CLASS); + } + if (flags & 8) { + var temp = bg; + bg = fg; + fg = temp; + if (fg === 256) { + fg = 0; + } + if (bg === 257) { + bg = 15; + } + } + if (flags & 1) { + if (fg < 8) { + fg += 8; + } + charElement.classList.add(exports.BOLD_CLASS); + } + if (flags & 64) { + charElement.classList.add(exports.ITALIC_CLASS); + } + charElement.textContent = char; + if (fg !== 257) { + charElement.classList.add("xterm-fg-" + fg); + } + if (bg !== 256) { + charElement.classList.add("xterm-bg-" + bg); + } + fragment.appendChild(charElement); + colCount += width; + } + return fragment; + }; + return DomRendererRowFactory; +}()); +exports.DomRendererRowFactory = DomRendererRowFactory; + +},{"../../Buffer":2}],42:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("./Types"); +var Browser_1 = require("../utils/Browser"); +function generateStaticCharAtlasTexture(context, canvasFactory, config) { + var cellWidth = config.scaledCharWidth + Types_1.CHAR_ATLAS_CELL_SPACING; + var cellHeight = config.scaledCharHeight + Types_1.CHAR_ATLAS_CELL_SPACING; + var canvas = canvasFactory(255 * cellWidth, (2 + 16 + 16) * cellHeight); + var ctx = canvas.getContext('2d', { alpha: config.allowTransparency }); + ctx.fillStyle = config.colors.background.css; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.fillStyle = config.colors.foreground.css; + ctx.font = getFont(config.fontWeight, config); + ctx.textBaseline = 'top'; + for (var i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); + ctx.clip(); + ctx.fillText(String.fromCharCode(i), i * cellWidth, 0); + ctx.restore(); + } + ctx.save(); + ctx.font = getFont(config.fontWeightBold, config); + for (var i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); + ctx.clip(); + ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight); + ctx.restore(); + } + ctx.restore(); + ctx.font = getFont(config.fontWeight, config); + for (var colorIndex = 0; colorIndex < 16; colorIndex++) { + var y = (colorIndex + 2) * cellHeight; + for (var i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + ctx.clip(); + ctx.fillStyle = config.colors.ansi[colorIndex].css; + ctx.fillText(String.fromCharCode(i), i * cellWidth, y); + ctx.restore(); } } - return keys; + ctx.font = getFont(config.fontWeightBold, config); + for (var colorIndex = 0; colorIndex < 16; colorIndex++) { + var y = (colorIndex + 2 + 16) * cellHeight; + for (var i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + ctx.clip(); + ctx.fillStyle = config.colors.ansi[colorIndex].css; + ctx.fillText(String.fromCharCode(i), i * cellWidth, y); + ctx.restore(); + } + } + ctx.restore(); + if (!('createImageBitmap' in context) || Browser_1.isFirefox || Browser_1.isSafari) { + if (canvas instanceof HTMLCanvasElement) { + return canvas; + } + return new Promise(function (r) { return r(canvas.transferToImageBitmap()); }); + } + var charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + clearColor(charAtlasImageData, config.colors.background); + return context.createImageBitmap(charAtlasImageData); +} +exports.generateStaticCharAtlasTexture = generateStaticCharAtlasTexture; +function clearColor(imageData, color) { + var isEmpty = true; + var r = color.rgba >>> 24; + var g = color.rgba >>> 16 & 0xFF; + var b = color.rgba >>> 8 & 0xFF; + for (var offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } + else { + isEmpty = false; + } + } + return isEmpty; +} +exports.clearColor = clearColor; +function getFont(fontWeight, config) { + return fontWeight + " " + config.fontSize * config.devicePixelRatio + "px " + config.fontFamily; } -Terminal.translateBufferLineToString = BufferLine_1.translateBufferLineToString; -Terminal.EventEmitter = EventEmitter_1.EventEmitter; -Terminal.inherits = inherits; -Terminal.on = on; -Terminal.off = off; -Terminal.cancel = cancel; -module.exports = Terminal; +},{"../utils/Browser":44,"./Types":43}],43:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CHAR_ATLAS_CELL_SPACING = 1; +},{}],44:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var isNode = (typeof navigator === 'undefined') ? true : false; +var userAgent = (isNode) ? 'node' : navigator.userAgent; +var platform = (isNode) ? 'node' : navigator.platform; +exports.isFirefox = !!~userAgent.indexOf('Firefox'); +exports.isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); +exports.isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident'); +exports.isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform); +exports.isIpad = platform === 'iPad'; +exports.isIphone = platform === 'iPhone'; +exports.isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform); +exports.isLinux = platform.indexOf('Linux') >= 0; +function contains(arr, el) { + return arr.indexOf(el) >= 0; +} -},{"./BufferSet":2,"./CompositionHelper":4,"./EscapeSequences":5,"./EventEmitter":6,"./InputHandler":7,"./Linkifier":8,"./Parser":9,"./Renderer":10,"./SelectionManager":11,"./Viewport":13,"./handlers/Clipboard":14,"./utils/Browser":15,"./utils/BufferLine":16,"./utils/CharMeasure":17,"./utils/Mouse":21}]},{},[22])(22) +},{}],45:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter_1 = require("../EventEmitter"); +var CharMeasure = (function (_super) { + __extends(CharMeasure, _super); + function CharMeasure(document, parentElement) { + var _this = _super.call(this) || this; + _this._document = document; + _this._parentElement = parentElement; + _this._measureElement = _this._document.createElement('span'); + _this._measureElement.classList.add('xterm-char-measure-element'); + _this._measureElement.textContent = 'W'; + _this._measureElement.setAttribute('aria-hidden', 'true'); + _this._parentElement.appendChild(_this._measureElement); + return _this; + } + Object.defineProperty(CharMeasure.prototype, "width", { + get: function () { + return this._width; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CharMeasure.prototype, "height", { + get: function () { + return this._height; + }, + enumerable: true, + configurable: true + }); + CharMeasure.prototype.measure = function (options) { + this._measureElement.style.fontFamily = options.fontFamily; + this._measureElement.style.fontSize = options.fontSize + "px"; + var geometry = this._measureElement.getBoundingClientRect(); + if (geometry.width === 0 || geometry.height === 0) { + return; + } + if (this._width !== geometry.width || this._height !== geometry.height) { + this._width = geometry.width; + this._height = Math.ceil(geometry.height); + this.emit('charsizechanged'); + } + }; + return CharMeasure; +}(EventEmitter_1.EventEmitter)); +exports.CharMeasure = CharMeasure; + +},{"../EventEmitter":7}],46:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function addDisposableDomListener(node, type, handler, useCapture) { + node.addEventListener(type, handler, useCapture); + return { + dispose: function () { + if (!handler) { + return; + } + node.removeEventListener(type, handler, useCapture); + node = null; + handler = null; + } + }; +} +exports.addDisposableDomListener = addDisposableDomListener; + +},{}],47:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("../common/Lifecycle"); +var Lifecycle_2 = require("./Lifecycle"); +var HOVER_DURATION = 500; +var MouseZoneManager = (function (_super) { + __extends(MouseZoneManager, _super); + function MouseZoneManager(_terminal) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._zones = []; + _this._areZonesActive = false; + _this._tooltipTimeout = null; + _this._currentZone = null; + _this._lastHoverCoords = [null, null]; + _this.register(Lifecycle_2.addDisposableDomListener(_this._terminal.element, 'mousedown', function (e) { return _this._onMouseDown(e); })); + _this._mouseMoveListener = function (e) { return _this._onMouseMove(e); }; + _this._clickListener = function (e) { return _this._onClick(e); }; + return _this; + } + MouseZoneManager.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._deactivate(); + }; + MouseZoneManager.prototype.add = function (zone) { + this._zones.push(zone); + if (this._zones.length === 1) { + this._activate(); + } + }; + MouseZoneManager.prototype.clearAll = function (start, end) { + if (this._zones.length === 0) { + return; + } + if (!end) { + start = 0; + end = this._terminal.rows - 1; + } + for (var i = 0; i < this._zones.length; i++) { + var zone = this._zones[i]; + if ((zone.y1 > start && zone.y1 <= end + 1) || + (zone.y2 > start && zone.y2 <= end + 1) || + (zone.y1 < start && zone.y2 > end + 1)) { + if (this._currentZone && this._currentZone === zone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + } + this._zones.splice(i--, 1); + } + } + if (this._zones.length === 0) { + this._deactivate(); + } + }; + MouseZoneManager.prototype._activate = function () { + if (!this._areZonesActive) { + this._areZonesActive = true; + this._terminal.element.addEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.addEventListener('click', this._clickListener); + } + }; + MouseZoneManager.prototype._deactivate = function () { + if (this._areZonesActive) { + this._areZonesActive = false; + this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.removeEventListener('click', this._clickListener); + } + }; + MouseZoneManager.prototype._onMouseMove = function (e) { + if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) { + this._onHover(e); + this._lastHoverCoords = [e.pageX, e.pageY]; + } + }; + MouseZoneManager.prototype._onHover = function (e) { + var _this = this; + var zone = this._findZoneEventAt(e); + if (zone === this._currentZone) { + return; + } + if (this._currentZone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + if (this._tooltipTimeout) { + clearTimeout(this._tooltipTimeout); + } + } + if (!zone) { + return; + } + this._currentZone = zone; + if (zone.hoverCallback) { + zone.hoverCallback(e); + } + this._tooltipTimeout = setTimeout(function () { return _this._onTooltip(e); }, HOVER_DURATION); + }; + MouseZoneManager.prototype._onTooltip = function (e) { + this._tooltipTimeout = null; + var zone = this._findZoneEventAt(e); + if (zone && zone.tooltipCallback) { + zone.tooltipCallback(e); + } + }; + MouseZoneManager.prototype._onMouseDown = function (e) { + if (!this._areZonesActive) { + return; + } + var zone = this._findZoneEventAt(e); + if (zone) { + if (zone.willLinkActivate(e)) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + }; + MouseZoneManager.prototype._onClick = function (e) { + var zone = this._findZoneEventAt(e); + if (zone) { + zone.clickCallback(e); + e.preventDefault(); + e.stopImmediatePropagation(); + } + }; + MouseZoneManager.prototype._findZoneEventAt = function (e) { + var coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); + if (!coords) { + return null; + } + var x = coords[0]; + var y = coords[1]; + for (var i = 0; i < this._zones.length; i++) { + var zone = this._zones[i]; + if (zone.y1 === zone.y2) { + if (y === zone.y1 && x >= zone.x1 && x < zone.x2) { + return zone; + } + } + else { + if ((y === zone.y1 && x >= zone.x1) || + (y === zone.y2 && x < zone.x2) || + (y > zone.y1 && y < zone.y2)) { + return zone; + } + } + } + return null; + }; + return MouseZoneManager; +}(Lifecycle_1.Disposable)); +exports.MouseZoneManager = MouseZoneManager; +var MouseZone = (function () { + function MouseZone(x1, y1, x2, y2, clickCallback, hoverCallback, tooltipCallback, leaveCallback, willLinkActivate) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.clickCallback = clickCallback; + this.hoverCallback = hoverCallback; + this.tooltipCallback = tooltipCallback; + this.leaveCallback = leaveCallback; + this.willLinkActivate = willLinkActivate; + } + return MouseZone; +}()); +exports.MouseZone = MouseZone; + +},{"../common/Lifecycle":17,"./Lifecycle":46}],48:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var RenderDebouncer = (function () { + function RenderDebouncer(_terminal, _callback) { + this._terminal = _terminal; + this._callback = _callback; + this._animationFrame = null; + } + RenderDebouncer.prototype.dispose = function () { + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + }; + RenderDebouncer.prototype.refresh = function (rowStart, rowEnd) { + var _this = this; + rowStart = rowStart || 0; + rowEnd = rowEnd || this._terminal.rows - 1; + this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart; + this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd; + if (this._animationFrame) { + return; + } + this._animationFrame = window.requestAnimationFrame(function () { return _this._innerRefresh(); }); + }; + RenderDebouncer.prototype._innerRefresh = function () { + this._rowStart = Math.max(this._rowStart, 0); + this._rowEnd = Math.min(this._rowEnd, this._terminal.rows - 1); + this._callback(this._rowStart, this._rowEnd); + this._rowStart = null; + this._rowEnd = null; + this._animationFrame = null; + }; + return RenderDebouncer; +}()); +exports.RenderDebouncer = RenderDebouncer; + +},{}],49:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("../common/Lifecycle"); +var ScreenDprMonitor = (function (_super) { + __extends(ScreenDprMonitor, _super); + function ScreenDprMonitor() { + return _super !== null && _super.apply(this, arguments) || this; + } + ScreenDprMonitor.prototype.setListener = function (listener) { + var _this = this; + if (this._listener) { + this.clearListener(); + } + this._listener = listener; + this._outerListener = function () { + _this._listener(window.devicePixelRatio, _this._currentDevicePixelRatio); + _this._updateDpr(); + }; + this._updateDpr(); + }; + ScreenDprMonitor.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this.clearListener(); + }; + ScreenDprMonitor.prototype._updateDpr = function () { + if (this._resolutionMediaMatchList) { + this._resolutionMediaMatchList.removeListener(this._outerListener); + } + this._currentDevicePixelRatio = window.devicePixelRatio; + this._resolutionMediaMatchList = window.matchMedia("screen and (resolution: " + window.devicePixelRatio + "dppx)"); + this._resolutionMediaMatchList.addListener(this._outerListener); + }; + ScreenDprMonitor.prototype.clearListener = function () { + if (!this._listener) { + return; + } + this._resolutionMediaMatchList.removeListener(this._outerListener); + this._listener = null; + this._outerListener = null; + }; + return ScreenDprMonitor; +}(Lifecycle_1.Disposable)); +exports.ScreenDprMonitor = ScreenDprMonitor; + +},{"../common/Lifecycle":17}],50:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.clone = function (val, depth) { + if (depth === void 0) { depth = 5; } + if (typeof val !== 'object') { + return val; + } + if (val === null) { + return null; + } + var clonedObject = Array.isArray(val) ? [] : {}; + for (var key in val) { + clonedObject[key] = depth <= 1 ? val[key] : exports.clone(val[key], depth - 1); + } + return clonedObject; +}; + +},{}],51:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var MouseHelper = (function () { + function MouseHelper(_renderer) { + this._renderer = _renderer; + } + MouseHelper.getCoordsRelativeToElement = function (event, element) { + if (event.pageX == null) { + return null; + } + var originalElement = element; + var x = event.pageX; + var y = event.pageY; + while (element) { + x -= element.offsetLeft; + y -= element.offsetTop; + element = element.offsetParent; + } + element = originalElement; + while (element && element !== element.ownerDocument.body) { + x += element.scrollLeft; + y += element.scrollTop; + element = element.parentElement; + } + return [x, y]; + }; + MouseHelper.prototype.getCoords = function (event, element, charMeasure, lineHeight, colCount, rowCount, isSelection) { + if (!charMeasure.width || !charMeasure.height) { + return null; + } + var coords = MouseHelper.getCoordsRelativeToElement(event, element); + if (!coords) { + return null; + } + coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderer.dimensions.actualCellWidth / 2 : 0)) / this._renderer.dimensions.actualCellWidth); + coords[1] = Math.ceil(coords[1] / this._renderer.dimensions.actualCellHeight); + coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0)); + coords[1] = Math.min(Math.max(coords[1], 1), rowCount); + return coords; + }; + MouseHelper.prototype.getRawByteCoords = function (event, element, charMeasure, lineHeight, colCount, rowCount) { + var coords = this.getCoords(event, element, charMeasure, lineHeight, colCount, rowCount); + var x = coords[0]; + var y = coords[1]; + x += 32; + y += 32; + return { x: x, y: y }; + }; + return MouseHelper; +}()); +exports.MouseHelper = MouseHelper; + +},{}],52:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Terminal_1 = require("./public/Terminal"); +module.exports = Terminal_1.Terminal; + +},{"./public/Terminal":23}]},{},[52])(52) }); //# sourceMappingURL=xterm.js.map diff --git a/apps/static/js/plugins/ztree/jquery.ztree.exhide.min.js b/apps/static/js/plugins/ztree/jquery.ztree.exhide.min.js new file mode 100644 index 000000000..6c78a1fe9 --- /dev/null +++ b/apps/static/js/plugins/ztree/jquery.ztree.exhide.min.js @@ -0,0 +1,23 @@ +/* + * JQuery zTree exHideNodes v3.5.33 + * http://treejs.cn/ + * + * Copyright (c) 2010 Hunter.z + * + * Licensed same as jquery - MIT License + * http://www.opensource.org/licenses/mit-license.php + * + * email: hunter.z@263.net + * Date: 2018-01-30 + */ +(function(j){j.extend(!0,j.fn.zTree._z,{view:{clearOldFirstNode:function(c,a){for(var b=a.getNextNode();b;){if(b.isFirstNode){b.isFirstNode=!1;e.setNodeLineIcos(c,b);break}if(b.isLastNode)break;b=b.getNextNode()}},clearOldLastNode:function(c,a,b){for(a=a.getPreNode();a;){if(a.isLastNode){a.isLastNode=!1;b&&e.setNodeLineIcos(c,a);break}if(a.isFirstNode)break;a=a.getPreNode()}},makeDOMNodeMainBefore:function(c,a,b){a=d.isHidden(a,b);c.push("
  • ")},showNode:function(c,a){d.isHidden(c,a,!1);d.initShowForExCheck(c,a);k(a,c).show()},showNodes:function(c,a,b){if(a&&a.length!=0){var f={},g,i;for(g=0,i=a.length;g0&&!f?b[0].isFirstNode=!0:b.length>0&&e.setFirstNodeForHide(c,b)},setLastNode:function(c,a){var b=d.nodeChildren(c,a),f=d.isHidden(c,b[0]);b.length>0&&!f?b[b.length-1].isLastNode=!0:b.length>0&&e.setLastNodeForHide(c,b)},setFirstNodeForHide:function(c,a){var b,f,g;for(f=0,g=a.length;f=0;f--){b=a[f];if(b.isLastNode)break;if(!d.isHidden(c,b)&&!b.isLastNode){b.isLastNode=!0;e.setNodeLineIcos(c,b);break}else b=null}return b},setLastNodeForShow:function(c, +a){var b,f,g,i;for(f=a.length-1;f>=0;f--){b=a[f];var h=d.isHidden(c,b);if(!g&&!h&&b.isLastNode){g=b;break}else if(!g&&!h&&!b.isLastNode)b.isLastNode=!0,g=b,e.setNodeLineIcos(c,b);else if(g&&b.isLastNode){b.isLastNode=!1;i=b;e.setNodeLineIcos(c,b);break}}return{"new":g,old:i}}},data:{initHideForExCheck:function(c,a){if(d.isHidden(c,a)&&c.check&&c.check.enable){if(typeof a._nocheck=="undefined")a._nocheck=!!a.nocheck,a.nocheck=!0;a.check_Child_State=-1;e.repairParentChkClassWithSelf&&e.repairParentChkClassWithSelf(c, +a)}},initShowForExCheck:function(c,a){if(!d.isHidden(c,a)&&c.check&&c.check.enable){if(typeof a._nocheck!="undefined")a.nocheck=a._nocheck,delete a._nocheck;if(e.setChkClass){var b=k(a,l.id.CHECK,c);e.setChkClass(c,b,a)}e.repairParentChkClassWithSelf&&e.repairParentChkClassWithSelf(c,a)}}}});var j=j.fn.zTree,m=j._z.tools,l=j.consts,e=j._z.view,d=j._z.data,k=m.$;d.isHidden=function(c,a,b){if(!a)return!1;c=c.data.key.isHidden;typeof b!=="undefined"&&(typeof b==="string"&&(b=m.eqs(checked,"true")),a[c]= +!!b);return a[c]};d.exSetting({data:{key:{isHidden:"isHidden"}}});d.addInitNode(function(c,a,b){a=d.isHidden(c,b);d.isHidden(c,b,a);d.initHideForExCheck(c,b)});d.addBeforeA(function(){});d.addZTreeTools(function(c,a){a.showNodes=function(a,b){e.showNodes(c,a,b)};a.showNode=function(a,b){a&&e.showNodes(c,[a],b)};a.hideNodes=function(a,b){e.hideNodes(c,a,b)};a.hideNode=function(a,b){a&&e.hideNodes(c,[a],b)};var b=a.checkNode;if(b)a.checkNode=function(f,e,i,h){(!f||!d.isHidden(c,f))&&b.apply(a,arguments)}}); +var n=d.initNode;d.initNode=function(c,a,b,f,g,i,h){var j=(f?f:d.getRoot(c))[c.data.key.children];d.tmpHideFirstNode=e.setFirstNodeForHide(c,j);d.tmpHideLastNode=e.setLastNodeForHide(c,j);h&&(e.setNodeLineIcos(c,d.tmpHideFirstNode),e.setNodeLineIcos(c,d.tmpHideLastNode));g=d.tmpHideFirstNode===b;i=d.tmpHideLastNode===b;n&&n.apply(d,arguments);h&&i&&e.clearOldLastNode(c,b,h)};var o=d.makeChkFlag;if(o)d.makeChkFlag=function(c,a){(!a||!d.isHidden(c,a))&&o.apply(d,arguments)};var p=d.getTreeCheckedNodes; +if(p)d.getTreeCheckedNodes=function(c,a,b,f){if(a&&a.length>0){var e=a[0].getParentNode();if(e&&d.isHidden(c,e))return[]}return p.apply(d,arguments)};var q=d.getTreeChangeCheckedNodes;if(q)d.getTreeChangeCheckedNodes=function(c,a,b){if(a&&a.length>0){var e=a[0].getParentNode();if(e&&d.isHidden(c,e))return[]}return q.apply(d,arguments)};var r=e.expandCollapseSonNode;if(r)e.expandCollapseSonNode=function(c,a,b,f,g){(!a||!d.isHidden(c,a))&&r.apply(e,arguments)};var s=e.setSonNodeCheckBox;if(s)e.setSonNodeCheckBox= +function(c,a,b,f){(!a||!d.isHidden(c,a))&&s.apply(e,arguments)};var t=e.repairParentChkClassWithSelf;if(t)e.repairParentChkClassWithSelf=function(c,a){(!a||!d.isHidden(c,a))&&t.apply(e,arguments)}})(jQuery); diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 06c8e6b46..61acada82 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -58,7 +58,6 @@ {% endif %}
  • -{% if request.user.is_superuser %}
  • {% trans 'Job Center' %} @@ -67,7 +66,6 @@
  • {% trans 'Task list' %}
  • -{% endif %}
  • {% trans 'Audits' %} @@ -77,17 +75,9 @@
  • {% trans 'FTP log' %}
  • {% trans 'Operate log' %}
  • {% trans 'Password change log' %}
  • +
  • {% trans 'Command execution' %}
  • -{#
  • #} -{# #} -{# {% trans 'File' %}#} -{# #} -{# #} -{#
  • #} {% if XPACK_PLUGINS %}
  • diff --git a/apps/templates/_nav_user.html b/apps/templates/_nav_user.html index a8f2dcca1..151ed124e 100644 --- a/apps/templates/_nav_user.html +++ b/apps/templates/_nav_user.html @@ -4,6 +4,11 @@ {% trans 'My assets' %}
  • +
  • + + {% trans 'Command execution' %} + +
  • {% trans 'Profile' %} diff --git a/apps/users/templates/users/user_granted_asset.html b/apps/users/templates/users/user_granted_asset.html index 8a68f3f60..aac788e40 100644 --- a/apps/users/templates/users/user_granted_asset.html +++ b/apps/users/templates/users/user_granted_asset.html @@ -57,7 +57,6 @@ - {% endblock %} {% block custom_foot_js %} - {% endblock %} \ No newline at end of file diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 61acada82..9a9b54d56 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -64,6 +64,7 @@
  • From 76aadad6feeb0e6419452f32e64d95b86a70d42d Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 10 Dec 2018 19:54:50 +0800 Subject: [PATCH 20/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E9=80=89=E4=B8=ADbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/templates/_nav.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 9a9b54d56..caa3296b7 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -64,7 +64,7 @@
  • From 31356e825f8814be76b3aeed3f3dcf6c96ec9aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 11 Dec 2018 11:09:46 +0800 Subject: [PATCH 21/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E5=91=BD=E4=BB=A4=E5=90=8D=E7=A7=B0=20(#2140)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 60006 -> 60012 bytes apps/locale/zh/LC_MESSAGES/django.po | 2 +- apps/ops/ansible/callback.py | 2 +- apps/ops/ansible/runner.py | 4 +--- apps/ops/models/command.py | 3 +++ .../templates/ops/command_execution_list.html | 15 ++++++++++++++- 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 250375107f5741ce064735d7c885afe6591c0366..f64c55ce422599610a7526afb8c6a8973c91aa8b 100644 GIT binary patch delta 2251 zcmXZde`u9e9LMof*VNl$TNmecn>pu?Wtiq#iY>Dh1yN{Gt4U>HWC%jVilX)7p+fSH zD@nBc6~zP_mV`MPlEjKyB;B^Dn{Bx{H{F`G8i_=#F?xUYeExc!@AsVNd(L^zdF~zV zOpJCW4&AjRx+;WF*cO6ZXv^&yOp(9O@e_C@@fQ1%ebZ(zkN%yQ#76stJ!)HVI{oc! zAqv;hI7LDWxMVMDkKVGjweuslO6Gc`M8<*ddKhC&rm!59yPuVwV^IMXvge?O-9G#fwS!bRH5alLY2`;`G+ykEC(ha-)PPA`iS<1pEX6Dq;5aV9{9i+O0as%&9>OBb zq82`cdZYj1Y@B(Tdx9l+H*P-7``2y%oP=KC5!4D>@Hxz4E3WS4obmELz6{)pcd8$? zv%E7Q%)(80HP+b-s_sXqy8G>6+j55YuK`&S`n*n}K9e3)z7O@eop(Hj+SxytgUSB* zb1guPFL7Lk+WB%@g<8M^sOL90em?Sm*HL$1y9a!VbBVu3z4Bfx#*3&zxo2aG?PAo~ zuW-EHZnCvDLM zo<{xIy^NZtUU}vZ`)Fu_FWu3Kdhi4$u-EZf)T{m-Rp4(s>3q&m+@G>(I}cxB{B@}L z8c=mhhlw@OA{r`uBQC;~sPFr6T#wuEFC0L9za78vNn&~=gk|^$>T+&Hy|D(z?Wmpq zfm-mS&AAZ58sef0p)6F>s3D<+97H|PjJxod^G`MPRMbB+ZB=D?c~kA4{T0(kT6T;y LWSR=w?w|8Ni+-(& delta 2244 zcmXZde`u9e9LMov*Tr1J>29m(e&~$KoJ+wq^%ngRiV|U3+6*HhFeKLRRg8W-Xmr&U5d0 ze`2gZ(YCHSx;lh#eRl|jSZS;68Z0Ki-tl%UAl_x4vM<^+7SX>S=U}V-$eyyDsCCbF zhbUCgI7dPon6UrY{GNCNC8++Tj&F0k5sS!gLlxZRe1mPWX?p-m8TY>9j-Du9AmhXk z7BOJV@f50X;#@qj0CyAL;P?%D40Yl!QRBZwZMesd*qoiRiRj08;5=J_Dzpq$sMh)0 zF^_m7>J@Cl<@lic)2NdjaC`zazr%io3F2=Ye`lj!H-_v*)XuM92~Of{Ea;8%MW{Pb zZdafdzRT`F&2O}?VlnZXwiUIpPvSTVpVQEUZd9RR$CL4ZFsmz0o!9I^d&IWcFKidi<^F}inFjA1_3r;h?J)1B*h16>lXkJKu_@H}4URY29au$v zxBFkmrNsMj34Vs9tRF7WD90%!FgXw}xEyujwWx{r;5>W|7hxJJ@wofXpbAeoo_IkiktR(&v^~eXX498K0@-N1g*d?fUzryi- zcBgHyO{fi|QLid`hlUECM0Iqc-c>IupR-e_6D59$Pn@(BsFUA}_v5XoI(wXNwue#m zj<~-KsTYOQH1g@_L*0cD)Te}_|mU&p;Pv2)W&)+g;~@F7V_hudNrv2 z)pi5wiPq1=Q3#LI(7=~bJKSsEM&0^WoLK;?h_mjWbt%rzMHQ@cTx-{%>TX4C=wZ~K z-P5Rb_9@T$;V=y?@Uc5OQ4_z%1P(Ypk9yRXPzC<5)6VBzj{6I3(k{d%##f=%JA|rR zHb$(47ST}QTku9)iTb`D!0osPCvX_`{dQdm;SNmx&ilsAsLL6lp4cJBXHh4=jN0(D z&C7*wCvkBu+!VIb*g`@ZIgXmphAnv7`G?wvRy995dv&UIZTmCt9$VF3(S2|E|F0*d A(*OVf diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 3aa1ee7f2..acbb25f5f 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -2364,7 +2364,7 @@ msgstr "执行历史" #: ops/templates/ops/adhoc_detail.html:72 #: ops/templates/ops/adhoc_detail.html:77 ops/templates/ops/task_adhoc.html:61 msgid "Run as" -msgstr "用户" +msgstr "运行用户" #: ops/templates/ops/adhoc_detail.html:94 ops/templates/ops/task_list.html:36 msgid "Run times" diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 96db5456c..8583b368e 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -159,7 +159,7 @@ class CommandResultCallback(AdHocResultCallback): def v2_playbook_on_play_start(self, play): now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - msg = '$ {} ({})'.format('echo', now) + msg = '$ {} ({})'.format(play.name, now) self._play = play self._display.banner(msg) diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index d13f05fa2..fe23b0d6d 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -250,7 +250,5 @@ class CommandRunner(AdHocRunner): tasks = [ {"action": {"module": module, "args": cmd}} ] - hosts = self.inventory.get_hosts(pattern=pattern) - name = "Run command {} on {}'s hosts".format(cmd, len(hosts)) - return self.run(tasks, pattern, play_name=name) + return self.run(tasks, pattern, play_name=cmd) diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index b2639c6ff..d2d132094 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -48,6 +48,9 @@ class CommandExecution(models.Model): return False return True + def get_hosts_names(self): + return ','.join(self.hosts.all().values_list('hostname', flat=True)) + def run(self): print('-'*10 + ' ' + ugettext('Task start') + ' ' + '-'*10) self.date_start = timezone.now() diff --git a/apps/ops/templates/ops/command_execution_list.html b/apps/ops/templates/ops/command_execution_list.html index 542847b73..10f3c0fe3 100644 --- a/apps/ops/templates/ops/command_execution_list.html +++ b/apps/ops/templates/ops/command_execution_list.html @@ -44,6 +44,7 @@ {% trans 'Hosts' %} {% trans 'User' %} {% trans 'Command' %} + {% trans 'Run as' %} {% trans 'Output' %} {% trans 'Finished' %} {% trans 'Success' %} @@ -55,9 +56,10 @@ {% for object in object_list %} - {{ object.hosts.count }} + {{ object.get_hosts_names }} {{ object.user }} {{ object.command| truncatechars:16 }} + {{ object.run_as }} 查看 {{ object.is_finished | state_show | safe }} {{ object.is_success | state_show | safe }} @@ -89,6 +91,17 @@ $(document).ready(function() { calendarWeeks: true, autoclose: true }); + $(".hosts").each(function (i) { + var data = $(this).text(); + var data_list = data.split(","); + if (data_list.length === 1 && data_list[0] === "") { + data_list.pop(); + } + var html = createPopover(data_list); + $(this).html(html); + }); + $('[data-toggle="popover"]').popover(); + }) {% endblock %} From b54d389c7c0854d81386b7b651b4a5d9d29b3ce4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 11 Dec 2018 11:12:47 +0800 Subject: [PATCH 22/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 60012 -> 60010 bytes apps/locale/zh/LC_MESSAGES/django.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index f64c55ce422599610a7526afb8c6a8973c91aa8b..65dbbaee802a31e7fe8db5f3cbec742b30d05839 100644 GIT binary patch delta 1468 zcmXZcO-Phc6vpuzgPLV(mX2YLXc~efnh|E^A_xQ_qO>FzMMA6SlR$}@Byge7Mhb;O zBchN9K@lQsBP9(aMWd$X=a{KwNko*m=>PPd)$g8r?mhS8y))m!zNN6Qt293t=bW1e zI~R{jn1FvV8e@k%6H(**X0};qmZ8R1SbWqxg_-o9v;1w$CBB1M_;SbvT`G+kI=15~ zD)tY13l?A!@d4DtBbbQwn1M|gz)s7*K^6XKaq@_B`-sc13hPh@8N|CdJ`!~91dYod zy@f+&1T|m{_hQSabGtBv$@mBJFn-LrD_DYQ_zY7qgxYunb)&yA1*6BEE5~#^glB^^ zVre`@U12Y3hkbYfL--0yJ~`KlZo;_&Xv2dTLY-{Wq;p%b7B^#^*@UWl4^{V}*<}X% zY(NO381No_IAZ1+`GR#o4Hn=bL4y4ID5Y6DG}%KLNoEzxUn1q6$4XU!ykm0ZTA~+CU)U)hk5h_nQ@{8?C{h5|?Ob;RaOUX0sLb>L20% z1u&C1V)>{!uRjS@Fw5d%vm8~o8nvM_n2uLb>$J>q|4MYwp#{1v(T|$=4t+Rg@g(Z1 z7f=O$m}}M_``ODUntn5Y^^DI!-FQ2yZpJ*37TPiIP27b!#CuS`?~_=Kjrbj>P`}^7 zFV2;se}NZ{m8i#g3w2}d7QaOua1piPH8XaRKPBRnTpE=$s!$v0L`~>H{oV(x|8)0g MQR0K{ui=K|fB(T8a{vGU delta 1471 zcmXZbTS(MF6vy$wrHgH-iPkQbmr4^tlPoE*k|2mch_aN(ha#b;iVPy7mWsd!g8ORWEY3rnyvQs^ZQvkkevKE0+&LR? z1@#7+Y`|m8Bz}Rq@)69yc~qgKQO^uBANB3GTU>3{n&-_BY6FcJ;QhJ#mguoWAL?5T zTmPK7fI3m)n0Ml!xdCJ_8%yUYsIjn-gDi8>lu_y($QlX(~Q>L23& z1u%y=Zuw=CUVj>@;6{r}%>$^q$50zOh3oJlYMtgu?q7*^I<&wuOZ1^8zC|C7SUifl z>KRmlALgR<`{Q0d)eM^1c!}|wP&eL&s+%)Kq=j;)yop;dm$(G=e;>zcY{2h0hWdX8 zzVJ(8@T+r$cog+GZ=!Cj&Ehwx1Aaqoc+vDvJGYxSkWZtMMipu!9jFQ2*n<7mf3hoD PoYIz1n%y-Wxt9J9S#2IQ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index acbb25f5f..5de533806 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -2341,7 +2341,7 @@ msgstr "结果" #: ops/models/command.py:52 msgid "Task start" -msgstr "任务开始: " +msgstr "任务开始" #: ops/models/command.py:64 msgid "Command `{}` is forbidden ........" From 9d1f5d318450f4ce5180816bda55dd0710c54a44 Mon Sep 17 00:00:00 2001 From: vkill Date: Tue, 11 Dec 2018 11:21:07 +0800 Subject: [PATCH 23/80] Add alpine_requirements.txt (#2139) * Add alpine_requirements.txt * Update alpine_requirements.txt --- requirements/alpine_requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 requirements/alpine_requirements.txt diff --git a/requirements/alpine_requirements.txt b/requirements/alpine_requirements.txt new file mode 100644 index 000000000..687abbee4 --- /dev/null +++ b/requirements/alpine_requirements.txt @@ -0,0 +1 @@ +tiff-dev jpeg-dev zlib-dev freetype-dev lcms-dev libwebp-dev tcl-dev tk-dev python3-dev libressl-dev openldap-dev cyrus-sasl-dev krb5-dev sshpass postgresql-dev mariadb-dev sqlite-dev libffi-dev openssh-client From 18e590effd8c8e9703ce41af8b7a0671b653f5da Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 11 Dec 2018 12:04:19 +0800 Subject: [PATCH 24/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=8E=88=E6=9D=83=E8=A7=84=E5=88=99=EF=BC=8C=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E8=B5=84=E4=BA=A7=E5=AF=BC=E8=87=B4=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E6=A1=86=E6=B8=85=E7=A9=BA=E7=9A=84bug=20-?= =?UTF-8?q?=20select2=20(#2141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/templates/assets/label_create_update.html | 2 +- apps/perms/templates/perms/asset_permission_create_update.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/templates/assets/label_create_update.html b/apps/assets/templates/assets/label_create_update.html index 0ff094e69..47de64df4 100644 --- a/apps/assets/templates/assets/label_create_update.html +++ b/apps/assets/templates/assets/label_create_update.html @@ -35,7 +35,7 @@ $(document).ready(function () { }) .on('click', '#btn_asset_modal_confirm', function () { var assets = asset_table2.selected; - $('.select2').val(assets).trigger('change'); + $('#id_assets').val(assets).trigger('change'); $("#asset_list_modal").modal('hide'); }) diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html index 0b2c5b93e..0be810255 100644 --- a/apps/perms/templates/perms/asset_permission_create_update.html +++ b/apps/perms/templates/perms/asset_permission_create_update.html @@ -113,7 +113,7 @@ $(document).ready(function () { .on('click', '#btn_asset_modal_confirm', function () { var assets = asset_table2.selected; - $('.select2').val(assets).trigger('change'); + $('#id_assets').val(assets).trigger('change'); $("#asset_list_modal").modal('hide'); }); From 6a2398333197d9c90bca3f01f2c1426644ae9b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 11 Dec 2018 12:51:22 +0800 Subject: [PATCH 25/80] Command (#2142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 修改节点 * [Update] 优化命令运行失败日志 --- apps/assets/models/node.py | 19 ++++++++----------- apps/ops/ansible/callback.py | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 3d5c50997..af4ffd839 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -203,17 +203,14 @@ class Node(OrgModelMixin): # 如果使用current_org 在set_current_org时会死循环 _current_org = get_current_org() with transaction.atomic(): - if _current_org.is_root(): - key = '0' - elif _current_org.is_default(): - key = '1' - else: - set_current_org(Organization.root()) - org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$') - org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True) or ['1'] - key = max([int(k) for k in org_nodes_roots_keys]) - key = str(key + 1) if key != 0 else '2' - set_current_org(_current_org) + if not _current_org.is_real(): + return cls.default_node() + set_current_org(Organization.root()) + org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$') + org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True) or ['1'] + key = max([int(k) for k in org_nodes_roots_keys]) + key = str(key + 1) if key != 0 else '2' + set_current_org(_current_org) root = cls.objects.create(key=key, value=_current_org.name) return root diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 8583b368e..f3e60a458 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ import datetime +import json from collections import defaultdict from ansible import constants as C @@ -163,6 +164,28 @@ class CommandResultCallback(AdHocResultCallback): self._play = play self._display.banner(msg) + def v2_runner_on_unreachable(self, result): + self.results_summary['success'] = False + self.gather_result("unreachable", result) + msg = result._result.get("msg") + if not msg: + msg = json.dumps(result._result, indent=4) + self._display.display("%s | FAILED! => \n%s" % ( + result._host.get_name(), + msg, + ), color=C.COLOR_ERROR) + + def v2_runner_on_failed(self, result, ignore_errors=False): + self.results_summary['success'] = False + self.gather_result("failed", result) + msg = result._result.get("msg") or result._result.get("module_stdout") + if not msg: + msg = json.dumps(result._result, indent=4) + self._display.display("%s | FAILED! => \n%s" % ( + result._host.get_name(), + msg, + ), color=C.COLOR_ERROR) + def _print_task_banner(self, task): pass From 9c0f00f625a220777c6b18ffc0d21aa59ddd323a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 11 Dec 2018 20:32:55 +0800 Subject: [PATCH 26/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E6=89=A7=E8=A1=8C=20(#2148)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 修改命令执行 * Update forms.py --- .../ops/command_execution_create.html | 42 +++++++++++++------ .../perms/asset_permission_create_update.html | 12 ++++-- apps/users/forms.py | 4 ++ apps/users/models/user.py | 2 - apps/users/templates/users/_user.html | 6 ++- apps/users/views/user.py | 2 + 6 files changed, 49 insertions(+), 19 deletions(-) diff --git a/apps/ops/templates/ops/command_execution_create.html b/apps/ops/templates/ops/command_execution_create.html index 7d1e49c32..9a0916153 100644 --- a/apps/ops/templates/ops/command_execution_create.html +++ b/apps/ops/templates/ops/command_execution_create.html @@ -11,6 +11,10 @@ + + + + +{% endblock %} + {% block content_left_head %} {% endblock %} @@ -39,46 +51,49 @@ {% endblock %} -{% block table_head %} - - {% trans 'Hosts' %} - {% trans 'User' %} - {% trans 'Command' %} - {% trans 'Run as' %} - {% trans 'Output' %} - {% trans 'Finished' %} - {% trans 'Success' %} - {% trans 'Date start' %} - {% trans 'Date finished' %} -{% endblock %} - -{% block table_body %} +{% block table_container %} + + + + + + + + + + + + + {% for object in object_list %} - - - + + + - + - {% endfor %} + +
    {% trans 'Hosts' %}{% trans 'User' %}{% trans 'Command' %}{% trans 'Run as' %}{% trans 'Output' %}{% trans 'Finished' %}{% trans 'Success' %}{% trans 'Date start' %}
    {{ object.get_hosts_names }}{{ object.user }}{{ forloop.counter }}{{ object.user.name }} {{ object.command| truncatechars:16 }}{{ object.run_as }}{{ object.run_as.username }} 查看 {{ object.is_finished | state_show | safe }} {{ object.is_success | state_show | safe }} {{ object.date_start }}{{ object.date_finished }}
    {% endblock %} {% block custom_foot_js %} + {% endblock %} From a99d5609fad2d38b97c43babd9179e2f3b646d47 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 12 Dec 2018 18:00:21 +0800 Subject: [PATCH 33/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=B5=8B=E8=AF=95=E7=A1=AC=E4=BB=B6=E7=AD=89=E6=8A=A5?= =?UTF-8?q?=E9=94=99=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models/adhoc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 1f322f944..82c6fa37e 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -229,7 +229,7 @@ class AdHoc(models.Model): history.result = raw history.summary = summary return raw, summary - except Exception as e: + except IndexError as e: return {}, {"dark": {"all": str(e)}, "contacted": []} finally: # f.close() From 55ff82545a4bb635901cdf2cc6c9e3c087d1098e Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 12 Dec 2018 18:03:28 +0800 Subject: [PATCH 34/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=89=A7=E8=A1=8Cargs=E4=B8=BA=E7=A9=BA=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/runner.py | 2 ++ apps/ops/models/adhoc.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index fe23b0d6d..7931b72ee 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -165,6 +165,8 @@ class AdHocRunner: ) def clean_args(self, module, args): + if not args: + return '' if module not in self.command_modules_choices: return args if isinstance(args, str): diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 82c6fa37e..1f322f944 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -229,7 +229,7 @@ class AdHoc(models.Model): history.result = raw history.summary = summary return raw, summary - except IndexError as e: + except Exception as e: return {}, {"dark": {"all": str(e)}, "contacted": []} finally: # f.close() From f40f6bc61e34a4eb7248d8e1fd92f8886faef9d4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 13 Dec 2018 10:06:51 +0800 Subject: [PATCH 35/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8Daccekt?= =?UTF-8?q?=E7=9A=84=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/v1/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/api/v1/terminal.py b/apps/terminal/api/v1/terminal.py index 744aa933c..7ab15d381 100644 --- a/apps/terminal/api/v1/terminal.py +++ b/apps/terminal/api/v1/terminal.py @@ -83,7 +83,7 @@ class TerminalTokenApi(APIView): if not terminal.is_accepted: return Response("Terminal was not accepted yet", status=400) - if not terminal.user or not terminal.user.access_key.all(): + if not terminal.user or not terminal.user.access_key: return Response("No access key generate", status=401) access_key = terminal.user.access_key.first() From 4e0b25ae0f4d51dbb1e671a12810a63554cf13dc Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 13 Dec 2018 10:15:31 +0800 Subject: [PATCH 36/80] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=94=B9readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a220c96f..cf2b97ec5 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点 我们提供了DEMO和截图可以让你快速了解Jumpserver -[DEMO](http://demo.jumpserver.org) +[DEMO](https://demo.jumpserver.org) [截图](http://docs.jumpserver.org/zh/docs/snapshot.html) ### SDK From c7ac93fcc1eb9b94ed1cdbd78104ba5439665337 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 17 Dec 2018 10:21:16 +0800 Subject: [PATCH 37/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jms | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jms b/jms index 1bb6bb56c..613de2fe3 100755 --- a/jms +++ b/jms @@ -26,8 +26,8 @@ LOG_DIR = os.path.join(BASE_DIR, 'logs') TMP_DIR = os.path.join(BASE_DIR, 'tmp') HTTP_HOST = CONFIG.HTTP_BIND_HOST or '127.0.0.1' HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080 -DEBUG = CONFIG.DEBUG -LOG_LEVEL = CONFIG.LOG_LEVEL +DEBUG = CONFIG.DEBUG or False +LOG_LEVEL = CONFIG.LOG_LEVEL or 'INFO' START_TIMEOUT = 40 WORKERS = 4 From 374039d287fbbc297cd11de6dd4511679e76a9e0 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Mon, 17 Dec 2018 11:18:55 +0800 Subject: [PATCH 38/80] =?UTF-8?q?[Update]=20=E7=BB=88=E7=AB=AF=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E6=B7=BB=E5=8A=A0coco=E7=AB=AF=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E5=88=97=E8=A1=A8=E9=A1=B5=E9=9D=A2=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=A1=B9=20(#2182)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 终端设置,添加coco端资产列表页面大小配置项 * [Update] 添加页面大小选项 --- apps/common/forms.py | 11 + apps/jumpserver/conf.py | 7 +- apps/jumpserver/settings.py | 6 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 60010 -> 60078 bytes apps/locale/zh/LC_MESSAGES/django.po | 349 ++++++++++++++++----------- 5 files changed, 227 insertions(+), 146 deletions(-) diff --git a/apps/common/forms.py b/apps/common/forms.py index ff599062f..d052819b6 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -134,6 +134,14 @@ class TerminalSettingForm(BaseForm): ('hostname', _('Hostname')), ('ip', _('IP')), ) + PAGE_SIZE_CHOICES = ( + ('all', _('All')), + ('auto', _('Auto')), + (10, 10), + (15, 15), + (25, 25), + (50, 50), + ) TERMINAL_PASSWORD_AUTH = forms.BooleanField( initial=True, required=False, label=_("Password auth") ) @@ -146,6 +154,9 @@ class TerminalSettingForm(BaseForm): TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by") ) + TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField( + choices=PAGE_SIZE_CHOICES, initial='auto', label=_("List page size"), + ) class TerminalCommandStorage(BaseForm): diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 08716f93b..b1d33c3cd 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -312,7 +312,12 @@ defaults = { 'SESSION_COOKIE_AGE': 3600 * 24, 'SESSION_EXPIRE_AT_BROWSER_CLOSE': False, 'AUTH_OPENID': False, - 'EMAIL_SUFFIX': 'jumpserver.org' + 'EMAIL_SUFFIX': 'jumpserver.org', + 'TERMINAL_PASSWORD_AUTH': True, + 'TERMINAL_PUBLIC_KEY_AUTH': True, + 'TERMINAL_HEARTBEAT_INTERVAL': 5, + 'TERMINAL_ASSET_LIST_SORT_BY': 'hostname', + 'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto', } diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index f1d1f8e8f..fb595a4df 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -484,6 +484,12 @@ SECURITY_PASSWORD_RULES = [ 'SECURITY_PASSWORD_SPECIAL_CHAR' ] +TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH +TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH +TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL +TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY +TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE + # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html BOOTSTRAP3 = { 'horizontal_label_class': 'col-md-2', diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 65dbbaee802a31e7fe8db5f3cbec742b30d05839..7c398c4ac4bccaaac775f7c0456b087d6bea835a 100644 GIT binary patch delta 17946 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 delta 17896 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 G!v6uJp0L;e diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 5de533806..f218e1995 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-12-07 18:11+0800\n" +"POT-Creation-Date: 2018-12-17 10:14+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -34,8 +34,8 @@ msgid "Test if the assets under the node are connectable: {}" msgstr "测试节点下资产是否可连接: {}" #: assets/forms/asset.py:27 assets/models/asset.py:83 assets/models/user.py:113 -#: assets/templates/assets/asset_detail.html:187 -#: assets/templates/assets/asset_detail.html:195 +#: assets/templates/assets/asset_detail.html:191 +#: assets/templates/assets/asset_detail.html:199 #: assets/templates/assets/system_user_asset.html:95 perms/models.py:32 msgid "Nodes" msgstr "节点管理" @@ -62,7 +62,8 @@ msgid "Label" msgstr "标签" #: assets/forms/asset.py:37 assets/forms/asset.py:76 assets/models/asset.py:79 -#: assets/models/domain.py:24 assets/models/domain.py:50 +#: assets/models/domain.py:26 assets/models/domain.py:52 +#: assets/templates/assets/asset_detail.html:81 #: assets/templates/assets/user_asset_list.html:157 #: xpack/plugins/orgs/templates/orgs/org_list.html:17 msgid "Domain" @@ -105,7 +106,7 @@ msgid "Select assets" msgstr "选择资产" #: assets/forms/asset.py:108 assets/models/asset.py:76 -#: assets/models/domain.py:48 assets/templates/assets/admin_user_assets.html:53 +#: assets/models/domain.py:50 assets/templates/assets/admin_user_assets.html:53 #: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/system_user_asset.html:52 @@ -137,13 +138,13 @@ msgstr "端口" msgid "Asset" msgstr "资产" -#: assets/forms/domain.py:42 +#: assets/forms/domain.py:46 msgid "Password should not contain special characters" msgstr "不能包含特殊字符" -#: assets/forms/domain.py:59 assets/forms/user.py:80 assets/forms/user.py:143 +#: assets/forms/domain.py:63 assets/forms/user.py:80 assets/forms/user.py:143 #: assets/models/base.py:22 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:20 assets/models/domain.py:18 +#: assets/models/cmd_filter.py:20 assets/models/domain.py:20 #: assets/models/group.py:20 assets/models/label.py:18 #: assets/templates/assets/admin_user_detail.html:56 #: assets/templates/assets/admin_user_list.html:26 @@ -183,7 +184,7 @@ msgstr "不能包含特殊字符" msgid "Name" msgstr "名称" -#: assets/forms/domain.py:60 assets/forms/user.py:81 assets/forms/user.py:144 +#: assets/forms/domain.py:64 assets/forms/user.py:81 assets/forms/user.py:144 #: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60 #: assets/templates/assets/admin_user_list.html:27 #: assets/templates/assets/domain_gateway_list.html:60 @@ -256,7 +257,7 @@ msgid "" "password." msgstr "如果选择手动登录模式,用户名和密码可以不填写" -#: assets/models/asset.py:73 assets/models/domain.py:47 +#: assets/models/asset.py:73 assets/models/domain.py:49 #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/asset_detail.html:61 @@ -285,7 +286,7 @@ msgstr "IP" msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:75 assets/models/domain.py:49 +#: assets/models/asset.py:75 assets/models/domain.py:51 #: assets/models/user.py:117 assets/templates/assets/asset_detail.html:73 #: assets/templates/assets/domain_gateway_list.html:59 #: assets/templates/assets/system_user_detail.html:70 @@ -295,14 +296,14 @@ msgstr "主机名" msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:77 assets/templates/assets/asset_detail.html:101 +#: assets/models/asset.py:77 assets/templates/assets/asset_detail.html:105 #: assets/templates/assets/user_asset_list.html:154 msgid "Platform" msgstr "系统平台" #: assets/models/asset.py:84 assets/models/cmd_filter.py:21 -#: assets/models/domain.py:52 assets/models/label.py:21 -#: assets/templates/assets/asset_detail.html:109 +#: assets/models/domain.py:54 assets/models/label.py:22 +#: assets/templates/assets/asset_detail.html:113 #: assets/templates/assets/user_asset_list.html:158 msgid "Is active" msgstr "激活" @@ -311,19 +312,19 @@ msgstr "激活" msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:117 +#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:121 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:96 assets/templates/assets/asset_detail.html:81 +#: assets/models/asset.py:96 assets/templates/assets/asset_detail.html:85 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:98 assets/templates/assets/asset_detail.html:85 +#: assets/models/asset.py:98 assets/templates/assets/asset_detail.html:89 msgid "Model" msgstr "型号" -#: assets/models/asset.py:100 assets/templates/assets/asset_detail.html:113 +#: assets/models/asset.py:100 assets/templates/assets/asset_detail.html:117 msgid "Serial number" msgstr "序列号" @@ -343,7 +344,7 @@ msgstr "CPU核数" msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:108 assets/templates/assets/asset_detail.html:93 +#: assets/models/asset.py:108 assets/templates/assets/asset_detail.html:97 msgid "Memory" msgstr "内存" @@ -355,7 +356,7 @@ msgstr "硬盘大小" msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:115 assets/templates/assets/asset_detail.html:105 +#: assets/models/asset.py:115 assets/templates/assets/asset_detail.html:109 #: assets/templates/assets/user_asset_list.html:155 msgid "OS" msgstr "操作系统" @@ -373,7 +374,7 @@ msgid "Hostname raw" msgstr "主机名原始" #: assets/models/asset.py:125 assets/templates/assets/asset_create.html:34 -#: assets/templates/assets/asset_detail.html:224 +#: assets/templates/assets/asset_detail.html:228 #: assets/templates/assets/asset_update.html:39 templates/_nav.html:26 msgid "Labels" msgstr "标签管理" @@ -382,7 +383,7 @@ msgstr "标签管理" #: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 #: assets/models/cmd_filter.py:55 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:68 -#: assets/templates/assets/asset_detail.html:121 +#: assets/templates/assets/asset_detail.html:125 #: assets/templates/assets/cmd_filter_detail.html:77 #: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/system_user_detail.html:100 @@ -394,8 +395,8 @@ msgid "Created by" msgstr "创建者" #: assets/models/asset.py:130 assets/models/cluster.py:26 -#: assets/models/domain.py:21 assets/models/group.py:22 -#: assets/models/label.py:24 assets/templates/assets/admin_user_detail.html:64 +#: assets/models/domain.py:23 assets/models/group.py:22 +#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64 #: assets/templates/assets/cmd_filter_detail.html:69 #: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/system_user_detail.html:96 @@ -413,11 +414,11 @@ msgstr "创建日期" #: assets/models/asset.py:132 assets/models/base.py:27 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 -#: assets/models/cmd_filter.py:52 assets/models/domain.py:19 -#: assets/models/domain.py:51 assets/models/group.py:23 -#: assets/models/label.py:22 assets/templates/assets/admin_user_detail.html:72 +#: assets/models/cmd_filter.py:52 assets/models/domain.py:21 +#: assets/models/domain.py:53 assets/models/group.py:23 +#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72 #: assets/templates/assets/admin_user_list.html:32 -#: assets/templates/assets/asset_detail.html:129 +#: assets/templates/assets/asset_detail.html:133 #: assets/templates/assets/cmd_filter_detail.html:65 #: assets/templates/assets/cmd_filter_list.html:27 #: assets/templates/assets/cmd_filter_rule_list.html:62 @@ -487,7 +488,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:422 +#: users/models/user.py:420 msgid "System" msgstr "系统" @@ -516,7 +517,7 @@ msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:36 ops/models/command.py:19 -#: ops/templates/ops/command_execution_list.html:46 terminal/models.py:144 +#: ops/templates/ops/command_execution_list.html:60 terminal/models.py:144 #: terminal/templates/terminal/command_list.html:55 #: terminal/templates/terminal/command_list.html:71 #: terminal/templates/terminal/session_detail.html:48 @@ -591,7 +592,7 @@ msgstr "每行一个命令" msgid "Action" msgstr "动作" -#: assets/models/domain.py:59 assets/templates/assets/domain_detail.html:21 +#: assets/models/domain.py:61 assets/templates/assets/domain_detail.html:21 #: assets/templates/assets/domain_detail.html:64 #: assets/templates/assets/domain_gateway_list.html:21 #: assets/templates/assets/domain_list.html:27 @@ -613,8 +614,8 @@ msgstr "默认资产组" #: audits/templates/audits/operate_log_list.html:66 #: audits/templates/audits/password_change_log_list.html:33 #: audits/templates/audits/password_change_log_list.html:50 -#: ops/templates/ops/command_execution_list.html:22 -#: ops/templates/ops/command_execution_list.html:45 perms/forms.py:28 +#: ops/templates/ops/command_execution_list.html:34 +#: ops/templates/ops/command_execution_list.html:59 perms/forms.py:28 #: perms/models.py:29 #: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/asset_permission_list.html:54 @@ -623,10 +624,10 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:310 -#: users/models/user.py:33 users/models/user.py:410 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:314 +#: users/models/user.py:33 users/models/user.py:408 #: users/templates/users/user_group_detail.html:78 -#: users/templates/users/user_group_list.html:13 users/views/user.py:384 +#: users/templates/users/user_group_list.html:13 users/views/user.py:386 #: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 @@ -638,7 +639,7 @@ msgstr "用户" msgid "Value" msgstr "值" -#: assets/models/label.py:20 +#: assets/models/label.py:21 msgid "Category" msgstr "分类" @@ -899,9 +900,9 @@ msgstr "其它" #: common/templates/common/replay_storage_create.html:139 #: common/templates/common/security_setting.html:70 #: common/templates/common/terminal_setting.html:68 -#: perms/templates/perms/asset_permission_create_update.html:69 +#: perms/templates/perms/asset_permission_create_update.html:75 #: terminal/templates/terminal/terminal_update.html:47 -#: users/templates/users/_user.html:46 +#: users/templates/users/_user.html:50 #: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_detail.html:176 #: users/templates/users/user_password_update.html:71 @@ -932,11 +933,11 @@ msgstr "重置" #: common/templates/common/replay_storage_create.html:140 #: common/templates/common/security_setting.html:71 #: common/templates/common/terminal_setting.html:70 -#: perms/templates/perms/asset_permission_create_update.html:70 +#: perms/templates/perms/asset_permission_create_update.html:76 #: terminal/templates/terminal/command_list.html:103 #: terminal/templates/terminal/session_list.html:127 #: terminal/templates/terminal/terminal_update.html:48 -#: users/templates/users/_user.html:47 +#: users/templates/users/_user.html:51 #: users/templates/users/forgot_password.html:45 #: users/templates/users/user_bulk_update.html:24 #: users/templates/users/user_list.html:45 @@ -999,12 +1000,12 @@ msgid "Quick update" msgstr "快速更新" #: assets/templates/assets/admin_user_assets.html:72 -#: assets/templates/assets/asset_detail.html:172 +#: assets/templates/assets/asset_detail.html:176 msgid "Test connective" msgstr "测试可连接性" #: assets/templates/assets/admin_user_assets.html:75 -#: assets/templates/assets/asset_detail.html:175 +#: assets/templates/assets/asset_detail.html:179 #: assets/templates/assets/system_user_asset.html:75 #: assets/templates/assets/system_user_asset.html:161 #: assets/templates/assets/system_user_detail.html:151 @@ -1087,7 +1088,7 @@ msgid "Select nodes" msgstr "选择节点" #: assets/templates/assets/admin_user_detail.html:100 -#: assets/templates/assets/asset_detail.html:204 +#: assets/templates/assets/asset_detail.html:208 #: assets/templates/assets/asset_list.html:633 #: assets/templates/assets/cmd_filter_detail.html:106 #: assets/templates/assets/system_user_asset.html:112 @@ -1153,28 +1154,28 @@ msgstr "选择需要修改属性" msgid "Select all" msgstr "全选" -#: assets/templates/assets/asset_detail.html:89 +#: assets/templates/assets/asset_detail.html:93 msgid "CPU" msgstr "CPU" -#: assets/templates/assets/asset_detail.html:97 +#: assets/templates/assets/asset_detail.html:101 msgid "Disk" msgstr "硬盘" -#: assets/templates/assets/asset_detail.html:125 +#: assets/templates/assets/asset_detail.html:129 #: users/templates/users/user_detail.html:115 #: users/templates/users/user_profile.html:104 msgid "Date joined" msgstr "创建日期" -#: assets/templates/assets/asset_detail.html:141 +#: assets/templates/assets/asset_detail.html:145 #: terminal/templates/terminal/session_detail.html:81 #: users/templates/users/user_detail.html:138 #: users/templates/users/user_profile.html:146 msgid "Quick modify" msgstr "快速修改" -#: assets/templates/assets/asset_detail.html:147 +#: assets/templates/assets/asset_detail.html:151 #: assets/templates/assets/asset_list.html:95 #: assets/templates/assets/user_asset_list.html:47 perms/models.py:34 #: perms/models.py:82 @@ -1191,15 +1192,15 @@ msgstr "快速修改" msgid "Active" msgstr "激活中" -#: assets/templates/assets/asset_detail.html:164 +#: assets/templates/assets/asset_detail.html:168 msgid "Refresh hardware" msgstr "更新硬件信息" -#: assets/templates/assets/asset_detail.html:167 +#: assets/templates/assets/asset_detail.html:171 msgid "Refresh" msgstr "刷新" -#: assets/templates/assets/asset_detail.html:304 +#: assets/templates/assets/asset_detail.html:308 #: users/templates/users/user_detail.html:305 #: users/templates/users/user_detail.html:332 msgid "Update successfully!" @@ -1680,7 +1681,7 @@ msgid "Filename" msgstr "文件名" #: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76 -#: ops/templates/ops/command_execution_list.html:49 +#: ops/templates/ops/command_execution_list.html:64 #: ops/templates/ops/task_list.html:39 users/models/authentication.py:73 #: users/templates/users/user_detail.html:456 xpack/plugins/cloud/api.py:61 msgid "Success" @@ -1706,7 +1707,7 @@ msgstr "修改者" #: audits/templates/audits/ftp_log_list.html:77 #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:61 -#: ops/templates/ops/command_execution_list.html:50 +#: ops/templates/ops/command_execution_list.html:65 #: ops/templates/ops/task_history.html:58 perms/models.py:35 #: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:148 #: terminal/templates/terminal/session_list.html:78 @@ -1722,8 +1723,8 @@ msgstr "选择用户" #: audits/templates/audits/login_log_list.html:40 #: audits/templates/audits/operate_log_list.html:58 #: audits/templates/audits/password_change_log_list.html:42 -#: ops/templates/ops/command_execution_list.html:30 -#: ops/templates/ops/command_execution_list.html:35 +#: ops/templates/ops/command_execution_list.html:42 +#: ops/templates/ops/command_execution_list.html:47 #: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26 #: templates/_base_list.html:43 templates/_header_bar.html:8 #: terminal/templates/terminal/command_list.html:60 @@ -1751,7 +1752,7 @@ msgstr "Agent" msgid "City" msgstr "城市" -#: audits/templates/audits/login_log_list.html:54 users/forms.py:168 +#: audits/templates/audits/login_log_list.html:54 users/forms.py:172 #: users/models/authentication.py:82 users/models/user.py:75 #: users/templates/users/first_login.html:45 msgid "MFA" @@ -1785,23 +1786,23 @@ msgid "Datetime" msgstr "日期" #: audits/views.py:68 audits/views.py:112 audits/views.py:148 -#: audits/views.py:192 audits/views.py:223 templates/_nav.html:71 +#: audits/views.py:192 audits/views.py:223 templates/_nav.html:72 msgid "Audits" msgstr "日志审计" -#: audits/views.py:69 templates/_nav.html:75 +#: audits/views.py:69 templates/_nav.html:76 msgid "FTP log" msgstr "FTP日志" -#: audits/views.py:113 templates/_nav.html:76 +#: audits/views.py:113 templates/_nav.html:77 msgid "Operate log" msgstr "操作日志" -#: audits/views.py:149 templates/_nav.html:77 +#: audits/views.py:149 templates/_nav.html:78 msgid "Password change log" msgstr "改密日志" -#: audits/views.py:193 templates/_nav.html:74 +#: audits/views.py:193 templates/_nav.html:75 msgid "Login log" msgstr "登录日志" @@ -1958,64 +1959,76 @@ msgid "Enable LDAP auth" msgstr "启用LDAP认证" #: common/forms.py:138 +msgid "All" +msgstr "全部" + +#: common/forms.py:139 +msgid "Auto" +msgstr "自动" + +#: common/forms.py:144 msgid "Password auth" msgstr "密码认证" -#: common/forms.py:141 +#: common/forms.py:147 msgid "Public key auth" msgstr "密钥认证" -#: common/forms.py:144 +#: common/forms.py:150 msgid "Heartbeat interval" msgstr "心跳间隔" -#: common/forms.py:144 ops/models/adhoc.py:38 +#: common/forms.py:150 ops/models/adhoc.py:38 msgid "Units: seconds" msgstr "单位: 秒" -#: common/forms.py:147 +#: common/forms.py:153 msgid "List sort by" msgstr "资产列表排序" -#: common/forms.py:159 +#: common/forms.py:156 +msgid "List page size" +msgstr "资产列表页面大小" + +#: common/forms.py:168 msgid "MFA Secondary certification" msgstr "MFA 二次认证" -#: common/forms.py:161 +#: common/forms.py:170 msgid "" "After opening, the user login must use MFA secondary authentication (valid " "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: common/forms.py:168 +#: common/forms.py:177 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: common/forms.py:173 +#: common/forms.py:182 msgid "No logon interval" msgstr "禁止登录时间间隔" -#: common/forms.py:175 +#: common/forms.py:184 msgid "" "Tip: (unit/minute) if the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" -#: common/forms.py:182 +#: common/forms.py:191 msgid "Connection max idle time" msgstr "SSH最大空闲时间" -#: common/forms.py:184 +#: common/forms.py:193 msgid "" "If idle time more than it, disconnect connection(only ssh now) Unit: minute" msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" -#: common/forms.py:190 +#: common/forms.py:199 msgid "Password expiration time" msgstr "密码过期时间" -#: common/forms.py:193 +#: common/forms.py:202 msgid "" "Tip: (unit: day) If the user does not update the password during the time, " "the user password will expire failure;The password expiration reminder mail " @@ -2025,45 +2038,45 @@ msgstr "" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" -#: common/forms.py:202 +#: common/forms.py:211 msgid "Password minimum length" msgstr "密码最小长度 " -#: common/forms.py:208 +#: common/forms.py:217 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: common/forms.py:210 +#: common/forms.py:219 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: common/forms.py:216 +#: common/forms.py:225 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: common/forms.py:217 +#: common/forms.py:226 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: common/forms.py:223 +#: common/forms.py:232 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: common/forms.py:224 +#: common/forms.py:233 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: common/forms.py:230 +#: common/forms.py:239 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: common/forms.py:231 +#: common/forms.py:240 msgid "" "After opening, the user password changes and resets must contain special " "characters" @@ -2126,7 +2139,7 @@ msgstr "安全设置" #: common/templates/common/command_storage_create.html:50 #: ops/models/adhoc.py:161 ops/templates/ops/adhoc_detail.html:53 -#: ops/templates/ops/command_execution_list.html:44 +#: ops/templates/ops/command_execution_list.html:58 #: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38 msgid "Hosts" msgstr "主机" @@ -2227,7 +2240,7 @@ msgstr "不能包含特殊字符" #: common/views.py:18 common/views.py:44 common/views.py:70 common/views.py:99 #: common/views.py:126 common/views.py:138 common/views.py:151 -#: templates/_nav.html:106 +#: templates/_nav.html:107 msgid "Settings" msgstr "系统设置" @@ -2339,15 +2352,15 @@ msgstr "汇总" msgid "Result" msgstr "结果" -#: ops/models/command.py:52 +#: ops/models/command.py:55 msgid "Task start" msgstr "任务开始" -#: ops/models/command.py:64 +#: ops/models/command.py:67 msgid "Command `{}` is forbidden ........" msgstr "命令 `{}` 不允许被执行 ......." -#: ops/models/command.py:70 +#: ops/models/command.py:73 msgid "Task end" msgstr "任务结束" @@ -2362,7 +2375,9 @@ msgid "Version run history" msgstr "执行历史" #: ops/templates/ops/adhoc_detail.html:72 -#: ops/templates/ops/adhoc_detail.html:77 ops/templates/ops/task_adhoc.html:61 +#: ops/templates/ops/adhoc_detail.html:77 +#: ops/templates/ops/command_execution_list.html:61 +#: ops/templates/ops/task_adhoc.html:61 msgid "Run as" msgstr "运行用户" @@ -2424,7 +2439,7 @@ msgid "Run history detail" msgstr "执行历史详情" #: ops/templates/ops/adhoc_history_detail.html:22 -#: ops/templates/ops/command_execution_list.html:47 +#: ops/templates/ops/command_execution_list.html:62 #: terminal/backends/command/models.py:16 msgid "Output" msgstr "输出" @@ -2450,24 +2465,20 @@ msgstr "没有资产" msgid "Success assets" msgstr "成功资产" -#: ops/templates/ops/command_execution_create.html:67 +#: ops/templates/ops/command_execution_create.html:71 #: terminal/templates/terminal/session_detail.html:91 #: terminal/templates/terminal/session_detail.html:100 msgid "Go" msgstr "" -#: ops/templates/ops/command_execution_create.html:244 +#: ops/templates/ops/command_execution_create.html:253 msgid "Pending" msgstr "" -#: ops/templates/ops/command_execution_list.html:48 +#: ops/templates/ops/command_execution_list.html:63 msgid "Finished" msgstr "结束" -#: ops/templates/ops/command_execution_list.html:51 -msgid "Date finished" -msgstr "结束日期" - #: ops/templates/ops/task_adhoc.html:19 ops/templates/ops/task_detail.html:20 #: ops/templates/ops/task_history.html:19 ops/views/adhoc.py:72 msgid "Task detail" @@ -2535,7 +2546,8 @@ msgstr "任务列表" msgid "Task run history" msgstr "执行历史" -#: ops/views/command.py:68 templates/_nav.html:78 templates/_nav_user.html:9 +#: ops/views/command.py:68 templates/_nav.html:67 templates/_nav.html:79 +#: templates/_nav_user.html:9 msgid "Command execution" msgstr "命令执行" @@ -2546,7 +2558,7 @@ msgstr "组织管理" #: perms/forms.py:31 perms/models.py:30 perms/models.py:80 #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:14 -#: users/forms.py:280 users/models/group.py:26 users/models/user.py:59 +#: users/forms.py:284 users/models/group.py:26 users/models/user.py:59 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:211 #: users/templates/users/user_list.html:26 @@ -2688,14 +2700,14 @@ msgstr "文档" msgid "Commercial support" msgstr "商业支持" -#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:147 -#: users/templates/users/_user.html:39 +#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:151 +#: users/templates/users/_user.html:43 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:57 -#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:366 +#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:368 msgid "Profile" msgstr "个人信息" @@ -2783,8 +2795,8 @@ msgstr "" #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 #: users/views/group.py:60 users/views/group.py:76 users/views/group.py:92 #: users/views/login.py:346 users/views/user.py:68 users/views/user.py:83 -#: users/views/user.py:111 users/views/user.py:192 users/views/user.py:353 -#: users/views/user.py:403 users/views/user.py:437 +#: users/views/user.py:113 users/views/user.py:194 users/views/user.py:355 +#: users/views/user.py:405 users/views/user.py:439 msgid "Users" msgstr "用户管理" @@ -2831,15 +2843,15 @@ msgstr "终端管理" msgid "Job Center" msgstr "作业中心" -#: templates/_nav.html:84 +#: templates/_nav.html:85 msgid "XPack" msgstr "" -#: templates/_nav.html:92 xpack/plugins/cloud/views.py:26 +#: templates/_nav.html:93 xpack/plugins/cloud/views.py:26 msgid "Account list" msgstr "账户列表" -#: templates/_nav.html:93 +#: templates/_nav.html:94 msgid "Sync instance" msgstr "同步实例" @@ -3309,11 +3321,11 @@ msgstr "MFA 验证码" msgid "Role" msgstr "角色" -#: users/forms.py:55 users/forms.py:226 +#: users/forms.py:55 users/forms.py:230 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:56 users/forms.py:227 +#: users/forms.py:56 users/forms.py:231 msgid "ssh-rsa AAAA..." msgstr "" @@ -3325,15 +3337,15 @@ msgstr "复制用户公钥到这里" msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:110 users/forms.py:241 +#: users/forms.py:110 users/forms.py:245 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:114 users/forms.py:245 users/serializers/v1.py:51 +#: users/forms.py:114 users/forms.py:249 users/serializers/v1.py:51 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:153 +#: users/forms.py:157 msgid "" "Tip: when enabled, you will enter the MFA binding process the next time you " "log in. you can also directly bind in \"personal information -> quick " @@ -3342,11 +3354,11 @@ msgstr "" "提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修" "改->更改MFA设置)中直接绑定!" -#: users/forms.py:163 +#: users/forms.py:167 msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全." -#: users/forms.py:173 +#: users/forms.py:177 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -3355,41 +3367,41 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,启用MFA认证)" -#: users/forms.py:180 users/templates/users/first_login.html:48 +#: users/forms.py:184 users/templates/users/first_login.html:48 #: users/templates/users/first_login.html:107 #: users/templates/users/first_login.html:130 msgid "Finish" msgstr "完成" -#: users/forms.py:186 +#: users/forms.py:190 msgid "Old password" msgstr "原来密码" -#: users/forms.py:191 +#: users/forms.py:195 msgid "New password" msgstr "新密码" -#: users/forms.py:196 +#: users/forms.py:200 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:206 +#: users/forms.py:210 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:214 +#: users/forms.py:218 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:224 +#: users/forms.py:228 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms.py:228 +#: users/forms.py:232 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:256 users/models/user.py:83 +#: users/forms.py:260 users/models/user.py:83 #: users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 #: users/templates/users/user_profile.html:68 @@ -3398,7 +3410,7 @@ msgstr "复制你的公钥到这里" msgid "Public key" msgstr "ssh公钥" -#: users/forms.py:263 users/forms.py:268 users/forms.py:314 +#: users/forms.py:267 users/forms.py:272 users/forms.py:318 #: xpack/plugins/orgs/forms.py:30 msgid "Select users" msgstr "选择用户" @@ -3456,7 +3468,7 @@ msgstr "Agent" msgid "Date login" msgstr "登录日期" -#: users/models/user.py:32 users/models/user.py:418 +#: users/models/user.py:32 users/models/user.py:416 msgid "Administrator" msgstr "管理员" @@ -3502,7 +3514,7 @@ msgstr "用户来源" msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:421 +#: users/models/user.py:419 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -3790,7 +3802,7 @@ msgid "Reset link will be generated and sent to the user. " msgstr "生成重置密码连接,通过邮件发送给用户" #: users/templates/users/user_detail.html:19 -#: users/templates/users/user_granted_asset.html:18 users/views/user.py:193 +#: users/templates/users/user_granted_asset.html:18 users/views/user.py:195 msgid "User detail" msgstr "用户详情" @@ -3985,8 +3997,8 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接 msgid "Administrator Settings force MFA login" msgstr "管理员设置强制使用MFA登录" -#: users/templates/users/user_profile.html:120 users/views/user.py:229 -#: users/views/user.py:283 +#: users/templates/users/user_profile.html:120 users/views/user.py:231 +#: users/views/user.py:285 msgid "User groups" msgstr "用户组" @@ -4036,7 +4048,7 @@ msgid "" "corresponding private key." msgstr "新的公钥已设置成功,请下载对应的私钥" -#: users/templates/users/user_update.html:4 users/views/user.py:112 +#: users/templates/users/user_update.html:4 users/views/user.py:114 msgid "Update user" msgstr "更新用户" @@ -4243,7 +4255,7 @@ msgstr "用户组授权资产" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:191 users/views/user.py:524 users/views/user.py:549 +#: users/views/login.py:191 users/views/user.py:526 users/views/user.py:551 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" @@ -4288,7 +4300,7 @@ msgstr "Token错误或失效" msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:308 users/views/user.py:126 users/views/user.py:420 +#: users/views/login.py:308 users/views/user.py:128 users/views/user.py:422 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" @@ -4296,51 +4308,51 @@ msgstr "* 您的密码不符合要求" msgid "First login" msgstr "首次登陆" -#: users/views/user.py:143 +#: users/views/user.py:145 msgid "Bulk update user success" msgstr "批量更新用户成功" -#: users/views/user.py:173 +#: users/views/user.py:175 msgid "Bulk update user" msgstr "批量更新用户" -#: users/views/user.py:258 +#: users/views/user.py:260 msgid "Invalid file." msgstr "文件不合法" -#: users/views/user.py:354 +#: users/views/user.py:356 msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:385 +#: users/views/user.py:387 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:404 +#: users/views/user.py:406 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:438 +#: users/views/user.py:440 msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:479 +#: users/views/user.py:481 msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/user.py:579 +#: users/views/user.py:581 msgid "MFA enable success" msgstr "MFA 绑定成功" -#: users/views/user.py:580 +#: users/views/user.py:582 msgid "MFA enable success, return login page" msgstr "MFA 绑定成功,返回到登录页面" -#: users/views/user.py:582 +#: users/views/user.py:584 msgid "MFA disable success" msgstr "MFA 解绑成功" -#: users/views/user.py:583 +#: users/views/user.py:585 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" @@ -4474,6 +4486,50 @@ msgstr "AWS (中国)" msgid "AWS (International)" msgstr "AWS (国际)" +#: xpack/plugins/cloud/providers/base.py:76 +msgid "任务执行开始: {}\n" +msgstr "" + +#: xpack/plugins/cloud/providers/base.py:79 +msgid "检测账户有效性: {}" +msgstr "" + +#: xpack/plugins/cloud/providers/base.py:82 +msgid "账户无效!\n" +msgstr "" + +#: xpack/plugins/cloud/providers/base.py:86 +msgid "账户有效!\n" +msgstr "" + +#: xpack/plugins/cloud/providers/base.py:90 +msgid "" +"\n" +"任务执行结束!\n" +msgstr "" + +#: xpack/plugins/cloud/providers/base.py:91 +msgid "" +"查看任务详细信息路径: XPack -> 云管中心 -> 任务列表 -> 任务详情(点击任务名" +"称) -> 查看同步历史列表/实例列表\n" +msgstr "" + +#: xpack/plugins/cloud/providers/base.py:126 +msgid "同步实例列表: {}" +msgstr "" + +#: xpack/plugins/cloud/providers/base.py:135 +msgid "同步地域列表: {}\n" +msgstr "" + +#: xpack/plugins/cloud/providers/base.py:138 +msgid "地域: {}" +msgstr "" + +#: xpack/plugins/cloud/providers/base.py:148 +msgid "实例: {}, 地域: {}" +msgstr "" + #: xpack/plugins/cloud/providers/qcloud.py:14 msgid "Qcloud" msgstr "腾讯云" @@ -4605,6 +4661,9 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "Date finished" +#~ msgstr "结束日期" + #, fuzzy #~| msgid "Audits" #~ msgid "Audit" From 985bd6fc8260349b79d07ca0c323a9986fe8be8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Mon, 17 Dec 2018 11:44:43 +0800 Subject: [PATCH 39/80] Bugfix2 (#2183) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Bugfix] 修复错误 * [Bugfix] 修复一些bug --- apps/assets/tasks.py | 21 +++++++++++---------- apps/common/models.py | 2 ++ apps/ops/serializers.py | 2 +- apps/ops/urls/api_urls.py | 1 + apps/ops/views/adhoc.py | 7 +++++-- jms | 4 ++-- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 4b4ec2867..846964d94 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -141,11 +141,11 @@ def update_assets_hardware_info_period(): logger.debug("Period task disabled, update assets hardware info pass") return - from ops.utils import update_or_create_ansible_task - from orgs.models import Organization - orgs = Organization.objects.all().values_list('id', flat=True) - orgs.append('') - task_name = _("Update assets hardware info period") + # from ops.utils import update_or_create_ansible_task + # from orgs.models import Organization + # orgs = Organization.objects.all().values_list('id', flat=True) + # orgs.append('') + # task_name = _("Update assets hardware info period") # for org_id in orgs: # org_id = str(org_id) # hostname_list = [ @@ -354,11 +354,12 @@ def test_system_user_connectability_period(): if PERIOD_TASK != "on": logger.debug("Period task disabled, test system user connectability pass") return - system_users = SystemUser.objects.all() - for system_user in system_users: - task_name = _("Test system user connectability period: {}").format(system_user) - # task_name = _("定期测试系统用户可连接性: {}".format(system_user)) - test_system_user_connectability_util(system_user, task_name) + # Todo: 暂时禁用定期测试 + # system_users = SystemUser.objects.all() + # for system_user in system_users: + # task_name = _("Test system user connectability period: {}").format(system_user) + # # task_name = _("定期测试系统用户可连接性: {}".format(system_user)) + # test_system_user_connectability_util(system_user, task_name) #### Push system user tasks #### diff --git a/apps/common/models.py b/apps/common/models.py index cc5ba8fc5..cb97b8988 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -45,6 +45,8 @@ class Setting(models.Model): def cleaned_value(self): try: value = self.value + if not isinstance(value, (str, bytes)): + return value if self.encrypted: value = signer.unsign(value) value = json.loads(value) diff --git a/apps/ops/serializers.py b/apps/ops/serializers.py index 13423486f..5eb16c5a8 100644 --- a/apps/ops/serializers.py +++ b/apps/ops/serializers.py @@ -53,7 +53,7 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer): @staticmethod def get_stat(obj): return { - "total": len(obj.adhoc.hosts), + "total": obj.adhoc.hosts.count(), "success": len(obj.summary.get("contacted", [])), "failed": len(obj.summary.get("dark", [])), } diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index 615f53a8b..5f955540d 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -11,6 +11,7 @@ app_name = "ops" router = DefaultRouter() router.register(r'tasks', api.TaskViewSet, 'task') router.register(r'adhoc', api.AdHocViewSet, 'adhoc') +router.register(r'history', api.AdHocRunHistoryViewSet, 'history') router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution') urlpatterns = [ diff --git a/apps/ops/views/adhoc.py b/apps/ops/views/adhoc.py index 737047290..f3efbcc70 100644 --- a/apps/ops/views/adhoc.py +++ b/apps/ops/views/adhoc.py @@ -27,7 +27,7 @@ class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): def get_queryset(self): queryset = super().get_queryset() - if current_org.is_real(): + if current_org: queryset = queryset.filter(created_by=current_org.id) else: queryset = queryset.filter(created_by='') @@ -62,8 +62,11 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView): def get_queryset(self): queryset = super().get_queryset() - if current_org: + # Todo: 需要整理默认组织等东西 + if current_org.is_real(): queryset = queryset.filter(created_by=current_org.id) + else: + queryset = queryset.filter(created_by='') return queryset def get_context_data(self, **kwargs): diff --git a/jms b/jms index 1bb6bb56c..613de2fe3 100755 --- a/jms +++ b/jms @@ -26,8 +26,8 @@ LOG_DIR = os.path.join(BASE_DIR, 'logs') TMP_DIR = os.path.join(BASE_DIR, 'tmp') HTTP_HOST = CONFIG.HTTP_BIND_HOST or '127.0.0.1' HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080 -DEBUG = CONFIG.DEBUG -LOG_LEVEL = CONFIG.LOG_LEVEL +DEBUG = CONFIG.DEBUG or False +LOG_LEVEL = CONFIG.LOG_LEVEL or 'INFO' START_TIMEOUT = 40 WORKERS = 4 From b95f8a7d6b55e74b29cf7a345f42c3d771f6bcd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Mon, 17 Dec 2018 11:49:57 +0800 Subject: [PATCH 40/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=8A=82=E7=82=B9=E6=95=B0=E9=87=8F=E6=AF=94=E8=BE=83?= =?UTF-8?q?=E6=85=A2=E7=9A=84=E9=97=AE=E9=A2=98=20(#2184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/node.py | 3 +- apps/assets/models/node.py | 79 ++++++++++++------- apps/assets/serializers/node.py | 18 ++--- apps/assets/signals_handler.py | 20 ++++- .../templates/assets/_asset_list_modal.html | 1 + apps/assets/templates/assets/asset_list.html | 7 ++ 6 files changed, 83 insertions(+), 45 deletions(-) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 831c85e8a..73313f222 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -20,11 +20,10 @@ from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 -from django.db.models import Count from common.utils import get_logger, get_object_or_none from ..hands import IsOrgAdmin -from ..models import Node +from ..models import Node, Asset from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util from .. import serializers diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index af4ffd839..47a835861 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -22,7 +22,9 @@ class Node(OrgModelMixin): date_create = models.DateTimeField(auto_now_add=True) is_node = True - _full_value_cache_key_prefix = '_NODE_VALUE_{}' + _assets_amount = None + _full_value_cache_key = '_NODE_VALUE_{}' + _assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}' class Meta: verbose_name = _("Node") @@ -49,30 +51,56 @@ class Node(OrgModelMixin): def name(self): return self.value + @property + def assets_amount(self): + """ + 获取节点下所有资产数量速度太慢,所以需要重写,使用cache等方案 + :return: + """ + if self._assets_amount is not None: + return self._assets_amount + cache_key = self._assets_amount_cache_key.format(self.key) + cached = cache.get(cache_key) + if cached is not None: + return cached + assets_amount = self.get_all_assets().count() + cache.set(cache_key, assets_amount, 3600) + return assets_amount + + @assets_amount.setter + def assets_amount(self, value): + self._assets_amount = value + + def expire_assets_amount(self): + ancestor_keys = self.get_ancestor_keys(with_self=True) + cache_keys = [self._assets_amount_cache_key.format(k) for k in ancestor_keys] + cache.delete_many(cache_keys) + + @classmethod + def expire_nodes_assets_amount(cls, nodes=None): + if nodes: + for node in nodes: + node.expire_assets_amount() + return + key = cls._assets_amount_cache_key.format('*') + cache.delete_pattern(key) + @property def full_value(self): - key = self._full_value_cache_key_prefix.format(self.key) + key = self._full_value_cache_key.format(self.key) cached = cache.get(key) if cached: return cached - value = self.get_full_value() - self.cache_full_value(value) - return value - - def get_full_value(self): - # ancestor = [a.value for a in self.get_ancestor(with_self=True)] if self.is_root(): return self.value parent_full_value = self.parent.full_value value = parent_full_value + ' / ' + self.value + key = self._full_value_cache_key.format(self.key) + cache.set(key, value, 3600) return value - def cache_full_value(self, value): - key = self._full_value_cache_key_prefix.format(self.key) - cache.set(key, value, 3600) - def expire_full_value(self): - key = self._full_value_cache_key_prefix.format(self.key) + key = self._full_value_cache_key.format(self.key) cache.delete_pattern(key+'*') @property @@ -182,17 +210,18 @@ class Node(OrgModelMixin): child.save() self.save() - def get_ancestor(self, with_self=False): - if self.is_root(): - root = self.__class__.root() - return [root] - _key = self.key.split(':') + def get_ancestor_keys(self, with_self=False): + parent_keys = [] + key_list = self.key.split(":") if not with_self: - _key.pop() - ancestor_keys = [] - for i in range(len(_key)): - ancestor_keys.append(':'.join(_key)) - _key.pop() + key_list.pop() + for i in range(len(key_list)): + parent_keys.append(":".join(key_list)) + key_list.pop() + return parent_keys + + def get_ancestor(self, with_self=False): + ancestor_keys = self.get_ancestor_keys(with_self=with_self) ancestor = self.__class__.objects.filter( key__in=ancestor_keys ).order_by('key') @@ -227,10 +256,6 @@ class Node(OrgModelMixin): defaults = {'value': 'Default'} return cls.objects.get_or_create(defaults=defaults, key='1') - @classmethod - def get_tree_name_ref(cls): - pass - @classmethod def generate_fake(cls, count=100): import random diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index f1be42d06..f44ff44d6 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -43,7 +43,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): class NodeSerializer(serializers.ModelSerializer): - assets_amount = serializers.SerializerMethodField() + assets_amount = serializers.IntegerField() tree_id = serializers.SerializerMethodField() tree_parent = serializers.SerializerMethodField() @@ -53,6 +53,10 @@ class NodeSerializer(serializers.ModelSerializer): 'id', 'key', 'value', 'assets_amount', 'is_node', 'org_id', 'tree_id', 'tree_parent', ] + read_only_fields = [ + 'id', 'key', 'assets_amount', 'is_node', + 'org_id', + ] list_serializer_class = BulkListSerializer def validate(self, data): @@ -66,12 +70,6 @@ class NodeSerializer(serializers.ModelSerializer): ) return data - @staticmethod - def get_assets_amount(obj): - if hasattr(obj, 'assets_amount'): - return obj.assets_amount - return obj.get_all_assets().count() - @staticmethod def get_tree_id(obj): return obj.key @@ -80,12 +78,6 @@ class NodeSerializer(serializers.ModelSerializer): def get_tree_parent(obj): return obj.parent_key - def get_fields(self): - fields = super().get_fields() - field = fields["key"] - field.required = False - return fields - class NodeAssetsSerializer(serializers.ModelSerializer): assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 9028f52c3..08ee6e670 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from collections import defaultdict -from django.db.models.signals import post_save, m2m_changed +from django.db.models.signals import post_save, m2m_changed, post_delete from django.dispatch import receiver from common.utils import get_logger @@ -35,6 +35,17 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs): update_asset_hardware_info_on_created(instance) test_asset_conn_on_created(instance) + # 过期节点资产数量 + nodes = instance.nodes.all() + Node.expire_nodes_assets_amount(nodes) + + +@receiver(post_delete, sender=Asset, dispatch_uid="my_unique_identifier") +def on_asset_delete(sender, instance=None, **kwargs): + # 过期节点资产数量 + nodes = instance.nodes.all() + Node.expire_nodes_assets_amount(nodes) + @receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier") def on_system_user_update(sender, instance=None, created=True, **kwargs): @@ -63,10 +74,11 @@ def on_system_user_assets_change(sender, instance=None, **kwargs): @receiver(m2m_changed, sender=Asset.nodes.through) def on_asset_node_changed(sender, instance=None, **kwargs): + logger.debug("Asset node change signal received") if isinstance(instance, Asset): if kwargs['action'] == 'post_add': - logger.debug("Asset node change signal received") nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) + Node.expire_nodes_assets_amount(nodes) system_users_assets = defaultdict(set) system_users = SystemUser.objects.filter(nodes__in=nodes) # 清理节点缓存 @@ -79,9 +91,11 @@ def on_asset_node_changed(sender, instance=None, **kwargs): @receiver(m2m_changed, sender=Asset.nodes.through) def on_node_assets_changed(sender, instance=None, **kwargs): if isinstance(instance, Node): + logger.debug("Node assets change signal received") + # 当节点和资产关系发生改变时,过期资产数量缓存 + instance.expire_assets_amount() assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) if kwargs['action'] == 'post_add': - logger.debug("Node assets change signal received") # 重新关联系统用户和资产的关系 system_users = SystemUser.objects.filter(nodes=instance) for system_user in system_users: diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html index ea8d59e49..fe50ac2b3 100644 --- a/apps/assets/templates/assets/_asset_list_modal.html +++ b/apps/assets/templates/assets/_asset_list_modal.html @@ -116,6 +116,7 @@ function initTree2() { $(document).ready(function(){ +}).on('show.bs.modal', function () { initTable2(); initTree2(); }) diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 7e093688f..bd3a776b3 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -305,6 +305,9 @@ function onSelected(event, treeNode) { } function selectQueryNode() { + // TODO: 是否应该添加 + // 暂时忽略之前选中的内容 + return var query_node_id = $.getUrlParam("node"); var cookie_node_id = getCookie('node_selected'); var node; @@ -355,6 +358,9 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) { } function initTree() { + if (zTree) { + return + } var setting = { view: { dblClickExpand: false, @@ -387,6 +393,7 @@ function initTree() { }; var zNodes = []; + console.log("Get assets") $.get("{% url 'api-assets:node-list' %}", function(data, status){ $.each(data, function (index, value) { value["node_id"] = value["id"]; From ab6c88823d39f240fcdb3d9c01f3284c3f75e7b9 Mon Sep 17 00:00:00 2001 From: vkill Date: Mon, 17 Dec 2018 14:26:00 +0800 Subject: [PATCH 41/80] Support for TOTP valid_window configuration (#2187) --- apps/jumpserver/settings.py | 1 + apps/users/utils.py | 3 ++- config_docker.py | 7 +++++++ config_example.py | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index fb595a4df..a58642877 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -356,6 +356,7 @@ FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755 # OTP settings OTP_ISSUER_NAME = CONFIG.OTP_ISSUER_NAME +OTP_VALID_WINDOW = CONFIG.OTP_VALID_WINDOW # Auth LDAP settings AUTH_LDAP = False diff --git a/apps/users/utils.py b/apps/users/utils.py index eac1c6f99..c998774b0 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -292,7 +292,8 @@ def check_otp_code(otp_secret_key, otp_code): if not otp_secret_key or not otp_code: return False totp = pyotp.TOTP(otp_secret_key) - return totp.verify(otp_code) + otp_valid_window = settings.OTP_VALID_WINDOW or 0 + return totp.verify(otp=otp_code, valid_window=otp_valid_window) def get_password_check_rules(): diff --git a/config_docker.py b/config_docker.py index ca322b4b8..643c11055 100644 --- a/config_docker.py +++ b/config_docker.py @@ -100,6 +100,9 @@ class Config: } AUTH_LDAP_START_TLS = False + # + # OTP_VALID_WINDOW = 0 + def __init__(self): pass @@ -200,6 +203,10 @@ class DockerConfig(Config): AUTH_LDAP_START_TLS = False + # + OTP_VALID_WINDOW = int(os.environ.get("OTP_VALID_WINDOW")) if os.environ.get("OTP_VALID_WINDOW") else 0 + + # Default using Config settings, you can write if/else for different env config = DockerConfig() diff --git a/config_example.py b/config_example.py index dfcc876a3..e37df23b0 100644 --- a/config_example.py +++ b/config_example.py @@ -90,6 +90,9 @@ class Config: # AUTH_OPENID_CLIENT_ID = 'client-id' # AUTH_OPENID_CLIENT_SECRET = 'client-secret' + # + # OTP_VALID_WINDOW = 0 + def __init__(self): pass From 517a27ea337a6b49d42cf9829901dd5419acb26d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Mon, 17 Dec 2018 18:20:44 +0800 Subject: [PATCH 42/80] Node asset amount (#2191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Bugfix] 修复错误 * [Update] 修改树结构,统一api --- apps/assets/api/asset.py | 48 ++-- apps/assets/api/node.py | 138 ++++++++---- apps/assets/models/asset.py | 30 +++ apps/assets/models/node.py | 43 +++- apps/assets/serializers/asset.py | 14 ++ apps/assets/serializers/node.py | 56 +---- apps/assets/templates/assets/asset_list.html | 132 +++++------ apps/assets/urls/api_urls.py | 2 + apps/assets/views/asset.py | 1 - apps/locale/zh/LC_MESSAGES/django.mo | Bin 60078 -> 59786 bytes apps/locale/zh/LC_MESSAGES/django.po | 210 +++++++----------- apps/perms/api.py | 6 +- apps/perms/hands.py | 2 +- apps/perms/serializers.py | 29 +++ .../perms/asset_permission_list.html | 79 ++----- 15 files changed, 393 insertions(+), 397 deletions(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 92a1775d0..986829def 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -41,40 +41,46 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdminOrAppUser,) - def filter_node(self): + def filter_node(self, queryset): node_id = self.request.query_params.get("node_id") if not node_id: - return + return queryset node = get_object_or_404(Node, id=node_id) show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true') - if node.is_root(): - if show_current_asset: - self.queryset = self.queryset.filter( - Q(nodes=node_id) | Q(nodes__isnull=True) - ) - return - if show_current_asset: - self.queryset = self.queryset.filter(nodes=node) + if node.is_root() and show_current_asset: + queryset = queryset.filter( + Q(nodes=node_id) | Q(nodes__isnull=True) + ) + elif node.is_root() and not show_current_asset: + pass + elif not node.is_root() and show_current_asset: + queryset = queryset.filter(nodes=node) else: - self.queryset = self.queryset.filter( + queryset = queryset.filter( nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key), ) + return queryset - def filter_admin_user_id(self): + def filter_admin_user_id(self, queryset): admin_user_id = self.request.query_params.get('admin_user_id') - if admin_user_id: - admin_user = get_object_or_404(AdminUser, id=admin_user_id) - self.queryset = self.queryset.filter(admin_user=admin_user) + if not admin_user_id: + return queryset + admin_user = get_object_or_404(AdminUser, id=admin_user_id) + queryset = queryset.filter(admin_user=admin_user) + return queryset + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + queryset = self.filter_node(queryset) + queryset = self.filter_admin_user_id(queryset) + return queryset def get_queryset(self): - self.queryset = super().get_queryset()\ - .prefetch_related('labels', 'nodes')\ - .select_related('admin_user') - self.filter_admin_user_id() - self.filter_node() - return self.queryset.distinct() + queryset = super().get_queryset().distinct() + queryset = self.get_serializer_class().setup_eager_loading(queryset) + return queryset class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 73313f222..84ba4c69f 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -17,13 +17,13 @@ from rest_framework import generics, mixins, viewsets from rest_framework.serializers import ValidationError from rest_framework.views import APIView from rest_framework.response import Response -from rest_framework_bulk import BulkModelViewSet from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 from common.utils import get_logger, get_object_or_none +from common.tree import TreeNodeSerializer from ..hands import IsOrgAdmin -from ..models import Node, Asset +from ..models import Node from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util from .. import serializers @@ -33,7 +33,8 @@ __all__ = [ 'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi', 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi', 'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi', - 'TestNodeConnectiveApi' + 'TestNodeConnectiveApi', 'NodeListAsTreeApi', + 'NodeChildrenAsTreeApi', ] @@ -42,22 +43,89 @@ class NodeViewSet(viewsets.ModelViewSet): permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer - def perform_create(self, serializer): - child_key = Node.root().get_next_child_key() - serializer.validated_data["key"] = child_key - serializer.save() - def update(self, request, *args, **kwargs): - node = self.get_object() - if node.is_root(): - node_value = node.value - post_value = request.data.get('value') - if node_value != post_value: - return Response( - {"msg": _("You can't update the root node name")}, - status=400 - ) - return super().update(request, *args, **kwargs) +class NodeListAsTreeApi(generics.ListAPIView): + """ + 获取节点列表树 + [ + { + "id": "", + "name": "", + "pId": "", + "meta": "" + } + ] + """ + permission_classes = (IsOrgAdmin,) + serializer_class = TreeNodeSerializer + + def get_queryset(self): + queryset = [node.as_tree_node() for node in Node.objects.all()] + return queryset + + def filter_queryset(self, queryset): + if self.request.query_params.get('refresh', '0') == '1': + queryset = self.refresh_nodes(queryset) + return queryset + + @staticmethod + def refresh_nodes(queryset): + Node.expire_nodes_assets_amount() + Node.expire_nodes_full_value() + return queryset + + +class NodeChildrenAsTreeApi(generics.ListAPIView): + """ + 节点子节点作为树返回, + [ + { + "id": "", + "name": "", + "pId": "", + "meta": "" + } + ] + + """ + permission_classes = (IsOrgAdmin,) + serializer_class = TreeNodeSerializer + node = None + is_root = False + + def get_queryset(self): + node_key = self.request.query_params.get('key') + if node_key: + self.node = Node.objects.get(key=node_key) + queryset = self.node.get_children(with_self=False) + else: + self.is_root = True + self.node = Node.root() + queryset = list(self.node.get_children(with_self=True)) + nodes_invalid = Node.objects.exclude(key__startswith=self.node.key) + queryset.extend(list(nodes_invalid)) + queryset = [node.as_tree_node() for node in queryset] + return queryset + + def filter_assets(self, queryset): + include_assets = self.request.query_params.get('assets', '0') == '1' + if not include_assets: + return queryset + assets = self.node.get_assets() + for asset in assets: + queryset.append(asset.as_tree_node(self.node)) + return queryset + + def filter_queryset(self, queryset): + queryset = self.filter_assets(queryset) + queryset = self.filter_refresh_nodes(queryset) + return queryset + + def filter_refresh_nodes(self, queryset): + if self.request.query_params.get('refresh', '0') == '1': + Node.expire_nodes_assets_amount() + Node.expire_nodes_full_value() + return queryset class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): @@ -66,19 +134,10 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): serializer_class = serializers.NodeSerializer instance = None - def counter(self): - values = [ - child.value[child.value.rfind(' '):] - for child in self.get_object().get_children() - if child.value.startswith("新节点 ") - ] - values = [int(value) for value in values if value.strip().isdigit()] - count = max(values)+1 if values else 1 - return count - def post(self, request, *args, **kwargs): + instance = self.get_object() if not request.data.get("value"): - request.data["value"] = _("New node {}").format(self.counter()) + request.data["value"] = instance.get_next_child_preset_name() return super().post(request, *args, **kwargs) def create(self, request, *args, **kwargs): @@ -90,10 +149,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): 'The same level node name cannot be the same' ) node = instance.create_child(value=value) - return Response( - {"id": node.id, "key": node.key, "value": node.value}, - status=201, - ) + return Response(self.serializer_class(instance=node).data, status=201) def get_object(self): pk = self.kwargs.get('pk') or self.request.query_params.get('id') @@ -106,7 +162,6 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): def get_queryset(self): queryset = [] query_all = self.request.query_params.get("all") - query_assets = self.request.query_params.get('assets') node = self.get_object() if node is None: @@ -119,23 +174,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): else: children = node.get_children() queryset.extend(list(children)) - - if query_assets: - assets = node.get_assets() - for asset in assets: - node_fake = Node() - node_fake.assets__count = 0 - node_fake.id = asset.id - node_fake.is_node = False - node_fake.key = node.key + ':0' - node_fake.value = asset.hostname - queryset.append(node_fake) - queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True) return queryset - def get(self, request, *args, **kwargs): - return super().list(request, *args, **kwargs) - class NodeAssetsApi(generics.ListAPIView): permission_classes = (IsOrgAdmin,) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index cde9cde2e..06fb29a51 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -255,6 +255,36 @@ class Asset(OrgModelMixin): }) return data + def as_tree_node(self, parent_node): + from common.tree import TreeNode + icon_skin = 'file' + if self.platform.lower() == 'windows': + icon_skin = 'windows' + elif self.platform.lower() == 'linux': + icon_skin = 'linux' + data = { + 'id': str(self.id), + 'name': self.hostname, + 'title': self.ip, + 'pId': parent_node.key, + 'isParent': False, + 'open': False, + 'iconSkin': icon_skin, + 'meta': { + 'type': 'asset', + 'asset': { + 'id': self.id, + 'hostname': self.hostname, + 'ip': self.ip, + 'port': self.port, + 'platform': self.platform, + 'protocol': self.protocol, + } + } + } + tree_node = TreeNode(**data) + return tree_node + class Meta: unique_together = [('org_id', 'hostname')] verbose_name = _("Asset") diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 47a835861..881b041d7 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -5,6 +5,7 @@ import uuid from django.db import models, transaction from django.db.models import Q from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext from django.core.cache import cache from orgs.mixins import OrgModelMixin @@ -103,6 +104,15 @@ class Node(OrgModelMixin): key = self._full_value_cache_key.format(self.key) cache.delete_pattern(key+'*') + @classmethod + def expire_nodes_full_value(cls, nodes=None): + if nodes: + for node in nodes: + node.expire_full_value() + return + key = cls._full_value_cache_key.format('*') + cache.delete_pattern(key+'*') + @property def level(self): return len(self.key.split(':')) @@ -113,6 +123,17 @@ class Node(OrgModelMixin): self.save() return "{}:{}".format(self.key, mark) + def get_next_child_preset_name(self): + name = ugettext("New node") + values = [ + child.value[child.value.rfind(' '):] + for child in self.get_children() + if child.value.startswith(name) + ] + values = [int(value) for value in values if value.strip().isdigit()] + count = max(values) + 1 if values else 1 + return '{} {}'.format(name, count) + def create_child(self, value): with transaction.atomic(): child_key = self.get_next_child_key() @@ -162,7 +183,7 @@ class Node(OrgModelMixin): pattern = r'^{0}$|^{0}:'.format(self.key) args = [] kwargs = {} - if self.is_default_node(): + if self.is_root(): args.append(Q(nodes__key__regex=pattern) | Q(nodes=None)) else: kwargs['nodes__key__regex'] = pattern @@ -256,6 +277,26 @@ class Node(OrgModelMixin): defaults = {'value': 'Default'} return cls.objects.get_or_create(defaults=defaults, key='1') + def as_tree_node(self): + from common.tree import TreeNode + from ..serializers import NodeSerializer + name = '{} ({})'.format(self.value, self.assets_amount) + node_serializer = NodeSerializer(instance=self) + data = { + 'id': self.key, + 'name': name, + 'title': name, + 'pId': self.parent_key, + 'isParent': True, + 'open': self.is_root(), + 'meta': { + 'node': node_serializer.data, + 'type': 'node' + } + } + tree_node = TreeNode(**data) + return tree_node + @classmethod def generate_fake(cls, count=100): import random diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index dae9ab9af..1066ae0b7 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -9,6 +9,7 @@ from .system_user import AssetSystemUserSerializer __all__ = [ 'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer', + 'AssetAsNodeSerializer', ] @@ -22,6 +23,13 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): fields = '__all__' validators = [] + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.prefetch_related('labels', 'nodes')\ + .select_related('admin_user') + return queryset + def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) fields.extend([ @@ -30,6 +38,12 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): return fields +class AssetAsNodeSerializer(serializers.ModelSerializer): + class Meta: + model = Asset + fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol'] + + class AssetGrantedSerializer(serializers.ModelSerializer): """ 被授权资产的数据结构 diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index f44ff44d6..79c573c60 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -8,76 +8,33 @@ from .asset import AssetGrantedSerializer __all__ = [ - 'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer", + 'NodeSerializer', "NodeAddChildrenSerializer", "NodeAssetsSerializer", ] -class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): - """ - 授权资产组 - """ - assets_granted = AssetGrantedSerializer(many=True, read_only=True) - assets_amount = serializers.SerializerMethodField() - parent = serializers.SerializerMethodField() - name = serializers.SerializerMethodField() - - class Meta: - model = Node - fields = [ - 'id', 'key', 'name', 'value', 'parent', - 'assets_granted', 'assets_amount', 'org_id', - ] - - @staticmethod - def get_assets_amount(obj): - return len(obj.assets_granted) - - @staticmethod - def get_name(obj): - return obj.name - - @staticmethod - def get_parent(obj): - return obj.parent.id - - class NodeSerializer(serializers.ModelSerializer): - assets_amount = serializers.IntegerField() - tree_id = serializers.SerializerMethodField() - tree_parent = serializers.SerializerMethodField() + assets_amount = serializers.IntegerField(read_only=True) class Meta: model = Node fields = [ - 'id', 'key', 'value', 'assets_amount', - 'is_node', 'org_id', 'tree_id', 'tree_parent', + 'id', 'key', 'value', 'assets_amount', 'org_id', ] read_only_fields = [ - 'id', 'key', 'assets_amount', 'is_node', - 'org_id', + 'id', 'key', 'assets_amount', 'org_id', ] - list_serializer_class = BulkListSerializer - def validate(self, data): - value = data.get('value') + def validate_value(self, data): instance = self.instance if self.instance else Node.root() children = instance.parent.get_children().exclude(key=instance.key) values = [child.value for child in children] - if value in values: + if data in values: raise serializers.ValidationError( 'The same level node name cannot be the same' ) return data - @staticmethod - def get_tree_id(obj): - return obj.key - - @staticmethod - def get_tree_parent(obj): - return obj.parent_key - class NodeAssetsSerializer(serializers.ModelSerializer): assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) @@ -89,3 +46,4 @@ class NodeAssetsSerializer(serializers.ModelSerializer): class NodeAddChildrenSerializer(serializers.Serializer): nodes = serializers.ListField() + diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index bd3a776b3..4a77f1de5 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -136,6 +136,7 @@
  • +
  • {% trans 'Refresh' %}
  • @@ -147,6 +148,8 @@ {% endblock %} From 1293d72189fdd8d3b565ddccc9052d2809661a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 18 Dec 2018 11:29:21 +0800 Subject: [PATCH 44/80] Session task (#2196) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Bugfix] 修复错误 * [Update] 增加会话定期清理 --- apps/common/forms.py | 9 ++--- apps/common/signals_handler.py | 9 +++-- apps/jumpserver/conf.py | 1 + apps/jumpserver/settings.py | 2 ++ apps/locale/zh/LC_MESSAGES/django.mo | Bin 59907 -> 60196 bytes apps/locale/zh/LC_MESSAGES/django.po | 50 ++++++++++++++++----------- apps/terminal/api/v1/session.py | 41 ++++------------------ apps/terminal/models.py | 32 +++++++++++++++++ apps/terminal/tasks.py | 32 ++++++++++++++++- 9 files changed, 111 insertions(+), 65 deletions(-) diff --git a/apps/common/forms.py b/apps/common/forms.py index d052819b6..f29d19ec3 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -15,8 +15,6 @@ class BaseForm(forms.Form): super().__init__(*args, **kwargs) for name, field in self.fields.items(): value = getattr(settings, name, None) - # django_value = getattr(settings, name) if hasattr(settings, name) else None - if value is None: # and django_value is None: continue @@ -24,8 +22,6 @@ class BaseForm(forms.Form): if isinstance(value, dict): value = json.dumps(value) initial_value = value - # elif django_value is False or django_value: - # initial_value = django_value else: initial_value = '' field.initial = initial_value @@ -157,6 +153,11 @@ class TerminalSettingForm(BaseForm): TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField( choices=PAGE_SIZE_CHOICES, initial='auto', label=_("List page size"), ) + TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField( + label=_("Session keep duration"), + help_text=_("Units: days, Session, record, command will be delete " + "if more than duration, only in database") + ) class TerminalCommandStorage(BaseForm): diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py index 207dd2ce5..96142e394 100644 --- a/apps/common/signals_handler.py +++ b/apps/common/signals_handler.py @@ -26,21 +26,20 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): def refresh_all_settings_on_django_ready(sender, **kwargs): logger.debug("Receive django ready signal") logger.debug(" - fresh all settings") - CACHE_KEY_PREFIX = '_SETTING_' + cache_key_prefix = '_SETTING_' def monkey_patch_getattr(self, name): - key = CACHE_KEY_PREFIX + name + key = cache_key_prefix + name cached = cache.get(key) if cached is not None: return cached if self._wrapped is empty: self._setup(name) val = getattr(self._wrapped, name) - # self.__dict__[name] = val # Never set it return val def monkey_patch_setattr(self, name, value): - key = CACHE_KEY_PREFIX + name + key = cache_key_prefix + name cache.set(key, value, None) if name == '_wrapped': self.__dict__.clear() @@ -51,7 +50,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs): def monkey_patch_delattr(self, name): super(LazySettings, self).__delattr__(name) self.__dict__.pop(name, None) - key = CACHE_KEY_PREFIX + name + key = cache_key_prefix + name cache.delete(key) try: diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index b1d33c3cd..d537b9e16 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -318,6 +318,7 @@ defaults = { 'TERMINAL_HEARTBEAT_INTERVAL': 5, 'TERMINAL_ASSET_LIST_SORT_BY': 'hostname', 'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto', + 'TERMINAL_SESSION_KEEP_DURATION': 9999, } diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index a58642877..167de3180 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -467,6 +467,7 @@ DEFAULT_TERMINAL_REPLAY_STORAGE = { TERMINAL_REPLAY_STORAGE = { } + SECURITY_MFA_AUTH = False SECURITY_LOGIN_LIMIT_COUNT = 7 SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute @@ -490,6 +491,7 @@ TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE +TERMINAL_SESSION_KEEP_DURATION = CONFIG.TERMINAL_SESSION_KEEP_DURATION # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html BOOTSTRAP3 = { diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index bbbee180bb402790b79c2a3e0ad2573935d7d393..2192ac16e338d572c34aa4c8f2faf64acb2b7b62 100644 GIT binary patch delta 15877 zcmYk@3w)3D|Nrr87{+F^VaC|zG;%G39Xv-|zO&c=LkuG>+(i@cF(V3Ec?_1q7FYzkVF~Pm zn%K)2h@;IZsE+0!D{+>aYf%gO6oYXGhT{>`{a2CAcJ5&S<2!#+&;$y0^|mM)ixM}$ z^4J{ncgFH5Sc&`))O~YMJCuc*_@@|xIj994K!5xhi(#I58+|27JffhPmgwd<{jnly z1+QZ@oQ-j~35Vg&n1P+UI}W!xdyte9-orB;mH!G8Ftn#PfsUx1>WtcfUOm}=&8!cJ zwwR1M)0L=+9Y?MF3hG(7i(wen%bQ>O(Y#F;waRSzK8X2xz!&> z^>^OteR)=KAGL+fbKZ^=Ml~#DmPd713AGdTQ4?x~dgz|SaC`=}XTH59#xkPe%=qfm-=w(?z}S%TWVuMm>v3d>_>)Cvcq9=?gFvz~$4%4MjPW??zpjoOKG zsEOZ0^;hsk@BUEK!lGVe|Mj%jC!v`qq6X@U>M#Yhg4a+TOhY|9ndW*_NBdFj&Y)9+11DJiW7ONV5jk?7^96-C5(iNo-bW2kDAn8g;;5NMqE;G< z8lbM(7`0Qa&>y>?cCZ(!p8=>Hd>M7$SoFuqScLJNHz=s#9MlBnqPB7cM&qXzA47HU zGphYH)P#OR4fu!U3k~$1{^F?il~DCH%>-;n+y?XC|5qt!;HjvG#YL@X1!{oLQ4`vR zdiuY`LU*Dj6|NB3af@ZiF)!|xH!*5U> zAF}u)YDH&I6Us$@yn|}@2Uf!ZgSarxtc8_`>zZA$6Y;1)?0-E9J4t9pcd#c`N^_hJ zI2H@zZq$I^q9%G0H{&II4Hv!aI2EvLx;LRls0B2)xD~4Z_E-eRU{Um?bN+fb-XIZx zbFE^jxdwGK*{ChuiE4Koi{p9J!+FEv`|)K}{YRQ*=eH|5u;6`i#>5A_T^LbVTMG%d6;>L}`4?CU`x zkVFb(8`yiR=ffAkZi(W+>UyZ_oG!2bp#Di19w18s4wbmdIj~( zIuoOCop})R|Nj4lf*z9lsFnSN+Ok3;ynIR27MDSF7>)VAmZQH9K(n^nlGTf8Apud{B@>pklAP|!@Lp^jh{YDX5MZp=a5umjcZYl{z~ z&iVvu#kp7-Z==q>$XG9*fNIwUt7BJGf8+2u#&;%LgR|x@sDb}N&Df7$7_GcGHo$09 zhuyIZ4!~OY8tQvumF4%Mw)_n0XmU{l-!&g%A;x$7#(4u4M!g<^sD>e^tqey^qzY=K zbx_YrQ_DYX_CgJmZ1FI2tmUVmCiITE0DU_1WfZicY}CNJFdFxxo`LJAc8^d;6gb}d zYOa8qU;^s(YK!WpH)1%D z9<_CSP#vY1gDpQ2^~_8_t!xU$;%wB8eu0|kcGQlXwERUM1$BJQ5_il$Q3L4#)e4HD z;xed-RmAF88?_TXQSJJmo{cnfJeDJ#joOhls2$ve`S1T>3ObWhn19x&89zd;yx=5n zhr+P}ac$HJJE7Y5!j70`@n@(B?8Ccw3f2Fi*S&$yq3*wdEZpZjprDyJlRb-}ZYYJ? zs;a0N*F&u^5w+EwP&?Pfd=7Pl15oWUQ2o4)>i=ET#1^6^z8<5sy*nvr3ol|EUcpLO za*DTg4Y4Bee^KxIP}D$E%}k6ZUV-ZPH0u7FsPB!C|M6!RRz>yS0n;!8+cLg$mVz2q zn(8=9F#-7!b55ZK?mEruU^wOwXJTLMJl%0dVixLXD!t+TXFV-(4e@ZShLLZ2za4F` zI`Ihe1N6m`*h3)!f59eLsBxCw8~upM}te})mC#`;)c zruWo$Mm>CKs2zMA^$g6L$^NUb+!7m5Pxn^Tzz47Z9>Icm4AuT57RF0h1h1o>kvpiJ z`3Kcc-&tP!bX5B>sD(^Kz4mX;V*mAeEGAI`b5LivAJyS$)I)O>brgT0R#I-Z*Fk;M z{ZC;K4!{yP9CiOBtbsF81AmFZxEnRjF`p%JQQutmFd9qF@t%qLs0p-34g4Gi;|SDF zOh$Dy3$+tV%#SgEcmt+m4(b`J_Kvr+%~1FIT2j!=|AX3+=TIwn1=XPswZiGB6?}wh zpM|No1#4o=yI%d%sE&K1`bohCn1Nxq3f0dR4552xABB=6PNKdduc200!1X!`Ma317 z8=N|*l_jAj(g8JrUZ?@5pkB9kQSCNiDDFTlJAM0Qm#^5~cfH@fA$31iTPr!*w&+}f#o>*V)Q0)$&cB1rr7NM2Z zqoA2Si5j2>>W03kfo7v7xE*x_-ymOZ&MDM=&oAIVYG>fVs0HPGz`0}7h5Qp97vp4% zSi~Q$v|ET@VgJQ6_u~bxw3MGn8dO>4J^g1;13gBaU5Vx1PQ;)du4<^QtdBa&W*CJ} zTYd=YwHs|NMSVja#7TG^+hO_&rW;9N;|kC3@DTA0)ERAF>A4TJGsjR9`pNP)E&tGB zzg1rQ5@va`x*3m!xxY1PM?3heqMO+d%Th7K;%TTGT-3w1!18NQNB4!rhs>W)?XFn- zz~Vw5dlM^#>MzFhRkuQ2vk_`$iKrcDZ+1tmJlPzHsl?M!?Qdcge1O$4ay73cw!?n7 z2$S${v&EYH*yl|23eI+Hq6VlLHeTy>oP>%yn4PgHaWB+s`Y!7A+K8I)UW<>Kd8qG+ zhp2vvuJa7em-8<}K{r+~YnTbBt!!&?cZ-uTfc$XOLp2Vy;`tbeyUfFuKV$l5dG&S7 zW~e{ooTn`D66)+nqPBXb#Y?PyGipNnEIxr+(IqU1*DZg`@_$)e;1gRhs^2oGiN>KX zhC(At^hRxIDyrlEnKP{ZU5gi+D=nX8aSm!CJ1xK0Jc`B0pU0wj9rf_u{e<^lg@EU~zwoN1+Cqg=+t{K?JqbAbB>IY%4ey&DQ(9EV{5uBZ0!GF|h`BkVL*od0om*!sc zsCgbWz%S-MmJj{hn@AMu{>ElIsrSDt1r6|`RSdK^1B1xFhB~`9EI$v`(PDF*x!K%@ z>hGBO6Y@*#T(EfDChz{q=*!=73i)S@8fclt*{J#)^J~;Y`Gffz>MJ{t-)?^TohMQ4 zFQEFnW!|&=BUJwZUwZ8#zGVN^uo?+{r`N_<9E8fxG3R1Q;tx?@SQ}6S9ku%N7($$f zI?4x@_uK5XD~jr`w8fDY$8EOvzn&!$tU(KlJD~>dit0GU@MD`ZwNH^9I%-zGYU}${(1-|G}2H#=L>8iEC}+AE-DCHGvbT37)n1 zI?gA)jp;aJyY~h30Q3L%KS5u4HwWU zs-HdP_m)40m3>s?T1CJPZz5$-9aO*>SP#47R@By&-02yKT5&9DfI4O~)csFk1opA~ zD9cZ_I1_z3!;dJaqb;a-mo+$T@m14rm)CJQ)PS|I95zMW-^22K%{0_sMx!lWjCxkq zq5clow2SjsM_*ZkgXSqS&%BFTk+a)-io;O9|1D7OeQUED=D((>qZo?%2AznF@NHE4 z-B=ZW*vm9$3fP!Ed-qS}u&r8g=v`hwbyZ5)XKV8 z+y_-Z2+QLzi)UENTD(f@1a%}@r`FS)JhtlR`R69 zJ*_?swUwh$&&q66yJeQoMjh=zi!WOJU#M}4?`J`b@03*m8=_|18P%Yd#VHo2qdI)m z;#n9=`~m8#dpE}5Mbz0B`qt|&4g-kmnN7{M=u^cr6f|IO)QVG46M5D0J}gT7HmZH5 z9iH|ooQvkK<|8xUh_`iRPy^OQ4ba@;|6(w4UyBE$`W=hf z@<})d=jX?qe}V74it?y4Y>2v{1!{nHW>?feFIqgp^qFs)^Uam0{<2a1ZL|Cy^e6rv zi|PG8Nud^AvM6ptacn3lev>xVzcM>~E%_?tcaKy)gj`p=o5hZK?ZV9(sEH+7+!oco7pk8Wizit9TiA>IB8%@G%o01T;dd4v zH!q^TT7R|rk|(@xw6dsgv{snEVvHo7VDSP}|JkU8?Ld7K9>WlPA9(^1lVZc>GwaGLU9sjP%F8L>hO-mf1-Av;Lo06sEJfYbyUydHkiLt7)ib#s(u2N#yMCX zSEBCUC4CeQTjC7r?5|lIc)@EJVOBF6pgL}eI`eLpA8h%tsH1w@^2^K(s0nUEE%XrP z|Nft(pcP-hZg>?nK=X@U{=a5V)Xbkp)xV7DZ~_*^nW&E6M;*ly)Kk6^C*l{_7#s1f ztDj+)?Du~>33c=)HpTg<0e(Prc*W{(n|_zQEiH+=1|mIFwW{% zUuOSfNNlzSr>){D>dgMIxJ0fuP({>0bx;#*jQaKKgc>N#@}n_-L6)D7x^E>G#0?gI z=A)pk-;U~FuX)t+=gmCxrujRLrTt&1XJ}NO*YQ2nK!2k8EqKNImrgO%`(6)wV_&?5 zzRxM>eO`W*^TwN42WwpOw(42bQ#&2C;$^6bZZdac3*sZ#9fPlX6G%a|ABuYKU$=a@ z8~o#gSf6ozj{BfNOvH!+ye(W|N1^ref+e2_9;a9 zNAeGdXQKW)rBAr(Q~s3eKKY}>%_!@WKzpt2Q?4*~Z^LGO6J5XfHoo%Y=g?-JwbOj{ z8AkjauD~GrS!4C7lz*l?8};dA`DmM43v6V$o3vTPb%OXL*DI8@f7<-v#GTlK-SHj# z-=^HeE!ilnb~>d4)NaHgT;;eU1SfG#qkIeXiNasqE{*E?wp!+#IoWIAoFUGnT%5b| zKQ_N|t6wFo0TEx)eAaMh{ zLvEGLa4k7~3gCcK!h`Tlscf|K;Q-kumZcx+k=U<>SknD9X zezKj?wBA5@F6IBBKG$dyPQDFT7ciRvQ!qJVg}Z<&ZoG~Hme&qgv?>C#x&iA|9<|l z@>v@u)7{vta=?3J`;f_V&o-;<7vlOg4~vMi{%%;y5~%-TyMkN0x$lXQ)=yhHZb!C+ zwSH`^BQcO%P3k*P{?a;0)T;L_LjPSj@<7Uz0_esXZ2%H;jAhWmYDY}|*G zmT-NH zTDId#b0;K)S7>Vca-5p$-1!KLp+52M#-yl{mx-owjVIHM+9%u#NqtHNao>Gwr?wxs zPq%DSp*Y#DWPjpX&2@qH`rOAq+^m*SezESMmQntTse9<&ZW&eK4y6TLy}ADV^r0NV z9kt!sts40MP88s#wu*iB2TH5RZ|7QR?WfvJf8r5Sh=P~Lm%5}N!QT|$a z>XWf8*Fu|B6CCWm*(NHWA$QHErn0-SO>Dpyl=Ru-UT9O(ZTWWShBu?aBwOBco4$uB7a^c3T?dPh7B2#JTxVGOxobV(fw1SQ&I<~ zj!cbC8yG!g*of5Vk%N+l{=bfDMGqS~cx-eUndFhl{gX3Nv%i^{7FeWl^b_@(KG7h% z@q!ky!PBlS{XB2|Z2jNN4cDe;Pu2Y62B|Htu@5QIcXL?lKML5Ly*35mU_YN@^Vs@0ZOhez#Ev^_Pdv|`kz#AxlB zwOX-j@752ks?sX;e}8hmy{`XrUFUk;-|u~&d!F+=(JnrnZS2WxzDuFmXF7apWOJM% zm_5pIT4#5hyGhDAPVEMcGY#wDJNP3`!KMuzXEdI|6Ml}_$9~5;W$1gjzTbr1k8geSP<)DG{t4#8xu^xL!~oodxpBXliN5?KE>O@+f5jg7FKPwdTRBcC9D-$V z77oE(_y#7vnl)EvF_Ll~o6TB#`7be<`~%bk654n>m5ADb0kKCYZCWxAJgQKT)VHjzJw=IV^~2sD4^mzALKT;I{0)I(pX{OvYH^ znOFihqqgh7c#0Uu#GjBUpmVhdDY2IiqoA$7 zkD8&=-WxCowM7xAhJ{gQTpTq}yv3DKJ5>!ek!Dy7Tci4Y6BBWq)o(=gx6|r<`>o~8^1S?n-|Pm=6%##@&vWj**dZdSO7KPOQ?ypMoqjks{J5T|0A%x-v7}Q zDv(%m!lrqov4MLKuzc_ z2I>8OYKg$k-V7sA0~W_nOh6q;4a+w}4bTd;@*d_JsP}ywYM{?h&(wNUe>+h3?YH<8 z`m`k%DJ0=7)Ye6H@fyZrVd6y83SUI+NEg&u4@7O{Sky|VVH7Sx?Zgh$#806HzK**8 z0cv5-yRiRyy+XTsGcSr7s1mBf`luCjKy}au_3#WgXP`P-j%v3Jb^rIMe$Sy+eiOA* z_fQl57j@LRy7{~r1$XndJ_UbLJ{@JLVU5uL0 zYSct_qR##R>X|s>qo6IngW9SGsDYjCUc(^Nz(r6qE{kfPh}$q3)$v2rz)#IUzQ;9z zf~bL_QTHdICYpj;h_5b%7z)j?7!Jm!I1TUPSyV>{dwLxlM@`@iYQmRL1Kh=|iTCo_ z2Vo=f;h2D(Ek6nMc72RItUhNpg)$^op*lQ`8sG+M>+hgu`V_U&oV~pPLd-&_Esezh ztcaROGOC|?s2yyIT1a~gz#f=W?|(lEYB&@%fwxdwIUZy16N@uY9qdB2KZu&pG1P!( zEq?>`^xr|X_wVD?=P@I&GWj^ndjH!|(7?S>56c^<6^%y?FcUSQ`KYIV1!l*+SOgEE z&h!>)!275p`5X0`2KV*el9H(VQm_Knz^vc@K@_wCAF9KtsD{f>9j~@{Gircss0r=E z06dOrcNRTYMU`cEIw1uXz(GfO?oCaTO-uU_9fakcUFULEZ{mqGsF`)p2js z4h+R$d<*q(jYrkbLw!>&MXhMN#rsju&;?ZcyQqa`V^keQC@S_m@UAE6$SSr~#}pdPL@s0kb~Z=!x~|3w`^*f4M4GN=iqq28vKQQxeCFd9EJS7FxQ z|3@h3bvun(*?H8K-LU-cs4aek>d<-Jel1b$D`9?2MfKAhb!0733+jwoaZgmc0hkZp zL!W-drcu!Qx(v1Qm8cFfP!rjKp?Dazm6uQ}zlYi(|2MpLp_oJ*jq0ZrYG*s5CfXNu z#6zwA%{MrIb^M-H%s>t7qHg#CHG!oVj2loZ+>Kh<0o2NlqS~KCP4FUCz`LmXioEF^ zNrG7u_08DwP0n9uI)H?>WUTqAHCTe$vhAqX=121ZmM0D$?$y^v?L-^Y!`sIkgW8EN zP!s;jT!otOdLIQH!B*5xoW@#s8{@Ie2=5;hub>7Tf!cv3s18<`8K{+RLv8sX)WD}v z6T5_J|Eu{J^-TGkx4bRSjcOQW7JF+5X z8Pv+Fq6SPuwQGolG=6gmnrR=@5e!D{h!1t+T+|HhvArsI{Tk3 zA2HHv7l)pI5j!xx)598UH;;kz|BX1EYza5?H3ID~3<0d+)oF%qAlCK&Oa_j;8;^^<~{ zNF&ti+}h%9n03UcBlEq-{;PwjBoc5As)IckhR3bpE!2u1pxOt#?>&^ESco_R>tiAo z#^D%&AEPF+9QEvMM)kkb{Na7}Un{#vLT7x>D*Q)z6UdL+vXZC{%9~X!UlVn7jZiCU zj`7$YwUZy9CO8ST6X}*;h3YrMXNhg*9@IdGP%}Sn@fFm}f5FoD81-7lumH6yiQ1tg zvmr(iw@2-SzUZ`bAEKU_&rnChG6|?L8Uqzt|iFH^U&!e{PUo3`^W4-r01vO9$ zvny619)jw43F`jMsAuUM#^G#OPJgU{dRUiY0mgUkQbzJ)rPyO@Z%C-HlLsaOhcVR;Om>^(DSW^arqKOKF^6gE+)hQDG? zEIq}0xDruEQ3W{#r#AkHyKxhKGu3wBL%wu~mtZCQ8THghP4gbUB-A%$Gt@KC!yG(~ z{a4~05_-BPV0N5|{x}!2;e1rPbj*RPQ4`&OdPcUP?mviXcNW#|25JF!P!IDX)X@e_ z_u9u#Xa99}RY|C$#;Av-HR=fZpS)7il--7CRAF7{B)UV_f z)b~h%PrP=eF&}Xf=EwTTGvagFP|ym8pgQtdJQa1r=ctvfLd|>=Y65#v1KvlyX3tUW z;#_YB%b^xh8#Q1@48uXF?~e~KLht_+3OehB7=YiQw&nn4?F{OMyQqo&iE0<{srQiO zLG4^9s(loOVHwnYHBiq+L(~FWp(ffD)Aj!Mr?3w5%=FG`JL-nrsHgZxERUx#76WE^ z|2mGxe180%V{P(dW_z#U9;~ExbG&vbsGS&t;W!^P(G2uyfZY^y!$H(QPf;^0`SA1zT|kXXDr}blKPJq za(fJgCX2j*24E=hyQrsjI%>;Y)K-3pI?H7kjaw{#4ikv4n|T*|-;C98H2JpJ0MB9( zEVjfmbqUiyN}?SJZF#A$Jd;p6QyVp*Mwahj`MwqpHs3WTnKR9Wn1lP*qIPtX<#(A! zeH02&an2HdU=CtusrRtuL`^ILb#^5zu5LC$J(RCn+{@x&7Jq>1Z@M|toM-wLQP9j* zpa$AteurB559U?uP5cnmzQZ!_@Bdy{ns_p{#r4emo<29 zanKsCJ`96s7iDogY9i$=U&*Y6xyiqXxv(wj;q7LQK=t$C8s2|x?Pnx3(?zHq*ktjK z7GFaR^be~2b2D(Q7l)Ze&3M#;Dq5U^m5J+G{w>RoTFd^2P%+6W=2*p-Sc3d=tcpKa zeZbdV$3dtSgjpP8#$ze+<#8~!Mz!0ET3{xI;0@G{JoH&1H$MRSgCGWVLmKLaCKh)x z`=eGi!s7QWo??D%`8lY8mRP*e;@zl$kE3?d_lp%Cpay=58X$1JXFg0NjQA9|0S$|39^g>>Ip_0;nA*hFWP!vyxfMd=WK3d-FBR`%n}4 z(BdWLdUGeL|09a^{-3bK6$~c+88y>CEg!Jan@EruX_hjRP#xAb8zH~U&Px{mf?CLL z=2OcDZess6P(BLkAO_Vi-mHu|vpQyHtVTQ%=VAt`{Y(6q>;8^r4^+NCs{audPq2Cy z^_4z%Gv^;q;gnT8GqZo={m$n>ePb0xbyy2E(2JN4TcOUfm*oee+6}jOjKz~Jo@LHA z7k|V4>xPw<_y+S6??iQ+Y5DW0cGoQa4R!X9EFbu-*S??`gPLf9S2umq zC{LoJIR!fte~Yy-Vw-0>tV{eE_Q4CN3Dn!}EubkXZi}B`7aWLxVL0~M;nlxmevl>S zKZ!ye8q7fLM5Z;kWbsXlADDle&Q5QoIZ-!Q&9=^u04l3@8n&{hm*ndqR zokSR}!K{^;2T}FMEWT*oFz=uy`oIj>>&1mo6Dn(_TD}?TVeEq1`8R!5m}Y)u?nDiI z7B!)psFmI|ADPZRuU&2|L4AaoX8G=@fd-@cnP~axrtfpBScY23TGSTpw)g~UBDc&x zOlQBB4@TV=g<5eri)&c@%VuY+Mg0KGfh)Yc&sj%7E8F2EobNFQ@hL2f7cBk@HIe6L z&;c)wG)rL)>XXdsW*xIJYN9PL4trn_&!00PtH3`pt-%7+N-`|oWA&#|TX`MztUN`v z%lEyPk3pSnHH%xI>ieN4{x)hsn^tiba}ocIf#`q8yCDdR5l32_g1WB( zs-G9JC3eNqxE_n+c~n3Dq6W@&*fabv`>zg)lTd>)s5l9=)zvL-g&Lp(Y662$Tl=Ba z&odX9D^LTiGk2icAI6G!-s0dRoWClH9`Ob&hnhe`9E;5{2s6z~sQNpo2|qyH{}lCw z6Li#TUk^3mmoWi*q82nAb)-vC{cQJ92%@mp5}D>%^Rju%yoYN4(DeJki}RX=Q3IE@ zI2CgfH#S?DT})qp3R=ONs1=Pfr<-%kg{T>?!lIagnnU`O%9Dq1u;4 zjhBM-=W`lcMGLc&+20(2+Pbk8&qEEc+~RE*LVVETpHLIMg}Uz^_QM>1 zr&G`kD^W9CZ|*dYm_M1f%s)&g)7!aVR6h}@eu`lLmPH*wMSKC%tiC^H{ri7}C1#*H zm}f4+Y{V-pUSn=Fx0!oT10F&>TxT#l-n0Az)Q&wjLyvnqQ4+I$|7%iE2W`z>7)m_M z9EY0NY>U4@wO@zY;vE*BvHBa>mi%LjTb%Iv>w(wF54X7eN%mhIy>`;;aHKiGbj=0k zYI6(b<-P+Lf~Qdvyk*`)_50BDJLScB&BCbmrB8AGYFNQ4(okQiO{~E{)Hm7dsBf~* zF>A$Gg!qib4^jODpY~Q7g}SdC=EF7^jeSvHU=vaOedVK|h6k}4KET%a!Wpl_F*u8O z25L)7o%JR#%AAd={~D`eqn{WQM`1Xw!m_v<-@so{KSDjudHwiCQ3xe58?}`yupn;1 z#+Zo>FzUSbly^gI-2l{zhv5($h5TvY{EBmM!v*huN;SXe{nY87kinbwmSE`7!1U)CA|DR=UdEgxc9%*a{Dz`YC*s_g@v|Dab0Q z4%1K#nxH!Dgt_oFRL8?nN8m#}-Q#f-&cYN7zvlJxlGzc}Pd}`VZ=?FpxW@ah4)gn%b^^-7`c#hR?vHSs4|7WhV|LX9bRXjrt z6nw**VFc>eE&(-AW6QV3tQA@QZPb0^F&oaX*hTH^eANAm&2^UF;j_X%^N4u@M{>hC z)HCwxO|RpVsDaL+I=+S_@iyu`54pwjfvNZ_eu{dH$NtQ*;t?#5fxmb=)iQ;Gp4vXB zGZ}-L=}dDW)+Anwui~$$3DmpowQr7dh`XWkf8TaGJ$%1o|`KB)Als+W6B?*{P1^{nU0 z@7}DO>i51ITcw^alKc$X%(ix#qCSI(f562UOh3!5zBlDdlxLtm%`9Jxe)LJhs+PM) zoB3QPiBEA2rK~N}eh(#X=I6NAt27CiN;%oBST(HN07{3c{Tg#}MR7+y^l?p~d>8eJ z#(VCtsug@&Epx#f<27*35znNYhr9Io*XpBil9iSJo$GVr$~OPkDbMAeE#!0IkHoI^ zan$ZRhS_L8!ryVDlZytFq8#DYNsjg#?{-g4ip`*YzdGlW)5GabA74{0O50KH#^kbo zzq?nHBLa@lcCY&^IWAHSUm@ZTR;LdZu^DxAtEJSd(wr#?iccK)+Roq%c)8a63MSMVRsRrh=f}B47*cCTm5w0heFF@O$D1XHDk6WW! z*^*l*m9}2bU}f&=VXYhEWpc&cY1Jb9`nyZ2mG$#;Gpof{iDKMm8i>zGYyH^FMO{_y z+DP0KXVE5s^2ctu>JgngQ|d$Ze_Z_9I$^Z_n(}PQ<4~WUX%kL98@|i+-{+>4pO6?r z+YgAf<+Ug$V}P4pJuLDN*=0n3;8c9mdL85*tsdbkPrrYVnMciATx)3+!}Z_i3Uy}* zDsb&2UzPUyw52?qYt{d2dxv}`aeEs<-(kBc=dd=Z+`ETdI+s3g`SVj&NR?Df!7|qQ z78t0eSjZF)SjJN*oSj)Vq z|94&3ZJy?<_=fdUpMD#V4YJnHtaTC0O-^5}O(-99pQS~I$B@ylLI77H0HIQ+R(B(qp!tAw46+izxkazZk?K8ft!i+32{5u zj0?<1yG68H<4&$wui_%=4sfmKx?=4H(snH61{OzRD0k?y)O}VnE?^#+5^l*_seUiG z-D|}~mZQ(pvNb^J_&EO|gABMa?hV`4n?o-$`y}t?2yMh{kcf$33m6 zt>F6A?wCIh_dT?BR5)|pLAC1@4J6x=>{+g5TvutY&m(-~9<3eiSJJ&#J38PC>i%-` z*NHBApVDVs?YRE?bf6r`9cA6-b&>)e5ao2o*NJa`oYGSAJGhou`|)U?uLFLrtukSuZ|t6D582xPJA^ z`hDe=tDhWun_PFUSGlK(SM4++K1Q6t-J$OI`ZI!mB@;*OVY1cS*an4zSCY}E2Um@Z zCJnZwWE38CCnTfi^zK19lVd6+R!^*wad>Xc_>kGZq%XUjzVY__g*O*3z4>)Uq1}x_ QGG5R8GdSbg<+^$P4=%nw82|tP diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 7f42c17be..fda3df537 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-12-17 20:06+0800\n" +"POT-Creation-Date: 2018-12-18 10:13+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -1976,47 +1976,57 @@ msgstr "资产列表排序" #: common/forms.py:158 msgid "List page size" -msgstr "资产列表页面大小" +msgstr "资产分页每页数量" -#: common/forms.py:170 +#: common/forms.py:161 +msgid "Session keep duration" +msgstr "会话保留时长" + +#: common/forms.py:162 +msgid "" +"Units: days, Session, record, command will be delete if more than duration, " +"only in database" +msgstr "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不受影响)" + +#: common/forms.py:175 msgid "MFA Secondary certification" msgstr "MFA 二次认证" -#: common/forms.py:172 +#: common/forms.py:177 msgid "" "After opening, the user login must use MFA secondary authentication (valid " "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: common/forms.py:179 +#: common/forms.py:184 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: common/forms.py:184 +#: common/forms.py:189 msgid "No logon interval" msgstr "禁止登录时间间隔" -#: common/forms.py:186 +#: common/forms.py:191 msgid "" "Tip: (unit/minute) if the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" -#: common/forms.py:193 +#: common/forms.py:198 msgid "Connection max idle time" msgstr "SSH最大空闲时间" -#: common/forms.py:195 +#: common/forms.py:200 msgid "" "If idle time more than it, disconnect connection(only ssh now) Unit: minute" msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" -#: common/forms.py:201 +#: common/forms.py:206 msgid "Password expiration time" msgstr "密码过期时间" -#: common/forms.py:204 +#: common/forms.py:209 msgid "" "Tip: (unit: day) If the user does not update the password during the time, " "the user password will expire failure;The password expiration reminder mail " @@ -2026,45 +2036,45 @@ msgstr "" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" -#: common/forms.py:213 +#: common/forms.py:218 msgid "Password minimum length" msgstr "密码最小长度 " -#: common/forms.py:219 +#: common/forms.py:224 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: common/forms.py:221 +#: common/forms.py:226 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: common/forms.py:227 +#: common/forms.py:232 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: common/forms.py:228 +#: common/forms.py:233 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: common/forms.py:234 +#: common/forms.py:239 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: common/forms.py:235 +#: common/forms.py:240 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: common/forms.py:241 +#: common/forms.py:246 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: common/forms.py:242 +#: common/forms.py:247 msgid "" "After opening, the user password changes and resets must contain special " "characters" diff --git a/apps/terminal/api/v1/session.py b/apps/terminal/api/v1/session.py index de3e09a55..5788df775 100644 --- a/apps/terminal/api/v1/session.py +++ b/apps/terminal/api/v1/session.py @@ -94,44 +94,15 @@ class SessionReplayViewSet(viewsets.ViewSet): serializer_class = serializers.ReplaySerializer permission_classes = (IsOrgAdminOrAppUser,) session = None - upload_to = 'replay' # 仅添加到本地存储中 - - def get_session_path(self, version=2): - """ - 获取session日志的文件路径 - :param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz - :return: - """ - suffix = '.replay.gz' - if version == 1: - suffix = '.gz' - date = self.session.date_start.strftime('%Y-%m-%d') - return os.path.join(date, str(self.session.id) + suffix) - - def get_local_path(self, version=2): - session_path = self.get_session_path(version=version) - if version == 2: - local_path = os.path.join(self.upload_to, session_path) - else: - local_path = session_path - return local_path - - def save_to_storage(self, f): - local_path = self.get_local_path() - try: - name = default_storage.save(local_path, f) - return name, None - except OSError as e: - return None, e def create(self, request, *args, **kwargs): session_id = kwargs.get('pk') - self.session = get_object_or_404(Session, id=session_id) + session = get_object_or_404(Session, id=session_id) serializer = self.serializer_class(data=request.data) if serializer.is_valid(): file = serializer.validated_data['file'] - name, err = self.save_to_storage(file) + name, err = session.save_to_storage(file) if not name: msg = "Failed save replay `{}`: {}".format(session_id, err) logger.error(msg) @@ -145,7 +116,7 @@ class SessionReplayViewSet(viewsets.ViewSet): def retrieve(self, request, *args, **kwargs): session_id = kwargs.get('pk') - self.session = get_object_or_404(Session, id=session_id) + session = get_object_or_404(Session, id=session_id) data = { 'type': 'guacamole' if self.session.protocol == 'rdp' else 'json', @@ -153,9 +124,9 @@ class SessionReplayViewSet(viewsets.ViewSet): } # 新版本和老版本的文件后缀不同 - session_path = self.get_session_path() # 存在外部存储上的路径 - local_path = self.get_local_path() - local_path_v1 = self.get_local_path(version=1) + session_path = session.get_rel_replay_path() # 存在外部存储上的路径 + local_path = session.get_local_path() + local_path_v1 = session.get_local_path(version=1) # 去default storage中查找 for _local_path in (local_path, local_path_v1, session_path): diff --git a/apps/terminal/models.py b/apps/terminal/models.py index 661b4a57d..6491bdf35 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -1,11 +1,13 @@ from __future__ import unicode_literals +import os import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.conf import settings +from django.core.files.storage import default_storage from users.models import User from orgs.mixins import OrgModelMixin @@ -148,6 +150,36 @@ class Session(OrgModelMixin): date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now) date_end = models.DateTimeField(verbose_name=_("Date end"), null=True) + upload_to = 'replay' + + def get_rel_replay_path(self, version=2): + """ + 获取session日志的文件路径 + :param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz + :return: + """ + suffix = '.replay.gz' + if version == 1: + suffix = '.gz' + date = self.date_start.strftime('%Y-%m-%d') + return os.path.join(date, str(self.id) + suffix) + + def get_local_path(self, version=2): + rel_path = self.get_rel_replay_path(version=version) + if version == 2: + local_path = os.path.join(self.upload_to, rel_path) + else: + local_path = rel_path + return local_path + + def save_to_storage(self, f): + local_path = self.get_local_path() + try: + name = default_storage.save(local_path, f) + return name, None + except OSError as e: + return None, e + class Meta: db_table = "terminal_session" ordering = ["-date_start"] diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py index 4e57c5f5e..77aa66226 100644 --- a/apps/terminal/tasks.py +++ b/apps/terminal/tasks.py @@ -4,15 +4,20 @@ import datetime from celery import shared_task +from celery.utils.log import get_task_logger from django.utils import timezone +from django.conf import settings +from django.core.files.storage import default_storage + from ops.celery.utils import register_as_period_task, after_app_ready_start, \ after_app_shutdown_clean -from .models import Status, Session +from .models import Status, Session, Command CACHE_REFRESH_INTERVAL = 10 RUNNING = False +logger = get_task_logger(__name__) @shared_task @@ -34,3 +39,28 @@ def clean_orphan_session(): if not session.terminal or not session.terminal.is_active: session.is_finished = True session.save() + + +@shared_task +@register_as_period_task(interval=3600*24) +@after_app_ready_start +@after_app_shutdown_clean +def clean_expired_session_period(): + logger.info("Start clean expired session record, commands and replay") + days = settings.TERMINAL_SESSION_KEEP_DURATION + dt = timezone.now() - timezone.timedelta(days=days) + expired_sessions = Session.objects.filter(date_start__lt=dt) + for session in expired_sessions: + logger.info("Clean session: {}".format(session.id)) + Command.objects.filter(session=str(session.id)).delete() + # 删除录像文件 + session_path = session.get_rel_replay_path() + local_path = session.get_local_path() + local_path_v1 = session.get_local_path(version=1) + + # 去default storage中查找 + for _local_path in (local_path, local_path_v1, session_path): + if default_storage.exists(_local_path): + default_storage.delete(_local_path) + # 删除session记录 + session.delete() From a609f1707834aee935096bdb8802acc2cd857bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 18 Dec 2018 17:28:45 +0800 Subject: [PATCH 45/80] [Update] Stash it (#2197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] Stash it * [Bugfix] 修复错误 * [Update] 修改jms --- apps/assets/api/admin_user.py | 25 +- apps/assets/api/asset.py | 6 +- apps/assets/api/node.py | 4 +- apps/assets/api/system_user.py | 15 +- apps/assets/forms/user.py | 6 +- apps/assets/models/asset.py | 107 +++---- apps/assets/models/base.py | 7 + apps/assets/models/user.py | 99 ++++-- apps/assets/serializers/asset.py | 10 +- apps/assets/serializers/system_user.py | 15 +- apps/assets/signals_handler.py | 6 +- apps/assets/tasks.py | 289 +++++++----------- .../templates/assets/admin_user_assets.html | 47 ++- .../templates/assets/system_user_asset.html | 10 +- .../templates/assets/system_user_list.html | 2 +- apps/assets/urls/api_urls.py | 14 +- apps/assets/views/admin_user.py | 2 +- apps/jumpserver/conf.py | 3 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 60196 -> 60280 bytes apps/locale/zh/LC_MESSAGES/django.po | 18 +- apps/ops/api/adhoc.py | 4 +- apps/ops/views/adhoc.py | 2 +- apps/users/api/user.py | 3 + 23 files changed, 361 insertions(+), 333 deletions(-) diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index 8d30ee9d9..263d669fd 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -14,6 +14,7 @@ # limitations under the License. from django.db import transaction +from django.shortcuts import get_object_or_404 from rest_framework import generics from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet @@ -24,13 +25,14 @@ from common.utils import get_logger from ..hands import IsOrgAdmin from ..models import AdminUser, Asset from .. import serializers -from ..tasks import test_admin_user_connectability_manual +from ..tasks import test_admin_user_connectivity_manual logger = get_logger(__file__) __all__ = [ 'AdminUserViewSet', 'ReplaceNodesAdminUserApi', 'AdminUserTestConnectiveApi', 'AdminUserAuthApi', + 'AdminUserAssetsListView', ] @@ -81,12 +83,29 @@ class ReplaceNodesAdminUserApi(generics.UpdateAPIView): class AdminUserTestConnectiveApi(generics.RetrieveAPIView): """ - Test asset admin user connectivity + Test asset admin user assets_connectivity """ queryset = AdminUser.objects.all() permission_classes = (IsOrgAdmin,) def retrieve(self, request, *args, **kwargs): admin_user = self.get_object() - task = test_admin_user_connectability_manual.delay(admin_user) + task = test_admin_user_connectivity_manual.delay(admin_user) return Response({"task": task.id}) + + +class AdminUserAssetsListView(generics.ListAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = serializers.AssetSimpleSerializer + pagination_class = LimitOffsetPagination + filter_fields = ("hostname", "ip") + http_method_names = ['get'] + search_fields = filter_fields + + def get_object(self): + pk = self.kwargs.get('pk') + return get_object_or_404(AdminUser, pk=pk) + + def get_queryset(self): + admin_user = self.get_object() + return admin_user.get_related_assets() diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 986829def..cd343b2a5 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -17,7 +17,7 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from ..models import Asset, AdminUser, Node from .. import serializers from ..tasks import update_asset_hardware_info_manual, \ - test_asset_connectability_manual + test_asset_connectivity_manual from ..utils import LabelFilter @@ -109,7 +109,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView): class AssetAdminUserTestApi(generics.RetrieveAPIView): """ - Test asset admin user connectivity + Test asset admin user assets_connectivity """ queryset = Asset.objects.all() permission_classes = (IsOrgAdmin,) @@ -117,7 +117,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): asset_id = kwargs.get('pk') asset = get_object_or_404(Asset, pk=asset_id) - task = test_asset_connectability_manual.delay(asset) + task = test_asset_connectivity_manual.delay(asset) return Response({"task": task.id}) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 84ba4c69f..4295b2618 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -24,7 +24,7 @@ from common.utils import get_logger, get_object_or_none from common.tree import TreeNodeSerializer from ..hands import IsOrgAdmin from ..models import Node -from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util +from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util from .. import serializers @@ -273,5 +273,5 @@ class TestNodeConnectiveApi(APIView): assets = node.assets.all() # task_name = _("测试节点下资产是否可连接: {}".format(node.name)) task_name = _("Test if the assets under the node are connectable: {}".format(node.name)) - task = test_asset_connectability_util.delay(assets, task_name=task_name) + task = test_asset_connectivity_util.delay(assets, task_name=task_name) return Response({"task": task.id}) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 3c1d0b3bd..e66e4bfc9 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -24,8 +24,8 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from ..models import SystemUser, Asset from .. import serializers from ..tasks import push_system_user_to_assets_manual, \ - test_system_user_connectability_manual, push_system_user_a_asset_manual, \ - test_system_user_connectability_a_asset + test_system_user_connectivity_manual, push_system_user_a_asset_manual, \ + test_system_user_connectivity_a_asset logger = get_logger(__file__) @@ -33,7 +33,7 @@ __all__ = [ 'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserPushApi', 'SystemUserTestConnectiveApi', 'SystemUserAssetsListView', 'SystemUserPushToAssetApi', - 'SystemUserTestAssetConnectabilityApi', 'SystemUserCommandFilterRuleListApi', + 'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi', ] @@ -93,15 +93,16 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): def retrieve(self, request, *args, **kwargs): system_user = self.get_object() - task = test_system_user_connectability_manual.delay(system_user) + task = test_system_user_connectivity_manual.delay(system_user) return Response({"task": task.id}) class SystemUserAssetsListView(generics.ListAPIView): permission_classes = (IsOrgAdmin,) - serializer_class = serializers.AssetSerializer + serializer_class = serializers.AssetSimpleSerializer pagination_class = LimitOffsetPagination filter_fields = ("hostname", "ip") + http_method_names = ['get'] search_fields = filter_fields def get_object(self): @@ -125,7 +126,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView): return Response({"task": task.id}) -class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView): +class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView): queryset = SystemUser.objects.all() permission_classes = (IsOrgAdmin,) @@ -133,7 +134,7 @@ class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView): system_user = self.get_object() asset_id = self.kwargs.get('aid') asset = get_object_or_404(Asset, id=asset_id) - task = test_system_user_connectability_a_asset.delay(system_user, asset) + task = test_system_user_connectivity_a_asset.delay(system_user, asset) return Response({"task": task.id}) diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py index f5c62a4ff..70fcdafad 100644 --- a/apps/assets/forms/user.py +++ b/apps/assets/forms/user.py @@ -99,8 +99,8 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm): auto_generate_key = self.cleaned_data.get('auto_generate_key', False) private_key, public_key = super().gen_keys() - if login_mode == SystemUser.MANUAL_LOGIN or \ - protocol in [SystemUser.RDP_PROTOCOL, SystemUser.TELNET_PROTOCOL]: + if login_mode == SystemUser.LOGIN_MANUAL or \ + protocol in [SystemUser.PROTOCOL_RDP, SystemUser.PROTOCOL_TELNET]: system_user.auto_push = 0 auto_generate_key = False system_user.save() @@ -124,7 +124,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm): validated = super().is_valid() username = self.cleaned_data.get('username') login_mode = self.cleaned_data.get('login_mode') - if login_mode == SystemUser.AUTO_LOGIN and not username: + if login_mode == SystemUser.LOGIN_AUTO and not username: self.add_error( "username", _('* Automatic login mode,' ' must fill in the username.') diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 06fb29a51..ccff0fff5 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -13,7 +13,6 @@ from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from django.core.cache import cache -from ..const import ASSET_ADMIN_CONN_CACHE_KEY from .user import AdminUser, SystemUser from orgs.mixins import OrgModelMixin, OrgManager @@ -75,63 +74,48 @@ class Asset(OrgModelMixin): protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform')) - domain = models.ForeignKey("assets.Domain", null=True, blank=True, - related_name='assets', verbose_name=_("Domain"), - on_delete=models.SET_NULL) - nodes = models.ManyToManyField('assets.Node', default=default_node, - related_name='assets', - verbose_name=_("Nodes")) + domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) + nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) # Auth - admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, - null=True, verbose_name=_("Admin user")) + admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user")) # Some information public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP')) number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number')) # Collect - vendor = models.CharField(max_length=64, null=True, blank=True, - verbose_name=_('Vendor')) - model = models.CharField(max_length=54, null=True, blank=True, - verbose_name=_('Model')) - sn = models.CharField(max_length=128, null=True, blank=True, - verbose_name=_('Serial number')) + vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) + model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) + sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) - cpu_model = models.CharField(max_length=64, null=True, blank=True, - verbose_name=_('CPU model')) + cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model')) cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) - memory = models.CharField(max_length=64, null=True, blank=True, - verbose_name=_('Memory')) - disk_total = models.CharField(max_length=1024, null=True, blank=True, - verbose_name=_('Disk total')) - disk_info = models.CharField(max_length=1024, null=True, blank=True, - verbose_name=_('Disk info')) + memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) + disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) + disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) - os = models.CharField(max_length=128, null=True, blank=True, - verbose_name=_('OS')) - os_version = models.CharField(max_length=16, null=True, blank=True, - verbose_name=_('OS version')) - os_arch = models.CharField(max_length=16, blank=True, null=True, - verbose_name=_('OS arch')) - hostname_raw = models.CharField(max_length=128, blank=True, null=True, - verbose_name=_('Hostname raw')) + os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) + os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) + os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) + hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) - labels = models.ManyToManyField('assets.Label', blank=True, - related_name='assets', - verbose_name=_("Labels")) - created_by = models.CharField(max_length=32, null=True, blank=True, - verbose_name=_('Created by')) - date_created = models.DateTimeField(auto_now_add=True, null=True, - blank=True, - verbose_name=_('Date created')) - comment = models.TextField(max_length=128, default='', blank=True, - verbose_name=_('Comment')) + labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) + created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) + date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) + comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) objects = OrgManager.from_queryset(AssetQuerySet)() + CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}' + UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3) + CONNECTIVITY_CHOICES = ( + (UNREACHABLE, _("Unreachable")), + (REACHABLE, _('Reachable')), + (UNKNOWN, _("Unknown")), + ) def __str__(self): return '{0.hostname}({0.ip})'.format(self) @@ -197,25 +181,17 @@ class Asset(OrgModelMixin): return '' @property - def is_connective(self): + def connectivity(self): if not self.is_unixlike(): - return True - val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname)) - if val == 1: - return True - else: - return False + return self.UNKNOWN + key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id)) + cached = cache.get(key, None) + return cached if cached is not None else self.UNKNOWN - def to_json(self): - info = { - 'id': self.id, - 'hostname': self.hostname, - 'ip': self.ip, - 'port': self.port, - } - if self.domain and self.domain.gateway_set.all(): - info["gateways"] = [d.id for d in self.domain.gateway_set.all()] - return info + @connectivity.setter + def connectivity(self, value): + key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id)) + cache.set(key, value, 3600*2) def get_auth_info(self): if self.admin_user: @@ -236,11 +212,20 @@ class Asset(OrgModelMixin): fake_node.is_node = False return fake_node + def to_json(self): + info = { + 'id': self.id, + 'hostname': self.hostname, + 'ip': self.ip, + 'port': self.port, + } + if self.domain and self.domain.gateway_set.all(): + info["gateways"] = [d.id for d in self.domain.gateway_set.all()] + return info + def _to_secret_json(self): """ - Ansible use it create inventory, First using asset user, - otherwise using cluster admin user - + Ansible use it create inventory Todo: May be move to ops implements it """ data = self.to_json() diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index af59f000c..37e099e99 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -29,6 +29,13 @@ class AssetUser(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) + UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3) + CONNECTIVITY_CHOICES = ( + (UNREACHABLE, _("Unreachable")), + (REACHABLE, _('Reachable')), + (UNKNOWN, _("Unknown")), + ) + @property def password(self): if self._password: diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index f5c8e17a1..147687471 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -14,7 +14,7 @@ from ..const import SYSTEM_USER_CONN_CACHE_KEY from .base import AssetUser -__all__ = ['AdminUser', 'SystemUser',] +__all__ = ['AdminUser', 'SystemUser'] logger = logging.getLogger(__name__) signer = get_signer() @@ -31,6 +31,7 @@ class AdminUser(AssetUser): become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) become_user = models.CharField(default='root', max_length=64) _become_pass = models.CharField(default='', max_length=128) + CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}' def __str__(self): return self.name @@ -67,6 +68,23 @@ class AdminUser(AssetUser): def assets_amount(self): return self.get_related_assets().count() + @property + def connectivity(self): + from .asset import Asset + assets = self.get_related_assets().values_list('id', 'hostname', flat=True) + data = { + 'unreachable': [], + 'reachable': [], + } + for asset_id, hostname in assets: + key = Asset.CONNECTIVITY_CACHE_KEY.format(str(self.id)) + value = cache.get(key, Asset.UNKNOWN) + if value == Asset.REACHABLE: + data['reachable'].append(hostname) + elif value == Asset.UNREACHABLE: + data['unreachable'].append(hostname) + return data + class Meta: ordering = ['name'] unique_together = [('name', 'org_id')] @@ -94,34 +112,34 @@ class AdminUser(AssetUser): class SystemUser(AssetUser): - SSH_PROTOCOL = 'ssh' - RDP_PROTOCOL = 'rdp' - TELNET_PROTOCOL = 'telnet' + PROTOCOL_SSH = 'ssh' + PROTOCOL_RDP = 'rdp' + PROTOCOL_TELNET = 'telnet' PROTOCOL_CHOICES = ( - (SSH_PROTOCOL, 'ssh'), - (RDP_PROTOCOL, 'rdp'), - (TELNET_PROTOCOL, 'telnet (beta)'), + (PROTOCOL_SSH, 'ssh'), + (PROTOCOL_RDP, 'rdp'), + (PROTOCOL_TELNET, 'telnet (beta)'), ) - AUTO_LOGIN = 'auto' - MANUAL_LOGIN = 'manual' + LOGIN_AUTO = 'auto' + LOGIN_MANUAL = 'manual' LOGIN_MODE_CHOICES = ( - (AUTO_LOGIN, _('Automatic login')), - (MANUAL_LOGIN, _('Manually login')) + (LOGIN_AUTO, _('Automatic login')), + (LOGIN_MANUAL, _('Manually login')) ) nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets")) - priority = models.IntegerField(default=20, verbose_name=_("Priority"), - validators=[MinValueValidator(1), MaxValueValidator(100)]) + priority = models.IntegerField(default=20, verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)]) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) - login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode')) + login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True) - cache_key = "__SYSTEM_USER_CACHED_{}" + SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}" + CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}' def __str__(self): return '{0.name}({0.username})'.format(self) @@ -136,34 +154,61 @@ class SystemUser(AssetUser): 'auto_push': self.auto_push, } - def get_assets(self): + def get_related_assets(self): assets = set(self.assets.all()) return assets @property - def assets_connective(self): - _result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {}) - return _result + def connectivity(self): + cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id)) + value = cache.get(cache_key, None) + if not value or 'unreachable' not in value: + return {'unreachable': [], 'reachable': []} + else: + return value + + @connectivity.setter + def connectivity(self, value): + data = self.connectivity + unreachable = data['unreachable'] + reachable = data['reachable'] + + for host in value.get('dark', {}).keys(): + if host not in unreachable: + unreachable.append(host) + if host in reachable: + reachable.remove(host) + for host in value.get('contacted'): + if host not in reachable: + reachable.append(host) + if host in unreachable: + unreachable.remove(host) + cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id)) + cache.set(cache_key, data, 3600) @property - def unreachable_assets(self): - return list(self.assets_connective.get('dark', {}).keys()) + def assets_unreachable(self): + return self.connectivity.get('unreachable') @property - def reachable_assets(self): - return self.assets_connective.get('contacted', []) + def assets_reachable(self): + return self.connectivity.get('reachable') + + @property + def login_mode_display(self): + return self.get_login_mode_display() def is_need_push(self): - if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL: + if self.auto_push and self.protocol == self.PROTOCOL_SSH: return True else: return False def set_cache(self): - cache.set(self.cache_key.format(self.id), self, 3600) + cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600) def expire_cache(self): - cache.delete(self.cache_key.format(self.id)) + cache.delete(self.SYSTEM_USER_CACHE_KEY.format(self.id)) @property def cmd_filter_rules(self): @@ -184,7 +229,7 @@ class SystemUser(AssetUser): @classmethod def get_system_user_by_id_or_cached(cls, sid): - cached = cache.get(cls.cache_key.format(sid)) + cached = cache.get(cls.SYSTEM_USER_CACHE_KEY.format(sid)) if cached: return cached try: diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 1066ae0b7..9640aff7f 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -9,7 +9,7 @@ from .system_user import AssetSystemUserSerializer __all__ = [ 'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer', - 'AssetAsNodeSerializer', + 'AssetAsNodeSerializer', 'AssetSimpleSerializer', ] @@ -33,7 +33,7 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) fields.extend([ - 'hardware_info', 'is_connective', 'org_name' + 'hardware_info', 'connectivity', 'org_name' ]) return fields @@ -78,3 +78,9 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer): "is_active", "system_users_join", "org_name", "os", "platform", "comment", "org_id", "protocol" ) + + +class AssetSimpleSerializer(serializers.ModelSerializer): + class Meta: + model = Asset + fields = ['id', 'hostname', 'port', 'ip', 'connectivity'] diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index a295f245c..be1f594ec 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from ..models import SystemUser +from ..models import SystemUser, Asset from .base import AuthSerializer @@ -21,17 +21,17 @@ class SystemUserSerializer(serializers.ModelSerializer): def get_field_names(self, declared_fields, info): fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) fields.extend([ - 'get_login_mode_display', + 'login_mode_display', ]) return fields @staticmethod def get_unreachable_assets(obj): - return obj.unreachable_assets + return obj.assets_unreachable @staticmethod def get_reachable_assets(obj): - return obj.reachable_assets + return obj.assets_reachable def get_unreachable_amount(self, obj): return len(self.get_unreachable_assets(obj)) @@ -41,7 +41,7 @@ class SystemUserSerializer(serializers.ModelSerializer): @staticmethod def get_assets_amount(obj): - return len(obj.get_assets()) + return len(obj.get_related_assets()) class SystemUserAuthSerializer(AuthSerializer): @@ -75,4 +75,7 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer): """ class Meta: model = SystemUser - fields = ('id', 'name', 'username') \ No newline at end of file + fields = ('id', 'name', 'username') + + + diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 08ee6e670..85156c60d 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -7,7 +7,7 @@ from django.dispatch import receiver from common.utils import get_logger from .models import Asset, SystemUser, Node from .tasks import update_assets_hardware_info_util, \ - test_asset_connectability_util, push_system_user_to_assets + test_asset_connectivity_util, push_system_user_to_assets logger = get_logger(__file__) @@ -19,8 +19,8 @@ def update_asset_hardware_info_on_created(asset): def test_asset_conn_on_created(asset): - logger.debug("Test asset `{}` connectability".format(asset)) - test_asset_connectability_util.delay([asset]) + logger.debug("Test asset `{}` connectivity".format(asset)) + test_asset_connectivity_util.delay([asset]) def set_asset_root_node(asset): diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 846964d94..0e6a6ec99 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -26,6 +26,23 @@ disk_pattern = re.compile(r'^hd|sd|xvd|vd') PERIOD_TASK = os.environ.get("PERIOD_TASK", "off") +def clean_hosts(assets): + clean_assets = [] + for asset in assets: + if not asset.is_active: + msg = _("Asset has been disabled, skipped: {}").format(asset) + logger.info(msg) + continue + if not asset.support_ansible(): + msg = _("Asset may not be support ansible, skipped: {}").format(asset) + logger.info(msg) + continue + clean_assets.append(asset) + if not clean_assets: + logger.info(_("No assets matched, stop task")) + return clean_assets + + @shared_task def set_assets_hardware_info(assets, result, **kwargs): """ @@ -60,9 +77,12 @@ def set_assets_hardware_info(assets, result, **kwargs): ___cpu_model = 'Unknown' ___cpu_model = ___cpu_model[:64] ___cpu_count = info.get('ansible_processor_count', 0) - ___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', [])) + ___cpu_cores = info.get('ansible_processor_cores', None) or \ + len(info.get('ansible_processor', [])) ___cpu_vcpus = info.get('ansible_processor_vcpus', 0) - ___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb'))) + ___memory = '%s %s' % capacity_convert( + '{} MB'.format(info.get('ansible_memtotal_mb')) + ) disk_info = {} for dev, dev_info in info.get('ansible_devices', {}).items(): if disk_pattern.match(dev) and dev_info['removable'] == '0': @@ -96,19 +116,8 @@ def update_assets_hardware_info_util(assets, task_name=None): if task_name is None: task_name = _("Update some assets hardware info") tasks = const.UPDATE_ASSETS_HARDWARE_TASKS - hosts = [] - for asset in assets: - if not asset.is_active: - msg = _("Asset has been disabled, skipped: {}").format(asset) - logger.info(msg) - continue - if not asset.support_ansible(): - msg = _("Asset may not be support ansible, skipped: {}").format(asset) - logger.info(msg) - continue - hosts.append(asset) + hosts = clean_hosts(assets) if not hosts: - logger.info(_("No assets matched, stop task")) return {} created_by = str(assets[0].org_id) task, created = update_or_create_ansible_task( @@ -125,7 +134,6 @@ def update_assets_hardware_info_util(assets, task_name=None): @shared_task def update_asset_hardware_info_manual(asset): task_name = _("Update asset hardware info: {}").format(asset.hostname) - # task_name = _("更新资产硬件信息") return update_assets_hardware_info_util( [asset], task_name=task_name ) @@ -141,123 +149,18 @@ def update_assets_hardware_info_period(): logger.debug("Period task disabled, update assets hardware info pass") return - # from ops.utils import update_or_create_ansible_task - # from orgs.models import Organization - # orgs = Organization.objects.all().values_list('id', flat=True) - # orgs.append('') - # task_name = _("Update assets hardware info period") - # for org_id in orgs: - # org_id = str(org_id) - # hostname_list = [ - # asset for asset in Asset.objects.all() - # if asset.is_active and asset.is_unixlike() - # ] - # tasks = const.UPDATE_ASSETS_HARDWARE_TASKS - # - # # Only create, schedule by celery beat - # update_or_create_ansible_task( - # task_name, hosts=hostname_list, tasks=tasks, pattern='all', - # options=const.TASK_OPTIONS, run_as_admin=True, created_by='System', - # interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name, - # ) - ## ADMIN USER CONNECTIVE ## -def set_admin_user_connectability_info(result, **kwargs): - admin_user = kwargs.get("admin_user") - task_name = kwargs.get("task_name") - if admin_user is None and task_name is not None: - admin_user = task_name.split(":")[-1] - - raw, summary = result - cache_key = const.ADMIN_USER_CONN_CACHE_KEY.format(admin_user) - cache.set(cache_key, summary, CACHE_MAX_TIME) - - for i in summary.get('contacted', []): - asset_conn_cache_key = const.ASSET_ADMIN_CONN_CACHE_KEY.format(i) - cache.set(asset_conn_cache_key, 1, CACHE_MAX_TIME) - - for i, msg in summary.get('dark', {}).items(): - asset_conn_cache_key = const.ASSET_ADMIN_CONN_CACHE_KEY.format(i) - cache.set(asset_conn_cache_key, 0, CACHE_MAX_TIME) - logger.error(msg) - @shared_task -def test_admin_user_connectability_util(admin_user, task_name): - """ - Test asset admin user can connect or not. Using ansible api do that - :param admin_user: - :param task_name: - :return: - """ - from ops.utils import update_or_create_ansible_task - - assets = admin_user.get_related_assets() - hosts = [] - for asset in assets: - if not asset.is_active: - msg = _("Asset has been disabled, skipped: {}").format(asset) - logger.info(msg) - continue - if not asset.support_ansible(): - msg = _("Asset may not be support ansible, skipped: {}").format(asset) - logger.info(msg) - continue - hosts.append(asset) - if not hosts: - logger.info(_("No assets matched, stop task")) - return {} - tasks = const.TEST_ADMIN_USER_CONN_TASKS - task, created = update_or_create_ansible_task( - task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, created_by=admin_user.org_id, - ) - result = task.run() - set_admin_user_connectability_info(result, admin_user=admin_user.name) - return result - - -@shared_task -@register_as_period_task(interval=3600) -def test_admin_user_connectability_period(): - """ - A period task that update the ansible task period - """ - admin_users = AdminUser.objects.all() - for admin_user in admin_users: - task_name = _("Test admin user connectability period: {}").format(admin_user.name) - test_admin_user_connectability_util(admin_user, task_name) - - -@shared_task -def test_admin_user_connectability_manual(admin_user): - task_name = _("Test admin user connectability: {}").format(admin_user.name) - # task_name = _("测试管理行号可连接性: {}").format(admin_user.name) - return test_admin_user_connectability_util(admin_user, task_name) - - -@shared_task -def test_asset_connectability_util(assets, task_name=None): +def test_asset_connectivity_util(assets, task_name=None): from ops.utils import update_or_create_ansible_task if task_name is None: - task_name = _("Test assets connectability") - # task_name = _("测试资产可连接性") - hosts = [] - for asset in assets: - if not asset.is_active: - msg = _("Asset has been disabled, skip: {}").format(asset) - logger.info(msg) - continue - if not asset.support_ansible(): - msg = _("Asset may not be support ansible, skip: {}").format(asset) - logger.info(msg) - continue - hosts.append(asset) + task_name = _("Test assets connectivity") + hosts = clean_hosts(assets) if not hosts: - logger.info(_("No assets, task stop")) return {} tasks = const.TEST_ADMIN_USER_CONN_TASKS created_by = assets[0].org_id @@ -267,18 +170,20 @@ def test_asset_connectability_util(assets, task_name=None): ) result = task.run() summary = result[1] - for k in summary.get('dark'): - cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 0, CACHE_MAX_TIME) - - for k in summary.get('contacted'): - cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 1, CACHE_MAX_TIME) + for asset in assets: + if asset.hostname in summary.get('dark', {}): + asset.connectivity = asset.UNREACHABLE + elif asset.hostname in summary.get('contacted', []): + asset.connectivity = asset.REACHABLE + else: + asset.connectivity = asset.UNKNOWN return summary @shared_task -def test_asset_connectability_manual(asset): - task_name = _("Test assets connectability: {}").format(asset) - summary = test_asset_connectability_util([asset], task_name=task_name) +def test_asset_connectivity_manual(asset): + task_name = _("Test assets connectivity: {}").format(asset) + summary = test_asset_connectivity_util([asset], task_name=task_name) if summary.get('dark'): return False, summary['dark'] @@ -286,21 +191,50 @@ def test_asset_connectability_manual(asset): return True, "" +@shared_task +def test_admin_user_connectivity_util(admin_user, task_name): + """ + Test asset admin user can connect or not. Using ansible api do that + :param admin_user: + :param task_name: + :return: + """ + assets = admin_user.get_related_assets() + hosts = clean_hosts(assets) + if not hosts: + return {} + summary = test_asset_connectivity_util(hosts, task_name) + return summary + + +@shared_task +@register_as_period_task(interval=3600) +def test_admin_user_connectivity_period(): + """ + A period task that update the ansible task period + """ + admin_users = AdminUser.objects.all() + for admin_user in admin_users: + task_name = _("Test admin user connectivity period: {}").format(admin_user.name) + test_admin_user_connectivity_util(admin_user, task_name) + + +@shared_task +def test_admin_user_connectivity_manual(admin_user): + task_name = _("Test admin user connectivity: {}").format(admin_user.name) + return test_admin_user_connectivity_util(admin_user, task_name) + + ## System user connective ## @shared_task -def set_system_user_connectablity_info(result, **kwargs): +def set_system_user_connectivity_info(system_user, result): summary = result[1] - task_name = kwargs.get("task_name") - system_user = kwargs.get("system_user") - if system_user is None: - system_user = task_name.split(":")[-1] - cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(str(system_user.id)) - cache.set(cache_key, summary, CACHE_MAX_TIME) + system_user.connectivity = summary @shared_task -def test_system_user_connectability_util(system_user, assets, task_name): +def test_system_user_connectivity_util(system_user, assets, task_name): """ Test system cant connect his assets or not. :param system_user: @@ -309,20 +243,9 @@ def test_system_user_connectability_util(system_user, assets, task_name): :return: """ from ops.utils import update_or_create_ansible_task - hosts = [] tasks = const.TEST_SYSTEM_USER_CONN_TASKS - for asset in assets: - if not asset.is_active: - msg = _("Asset has been disabled, skip: {}").format(asset) - logger.info(msg) - continue - if not asset.support_ansible(): - msg = _("Asset may not be support ansible, skip: {}").format(asset) - logger.info(msg) - continue - hosts.append(asset) + hosts = clean_hosts(assets) if not hosts: - logger.info(_("No assets matched, stop task")) return {} task, created = update_or_create_ansible_task( task_name, hosts=hosts, tasks=tasks, pattern='all', @@ -330,36 +253,35 @@ def test_system_user_connectability_util(system_user, assets, task_name): run_as=system_user, created_by=system_user.org_id, ) result = task.run() - set_system_user_connectablity_info(result, system_user=system_user) + set_system_user_connectivity_info(system_user, result) return result @shared_task -def test_system_user_connectability_manual(system_user): - task_name = _("Test system user connectability: {}").format(system_user) - assets = system_user.get_assets() - return test_system_user_connectability_util(system_user, assets, task_name) +def test_system_user_connectivity_manual(system_user): + task_name = _("Test system user connectivity: {}").format(system_user) + assets = system_user.get_related_assets() + return test_system_user_connectivity_util(system_user, assets, task_name) @shared_task -def test_system_user_connectability_a_asset(system_user, asset): - task_name = _("Test system user connectability: {} => {}").format( +def test_system_user_connectivity_a_asset(system_user, asset): + task_name = _("Test system user connectivity: {} => {}").format( system_user, asset ) - return test_system_user_connectability_util(system_user, [asset], task_name) + return test_system_user_connectivity_util(system_user, [asset], task_name) @shared_task -def test_system_user_connectability_period(): +def test_system_user_connectivity_period(): if PERIOD_TASK != "on": - logger.debug("Period task disabled, test system user connectability pass") + logger.debug("Period task disabled, test system user connectivity pass") return - # Todo: 暂时禁用定期测试 - # system_users = SystemUser.objects.all() - # for system_user in system_users: - # task_name = _("Test system user connectability period: {}").format(system_user) - # # task_name = _("定期测试系统用户可连接性: {}".format(system_user)) - # test_system_user_connectability_util(system_user, task_name) + system_users = SystemUser.objects.all() + for system_user in system_users: + task_name = _("Test system user connectivity period: {}").format(system_user) + assets = system_user.get_related_assets() + test_system_user_connectivity_util(system_user, assets, task_name) #### Push system user tasks #### @@ -381,6 +303,24 @@ def get_push_system_user_tasks(system_user): ), } }) + tasks.extend([ + { + 'name': 'Check home dir exists', + 'action': { + 'module': 'stat', + 'args': 'path=/home/{}'.format(system_user.username) + }, + 'register': 'home_existed' + }, + { + 'name': "Set home dir permission", + 'action': { + 'module': 'file', + 'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username) + }, + 'when': 'home_existed.stat.exists == true' + } + ]) if system_user.public_key: tasks.append({ 'name': 'Set {} authorized key'.format(system_user.username), @@ -417,19 +357,8 @@ def push_system_user_util(system_user, assets, task_name): return tasks = get_push_system_user_tasks(system_user) - hosts = [] - for asset in assets: - if not asset.is_active: - msg = _("Asset has been disabled, skip: {}").format(asset) - logger.info(msg) - continue - if not asset.support_ansible(): - msg = _("Asset may not be support ansible, skip: {}").format(asset) - logger.info(msg) - continue - hosts.append(asset) + hosts = clean_hosts(assets) if not hosts: - logger.info(_("No assets matched, stop task")) return {} task, created = update_or_create_ansible_task( task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', @@ -441,7 +370,7 @@ def push_system_user_util(system_user, assets, task_name): @shared_task def push_system_user_to_assets_manual(system_user): - assets = system_user.get_assets() + assets = system_user.get_related_assets() task_name = _("Push system users to assets: {}").format(system_user.name) return push_system_user_util(system_user, assets, task_name=task_name) diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index 31314392f..7a9882563 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -45,13 +45,11 @@ - + @@ -91,26 +89,36 @@ {% endblock %} diff --git a/apps/ops/templates/ops/adhoc_history_detail.html b/apps/ops/templates/ops/adhoc_history_detail.html index b8a48d4e5..5091470d5 100644 --- a/apps/ops/templates/ops/adhoc_history_detail.html +++ b/apps/ops/templates/ops/adhoc_history_detail.html @@ -19,7 +19,7 @@ {% trans 'Run history detail' %}
  • - {% trans 'Output' %} + {% trans 'Output' %}
  • @@ -141,4 +141,14 @@ {% include 'users/_user_update_pk_modal.html' %} {% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/ops/templates/ops/task_adhoc.html b/apps/ops/templates/ops/task_adhoc.html index 82c25e667..34e0797e6 100644 --- a/apps/ops/templates/ops/task_adhoc.html +++ b/apps/ops/templates/ops/task_adhoc.html @@ -25,7 +25,7 @@ {% trans 'Run history' %}
  • - {% trans 'Last run output' %} + {% trans 'Last run output' %}
  • @@ -78,52 +78,60 @@ {% endblock %} {% block custom_foot_js %} - + $(td).html(cellData); + }}, + {targets: 2, createdCell: function (td, cellData, rowData) { + var dataLength = cellData.length; + $(td).html(dataLength); + }}, + {targets: 4, createdCell: function (td, cellData) { + if (!cellData) { + $(td).html("Admin") + } else { + $(td).html(cellData) + } + }}, + {targets: 5, createdCell: function (td, cellData, rowData) { + if (!cellData) { + $(td).html("") + } else { + $(td).html(cellData.user) + } + }}, + {targets: 6, createdCell: function (td, cellData) { + var d = new Date(cellData); + $(td).html(d.toLocaleString()) + }}, + {targets: 7, createdCell: function (td, cellData, rowData) { + var detail_btn = '{% trans "Detail" %}'.replace('{{ DEFAULT_PK }}', cellData); + if (cellData) { + $(td).html(detail_btn); + } + }} + ], + ajax_url: '{% url "api-ops:adhoc-list" %}?task={{ object.pk }}', + columns: [{data: function(){return ""}}, {data: "short_id" }, {data: "hosts"}, {data: "pattern"}, + {data: "run_as"}, {data: "become"}, {data: "date_created"}, {data: "id"}] + }; + jumpserver.initDataTable(options); +}).on('click', '.celery-task-log', function () { + var history_pk = "{{ object.latest_history.pk }}"; + if (!history_pk) { + alert("没有运行历史"); + return + } + var url = '{% url 'ops:celery-task-log' pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', history_pk); + window.open(url, '', 'width=800,height=600,left=400,top=400') +}) + {% endblock %} diff --git a/apps/ops/templates/ops/task_detail.html b/apps/ops/templates/ops/task_detail.html index d8f9520be..bbeaad660 100644 --- a/apps/ops/templates/ops/task_detail.html +++ b/apps/ops/templates/ops/task_detail.html @@ -26,7 +26,7 @@ {% trans 'Run history' %}
  • - {% trans 'Last run output' %} + {% trans 'Last run output' %}
  • @@ -165,4 +165,19 @@ {% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/ops/templates/ops/task_history.html b/apps/ops/templates/ops/task_history.html index 184987766..9a63acc6a 100644 --- a/apps/ops/templates/ops/task_history.html +++ b/apps/ops/templates/ops/task_history.html @@ -25,7 +25,7 @@ {% trans 'Run history' %}
  • - {% trans 'Last run output' %} + {% trans 'Last run output' %}
  • @@ -148,6 +148,14 @@ function initTable() { $(document).ready(function () { initTable(); +}).on('click', '.celery-task-log', function () { + var history_pk = "{{ object.latest_history.pk }}"; + if (!history_pk) { + alert("没有运行历史"); + return + } + var url = '{% url 'ops:celery-task-log' pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', history_pk); + window.open(url, '', 'width=800,height=600,left=400,top=400') }) diff --git a/config_example.py b/config_example.py index e37df23b0..5f5301f0e 100644 --- a/config_example.py +++ b/config_example.py @@ -78,7 +78,7 @@ class Config: REDIS_HOST = '127.0.0.1' REDIS_PORT = 6379 # REDIS_PASSWORD = '' - # REDIS_DB_CELERY_BROKER = 3 + # REDIS_DB_CELERY = 3 # REDIS_DB_CACHE = 4 # Use OpenID authorization From b2aef87fdd1a3099d95d44288e148f0ccabf4ec2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 21 Dec 2018 10:35:17 +0800 Subject: [PATCH 63/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=BF=83?= =?UTF-8?q?=E8=B7=B3=E5=81=B6=E4=BA=BA=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index d9eeb48cf..8f6a1213f 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -316,7 +316,7 @@ defaults = { 'EMAIL_SUFFIX': 'jumpserver.org', 'TERMINAL_PASSWORD_AUTH': True, 'TERMINAL_PUBLIC_KEY_AUTH': True, - 'TERMINAL_HEARTBEAT_INTERVAL': 5, + 'TERMINAL_HEARTBEAT_INTERVAL': 30, 'TERMINAL_ASSET_LIST_SORT_BY': 'hostname', 'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto', 'TERMINAL_SESSION_KEEP_DURATION': 9999, From 7ff39259af62d18a5c4d43b9e97d3486675a9661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Fri, 21 Dec 2018 14:57:42 +0800 Subject: [PATCH 64/80] Required opt (#2246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 修改心跳偶人时间 * [Update] 去掉required的label * [Update] 修改默认心跳时间 * [Update] 去掉默认的placeholder * [Update] 修改utils --- apps/assets/forms/asset.py | 9 +-------- apps/assets/forms/domain.py | 4 ---- apps/assets/forms/user.py | 5 ----- .../templates/common/command_storage_create.html | 1 - .../templates/common/replay_storage_create.html | 1 - apps/jumpserver/conf.py | 2 +- apps/jumpserver/settings.py | 3 ++- apps/perms/utils.py | 3 +-- apps/static/css/jumpserver.css | 5 ++++- apps/users/forms.py | 14 -------------- 10 files changed, 9 insertions(+), 38 deletions(-) diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py index 1ec2ea24f..0a6b2f093 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -41,9 +41,6 @@ class AssetCreateForm(OrgModelForm): 'nodes': _("Node"), } help_texts = { - 'hostname': '* required', - 'ip': '* required', - 'port': '* required', 'admin_user': _( 'root or other NOPASSWD sudo privilege user existed in asset,' 'If asset is windows or other set any one, more see admin user left menu' @@ -80,10 +77,6 @@ class AssetUpdateForm(OrgModelForm): 'nodes': _("Node"), } help_texts = { - 'hostname': '* required', - 'ip': '* required', - 'port': '* required', - 'cluster': '* required', 'admin_user': _( 'root or other NOPASSWD sudo privilege user existed in asset,' 'If asset is windows or other set any one, more see admin user left menu' @@ -95,7 +88,7 @@ class AssetUpdateForm(OrgModelForm): class AssetBulkUpdateForm(OrgModelForm): assets = forms.ModelMultipleChoiceField( - required=True, help_text='* required', + required=True, label=_('Select assets'), queryset=Asset.objects.all(), widget=forms.SelectMultiple( attrs={ diff --git a/apps/assets/forms/domain.py b/apps/assets/forms/domain.py index 3c733bcc4..635796c97 100644 --- a/apps/assets/forms/domain.py +++ b/apps/assets/forms/domain.py @@ -61,7 +61,3 @@ class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm): 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), } - help_texts = { - 'name': '* required', - 'username': '* required', - } diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py index 81138eab5..575d3e59c 100644 --- a/apps/assets/forms/user.py +++ b/apps/assets/forms/user.py @@ -80,10 +80,6 @@ class AdminUserForm(PasswordAndKeyAuthForm): 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), } - help_texts = { - 'name': '* required', - 'username': '* required', - } class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm): @@ -150,7 +146,6 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm): }), } help_texts = { - 'name': '* required', 'auto_push': _('Auto push system user to asset'), 'priority': _('1-100, High level will be using login asset as default, ' 'if user was granted more than 2 system user'), diff --git a/apps/common/templates/common/command_storage_create.html b/apps/common/templates/common/command_storage_create.html index a3a83f2e7..671b11c94 100644 --- a/apps/common/templates/common/command_storage_create.html +++ b/apps/common/templates/common/command_storage_create.html @@ -41,7 +41,6 @@
    -
    * required
    diff --git a/apps/common/templates/common/replay_storage_create.html b/apps/common/templates/common/replay_storage_create.html index 8b2ab8596..af56c210d 100644 --- a/apps/common/templates/common/replay_storage_create.html +++ b/apps/common/templates/common/replay_storage_create.html @@ -44,7 +44,6 @@
    -
    * required
    diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index d9eeb48cf..61fd84249 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -316,7 +316,7 @@ defaults = { 'EMAIL_SUFFIX': 'jumpserver.org', 'TERMINAL_PASSWORD_AUTH': True, 'TERMINAL_PUBLIC_KEY_AUTH': True, - 'TERMINAL_HEARTBEAT_INTERVAL': 5, + 'TERMINAL_HEARTBEAT_INTERVAL': 20, 'TERMINAL_ASSET_LIST_SORT_BY': 'hostname', 'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto', 'TERMINAL_SESSION_KEEP_DURATION': 9999, diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 167de3180..ca3d5afa3 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -499,8 +499,9 @@ BOOTSTRAP3 = { # Field class to use in horizontal forms 'horizontal_field_class': 'col-md-9', # Set placeholder attributes to label if no placeholder is provided - 'set_placeholder': True, + 'set_placeholder': False, 'success_css_class': '', + 'required_css_class': 'required', } TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION diff --git a/apps/perms/utils.py b/apps/perms/utils.py index ef8e3d09a..45ec6e039 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -18,8 +18,7 @@ class GenerateTree: "asset_instance": set("system_user") } """ - self.__all_nodes = Node.objects.all() - self.__node_asset_map = defaultdict(set) + self.__all_nodes = list(Node.objects.all()) self.nodes = defaultdict(dict) def add_asset(self, asset, system_users): diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css index 2a6f06e8c..c979678e0 100644 --- a/apps/static/css/jumpserver.css +++ b/apps/static/css/jumpserver.css @@ -438,4 +438,7 @@ div.dataTables_wrapper div.dataTables_filter { white-space: nowrap; } - +.form-group .required .control-label:after { + content:"*"; + color:red; +} diff --git a/apps/users/forms.py b/apps/users/forms.py index fb492412f..521bc13ae 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -63,11 +63,6 @@ class UserCreateUpdateForm(OrgModelForm): 'username', 'name', 'email', 'groups', 'wechat', 'phone', 'role', 'date_expired', 'comment', 'otp_level' ] - help_texts = { - 'username': '* required', - 'name': '* required', - 'email': '* required', - } widgets = { 'otp_level': forms.RadioSelect(), 'groups': forms.SelectMultiple( @@ -141,11 +136,6 @@ class UserProfileForm(forms.ModelForm): 'username', 'name', 'email', 'wechat', 'phone', ] - help_texts = { - 'username': '* required', - 'name': '* required', - 'email': '* required', - } UserProfileForm.verbose_name = _("Profile") @@ -263,7 +253,6 @@ UserPublicKeyForm.verbose_name = _("Public key") class UserBulkUpdateForm(OrgModelForm): users = forms.ModelMultipleChoiceField( required=True, - help_text='* required', label=_('Select users'), queryset=User.objects.all(), widget=forms.SelectMultiple( @@ -346,9 +335,6 @@ class UserGroupForm(OrgModelForm): fields = [ 'name', 'users', 'comment', ] - help_texts = { - 'name': '* required' - } class FileForm(forms.Form): From ac9178cb9399a22d75056180dd8c7bb65bb3349d Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Fri, 21 Dec 2018 14:59:00 +0800 Subject: [PATCH 65/80] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0deb=5Frequir?= =?UTF-8?q?ements=E4=BE=9D=E8=B5=96=20(#2239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/deb_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/deb_requirements.txt b/requirements/deb_requirements.txt index 07b3441c4..f32a217e6 100644 --- a/requirements/deb_requirements.txt +++ b/requirements/deb_requirements.txt @@ -1 +1 @@ -libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite libkrb5-dev sshpass +libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite libkrb5-dev sshpass libmysqlclient-dev From e7725e691045a4bbec4fa6eec6ee5937bb7bac14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Fri, 21 Dec 2018 15:47:52 +0800 Subject: [PATCH 66/80] Bugfix (#2252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 修改心跳偶人时间 * [Update] 修改Node比较 * [Bugfix] 修复bug --- apps/common/models.py | 4 ++-- apps/common/tree.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/common/models.py b/apps/common/models.py index cb97b8988..012cd7351 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -45,10 +45,10 @@ class Setting(models.Model): def cleaned_value(self): try: value = self.value - if not isinstance(value, (str, bytes)): - return value if self.encrypted: value = signer.unsign(value) + if not value: + return None value = json.loads(value) return value except json.JSONDecodeError: diff --git a/apps/common/tree.py b/apps/common/tree.py index 6b57a3db4..13756a35d 100644 --- a/apps/common/tree.py +++ b/apps/common/tree.py @@ -49,7 +49,9 @@ class TreeNode: return False elif not self.isParent and other.isParent: return True - return self.id > other.id + if self.pId != other.pId: + return self.pId > other.pId + return self.name > other.name def __eq__(self, other): return self.id == other.id From 94d65255489d903cb6c8ab1db4f97f92b5aa9d14 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 21 Dec 2018 15:57:23 +0800 Subject: [PATCH 67/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E5=B0=8Fbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/common/utils.py b/apps/common/utils.py index 13985d300..71913d196 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -391,6 +391,8 @@ def get_request_ip(request): def get_command_storage_setting(): default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE value = settings.TERMINAL_COMMAND_STORAGE + if not value: + return default value.update(default) return value @@ -398,6 +400,8 @@ def get_command_storage_setting(): def get_replay_storage_setting(): default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE value = settings.TERMINAL_REPLAY_STORAGE + if not value: + return default value.update(default) return value From 8e93bfecb07e66a2c1129815e15ef251fd0e328f Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Mon, 24 Dec 2018 15:50:59 +0800 Subject: [PATCH 68/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8DDEFAULT?= =?UTF-8?q?=E7=BB=84=E7=BB=87=E4=B8=8B=EF=BC=8C=E6=89=B9=E9=87=8F=E5=88=A0?= =?UTF-8?q?=E9=99=A4(=E6=9F=90=E7=BB=84=E7=BB=87=E4=B8=8B)=E4=B8=80?= =?UTF-8?q?=E9=83=A8=E5=88=86=E7=94=A8=E6=88=B7=E5=A4=B1=E8=B4=A5=E7=9A=84?= =?UTF-8?q?bug=20(#2261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index f838554e1..a1119e3a7 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -47,7 +47,7 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet): return super().get_permissions() def allow_bulk_destroy(self, qs, filtered): - return qs.count() == filtered.count() + return qs.count() != filtered.count() class UserChangePasswordApi(generics.RetrieveUpdateAPIView): From dab692c0eb61a41ce79cb0364ebb711e03380911 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 25 Dec 2018 09:58:01 +0800 Subject: [PATCH 69/80] =?UTF-8?q?[Update]=20=E9=99=90=E5=88=B6=E7=BB=88?= =?UTF-8?q?=E7=AB=AF=E8=AE=BE=E7=BD=AE=E4=B8=AD=E5=BF=83=E8=B7=B3=E9=97=B4?= =?UTF-8?q?=E9=9A=94=E5=92=8C=E4=BC=9A=E8=AF=9D=E4=BF=9D=E7=95=99=E6=97=B6?= =?UTF-8?q?=E9=95=BF=E7=9A=84form=E6=9C=80=E5=B0=8F=E5=80=BC=20(#2262)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 限制终端设置中心跳间隔和会话保留时长的form最小值 * [Update] 删除terminal forms表单的初始化值 * [Update] 取消安全设置中forms的初始化值,并采用默认值;添加密码过期时间的最大值限制 --- apps/common/forms.py | 44 +++++++++++++++++------------------------ apps/jumpserver/conf.py | 10 ++++++++++ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/apps/common/forms.py b/apps/common/forms.py index f29d19ec3..33ee48f03 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -139,22 +139,23 @@ class TerminalSettingForm(BaseForm): (50, 50), ) TERMINAL_PASSWORD_AUTH = forms.BooleanField( - initial=True, required=False, label=_("Password auth") + required=False, label=_("Password auth") ) TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField( - initial=True, required=False, label=_("Public key auth") + required=False, label=_("Public key auth") ) TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField( - initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds") + min_value=5, label=_("Heartbeat interval"), + help_text=_("Units: seconds") ) TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( - choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by") + choices=SORT_BY_CHOICES, label=_("List sort by") ) TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField( - choices=PAGE_SIZE_CHOICES, initial='auto', label=_("List page size"), + choices=PAGE_SIZE_CHOICES, label=_("List page size"), ) TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField( - label=_("Session keep duration"), + min_value=1, label=_("Session keep duration"), help_text=_("Units: days, Session, record, command will be delete " "if more than duration, only in database") ) @@ -167,8 +168,7 @@ class TerminalCommandStorage(BaseForm): class SecuritySettingForm(BaseForm): # MFA global setting SECURITY_MFA_AUTH = forms.BooleanField( - initial=False, required=False, - label=_("MFA Secondary certification"), + required=False, label=_("MFA Secondary certification"), help_text=_( 'After opening, the user login must use MFA secondary ' 'authentication (valid for all users, including administrators)' @@ -176,13 +176,11 @@ class SecuritySettingForm(BaseForm): ) # limit login count SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField( - initial=7, min_value=3, - label=_("Limit the number of login failures") + min_value=3, label=_("Limit the number of login failures") ) # limit login time SECURITY_LOGIN_LIMIT_TIME = forms.IntegerField( - initial=30, min_value=5, - label=_("No logon interval"), + min_value=5, label=_("No logon interval"), help_text=_( "Tip: (unit/minute) if the user has failed to log in for a limited " "number of times, no login is allowed during this time interval." @@ -190,8 +188,7 @@ class SecuritySettingForm(BaseForm): ) # ssh max idle time SECURITY_MAX_IDLE_TIME = forms.IntegerField( - initial=30, required=False, - label=_("Connection max idle time"), + required=False, label=_("Connection max idle time"), help_text=_( 'If idle time more than it, disconnect connection(only ssh now) ' 'Unit: minute' @@ -199,8 +196,8 @@ class SecuritySettingForm(BaseForm): ) # password expiration time SECURITY_PASSWORD_EXPIRATION_TIME = forms.IntegerField( - initial=9999, label=_("Password expiration time"), - min_value=1, + label=_("Password expiration time"), + min_value=1, max_value=99999, help_text=_( "Tip: (unit: day) " "If the user does not update the password during the time, " @@ -211,35 +208,30 @@ class SecuritySettingForm(BaseForm): ) # min length SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField( - initial=6, label=_("Password minimum length"), - min_value=6 + min_value=6, label=_("Password minimum length"), ) # upper case SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField( - initial=False, required=False, - label=_("Must contain capital letters"), + required=False, label=_("Must contain capital letters"), help_text=_( 'After opening, the user password changes ' 'and resets must contain uppercase letters') ) # lower case SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField( - initial=False, required=False, - label=_("Must contain lowercase letters"), + required=False, label=_("Must contain lowercase letters"), help_text=_('After opening, the user password changes ' 'and resets must contain lowercase letters') ) # number SECURITY_PASSWORD_NUMBER = forms.BooleanField( - initial=False, required=False, - label=_("Must contain numeric characters"), + required=False, label=_("Must contain numeric characters"), help_text=_('After opening, the user password changes ' 'and resets must contain numeric characters') ) # special char SECURITY_PASSWORD_SPECIAL_CHAR = forms.BooleanField( - initial=False, required=False, - label=_("Must contain special characters"), + required=False, label=_("Must contain special characters"), help_text=_('After opening, the user password changes ' 'and resets must contain special characters') ) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 61fd84249..ed19f3567 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -320,6 +320,16 @@ defaults = { 'TERMINAL_ASSET_LIST_SORT_BY': 'hostname', 'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto', 'TERMINAL_SESSION_KEEP_DURATION': 9999, + 'SECURITY_MFA_AUTH': False, + 'SECURITY_LOGIN_LIMIT_COUNT': 7, + 'SECURITY_LOGIN_LIMIT_TIME': 30, + 'SECURITY_MAX_IDLE_TIME': 30, + 'SECURITY_PASSWORD_EXPIRATION_TIME': 9999, + 'SECURITY_PASSWORD_MIN_LENGTH': 6, + 'SECURITY_PASSWORD_UPPER_CASE': False, + 'SECURITY_PASSWORD_LOWER_CASE': False, + 'SECURITY_PASSWORD_NUMBER': False, + 'SECURITY_PASSWORD_SPECIAL_CHAR': False, } From a990098744144c6db1f1d0b387959ea8ba55b62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 25 Dec 2018 13:33:37 +0800 Subject: [PATCH 70/80] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E9=80=89=E6=8B=A9=20(#2267)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 优化资产选择 * [Update] 优化资产任务 --- apps/assets/forms/domain.py | 9 +++++++++ apps/assets/forms/label.py | 9 +++++++++ apps/assets/tasks.py | 12 +++++++++--- .../templates/assets/domain_create_update.html | 13 ++++++++++++- .../templates/assets/label_create_update.html | 12 ++++++++++++ apps/perms/forms.py | 10 ++++++++++ .../perms/asset_permission_create_update.html | 14 +++++++++++++- apps/static/js/jumpserver.js | 2 ++ apps/terminal/api/v1/terminal.py | 2 +- 9 files changed, 77 insertions(+), 6 deletions(-) diff --git a/apps/assets/forms/domain.py b/apps/assets/forms/domain.py index 635796c97..25295782a 100644 --- a/apps/assets/forms/domain.py +++ b/apps/assets/forms/domain.py @@ -28,6 +28,15 @@ class DomainForm(forms.ModelForm): initial['assets'] = kwargs['instance'].assets.all() super().__init__(*args, **kwargs) + # 前端渲染优化, 防止过多资产 + assets_field = self.fields.get('assets') + if not self.data: + instance = kwargs.get('instance') + if instance: + assets_field.queryset = instance.assets.all() + else: + assets_field.queryset = Asset.objects.none() + def save(self, commit=True): instance = super().save(commit=commit) assets = self.cleaned_data['assets'] diff --git a/apps/assets/forms/label.py b/apps/assets/forms/label.py index ebdc9384e..8a5a54e4a 100644 --- a/apps/assets/forms/label.py +++ b/apps/assets/forms/label.py @@ -26,6 +26,15 @@ class LabelForm(forms.ModelForm): initial['assets'] = kwargs['instance'].assets.all() super().__init__(*args, **kwargs) + # 前端渲染优化, 防止过多资产 + assets_field = self.fields.get('assets') + if not self.data: + instance = kwargs.get('instance') + if instance: + assets_field.queryset = instance.assets.all() + else: + assets_field.queryset = Asset.objects.none() + def save(self, commit=True): label = super().save(commit=commit) assets = self.cleaned_data['assets'] diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py index 9dd58b78e..ce5be0b6f 100644 --- a/apps/assets/tasks.py +++ b/apps/assets/tasks.py @@ -1,16 +1,16 @@ # ~*~ coding: utf-8 ~*~ import json import re +import time import os from celery import shared_task from django.utils.translation import ugettext as _ +from django.core.cache import cache from common.utils import capacity_convert, \ sum_capacity, encrypt_password, get_logger -from ops.celery.utils import register_as_period_task, after_app_shutdown_clean, \ - after_app_ready_start -from orgs.utils import set_to_root_org +from ops.celery.utils import register_as_period_task, after_app_shutdown_clean from .models import SystemUser, AdminUser, Asset from . import const @@ -211,6 +211,12 @@ def test_admin_user_connectivity_period(): """ A period task that update the ansible task period """ + key = '_JMS_TEST_ADMIN_USER_CONNECTIVITY_PERIOD' + prev_execute_time = cache.get(key) + if prev_execute_time: + logger.debug("Test admin user connectivity, less than 40 minutes, skip") + return + cache.set(key, 1, 60*40) admin_users = AdminUser.objects.all() for admin_user in admin_users: task_name = _("Test admin user connectivity period: {}").format(admin_user.name) diff --git a/apps/assets/templates/assets/domain_create_update.html b/apps/assets/templates/assets/domain_create_update.html index 0d6935226..7a31e3e88 100644 --- a/apps/assets/templates/assets/domain_create_update.html +++ b/apps/assets/templates/assets/domain_create_update.html @@ -24,7 +24,6 @@ {% block custom_foot_js %} diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 82990ac5b..e46b32d13 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -528,6 +528,7 @@ jumpserver.initServerSideDataTable = function (options) { lengthMenu: [[10, 15, 25, 50], [10, 15, 25, 50]] }); table.selected = []; + table.selected_rows = []; table.on('select', function(e, dt, type, indexes) { var $node = table[ type ]( indexes ).nodes().to$(); $node.find('input.ipt_check').prop('checked', true); @@ -535,6 +536,7 @@ jumpserver.initServerSideDataTable = function (options) { if (type === 'row') { var rows = table.rows(indexes).data(); $.each(rows, function (id, row) { + table.selected_rows.push(row); if (row.id && $.inArray(row.id, table.selected) === -1){ table.selected.push(row.id) } diff --git a/apps/terminal/api/v1/terminal.py b/apps/terminal/api/v1/terminal.py index 7ab15d381..ac56b3ea9 100644 --- a/apps/terminal/api/v1/terminal.py +++ b/apps/terminal/api/v1/terminal.py @@ -86,7 +86,7 @@ class TerminalTokenApi(APIView): if not terminal.user or not terminal.user.access_key: return Response("No access key generate", status=401) - access_key = terminal.user.access_key.first() + access_key = terminal.user.access_key() data = OrderedDict() data['access_key'] = {'id': access_key.id, 'secret': access_key.secret} return Response(data, status=200) From ef02b1f83a7484030bef31b236359686fa8dbb9c Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 25 Dec 2018 16:53:52 +0800 Subject: [PATCH 71/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/signals_handler.py | 7 +++++-- apps/assets/templates/assets/asset_list.html | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 85156c60d..7d3b67f6c 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -74,8 +74,11 @@ def on_system_user_assets_change(sender, instance=None, **kwargs): @receiver(m2m_changed, sender=Asset.nodes.through) def on_asset_node_changed(sender, instance=None, **kwargs): - logger.debug("Asset node change signal received") + logger.debug("Asset nodes change signal received") if isinstance(instance, Asset): + if kwargs['action'] == 'pre_remove': + nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) + Node.expire_nodes_assets_amount(nodes) if kwargs['action'] == 'post_add': nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) Node.expire_nodes_assets_amount(nodes) @@ -91,7 +94,7 @@ def on_asset_node_changed(sender, instance=None, **kwargs): @receiver(m2m_changed, sender=Asset.nodes.through) def on_node_assets_changed(sender, instance=None, **kwargs): if isinstance(instance, Node): - logger.debug("Node assets change signal received") + logger.debug("Node assets change signal {} received".format(instance)) # 当节点和资产关系发生改变时,过期资产数量缓存 instance.expire_assets_amount() assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 2b4e3a9f8..c3c3a7ede 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -345,10 +345,10 @@ function beforeDrag() { function beforeDrop(treeId, treeNodes, targetNode, moveType) { var treeNodesNames = []; $.each(treeNodes, function (index, value) { - treeNodesNames.push(value.value); + treeNodesNames.push(value.name); }); - var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?"; + var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.name + "` 下吗?"; return confirm(msg); } @@ -358,10 +358,10 @@ function onDrag(event, treeId, treeNodes) { function onDrop(event, treeId, treeNodes, targetNode, moveType) { var treeNodesIds = []; $.each(treeNodes, function (index, value) { - treeNodesIds.push(value.node_id); + treeNodesIds.push(value.meta.node.id); }); - var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.node_id); + var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.meta.node.id); var body = {nodes: treeNodesIds}; APIUpdateAttr({ url: the_url, From b9b8c35a81d66f411d86abea6b35693348af05d6 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Wed, 26 Dec 2018 12:57:59 +0800 Subject: [PATCH 72/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=B5=84=E4=BA=A7=E9=80=89=E6=8B=A9vnc=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=EF=BC=8C=E9=BB=98=E8=AE=A4=E7=AB=AF=E5=8F=A35901=20(#?= =?UTF-8?q?2276)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 修改创建资产选择vnc协议,默认端口5901 * [update] 修改创建命令/录像存储时账户无效的提示信息 --- .../assets/templates/assets/asset_create.html | 5 +- apps/common/api.py | 12 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 60091 -> 60289 bytes apps/locale/zh/LC_MESSAGES/django.po | 241 +++++++++--------- 4 files changed, 137 insertions(+), 121 deletions(-) diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html index 40af1b5f3..7a32264cc 100644 --- a/apps/assets/templates/assets/asset_create.html +++ b/apps/assets/templates/assets/asset_create.html @@ -95,9 +95,12 @@ $(document).ready(function () { if(protocol === 'rdp'){ port = 3389; } - if(protocol === 'telnet (beta)'){ + else if(protocol === 'telnet (beta)'){ port = 23; } + else if(protocol === 'vnc'){ + port = 5901; + } $("#id_port").val(port); }); }) diff --git a/apps/common/api.py b/apps/common/api.py index d0512ebca..4aa8f82ce 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -104,7 +104,11 @@ class ReplayStorageCreateAPI(APIView): data = {storage_name: storage_data} if not self.is_valid(storage_data): - return Response({"error": _("Error: Account invalid")}, status=401) + return Response({ + "error": _("Error: Account invalid (Please make sure the " + "information such as Access key or Secret key is correct)")}, + status=401 + ) Setting.save_storage('TERMINAL_REPLAY_STORAGE', data) return Response({"msg": _('Create succeed')}, status=200) @@ -136,7 +140,11 @@ class CommandStorageCreateAPI(APIView): storage_name = storage_data.pop('NAME') data = {storage_name: storage_data} if not self.is_valid(storage_data): - return Response({"error": _("Error: Account invalid")}, status=401) + return Response( + {"error": _("Error: Account invalid (Please make sure the " + "information such as Access key or Secret key is correct)")}, + status=401 + ) Setting.save_storage('TERMINAL_COMMAND_STORAGE', data) return Response({"msg": _('Create succeed')}, status=200) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 154e0a5218e6421c986d725a9a4742b67bf7a5a6..a5a414c63b909c7501e047a31f208e278346cc0e 100644 GIT binary patch delta 17863 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

    XBVHAEI{d|FJh= z22{TY)X_&G3w50YDmwZ~s3WP1y72?l4c$;X>TB^J)U*B+wc|;c9cQDS{RYclM)kXc zMezk{yx0N0KeITcFbnfLV|*2UO-4<;1a(BKP&@wy%i>YgfcgoP146L`7D0VKyleU1 zs3RYZdNh+z6VEjlqaNidOvn7rdMf%9Y_SgCqKZC&0)3AdwUN50`YZ6OnDf^TcahMu{=qt2K`rDy>gduB@dgSrbD{G2 zQSVGK)Xw5D7L!pY+77kQE~pb3ZutqQ`@eE6F~?klTJcKM4mMi67qx>USQO8qPQsrJ zs9$>2No6;SVs7GO%!y4pjx zgc{(v8T6_52t!f*qEX|NMD4UHYGJie3vG`1u{-+y{6B$;o>{6jY)5^*e?>jJ`j_3KS(%ljRP;dE248+OkkJB*#XQBGf!ysIVTIdGUyRsd1GT))bDLldJpNQ&T z4YiS4sF%Cp1kPU{lTIYE;BeHl{1P?bLe$H%4)rMZqdrcTQ3E_h-5);Dd-+OW7UD{% z`|DtFY=WA27>45KsCj0&mRODY2HT1G@f_-%c#2v;*caZ!1u>L38FdnMQ3EwaokVA| zHwF_Az^*tPHQpW6$p%jH?stQ!gpkON+EGE&4w6s<)<*5HK57SDQT_X42ONQMc+=W* zeCZ7wgBqtKmc`1b@0DJtaYkU6KK~P`gp-(udRf+^cKAJNhvzN6hTPyhLhUR=s<)5` z)B^IOCVUI^X=#q?=VAnoL2YCfYQAqUEAu;tsbnDW8|vAnp`P^%)E7>^$#yd6I~i2} z+Ngy#K=o^jddWJXPOb;4e}Bx1BT@HFN4*>KF_S+3Yp7_Y+fkqY1Go-5e&s!@$SK|p zQK$*xuoNa>47SBOI2^EJyB{C$Tl07xbhtg=d;1qH@FqHldUj_}Z|zOgJ8=hfluuF5GH{{y%V-W%zC7w< zmtuB7eM5eQLvaJvz{Eu?H!qcei#(^|G2)G=XY}b}&xxp`pM_e`0?Ti<{6341m}kr@ z<~{QT262C;CEkffxKuPzUb7hHA}()nJ=6^?P%m3M%lAb+x}g?NF&Civt+jZ!#m6lE z4K?0P)4gYv$L4>iXC1WETUeNx54H2+W)18RkSE4+a-pyCKK5-SnsM}172qdr~(Q71XU;+f_e)ECx1)Ho;2 zUwrcXFH+HsY36-Xe{rLu3_-Q$v$!~F$CXe!t&W+oyE)MEqby!)?m#X0h{ZQBmp(54 zP|?wbuJWFJ0nALCj9Nf#i<_Z#&;bLmo8^01ez3(OES`iKcber_U<~mVi_f8}639WPs z>h1sD;wu(ELQNF5#yk3KW|UdfOfV~>HdNE%w=t2pspUVne5!uS4W(hWHLS9Rji|SI zCzi*{)}DKZ9&Clc)PMXyRU>gRts)C~<# zH?*?2w>bp0vvC%GY4KchvE^5xCfa845sS~D=DUVEdH14o6K8WfsO{;u02j zLM^Z-M&KvrM9a^&cnxZTy{HL~qBi)4wZA~d;s5_^@CJ-B<1m7b6)mo7HnV&O^JB}8 zGpC{6okiwO)Hr9X{U+)}9-$VNexrU&^Zdi9s38*7Pz-efWl=k=WY#hpnQc)M^e~56 zelltyUt7G*JY=3mjekk8KL6J(@eo6ZpP?3%ag*0EH)G4dqL(=rTM{3~YFOf1&+hm-@oM}4@1ahh*$!_5tx<6|oQ{35D~9a!zJLax z+9#S*eDeHfQ+bVsg{YHAvkvzy{@3F4ySxFinAuS~&5Jsz5|&RvjZ@Qn*Ya(!Ao-4# zAE&%aVj2|@!~wF zM^O}g|Ns9rOVqXwZ7uF$jzSGE&)PSbyD>NUW2gmPxBMOR8R|Er;Jx0&38;4@1@(JD zoxS{ZfL7ShIoko~>%_69NrLhoJvG{+e33{ULA8q-G<}AxE zLv3I!>ZJBreA=a=fv=elOuvI(J`3u`JgA+Pvbd_XH!xda3hiAn2v=BsJ?hczviL9t z5&w*N(EZI4Pf!bS4ta){xy@MAjfrNm`MOyLwb1%l5Zj@~8)Z(m_ODSJ+33Ztv)?+L zK^^54)JyUl)iL}#FCT+?rj;zNk81CXnrHxON1vJVQ48K_?FTGAW${_eu3t^kEb$Cu zxgq$l_pMz9V~Fdco_QbCfKxFT=a@^)_2##z1sz14$d9P|(ky=)GZ6oazW+5DaKw%r zqiD!&aT4mrny7)^#)jAii{b_>fWKle{)d`4_fUusF6yO)wGljx0ruvlBDp0gF$WXU*S_@%+{CnkD{1 z-SE)#Kkmh$W?t09#VoFj8Hwwdjm*|&7t{v&p*A$qoM=vWt+D{MkX4uuH=-7D%Dia) ziCVxDi!=Y=#gVA~B~TM4p~kCY`TAx{vy16|L`6q8!V4;NM zkD>;a!1~rc7`4!`7B4{Ezt$(u{~Ia+Bz9WEUh}Yd!aRf8!3ESyb_)a1@06DhLY-Vz zvj}S9B(ovv{;uZ77{UC`NL6qeYGo@dUXQxr0BWF97T>b=$Jms7rXRig+nN3GGWjtU zcR%foGt?Y!&OlcaEV0B!bB}o(v(SDXL-8hRf&ZF*KY0UZFteKl%y?A)$`-$2)<=D_ zw*1L{{<|dfjrJMp8*L5xc5L3lyyOFa_9l$M2;x$x`(DE^?1Ira2=zra)7rP8#=UHD z&>8Q~Sn+2#|Hd?QCZUtqg;Ve-HpO*S z4@=|N-@ISox1%=p0<&SG3*Mi(I-7$~d}sNisE^x^ z7GFT^>?#h#$CwHGUi3ENq9z<;@fXMmxXui(;w(okWE*OrLl&R2_G_qj;E}aw``tV8 zSj_Fw~I0WCp zig*k)P7oh{nH9CL{8$M~qZZH=HQo^P{nf;1D(W~5^%gI+cqgjkaf^R3ucIb-Y^J;7 zeF`E_<5a^KtdHu~$MP;}{D~GXy2A6<4Vy@4q64TE{($-^_y=kL=c<KS zs$c-tvbZkl=o_Q@wJ`8wZz#3Qg2?!l6H7xmIcHM`;MtP*OaZ=3I9RpO4=2p3~8K1H2S zx|`nTJ{Ky#4*gKqKq_TZizP-!_V(xBoltZIaX`P(a+mtf)V7HQ-9Ks3mz*wtyIEga zb;-X@`ONP0)6v=$Nc|V`4~aj=o!qyYQil3^$^-Hzi7QdpRe}E6_*zO<>cemv?S=gK z{_ICZR~fSn^?lSUb8}709aIOd!Q_6zv#CktV%^=e7NxxDYxDk-hv6sEYpPAH?`d5< zh>vB;= z40cDIxyswZ`_MLnd$y1Z!V?Goy2tX?{D#7+DrKH3Dbvdx^>o|Md=htxlg?d z`DaRU>5I3Eug3373{sI&!UoGvJ}>pd^lwJVN4Z2wBD7;~%p=iR9-~e@Y)RH9gbuIAKVUK{9mnjizYe=+D~0~Vz1UGl@Q0C5cJTi^-4M?M?sT53)q z_c7%Q;uOo}4zTAxkcJnO1R6GY_xXNYOrsuUaXs2%s1Lvz^zCT*7Syv)@{`vU!$yu0 zm&JSJ7I|rB1vy=Q<|fSJ&+~s~i3mEMr~U=yIpsES5;ycmegEaa+2kWJjrc!GEM+^n zBII=ajEVR^a=L2a1#*R0OeX5DduiuQayzJZe)$4@Nn$+ZL&~ey9D=Tv2uQt@l+_Ke zp%=c=Eic1AwRnKFKQS}XmdL1^iEHCz`W2%-$=ZwAjE}J+r6u|Hc!Tl_eY11_TIyd> zAK_;Y>;|23(%_GSY#_~l&FYV-_u!^s#F5miP_KaLxko=cbRDAXBL6*o7E^zSW3Vqd zUEPU4p#B;4N4N$*)d#jTgZxcm28n)@wRDW8%%bSJNZZdCPuWebBK>tWp+14K%K9mH zgYqMBQ|{6A5oHhcbk?T|_wFG#SB+dB>H9Ay#h-?;7>7A1pV|%T)Pa&hob*aAjzQbl zB{CWpDzNTyQWqf;8zczHLLB=;{Rj=Zk- zC}B)cl{n1?Z9uM=O?uOEnXxV*BW< z-#=d2hxN!0;-2o5j?{H^p#Jju%PNg&8$!MS^Xw&enfN-Ty}n-_QK?OG59<05Gg4Y` z!#3tm*ug+A46IZ94qU56JUoh!fs)y8^$`ML8l8Y(1sBgvZD3_?~D#9YOaigw3C|jsU z(f;y!pZY%p;rO>D(^KD0y%hI8!FR3yEi6Qw4&QQ8C!_?`h^Na>gdvpWl&v&prF3GN z1k6BbLS9!@N+tU1I!*2-eV$@)>e-Z7_X^Pn%0OBhkS#;cEYufK-$DN6^@4aI<#Q5! z@EaV9b+Ej3%0oPwa)z>uTn|b{>b)qFsE082Kd+2QD%Gu)&Mi*v9fGkGUFy=3dy4uZ z=AvGXa+jhj-o|*#Y=Gw|%P7BFpNIIEco+TmTF!Sf|M`RXdrGW#Dp5{Wg4@=5g?eFb z+=z4V-_)4bvbve6y}bUmdec{O?^Dmo9VHnujQY3KbycCB-TKWWw}9M__!_zTc%JwH z^u4_1@Gw~x_P40aqxfWV`BmC0pg_-E( zb&p&t%6BB@TYH45KAjlTyX=c>6^U#wb6D~JJMhMH<5@~U%23Kzw0(kK<4Ve$SNcvT zud9IBg8EhJ7w`&YG)31ZFYnR(uM>PuIYGT4o$pYePQ4f9GWBtkQ8wUma^=^E)o&G*kOI6Pp zkxZ8*lyTNnz%R8y^{lCFst4-v6z8V0l(#7*>2-_JmeP>8oZV5A_y^(=EG`>G*Lq4l zUyYv%v>l@8ilMKr|A><*_bJ_oW3AoS$bU9cQh%===Em9$f8cnMZ&;7=+|ZbMZSrlY zx3U4w<6UwEX*)tanFS59d&DBzLhu@GE2-;hOG&YXx1(N_w#Br)<>zeZSz}T3hJhVM bm&;HlDSw%Yl@cm$$T%}+)P~+$!xH`v|LB^Z diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 667dc3922..ecd343f82 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-12-19 18:28+0800\n" +"POT-Creation-Date: 2018-12-26 11:39+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -32,8 +32,8 @@ msgstr "测试节点下资产是否可连接: {}" msgid "Nodes" msgstr "节点管理" -#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:112 -#: assets/forms/asset.py:116 assets/models/asset.py:84 +#: assets/forms/asset.py:30 assets/forms/asset.py:66 assets/forms/asset.py:105 +#: assets/forms/asset.py:109 assets/models/asset.py:84 #: assets/models/cluster.py:19 assets/models/user.py:91 #: assets/templates/assets/asset_detail.html:77 templates/_nav.html:24 #: xpack/plugins/cloud/models.py:124 @@ -42,7 +42,7 @@ msgstr "节点管理" msgid "Admin user" msgstr "管理用户" -#: assets/forms/asset.py:33 assets/forms/asset.py:72 assets/forms/asset.py:128 +#: assets/forms/asset.py:33 assets/forms/asset.py:69 assets/forms/asset.py:121 #: assets/templates/assets/asset_create.html:36 #: assets/templates/assets/asset_create.html:38 #: assets/templates/assets/asset_list.html:81 @@ -53,7 +53,7 @@ msgstr "管理用户" msgid "Label" msgstr "标签" -#: assets/forms/asset.py:37 assets/forms/asset.py:76 assets/models/asset.py:79 +#: assets/forms/asset.py:37 assets/forms/asset.py:73 assets/models/asset.py:79 #: assets/models/domain.py:26 assets/models/domain.py:52 #: assets/templates/assets/asset_detail.html:81 #: assets/templates/assets/user_asset_list.html:157 @@ -61,11 +61,11 @@ msgstr "标签" msgid "Domain" msgstr "网域" -#: assets/forms/asset.py:41 assets/forms/asset.py:66 assets/forms/asset.py:80 -#: assets/forms/asset.py:131 assets/models/node.py:31 +#: assets/forms/asset.py:41 assets/forms/asset.py:63 assets/forms/asset.py:77 +#: assets/forms/asset.py:124 assets/models/node.py:31 #: assets/templates/assets/asset_create.html:30 -#: assets/templates/assets/asset_update.html:35 perms/forms.py:37 -#: perms/forms.py:44 perms/models.py:79 +#: assets/templates/assets/asset_update.html:35 perms/forms.py:45 +#: perms/forms.py:52 perms/models.py:79 #: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:117 #: xpack/plugins/cloud/models.py:123 @@ -74,7 +74,7 @@ msgstr "网域" msgid "Node" msgstr "节点" -#: assets/forms/asset.py:48 assets/forms/asset.py:88 +#: assets/forms/asset.py:45 assets/forms/asset.py:81 msgid "" "root or other NOPASSWD sudo privilege user existed in asset,If asset is " "windows or other set any one, more see admin user left menu" @@ -82,28 +82,28 @@ msgstr "" "root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置一" "个, 更多信息查看左侧 `管理用户` 菜单" -#: assets/forms/asset.py:51 assets/forms/asset.py:91 +#: assets/forms/asset.py:48 assets/forms/asset.py:84 msgid "Windows 2016 RDP protocol is different, If is window 2016, set it" msgstr "Windows 2016的RDP协议与之前不同,如果是请设置" -#: assets/forms/asset.py:52 assets/forms/asset.py:92 +#: assets/forms/asset.py:49 assets/forms/asset.py:85 msgid "" "If your have some network not connect with each other, you can set domain" msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录" -#: assets/forms/asset.py:99 assets/forms/asset.py:103 assets/forms/domain.py:17 +#: assets/forms/asset.py:92 assets/forms/asset.py:96 assets/forms/domain.py:17 #: assets/forms/label.py:15 #: perms/templates/perms/asset_permission_asset.html:88 msgid "Select assets" msgstr "选择资产" -#: assets/forms/asset.py:108 assets/models/asset.py:77 +#: assets/forms/asset.py:101 assets/models/asset.py:77 #: assets/models/domain.py:50 assets/templates/assets/admin_user_assets.html:50 #: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/system_user_asset.html:52 #: assets/templates/assets/user_asset_list.html:152 -#: common/templates/common/replay_storage_create.html:60 +#: common/templates/common/replay_storage_create.html:59 msgid "Port" msgstr "端口" @@ -114,7 +114,7 @@ msgstr "端口" #: assets/templates/assets/label_list.html:16 #: assets/templates/assets/system_user_list.html:33 audits/models.py:18 #: audits/templates/audits/ftp_log_list.html:41 -#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:34 +#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:42 #: perms/models.py:31 #: perms/templates/perms/asset_permission_create_update.html:40 #: perms/templates/perms/asset_permission_list.html:56 @@ -130,11 +130,11 @@ msgstr "端口" msgid "Asset" msgstr "资产" -#: assets/forms/domain.py:46 +#: assets/forms/domain.py:51 msgid "Password should not contain special characters" msgstr "不能包含特殊字符" -#: assets/forms/domain.py:63 assets/forms/user.py:80 assets/forms/user.py:146 +#: assets/forms/domain.py:70 assets/forms/user.py:80 assets/forms/user.py:142 #: assets/models/base.py:22 assets/models/cluster.py:18 #: assets/models/cmd_filter.py:20 assets/models/domain.py:20 #: assets/models/group.py:20 assets/models/label.py:18 @@ -176,7 +176,7 @@ msgstr "不能包含特殊字符" msgid "Name" msgstr "名称" -#: assets/forms/domain.py:64 assets/forms/user.py:81 assets/forms/user.py:147 +#: assets/forms/domain.py:71 assets/forms/user.py:81 assets/forms/user.py:143 #: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60 #: assets/templates/assets/admin_user_list.html:27 #: assets/templates/assets/domain_gateway_list.html:60 @@ -221,21 +221,21 @@ msgstr "ssh密钥不合法" msgid "Password and private key file must be input one" msgstr "密码和私钥, 必须输入一个" -#: assets/forms/user.py:134 +#: assets/forms/user.py:130 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/forms/user.py:149 assets/models/user.py:141 +#: assets/forms/user.py:145 assets/models/user.py:141 #: assets/templates/assets/_system_user.html:66 #: assets/templates/assets/system_user_detail.html:165 msgid "Command filter" msgstr "命令过滤器" -#: assets/forms/user.py:154 +#: assets/forms/user.py:149 msgid "Auto push system user to asset" msgstr "自动推送系统用户到资产" -#: assets/forms/user.py:155 +#: assets/forms/user.py:150 msgid "" "1-100, High level will be using login asset as default, if user was granted " "more than 2 system user" @@ -243,7 +243,7 @@ msgstr "" "1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会作为" "默认登录用户" -#: assets/forms/user.py:157 +#: assets/forms/user.py:152 msgid "" "If you choose manual login mode, you do not need to fill in the username and " "password." @@ -628,7 +628,7 @@ msgstr "默认资产组" #: audits/templates/audits/password_change_log_list.html:33 #: audits/templates/audits/password_change_log_list.html:50 #: ops/templates/ops/command_execution_list.html:34 -#: ops/templates/ops/command_execution_list.html:59 perms/forms.py:28 +#: ops/templates/ops/command_execution_list.html:59 perms/forms.py:36 #: perms/models.py:29 #: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/asset_permission_list.html:54 @@ -637,7 +637,7 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:314 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:303 #: users/models/user.py:33 users/models/user.py:420 #: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:13 users/views/user.py:386 @@ -715,7 +715,7 @@ msgstr "登录模式" #: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:156 #: audits/models.py:19 audits/templates/audits/ftp_log_list.html:49 -#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:40 +#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:48 #: perms/models.py:33 perms/models.py:81 #: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_list.html:58 @@ -734,68 +734,68 @@ msgstr "系统用户" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/tasks.py:33 +#: assets/tasks.py:31 msgid "Asset has been disabled, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" -#: assets/tasks.py:37 +#: assets/tasks.py:35 msgid "Asset may not be support ansible, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" -#: assets/tasks.py:42 +#: assets/tasks.py:40 msgid "No assets matched, stop task" msgstr "没有匹配到资产,结束任务" -#: assets/tasks.py:67 +#: assets/tasks.py:65 msgid "Get asset info failed: {}" msgstr "获取资产信息失败:{}" -#: assets/tasks.py:117 +#: assets/tasks.py:115 msgid "Update some assets hardware info" msgstr "更新资产硬件信息" -#: assets/tasks.py:136 +#: assets/tasks.py:134 msgid "Update asset hardware info: {}" msgstr "更新资产硬件信息: {}" -#: assets/tasks.py:161 +#: assets/tasks.py:159 msgid "Test assets connectivity" msgstr "测试资产可连接性" -#: assets/tasks.py:185 +#: assets/tasks.py:183 msgid "Test assets connectivity: {}" msgstr "测试资产可连接性: {}" -#: assets/tasks.py:218 +#: assets/tasks.py:222 msgid "Test admin user connectivity period: {}" msgstr "定期测试管理账号可连接性: {}" -#: assets/tasks.py:224 +#: assets/tasks.py:228 msgid "Test admin user connectivity: {}" msgstr "测试管理行号可连接性: {}" -#: assets/tasks.py:262 +#: assets/tasks.py:266 msgid "Test system user connectivity: {}" msgstr "测试系统用户可连接性: {}" -#: assets/tasks.py:269 +#: assets/tasks.py:273 msgid "Test system user connectivity: {} => {}" msgstr "测试系统用户可连接性: {} => {}" -#: assets/tasks.py:282 +#: assets/tasks.py:286 msgid "Test system user connectivity period: {}" msgstr "定期测试系统用户可连接性: {}" -#: assets/tasks.py:354 +#: assets/tasks.py:358 msgid "" "Push system user task skip, auto push not enable or protocol is not ssh: {}" msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh: {}" -#: assets/tasks.py:374 assets/tasks.py:388 +#: assets/tasks.py:378 assets/tasks.py:392 msgid "Push system users to assets: {}" msgstr "推送系统用户到入资产: {}" -#: assets/tasks.py:380 +#: assets/tasks.py:384 msgid "Push system users to asset: {} => {}" msgstr "推送系统用户到入资产: {} => {}" @@ -894,10 +894,10 @@ msgstr "其它" #: assets/templates/assets/gateway_create_update.html:58 #: assets/templates/assets/label_create_update.html:18 #: common/templates/common/basic_setting.html:61 -#: common/templates/common/command_storage_create.html:80 +#: common/templates/common/command_storage_create.html:79 #: common/templates/common/email_setting.html:62 #: common/templates/common/ldap_setting.html:62 -#: common/templates/common/replay_storage_create.html:139 +#: common/templates/common/replay_storage_create.html:138 #: common/templates/common/security_setting.html:70 #: common/templates/common/terminal_setting.html:68 #: perms/templates/perms/asset_permission_create_update.html:75 @@ -927,10 +927,10 @@ msgstr "重置" #: assets/templates/assets/gateway_create_update.html:59 #: assets/templates/assets/label_create_update.html:19 #: common/templates/common/basic_setting.html:62 -#: common/templates/common/command_storage_create.html:81 +#: common/templates/common/command_storage_create.html:80 #: common/templates/common/email_setting.html:63 #: common/templates/common/ldap_setting.html:63 -#: common/templates/common/replay_storage_create.html:140 +#: common/templates/common/replay_storage_create.html:139 #: common/templates/common/security_setting.html:71 #: common/templates/common/terminal_setting.html:70 #: perms/templates/perms/asset_permission_create_update.html:76 @@ -1738,7 +1738,7 @@ msgstr "Agent" msgid "City" msgstr "城市" -#: audits/templates/audits/login_log_list.html:54 users/forms.py:172 +#: audits/templates/audits/login_log_list.html:54 users/forms.py:162 #: users/models/authentication.py:82 users/models/user.py:75 #: users/templates/users/first_login.html:45 msgid "MFA" @@ -1812,15 +1812,17 @@ msgstr "在ou:{}中没有匹配条目" msgid "Match {} s users" msgstr "匹配 {} 个用户" -#: common/api.py:107 common/api.py:139 -msgid "Error: Account invalid" -msgstr "" +#: common/api.py:108 common/api.py:144 +msgid "" +"Error: Account invalid (Please make sure the information such as Access key " +"or Secret key is correct)" +msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)" -#: common/api.py:110 common/api.py:142 +#: common/api.py:114 common/api.py:150 msgid "Create succeed" msgstr "创建成功" -#: common/api.py:128 common/api.py:162 +#: common/api.py:132 common/api.py:170 #: common/templates/common/terminal_setting.html:151 msgid "Delete succeed" msgstr "删除成功" @@ -1964,23 +1966,23 @@ msgstr "密钥认证" msgid "Heartbeat interval" msgstr "心跳间隔" -#: common/forms.py:148 ops/models/adhoc.py:38 +#: common/forms.py:149 ops/models/adhoc.py:38 msgid "Units: seconds" msgstr "单位: 秒" -#: common/forms.py:151 +#: common/forms.py:152 msgid "List sort by" msgstr "资产列表排序" -#: common/forms.py:154 +#: common/forms.py:155 msgid "List page size" msgstr "资产分页每页数量" -#: common/forms.py:157 +#: common/forms.py:158 msgid "Session keep duration" msgstr "会话保留时长" -#: common/forms.py:158 +#: common/forms.py:159 msgid "" "Units: days, Session, record, command will be delete if more than duration, " "only in database" @@ -1998,35 +2000,35 @@ msgid "" "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: common/forms.py:180 +#: common/forms.py:179 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: common/forms.py:185 +#: common/forms.py:183 msgid "No logon interval" msgstr "禁止登录时间间隔" -#: common/forms.py:187 +#: common/forms.py:185 msgid "" "Tip: (unit/minute) if the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" -#: common/forms.py:194 +#: common/forms.py:191 msgid "Connection max idle time" msgstr "SSH最大空闲时间" -#: common/forms.py:196 +#: common/forms.py:193 msgid "" "If idle time more than it, disconnect connection(only ssh now) Unit: minute" msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" -#: common/forms.py:202 +#: common/forms.py:199 msgid "Password expiration time" msgstr "密码过期时间" -#: common/forms.py:205 +#: common/forms.py:202 msgid "" "Tip: (unit: day) If the user does not update the password during the time, " "the user password will expire failure;The password expiration reminder mail " @@ -2036,45 +2038,45 @@ msgstr "" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" -#: common/forms.py:214 +#: common/forms.py:211 msgid "Password minimum length" msgstr "密码最小长度 " -#: common/forms.py:220 +#: common/forms.py:215 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: common/forms.py:222 +#: common/forms.py:217 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: common/forms.py:228 +#: common/forms.py:222 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: common/forms.py:229 +#: common/forms.py:223 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: common/forms.py:235 +#: common/forms.py:228 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: common/forms.py:236 +#: common/forms.py:229 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: common/forms.py:242 +#: common/forms.py:234 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: common/forms.py:243 +#: common/forms.py:235 msgid "" "After opening, the user password changes and resets must contain special " "characters" @@ -2135,63 +2137,63 @@ msgstr "终端设置" msgid "Security setting" msgstr "安全设置" -#: common/templates/common/command_storage_create.html:50 +#: common/templates/common/command_storage_create.html:49 #: ops/models/adhoc.py:161 ops/templates/ops/adhoc_detail.html:53 #: ops/templates/ops/command_execution_list.html:58 #: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38 msgid "Hosts" msgstr "主机" -#: common/templates/common/command_storage_create.html:53 +#: common/templates/common/command_storage_create.html:52 msgid "Tips: If there are multiple hosts, separate them with a comma (,)" msgstr "提示: 如果有多台主机,请使用逗号 ( , ) 进行分割" -#: common/templates/common/command_storage_create.html:64 +#: common/templates/common/command_storage_create.html:63 msgid "Index" msgstr "索引" -#: common/templates/common/command_storage_create.html:71 +#: common/templates/common/command_storage_create.html:70 msgid "Doc type" msgstr "文档类型" -#: common/templates/common/replay_storage_create.html:53 +#: common/templates/common/replay_storage_create.html:52 #: ops/models/adhoc.py:162 templates/index.html:91 msgid "Host" msgstr "主机" -#: common/templates/common/replay_storage_create.html:67 +#: common/templates/common/replay_storage_create.html:66 msgid "Bucket" msgstr "桶名称" -#: common/templates/common/replay_storage_create.html:74 +#: common/templates/common/replay_storage_create.html:73 msgid "Access key" msgstr "" -#: common/templates/common/replay_storage_create.html:81 +#: common/templates/common/replay_storage_create.html:80 msgid "Secret key" msgstr "" -#: common/templates/common/replay_storage_create.html:88 +#: common/templates/common/replay_storage_create.html:87 msgid "Container name" msgstr "容器名称" -#: common/templates/common/replay_storage_create.html:95 +#: common/templates/common/replay_storage_create.html:94 msgid "Account name" msgstr "账户名称" -#: common/templates/common/replay_storage_create.html:102 +#: common/templates/common/replay_storage_create.html:101 msgid "Account key" msgstr "账户密钥" -#: common/templates/common/replay_storage_create.html:109 +#: common/templates/common/replay_storage_create.html:108 msgid "Endpoint" msgstr "端点" -#: common/templates/common/replay_storage_create.html:116 +#: common/templates/common/replay_storage_create.html:115 msgid "Endpoint suffix" msgstr "端点后缀" -#: common/templates/common/replay_storage_create.html:130 +#: common/templates/common/replay_storage_create.html:129 #: xpack/plugins/cloud/models.py:186 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 @@ -2313,36 +2315,36 @@ msgstr "Become" msgid "Create by" msgstr "创建者" -#: ops/models/adhoc.py:321 +#: ops/models/adhoc.py:324 msgid "Start time" msgstr "开始时间" -#: ops/models/adhoc.py:322 +#: ops/models/adhoc.py:325 msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:323 ops/templates/ops/adhoc_history.html:57 +#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_history.html:57 #: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41 msgid "Time" msgstr "时间" -#: ops/models/adhoc.py:324 ops/templates/ops/adhoc_detail.html:106 +#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_detail.html:106 #: ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/adhoc_history_detail.html:69 #: ops/templates/ops/task_detail.html:84 ops/templates/ops/task_history.html:61 msgid "Is finished" msgstr "是否完成" -#: ops/models/adhoc.py:325 ops/templates/ops/adhoc_history.html:56 +#: ops/models/adhoc.py:328 ops/templates/ops/adhoc_history.html:56 #: ops/templates/ops/task_history.html:62 msgid "Is success" msgstr "是否成功" -#: ops/models/adhoc.py:326 +#: ops/models/adhoc.py:329 msgid "Adhoc raw result" msgstr "结果" -#: ops/models/adhoc.py:327 +#: ops/models/adhoc.py:330 msgid "Adhoc result summary" msgstr "汇总" @@ -2553,10 +2555,10 @@ msgstr "命令执行" msgid "Organization" msgstr "组织管理" -#: perms/forms.py:31 perms/models.py:30 perms/models.py:80 +#: perms/forms.py:39 perms/models.py:30 perms/models.py:80 #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:111 templates/_nav.html:14 -#: users/forms.py:284 users/models/group.py:26 users/models/user.py:59 +#: users/forms.py:273 users/models/group.py:26 users/models/user.py:59 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:211 #: users/templates/users/user_list.html:26 @@ -2564,11 +2566,11 @@ msgstr "组织管理" msgid "User group" msgstr "用户组" -#: perms/forms.py:53 +#: perms/forms.py:61 msgid "User or group at least one required" msgstr "用户和用户组至少选一个" -#: perms/forms.py:62 +#: perms/forms.py:70 msgid "Asset or group at least one required" msgstr "资产和节点至少选一个" @@ -2698,7 +2700,7 @@ msgstr "文档" msgid "Commercial support" msgstr "商业支持" -#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:151 +#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:141 #: users/templates/users/_user.html:43 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 @@ -3319,11 +3321,11 @@ msgstr "MFA 验证码" msgid "Role" msgstr "角色" -#: users/forms.py:55 users/forms.py:230 +#: users/forms.py:55 users/forms.py:220 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:56 users/forms.py:231 +#: users/forms.py:56 users/forms.py:221 msgid "ssh-rsa AAAA..." msgstr "" @@ -3331,19 +3333,19 @@ msgstr "" msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:76 users/templates/users/user_detail.html:219 +#: users/forms.py:71 users/templates/users/user_detail.html:219 msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:110 users/forms.py:245 +#: users/forms.py:105 users/forms.py:235 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:114 users/forms.py:249 users/serializers/v1.py:38 +#: users/forms.py:109 users/forms.py:239 users/serializers/v1.py:38 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:157 +#: users/forms.py:147 msgid "" "Tip: when enabled, you will enter the MFA binding process the next time you " "log in. you can also directly bind in \"personal information -> quick " @@ -3352,11 +3354,11 @@ msgstr "" "提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修" "改->更改MFA设置)中直接绑定!" -#: users/forms.py:167 +#: users/forms.py:157 msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全." -#: users/forms.py:177 +#: users/forms.py:167 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -3365,41 +3367,41 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,启用MFA认证)" -#: users/forms.py:184 users/templates/users/first_login.html:48 +#: users/forms.py:174 users/templates/users/first_login.html:48 #: users/templates/users/first_login.html:107 #: users/templates/users/first_login.html:130 msgid "Finish" msgstr "完成" -#: users/forms.py:190 +#: users/forms.py:180 msgid "Old password" msgstr "原来密码" -#: users/forms.py:195 +#: users/forms.py:185 msgid "New password" msgstr "新密码" -#: users/forms.py:200 +#: users/forms.py:190 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:210 +#: users/forms.py:200 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:218 +#: users/forms.py:208 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:228 +#: users/forms.py:218 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms.py:232 +#: users/forms.py:222 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:260 users/models/user.py:83 +#: users/forms.py:250 users/models/user.py:83 #: users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 #: users/templates/users/user_profile.html:68 @@ -3408,7 +3410,7 @@ msgstr "复制你的公钥到这里" msgid "Public key" msgstr "ssh公钥" -#: users/forms.py:267 users/forms.py:272 users/forms.py:318 +#: users/forms.py:256 users/forms.py:261 users/forms.py:307 #: xpack/plugins/orgs/forms.py:30 msgid "Select users" msgstr "选择用户" @@ -4623,6 +4625,9 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "Error: Account invalid" +#~ msgstr "错误: 账户无效" + #~ msgid "Asset has been disabled, skip: {}" #~ msgstr "资产被禁用,跳过:{}" From 76a08c90398fe18a5647807b6c0df6f5c95ef75e Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Wed, 26 Dec 2018 12:59:09 +0800 Subject: [PATCH 73/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BA/=E6=9B=B4=E6=96=B0=20Perms=20=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E9=87=8D=E5=A4=8D=E4=B8=8D=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#2272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/perms/asset_permission_create_update.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html index 337b24de2..b6789abbf 100644 --- a/apps/perms/templates/perms/asset_permission_create_update.html +++ b/apps/perms/templates/perms/asset_permission_create_update.html @@ -29,6 +29,11 @@

    + {% if form.non_field_errors %} +
    + {{ form.non_field_errors }} +
    + {% endif %} {% csrf_token %}

    {% trans 'Basic' %}

    {% bootstrap_field form.name layout="horizontal" %} From 8e51f97dc7ca29a571870d5e92afe7ae3836c9de Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Wed, 26 Dec 2018 13:54:42 +0800 Subject: [PATCH 74/80] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=A0=91=E5=8F=B3=E5=87=BB=E8=8F=9C=E5=8D=95:=20?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E6=89=80=E6=9C=89=E8=8A=82=E7=82=B9=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=95=B0=E9=87=8F=20(#2274)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 添加资产树右击菜单: 刷新所有节点资产数量 * [Update] 修改右击菜单样式 --- apps/assets/api/node.py | 11 +++- apps/assets/templates/assets/asset_list.html | 12 +++++ apps/assets/urls/api_urls.py | 2 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 60289 -> 60399 bytes apps/locale/zh/LC_MESSAGES/django.po | 54 ++++++++++--------- 5 files changed, 53 insertions(+), 26 deletions(-) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 4295b2618..052d70e31 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -34,7 +34,7 @@ __all__ = [ 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi', 'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi', 'TestNodeConnectiveApi', 'NodeListAsTreeApi', - 'NodeChildrenAsTreeApi', + 'NodeChildrenAsTreeApi', 'RefreshAssetsAmount', ] @@ -275,3 +275,12 @@ class TestNodeConnectiveApi(APIView): task_name = _("Test if the assets under the node are connectable: {}".format(node.name)) task = test_asset_connectivity_util.delay(assets, task_name=task_name) return Response({"task": task.id}) + + +class RefreshAssetsAmount(APIView): + permission_classes = (IsOrgAdmin,) + model = Node + + def get(self, request, *args, **kwargs): + self.model.expire_nodes_assets_amount() + return Response("Ok") diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index c3c3a7ede..c560c8e6b 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -133,6 +133,8 @@
  • + +
  • {#
  • {% trans 'Refresh' %}
  • #} @@ -558,6 +560,15 @@ $(document).ready(function(){ hideRMenu(); }) +.on('click', '#menu_refresh_assets_amount', function () { + hideRMenu(); + var url = "{% url 'api-assets:refresh-assets-amount' %}"; + APIUpdateAttr({ + 'url': url, + 'method': 'GET' + }); + window.location.reload(); +}) .on('click', '.btn_asset_delete', function () { var $this = $(this); var $data_table = $("#asset_list_table").DataTable(); @@ -724,6 +735,7 @@ $(document).ready(function(){ }).on('click', '#menu_asset_move', function () { update_node_action = "move" }) + {% endblock %} \ No newline at end of file diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index ca426a833..4aceda2e8 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -74,6 +74,8 @@ urlpatterns = [ api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'), path('nodes//test-connective/', api.TestNodeConnectiveApi.as_view(), name='node-test-connective'), + path('nodes/refresh-assets-amount/', + api.RefreshAssetsAmount.as_view(), name='refresh-assets-amount'), path('gateway//test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index a5a414c63b909c7501e047a31f208e278346cc0e..a6518874eff0d18bf78d4278827e25b5eb29bf5f 100644 GIT binary patch delta 17975 zcmYk@2b@kv+sE;<#aeBxRoAlCVs(pMz4wv?LG<25XC-QGHAs|*kmymKs0oP@qC^c6 zB@&&8x@w3X5xl?uJ(u_6e4g>0>zX-pX6Br8-)rR=eKP3o$sphPh~SwXN3|f&%Yk{b zdtTdM&nuFstml1M%k!q-A{>VCwLR}6T#2KwR2|RzJ;3ue;x7ECp65jcdfr=akf*)p zTW;J+4LmP`xD{r`J{aP8K5s0QbR<5)H24K(!KIiBw;=QJ&SD9?fE6*Kq30FDhS(WD zzAK7d#5mRw}uPhZ!pgKlidkn>a7=^=86ZqWnOE8vr zHR`-0s2#eEn)p2o$ETYTQ1D&xB*D){g zL)2D9wr~UG#iGOsmisWFMHfuO2&{;@B@Hd#7IlHHsFf#~<5BPTEYyWop`P+RsPTS9op;LOYp5N$iwXD- z`m}XL+PRJi7)4wiwZe9&8TUus>yfCfoQYcLLd=dEP&@G}YU0;W7k-2~Ke)YHSO(O? zo)x3*25g2}L2uLmLr@RT1alE;hqj>l9Y&ph1~u?))XJZrcIqW+!r>j< z!V)dJ73-k}4&ifJ7Y;L{P!lMCx=;z! z`L$3Jt&dtrQ;fm(7>&MBRO(Y%h=1cv)IeuDy8*7CCU65a#Q@t|h2@z5)y4c4R!Ccb$qZ@EEoA&rvfC>*`h-iJDMuvk+>h5|ICSHTWY1 zu0Ty_4QjkCsGZz}I{zr9z%!^Fynz1i|Lat=vb(4)eU34hx|@p&qXsC88n_y2fV!v) zHnMyh)LYOQ)qj|^k2R-ZBKf(f>->u8c>cUUsOX`&g<9Ej)CJOYcO5gM-hx;R#)_B& ztD){~JJbbxpl;0o)ayDP^|s7MowpX_aRcglSJ9^xJfWfigL=4*F{pv_Ssag{#EGbx zSHcunAJwlB#$hWA#L?zBj3u66uEaNp4`FeP?8*JtjOzE~-N#{A507F9M!oAU7>k-{ zJZ{DcI1C?QPVC>yO=v1=rJq?m8#VqyOo>M@6i=d_jSIcl|5Q}&TEkN_u(x|A(xA39 z64kFLrol3p8f#eG0R1~)`O&C}Oh!G_({VK}#=h9FkNdjc?xUgscVSvQjN00>s0&;~ z?ZjSpcJ`JsawiGY@KI+2Y>T{vPT&qb;6_EX?Q4rV>WOmzW#ZU=SWf zeHk4`J^lAkPi>n1?%OUW>O!TlAXY)$s-CEcjYK{DOHm8?9(A2RFdT2A|NVdNuW-Kx zxRs?rtvD;{p~;EqF(2w-D~0;HZDRIDeX@;5-HJJ=3$H~@XgBKZI*IzoeTca*<3QB= zpFl-h*97&@v_-9~6KczPS$+tr-$>Me6Hxt@Vg~#gb^ed2Tet_ckRzyP=s2q1In)l^ zL7%?WUQ*F}n|+Y27&SmX)C7uS1XeR ztCpiK_zkMxCd{Vy|3@mC=_%CfaRGHJUZQp;e5f0sFzUn-sQzUwu8O+nbxKPbf`Q@m7>#+dtM*sVNn@TGZ53ECjVXk9a)XaOMW)%4%qGqUtbdh@hdr{FA4@6z?ebj3*QXOzKYD*`gCNdMX(uJsJW|if) zoBL50`px1C=1t2#L`^7g1pBW_8Y;T?nNTari@ILlL{6cev1E(yq5l?-;QnhR;UnDuIj|6MLDT?EQ3G~H^?M(+;<2a;&O+_n z63m9nu_o@sC=B_)y~eSqiIhkGTY(zC(Fg3mD(y+=q3MfS;W*U2{>(bAKuu&5YR3+u z20Cv3Vfm}5TX+XG;fI(XS*L$T3!o+%kJ^#CK5J-#8n~6koy~Vq7aD+C!BC4Qpe8;I z3*Z9OPV7VVJBZq;Gv;5Io!A@Yb|eb5gT4eRdZ?J}bD^}B)^=RRuups{XZ=`e%tOAM758cL$JunFp(wXpms)cd^% zb?-J|4m^sw&>iyymLd)t=XRtf#u7I}JxfDTw`wZp#_usd*Y{3S>4^d3-P79(b;4R) zgnMxacK^^mAu$i}OZ)-zOz^xmcpj6m!bEqA)?#tu!?+S(VjQlVsKl;Cal7 zPo}W{+S>F}-NO`TmNYA4813~@7jB7x*balRBkCdSiXk`vHPIocXJr&>XQp5n-bD3( zit3+2ua8!ecA9&-v!Y&;qNo$_DA=0@L9=)Hp9u z=ZAgbKH#EI*RAcNlAg-js0($pco6CnY%Ip$Lew*{12uu;s0&|5o$pO|D+@=BlLNIA z#mv&Er@u1x!n&yOeCw%bYY(8d<}hmJXHYA;j#|MB)PU(ebt}w_T0wDC{|eX@>tSJB zW9?^A<0hlVxsM4LFvI`3;`2&T(LnVu9GhYW?239=-bbzQ6VyOoTD%(7e=BNbhfota ziJHJw)CDtq=H8YVRKJ=SfekRb-v5qNbiomr5vO7(E=FzfD%8E+jVbUF`gaEXJA>+< zZl;@P7F54D)I(MTwQ~um{uNNysgM5O|Fxr{hod`cg-NKHjzYcvAK|xHWR|;C=TZGH zqb~3_7Q-i)8{b-C=zP!P)7V=!pXR-E zEVO{Xwcy_ic`$gv*Dm7Ykq+N2aZmrdOWlP&M%}x47>sLBJGLHm3wEGx z3lRtZ&l!*U5^ME8rZyH^Ew8i8*%Y-i9Z?hNVfo>fpJ?$k zbDp`v+-UChQPDuZqVD-gYq)6MLA?dfEY7st_0NrZD+-_{Ru*;ZYFXUc?1Abx*y8aP ze`>LBF%?ZX6`{v>=)F9j+>WIE5B<7u5j;vW>o*-m>0)m0bGgA@ptTig;)Cj zqT}->yNY+vOuNec4d_iQN5{#i3I2qs@Q}qP>5E{8iLz8nfEHUR6*NZenoi`vTHEWTv%UDU*0qE?#fYj=K0vkEHzrp1HJF&M4a zW11ztLEY;usI5I_@ilArzHt*shiPfgiQ1V$sBgm(mM>-b>K50txEX5PwwCXYzT6~6 zT4Et;3s<8C{>A*w+D}`YZ2o2WhZYB{aT5teO)#w)iK&U>FceFm9@g?}c>h&tWF5Pq zwsH_^rXx{1@`=SOEZ&M*@o`lD)8=LKFY}2Ru+}Xo4XS?xCSs1Y?7vp}wl%cC^u(P| z`GJ-nih7#IVrg7%?SGods1^TZ@xNvOpWAWdL$NQ$qxvmCEpU~OiY~YvwG)TUE2!`P ze^Do7`Ock?2Njn#YoI3D$l~S}cQJceejw^PK8vTlisw<$g;%4tW|w&ob)gfe3tTpD zVg=%R78hOT`Xyon`D$iU%Xhap2{rx%)b*w#3-o!*tix{fU(md4{%!5gEe>Dr`sGAj zu#j2F@{P>4sAuP0b1bU=JZoQr>Gk!rm5OF|5L4m_e*-_ITK*1d2mV1#@THk{gNq~0 zIMf9a%-WW3iJC|^i+$!~srP?26=9YCKdC#>Nz>Oway{uk9Az<03>Lp_vHW*O8+bQ9#O-kXBzS74Ky zKq<2#DqjONej|%JY-0a4(>^5h8J>jsaSbYe(manDh_9kPu%4g>jNI({#bG#cQPi!h zZ27vVehn>dV{vC}NWTAOd;fP>hkfQ@>v-JaOQ;FlKwaRO9{8Xr)bh4oG ziC73Lpgxj)-K=2%Mv)kT`VROEHGy@g6L+AtejjRuH>~|X)WlP5bt{cU^)F&3SiU@J zqBSkw6q$g}YfVK1bVL2!&S#FnV#MRk_1Kp9GFHWE+ni&tI`MAoh9TSC1p1&BkYw>_ zoJ~9tdtvO4`XJ)|Po<(6FEv-0-(fZKn^6NjvwY|d7pF&^m&43s7DBDG1Zt3K+P;}Ywhn@Jl33tI)8(;?>CQQcJh}{<2<)~@NRdV z45*)oqIa|Zx^Nv5dQF<5en4nv9lM)J=4f-OITtm7<*2863kG2_>hs{H`3yCF+CA>p zWI=th#_jRB@AE_wI-wWl!x0uQM$L2`YNF>bBi=y&%FN)su00JZpUuo=#-S!!)U0YY z@lnyk(#?F&8a~9#|IIDY4mH!Rm-2CX=}fY+R3M=XC&f)>lcU0mqy*vh8A~KJJJnjUq2HdOyu)P>_MZh$(kHENtr*cb<60o?x!?|&?ndn7barh{&vXtOYCfU>Ce zDi+s6ZD}Kmd!jDfA9bN=7B5EKs-38Dj+&>;KlN+!v@|4>kPlEN=r8>xARHA}Hyfia z+zB<{a2$(cQ0Ipnc4kDi$Dk&jA2m)1EREGr{fGFdXvX8Q5PpHWz;4vNJ%<|TKI(+0 z7Ka>h`E+I`Gs=ub^)G0aHfxxTP}lXfwM2hRO=6@u(VSr}!1T1QK&@!Ax!XKsocX>E;4+6>8@;S$q_=!t)m2 zLyh;s;;^F{m-`<@MJL8$53FGEQga(>#V1e`xqw>HHS;0rLIJSkS9h{FE_W{mA?=+8abNu|Yo4{Q&^o;9}8%xu^35()g z)J@NM*8QG50S6JcM}7PJj@i@?HSy4MZbzdq3vpqri`B6fe(a;7=lLjV3(ulvcnSOA zUF4?}ul*l$j)8I1HLe`=BZL@d}>X|xho<~jO4l<6pG4Gi#P~)b#q+8|V0ivP-%b*%+qV835 z%l9>hqgFBzwbC!l#i$jp#wNH9HBL%iKIOBUxlz|CfNC#={vToLQVAu|0yS_))UD`+ zdfErz2RH`HV!#zQPpVqGFesV**Y{kOWOt!NR6{lNugLOU zP$v$+ARKP74?~D2p(gr?Ip6Xt&2P<3=8rg({y!yi|M{O+^_m-a2kJt5Fa!@{K0JH=46Nta% zPAG?Z@83k_uVMh^4CL=o6dg|;{y*#R=R!Ixq3teqvp&CJapE6nkG0$a)Z)ESly?Zy z)1e^Vz&&c7XKg;UbP1no)-<49*w>pVq^s9$Oi8IkJBjp_BBt^#~+FMX= z1#tZyR2EyqpZKxsl2U_Q8sfYdPCN*8RH1CJ`c?A# zi8C;%h13HncLG`e5h^+9bPa#QVRnKB_?NPsypFGNud8|^=~In(1Mw$#hJc{32UwZ@ z4Jez*Pg2L%j!$UUx8W7q`6l+tphw|#_a5OT+VfN1Bko7sRQJCG!DE6a`1%n~ZX=}t z$q%hzD<=mtvA$S@wwjjTMf?M02XSul_b3}EvE(vQ^y>Xi{5|%@yO^E4|M{;+Vhp7f zB|$lk0scmQmqPA8>hsB^r+%IK6H0C3lQ@oYoRXeNB~aEGvz8ANkfejZ&C$ zp7tM6|45t$m)Uuxnb51l|Ne*4n1@b9DV3`t>*qgHAlZ=wAAa7SxZ6%nO`-q!yegl6CuP3?2)_wrn`p0Jd`XbP0c`&6CGt6iwG@w%`^%Im;6h8X> z$4c@usJ*oKA50>5kD}u{o3!da>cc6&TH66~v#9UFGL-s0f@m(f4*#QL3@$<)`XV{) zuem>ZkZ)~qbNqpLr1fpWd0EKiV60l?R#F0}FDL&7`Rb@IvMl62rmiCc`chK4K$%UM zYz^C}>li{D#6S%#uiQA|1meFjlEHKwp?pr9?2q_wE~t;D6r-P$FVq?h9cJJ|4D;-7dx>rE+CGj1kwK@MMpIGRG5`gm>sD? z{0sGOC^`-jFR{KZ!~Ituiaq}fLWg%KpSA^#Azqa6J;a&B`7FLk`wYr(avxf*KJ{qI|0rk3Rc0}V>7$>cwLPoJh2cEPIy+}Q?SE4^ zd|q~Xl_uy)xo;hsfv=dMNlte{pR5XJGUVD=EOUQS6NPDULm&~ z`(yFfU+<@M`izp2hV>Xlr$*EdVrk;`)VEL;5%%8CMrUq5n_`<8_Mjo*!Jxs?CUMA|!1E612RD(H1hB6yQhm-=dwQ|;uf)N@$b@&J93BoS@(OVqr^oh9jyJaoue*UsTaq8Xlp|$ZRd@*KA(~cq}~wy z>)%gMlG*9#Kq*W7E{(5_%)}oNY{Kj0&*BP-j?1R%_bAPXf3e&uoI?K-I1kI)H40!A z%H2S&|F+KL$U_OE<4ZbTq9jms{Eysb$_R^nbPyMLGx0-LFY2Inq$PP>GK3{Qw~sctfy2Z|AJD0TnhT9 zQ?YjS=}7sAb{*|0{V8dQ%PYa*(=R1-{J}{BUDf-F4(X}?M@h87)?jDyA5s=j22$Rj z9HgkrcuEMBvP|G7>J{ufs$M(#JR|;ql3M+UJ7c2$de?@^M9O{|n^IyKU%C$JYK%JOHi6z8n8_7Ct=;uveM9+<#)Bta@lej4ATu@#m> z9kt1SPrWXFW4Uv$eiqWtL+kq2s+*9dWAENEefxFk8q;ympsq=SVmkKeH@I)ox-~ delta 17905 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\n" "Language-Team: Jumpserver team\n" @@ -116,7 +116,7 @@ msgstr "端口" #: audits/templates/audits/ftp_log_list.html:41 #: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:42 #: perms/models.py:31 -#: perms/templates/perms/asset_permission_create_update.html:40 +#: perms/templates/perms/asset_permission_create_update.html:45 #: perms/templates/perms/asset_permission_list.html:56 #: perms/templates/perms/asset_permission_list.html:114 #: terminal/backends/command/models.py:13 terminal/models.py:140 @@ -630,7 +630,7 @@ msgstr "默认资产组" #: ops/templates/ops/command_execution_list.html:34 #: ops/templates/ops/command_execution_list.html:59 perms/forms.py:36 #: perms/models.py:29 -#: perms/templates/perms/asset_permission_create_update.html:36 +#: perms/templates/perms/asset_permission_create_update.html:41 #: perms/templates/perms/asset_permission_list.html:54 #: perms/templates/perms/asset_permission_list.html:108 templates/index.html:87 #: terminal/backends/command/models.py:12 terminal/models.py:139 @@ -858,7 +858,7 @@ msgstr "资产列表" #: assets/templates/assets/asset_create.html:16 #: assets/templates/assets/asset_update.html:21 #: assets/templates/assets/gateway_create_update.html:37 -#: perms/templates/perms/asset_permission_create_update.html:33 +#: perms/templates/perms/asset_permission_create_update.html:38 msgid "Basic" msgstr "基本" @@ -878,7 +878,7 @@ msgstr "自动生成密钥" #: assets/templates/assets/asset_create.html:60 #: assets/templates/assets/asset_update.html:64 #: assets/templates/assets/gateway_create_update.html:53 -#: perms/templates/perms/asset_permission_create_update.html:45 +#: perms/templates/perms/asset_permission_create_update.html:50 #: terminal/templates/terminal/terminal_update.html:42 msgid "Other" msgstr "其它" @@ -900,7 +900,7 @@ msgstr "其它" #: common/templates/common/replay_storage_create.html:138 #: common/templates/common/security_setting.html:70 #: common/templates/common/terminal_setting.html:68 -#: perms/templates/perms/asset_permission_create_update.html:75 +#: perms/templates/perms/asset_permission_create_update.html:80 #: terminal/templates/terminal/terminal_update.html:47 #: users/templates/users/_user.html:50 #: users/templates/users/user_bulk_update.html:23 @@ -933,7 +933,7 @@ msgstr "重置" #: common/templates/common/replay_storage_create.html:139 #: common/templates/common/security_setting.html:71 #: common/templates/common/terminal_setting.html:70 -#: perms/templates/perms/asset_permission_create_update.html:76 +#: perms/templates/perms/asset_permission_create_update.html:81 #: terminal/templates/terminal/command_list.html:103 #: terminal/templates/terminal/session_list.html:127 #: terminal/templates/terminal/terminal_update.html:48 @@ -1008,7 +1008,7 @@ msgstr "测试" #: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_list.html:88 #: assets/templates/assets/asset_detail.html:24 -#: assets/templates/assets/asset_list.html:175 +#: assets/templates/assets/asset_list.html:177 #: assets/templates/assets/cmd_filter_detail.html:29 #: assets/templates/assets/cmd_filter_list.html:57 #: assets/templates/assets/cmd_filter_rule_list.html:86 @@ -1040,7 +1040,7 @@ msgstr "更新" #: assets/templates/assets/admin_user_detail.html:28 #: assets/templates/assets/admin_user_list.html:89 #: assets/templates/assets/asset_detail.html:28 -#: assets/templates/assets/asset_list.html:176 +#: assets/templates/assets/asset_list.html:178 #: assets/templates/assets/cmd_filter_detail.html:33 #: assets/templates/assets/cmd_filter_list.html:58 #: assets/templates/assets/cmd_filter_rule_list.html:87 @@ -1082,7 +1082,7 @@ msgstr "选择节点" #: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/asset_detail.html:208 -#: assets/templates/assets/asset_list.html:624 +#: assets/templates/assets/asset_list.html:635 #: assets/templates/assets/cmd_filter_detail.html:106 #: assets/templates/assets/system_user_asset.html:112 #: assets/templates/assets/system_user_detail.html:182 @@ -1166,7 +1166,7 @@ msgstr "快速修改" #: assets/templates/assets/asset_detail.html:151 #: assets/templates/assets/user_asset_list.html:47 perms/models.py:34 #: perms/models.py:82 -#: perms/templates/perms/asset_permission_create_update.html:47 +#: perms/templates/perms/asset_permission_create_update.html:52 #: perms/templates/perms/asset_permission_detail.html:120 #: perms/templates/perms/asset_permission_list.html:59 #: terminal/templates/terminal/terminal_list.html:34 @@ -1272,34 +1272,38 @@ msgid "Test node connective" msgstr "测试节点资产可连接性" #: assets/templates/assets/asset_list.html:136 +msgid "Refresh all node assets amount" +msgstr "刷新所有节点资产数量" + +#: assets/templates/assets/asset_list.html:138 msgid "Display only current node assets" msgstr "仅显示当前节点资产" -#: assets/templates/assets/asset_list.html:137 +#: assets/templates/assets/asset_list.html:139 msgid "Displays all child node assets" msgstr "显示所有子节点资产" -#: assets/templates/assets/asset_list.html:214 +#: assets/templates/assets/asset_list.html:216 msgid "Create node failed" msgstr "创建节点失败" -#: assets/templates/assets/asset_list.html:226 +#: assets/templates/assets/asset_list.html:228 msgid "Have child node, cancel" msgstr "存在子节点,不能删除" -#: assets/templates/assets/asset_list.html:228 +#: assets/templates/assets/asset_list.html:230 msgid "Have assets, cancel" msgstr "存在资产,不能删除" -#: assets/templates/assets/asset_list.html:299 +#: assets/templates/assets/asset_list.html:301 msgid "Rename success" msgstr "重命名成功" -#: assets/templates/assets/asset_list.html:300 +#: assets/templates/assets/asset_list.html:302 msgid "Rename failed, do not change the root node name" msgstr "重命名失败,不能更改root节点的名称" -#: assets/templates/assets/asset_list.html:618 +#: assets/templates/assets/asset_list.html:629 #: assets/templates/assets/system_user_list.html:137 #: users/templates/users/user_detail.html:380 #: users/templates/users/user_detail.html:406 @@ -1309,11 +1313,11 @@ msgstr "重命名失败,不能更改root节点的名称" msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:619 +#: assets/templates/assets/asset_list.html:630 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:622 +#: assets/templates/assets/asset_list.html:633 #: assets/templates/assets/system_user_list.html:141 #: common/templates/common/terminal_setting.html:163 #: users/templates/users/user_detail.html:384 @@ -1326,16 +1330,16 @@ msgstr "删除选择资产" msgid "Cancel" msgstr "取消" -#: assets/templates/assets/asset_list.html:628 +#: assets/templates/assets/asset_list.html:639 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:629 -#: assets/templates/assets/asset_list.html:634 +#: assets/templates/assets/asset_list.html:640 +#: assets/templates/assets/asset_list.html:645 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:633 +#: assets/templates/assets/asset_list.html:644 msgid "Asset Deleting failed." msgstr "删除失败" @@ -2610,7 +2614,7 @@ msgstr "添加节点" msgid "Join" msgstr "加入" -#: perms/templates/perms/asset_permission_create_update.html:53 +#: perms/templates/perms/asset_permission_create_update.html:58 msgid "Validity period" msgstr "有效期" From 340c615efe748670774cfe119b8657aa66439704 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Wed, 26 Dec 2018 14:43:43 +0800 Subject: [PATCH 75/80] =?UTF-8?q?[Bugfix]=20session=20model=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20vnc=20=E5=8D=8F=E8=AE=AE=20(#2278)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Bugfix] session model 添加 vnc 协议 * [Update] 修改表结构 --- .../migrations/0014_auto_20181226_1441.py | 18 ++++++++++++++++++ apps/terminal/models.py | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 apps/terminal/migrations/0014_auto_20181226_1441.py diff --git a/apps/terminal/migrations/0014_auto_20181226_1441.py b/apps/terminal/migrations/0014_auto_20181226_1441.py new file mode 100644 index 000000000..fa890365a --- /dev/null +++ b/apps/terminal/migrations/0014_auto_20181226_1441.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1 on 2018-12-26 06:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0013_auto_20181123_1113'), + ] + + operations = [ + migrations.AlterField( + model_name='session', + name='protocol', + field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('vnc', 'vnc')], default='ssh', max_length=8), + ), + ] diff --git a/apps/terminal/models.py b/apps/terminal/models.py index 6491bdf35..880637efe 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -132,7 +132,8 @@ class Session(OrgModelMixin): ) PROTOCOL_CHOICES = ( ('ssh', 'ssh'), - ('rdp', 'rdp') + ('rdp', 'rdp'), + ('vnc', 'vnc') ) id = models.UUIDField(default=uuid.uuid4, primary_key=True) From c5d1ed126e515d42c2920f78fae1c92c5679209b Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 26 Dec 2018 19:20:32 +0800 Subject: [PATCH 76/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9Jms=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jms | 3 --- 1 file changed, 3 deletions(-) diff --git a/jms b/jms index 613de2fe3..f1d0a6944 100755 --- a/jms +++ b/jms @@ -58,9 +58,6 @@ def check_database_connection(): def make_migrations(): print("Check database structure change ...") os.chdir(os.path.join(BASE_DIR, 'apps')) - if len(os.listdir('assets/migrations')) < 4: - print("Make database migrations ...") - subprocess.call('python3 manage.py makemigrations', shell=True) print("Migrate model change to database ...") subprocess.call('python3 manage.py migrate', shell=True) From 7c10f8743f61e38b25125ef1621555eb8bc82302 Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Thu, 27 Dec 2018 11:39:11 +0800 Subject: [PATCH 77/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=9C=A8?= =?UTF-8?q?=E7=BA=BF=E4=BC=9A=E8=AF=9D=E7=BB=88=E7=AB=AF=E6=8C=89=E9=92=AE?= =?UTF-8?q?vnc=E5=8D=8F=E8=AE=AEdisable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/templates/terminal/session_list.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/terminal/templates/terminal/session_list.html b/apps/terminal/templates/terminal/session_list.html index 534dc4cd6..92c114966 100644 --- a/apps/terminal/templates/terminal/session_list.html +++ b/apps/terminal/templates/terminal/session_list.html @@ -104,10 +104,10 @@ {% trans "Replay" %} {% else %} - {% if session.protocol == 'rdp' %} - {% trans "Terminate" %} - {% else %} + {% if session.protocol == 'ssh' %} {% trans "Terminate" %} + {% else %} + {% trans "Terminate" %} {% endif %} {% endif %} From 560df5027a5522d7ef8b3dc4b3cec8bfe8d3d72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Thu, 27 Dec 2018 16:47:40 +0800 Subject: [PATCH 78/80] =?UTF-8?q?[Update]=20=E7=A6=81=E7=94=A8=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E8=AE=A4=E8=AF=81=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=AF=86=E7=A0=81=20(#2286)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 禁用其他认证方式修改密码 * [Update] 禁用其他认证方式修改密码 * [Update] 禁用其他认证方式修改密码 --- apps/locale/zh/LC_MESSAGES/django.po | 200 +++++++++--------- .../ops/command_execution_create.html | 1 - apps/users/models/user.py | 9 +- apps/users/templates/users/user_detail.html | 2 + apps/users/templates/users/user_profile.html | 18 +- apps/users/templates/users/user_update.html | 31 ++- apps/users/views/login.py | 5 +- apps/users/views/user.py | 6 + 8 files changed, 156 insertions(+), 116 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index a4cc97988..98e838293 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-12-26 13:42+0800\n" +"POT-Creation-Date: 2018-12-27 15:48+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -119,7 +119,7 @@ msgstr "端口" #: perms/templates/perms/asset_permission_create_update.html:45 #: perms/templates/perms/asset_permission_list.html:56 #: perms/templates/perms/asset_permission_list.html:114 -#: terminal/backends/command/models.py:13 terminal/models.py:140 +#: terminal/backends/command/models.py:13 terminal/models.py:141 #: terminal/templates/terminal/command_list.html:40 #: terminal/templates/terminal/command_list.html:73 #: terminal/templates/terminal/session_list.html:41 @@ -157,7 +157,7 @@ msgstr "不能包含特殊字符" #: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_list.html:53 #: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:20 -#: terminal/models.py:197 terminal/templates/terminal/terminal_detail.html:43 +#: terminal/models.py:198 terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 #: users/models/user.py:53 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_detail.html:63 @@ -206,6 +206,7 @@ msgstr "密码或密钥密码" #: users/templates/users/user_password_update.html:43 #: users/templates/users/user_profile_update.html:40 #: users/templates/users/user_pubkey_update.html:40 +#: users/templates/users/user_update.html:20 msgid "Password" msgstr "密码" @@ -500,7 +501,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:432 +#: users/models/user.py:439 msgid "System" msgstr "系统" @@ -529,7 +530,7 @@ msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:36 ops/models/command.py:19 -#: ops/templates/ops/command_execution_list.html:60 terminal/models.py:146 +#: ops/templates/ops/command_execution_list.html:60 terminal/models.py:147 #: terminal/templates/terminal/command_list.html:55 #: terminal/templates/terminal/command_list.html:71 #: terminal/templates/terminal/session_detail.html:48 @@ -633,12 +634,12 @@ msgstr "默认资产组" #: perms/templates/perms/asset_permission_create_update.html:41 #: perms/templates/perms/asset_permission_list.html:54 #: perms/templates/perms/asset_permission_list.html:108 templates/index.html:87 -#: terminal/backends/command/models.py:12 terminal/models.py:139 +#: terminal/backends/command/models.py:12 terminal/models.py:140 #: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:303 -#: users/models/user.py:33 users/models/user.py:420 +#: users/models/user.py:33 users/models/user.py:427 #: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:13 users/views/user.py:386 #: xpack/plugins/orgs/forms.py:26 @@ -720,7 +721,7 @@ msgstr "登录模式" #: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_list.html:58 #: perms/templates/perms/asset_permission_list.html:120 templates/_nav.html:25 -#: terminal/backends/command/models.py:14 terminal/models.py:141 +#: terminal/backends/command/models.py:14 terminal/models.py:142 #: terminal/templates/terminal/command_list.html:48 #: terminal/templates/terminal/command_list.html:74 #: terminal/templates/terminal/session_list.html:49 @@ -906,7 +907,7 @@ msgstr "其它" #: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_detail.html:176 #: users/templates/users/user_password_update.html:71 -#: users/templates/users/user_profile.html:202 +#: users/templates/users/user_profile.html:204 #: users/templates/users/user_profile_update.html:63 #: users/templates/users/user_pubkey_update.html:70 #: users/templates/users/user_pubkey_update.html:76 @@ -1027,9 +1028,9 @@ msgstr "测试" #: users/templates/users/user_group_detail.html:28 #: users/templates/users/user_group_list.html:43 #: users/templates/users/user_list.html:80 -#: users/templates/users/user_profile.html:155 -#: users/templates/users/user_profile.html:185 -#: users/templates/users/user_profile.html:194 +#: users/templates/users/user_profile.html:177 +#: users/templates/users/user_profile.html:187 +#: users/templates/users/user_profile.html:196 #: xpack/plugins/cloud/templates/cloud/account_detail.html:25 #: xpack/plugins/cloud/templates/cloud/account_list.html:38 #: xpack/plugins/orgs/templates/orgs/org_detail.html:25 @@ -1089,14 +1090,14 @@ msgstr "选择节点" #: assets/templates/assets/system_user_list.html:143 #: common/templates/common/terminal_setting.html:165 templates/_modal.html:22 #: terminal/templates/terminal/session_detail.html:108 -#: users/templates/users/user_detail.html:386 -#: users/templates/users/user_detail.html:412 -#: users/templates/users/user_detail.html:435 -#: users/templates/users/user_detail.html:480 +#: users/templates/users/user_detail.html:388 +#: users/templates/users/user_detail.html:414 +#: users/templates/users/user_detail.html:437 +#: users/templates/users/user_detail.html:482 #: users/templates/users/user_group_create_update.html:32 #: users/templates/users/user_group_list.html:88 #: users/templates/users/user_list.html:208 -#: users/templates/users/user_profile.html:236 +#: users/templates/users/user_profile.html:238 #: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 @@ -1187,8 +1188,8 @@ msgid "Refresh" msgstr "刷新" #: assets/templates/assets/asset_detail.html:308 -#: users/templates/users/user_detail.html:305 -#: users/templates/users/user_detail.html:332 +#: users/templates/users/user_detail.html:307 +#: users/templates/users/user_detail.html:334 msgid "Update successfully!" msgstr "更新成功" @@ -1305,9 +1306,9 @@ msgstr "重命名失败,不能更改root节点的名称" #: assets/templates/assets/asset_list.html:629 #: assets/templates/assets/system_user_list.html:137 -#: users/templates/users/user_detail.html:380 -#: users/templates/users/user_detail.html:406 -#: users/templates/users/user_detail.html:474 +#: users/templates/users/user_detail.html:382 +#: users/templates/users/user_detail.html:408 +#: users/templates/users/user_detail.html:476 #: users/templates/users/user_group_list.html:82 #: users/templates/users/user_list.html:202 msgid "Are you sure?" @@ -1320,9 +1321,9 @@ msgstr "删除选择资产" #: assets/templates/assets/asset_list.html:633 #: assets/templates/assets/system_user_list.html:141 #: common/templates/common/terminal_setting.html:163 -#: users/templates/users/user_detail.html:384 -#: users/templates/users/user_detail.html:410 -#: users/templates/users/user_detail.html:478 +#: users/templates/users/user_detail.html:386 +#: users/templates/users/user_detail.html:412 +#: users/templates/users/user_detail.html:480 #: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_list.html:86 #: users/templates/users/user_list.html:206 @@ -1656,7 +1657,7 @@ msgstr "系统用户资产" #: audits/templates/audits/ftp_log_list.html:73 #: audits/templates/audits/operate_log_list.html:70 #: audits/templates/audits/password_change_log_list.html:52 -#: terminal/models.py:143 terminal/templates/terminal/session_list.html:74 +#: terminal/models.py:144 terminal/templates/terminal/session_list.html:74 #: terminal/templates/terminal/terminal_detail.html:47 msgid "Remote addr" msgstr "远端地址" @@ -1673,7 +1674,7 @@ msgstr "文件名" #: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76 #: ops/templates/ops/command_execution_list.html:64 #: ops/templates/ops/task_list.html:39 users/models/authentication.py:73 -#: users/templates/users/user_detail.html:456 xpack/plugins/cloud/api.py:61 +#: users/templates/users/user_detail.html:458 xpack/plugins/cloud/api.py:61 msgid "Success" msgstr "成功" @@ -1699,7 +1700,7 @@ msgstr "修改者" #: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/command_execution_list.html:65 #: ops/templates/ops/task_history.html:58 perms/models.py:35 -#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:150 +#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:151 #: terminal/templates/terminal/session_list.html:78 msgid "Date start" msgstr "开始日期" @@ -2564,7 +2565,7 @@ msgstr "组织管理" #: perms/templates/perms/asset_permission_list.html:111 templates/_nav.html:14 #: users/forms.py:273 users/models/group.py:26 users/models/user.py:59 #: users/templates/users/_select_user_modal.html:16 -#: users/templates/users/user_detail.html:211 +#: users/templates/users/user_detail.html:213 #: users/templates/users/user_list.html:26 #: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User group" @@ -2610,7 +2611,7 @@ msgid "Add node to this permission" msgstr "添加节点" #: perms/templates/perms/asset_permission_asset.html:125 -#: users/templates/users/user_detail.html:228 +#: users/templates/users/user_detail.html:230 msgid "Join" msgstr "加入" @@ -2798,9 +2799,9 @@ msgstr "" #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 #: users/views/group.py:60 users/views/group.py:76 users/views/group.py:92 -#: users/views/login.py:346 users/views/user.py:68 users/views/user.py:83 +#: users/views/login.py:349 users/views/user.py:68 users/views/user.py:83 #: users/views/user.py:113 users/views/user.py:194 users/views/user.py:355 -#: users/views/user.py:405 users/views/user.py:439 +#: users/views/user.py:405 users/views/user.py:444 msgid "Users" msgstr "用户管理" @@ -3089,19 +3090,19 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" -#: terminal/models.py:145 terminal/templates/terminal/session_list.html:104 +#: terminal/models.py:146 terminal/templates/terminal/session_list.html:104 msgid "Replay" msgstr "回放" -#: terminal/models.py:149 +#: terminal/models.py:150 msgid "Date last active" msgstr "最后活跃日期" -#: terminal/models.py:151 +#: terminal/models.py:152 msgid "Date end" msgstr "结束日期" -#: terminal/models.py:198 +#: terminal/models.py:199 msgid "Args" msgstr "参数" @@ -3337,7 +3338,7 @@ msgstr "" msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:71 users/templates/users/user_detail.html:219 +#: users/forms.py:71 users/templates/users/user_detail.html:221 msgid "Join user groups" msgstr "添加到用户组" @@ -3472,7 +3473,7 @@ msgstr "Agent" msgid "Date login" msgstr "登录日期" -#: users/models/user.py:32 users/models/user.py:428 +#: users/models/user.py:32 users/models/user.py:435 msgid "Administrator" msgstr "管理员" @@ -3481,13 +3482,13 @@ msgid "Application" msgstr "应用程序" #: users/models/user.py:37 users/templates/users/user_profile.html:92 -#: users/templates/users/user_profile.html:167 -#: users/templates/users/user_profile.html:170 +#: users/templates/users/user_profile.html:159 +#: users/templates/users/user_profile.html:162 msgid "Disable" msgstr "禁用" #: users/models/user.py:38 users/templates/users/user_profile.html:90 -#: users/templates/users/user_profile.html:174 +#: users/templates/users/user_profile.html:166 msgid "Enable" msgstr "启用" @@ -3518,7 +3519,12 @@ msgstr "用户来源" msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:431 +#: users/models/user.py:126 users/templates/users/user_update.html:22 +#: users/views/login.py:243 users/views/login.py:302 users/views/user.py:418 +msgid "User auth from {}, go there change password" +msgstr "用户认证源来自 {}, 请去相应系统修改密码" + +#: users/models/user.py:438 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -3735,19 +3741,19 @@ msgid "Always young, always with tears in my eyes. Stay foolish Stay hungry" msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry" #: users/templates/users/reset_password.html:46 -#: users/templates/users/user_detail.html:371 users/utils.py:81 +#: users/templates/users/user_detail.html:373 users/utils.py:81 msgid "Reset password" msgstr "重置密码" #: users/templates/users/reset_password.html:59 #: users/templates/users/user_password_update.html:61 -#: users/templates/users/user_update.html:12 +#: users/templates/users/user_update.html:13 msgid "Your password must satisfy" msgstr "您的密码必须满足:" #: users/templates/users/reset_password.html:60 #: users/templates/users/user_password_update.html:62 -#: users/templates/users/user_update.html:13 +#: users/templates/users/user_update.html:14 msgid "Password strength" msgstr "密码强度:" @@ -3762,37 +3768,37 @@ msgstr "设置" #: users/templates/users/reset_password.html:105 #: users/templates/users/user_password_update.html:99 -#: users/templates/users/user_update.html:34 +#: users/templates/users/user_update.html:46 msgid "Very weak" msgstr "很弱" #: users/templates/users/reset_password.html:106 #: users/templates/users/user_password_update.html:100 -#: users/templates/users/user_update.html:35 +#: users/templates/users/user_update.html:47 msgid "Weak" msgstr "弱" #: users/templates/users/reset_password.html:107 #: users/templates/users/user_password_update.html:101 -#: users/templates/users/user_update.html:36 +#: users/templates/users/user_update.html:48 msgid "Normal" msgstr "正常" #: users/templates/users/reset_password.html:108 #: users/templates/users/user_password_update.html:102 -#: users/templates/users/user_update.html:37 +#: users/templates/users/user_update.html:49 msgid "Medium" msgstr "一般" #: users/templates/users/reset_password.html:109 #: users/templates/users/user_password_update.html:103 -#: users/templates/users/user_update.html:38 +#: users/templates/users/user_update.html:50 msgid "Strong" msgstr "强" #: users/templates/users/reset_password.html:110 #: users/templates/users/user_password_update.html:104 -#: users/templates/users/user_update.html:39 +#: users/templates/users/user_update.html:51 msgid "Very strong" msgstr "很强" @@ -3839,71 +3845,71 @@ msgstr "强制启用MFA" msgid "Reset MFA" msgstr "重置MFA" -#: users/templates/users/user_detail.html:181 +#: users/templates/users/user_detail.html:182 msgid "Send reset password mail" msgstr "发送重置密码邮件" -#: users/templates/users/user_detail.html:184 -#: users/templates/users/user_detail.html:192 +#: users/templates/users/user_detail.html:185 +#: users/templates/users/user_detail.html:194 msgid "Send" msgstr "发送" -#: users/templates/users/user_detail.html:189 +#: users/templates/users/user_detail.html:191 msgid "Send reset ssh key mail" msgstr "发送重置密钥邮件" -#: users/templates/users/user_detail.html:197 -#: users/templates/users/user_detail.html:459 +#: users/templates/users/user_detail.html:199 +#: users/templates/users/user_detail.html:461 msgid "Unblock user" msgstr "解除登录限制" -#: users/templates/users/user_detail.html:200 +#: users/templates/users/user_detail.html:202 msgid "Unblock" msgstr "解除" -#: users/templates/users/user_detail.html:314 +#: users/templates/users/user_detail.html:316 msgid "Goto profile page enable MFA" msgstr "请去个人信息页面启用自己的MFA" -#: users/templates/users/user_detail.html:370 +#: users/templates/users/user_detail.html:372 msgid "An e-mail has been sent to the user`s mailbox." msgstr "已发送邮件到用户邮箱" -#: users/templates/users/user_detail.html:381 +#: users/templates/users/user_detail.html:383 msgid "This will reset the user password and send a reset mail" msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱" -#: users/templates/users/user_detail.html:396 +#: users/templates/users/user_detail.html:398 msgid "" "The reset-ssh-public-key E-mail has been sent successfully. Please inform " "the user to update his new ssh public key." msgstr "重设密钥邮件将会发送到用户邮箱" -#: users/templates/users/user_detail.html:397 +#: users/templates/users/user_detail.html:399 msgid "Reset SSH public key" msgstr "重置SSH密钥" -#: users/templates/users/user_detail.html:407 +#: users/templates/users/user_detail.html:409 msgid "This will reset the user public key and send a reset mail" msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱" -#: users/templates/users/user_detail.html:425 -#: users/templates/users/user_profile.html:225 +#: users/templates/users/user_detail.html:427 +#: users/templates/users/user_profile.html:227 msgid "Successfully updated the SSH public key." msgstr "更新ssh密钥成功" -#: users/templates/users/user_detail.html:426 -#: users/templates/users/user_detail.html:430 -#: users/templates/users/user_profile.html:226 -#: users/templates/users/user_profile.html:231 +#: users/templates/users/user_detail.html:428 +#: users/templates/users/user_detail.html:432 +#: users/templates/users/user_profile.html:228 +#: users/templates/users/user_profile.html:233 msgid "User SSH public key update" msgstr "ssh密钥" -#: users/templates/users/user_detail.html:475 +#: users/templates/users/user_detail.html:477 msgid "After unlocking the user, the user can log in normally." msgstr "解除用户登录限制后,此用户即可正常登录" -#: users/templates/users/user_detail.html:489 +#: users/templates/users/user_detail.html:491 msgid "Reset user MFA success" msgstr "重置用户MFA成功" @@ -4021,26 +4027,26 @@ msgid "User groups" msgstr "用户组" #: users/templates/users/user_profile.html:152 -msgid "Update password" -msgstr "更改密码" - -#: users/templates/users/user_profile.html:160 msgid "Set MFA" msgstr "设置MFA" -#: users/templates/users/user_profile.html:182 +#: users/templates/users/user_profile.html:174 +msgid "Update password" +msgstr "更改密码" + +#: users/templates/users/user_profile.html:184 msgid "Update MFA" msgstr "更改MFA" -#: users/templates/users/user_profile.html:191 +#: users/templates/users/user_profile.html:193 msgid "Update SSH public key" msgstr "更改SSH密钥" -#: users/templates/users/user_profile.html:199 +#: users/templates/users/user_profile.html:201 msgid "Reset public key and download" msgstr "重置并下载SSH密钥" -#: users/templates/users/user_profile.html:229 +#: users/templates/users/user_profile.html:231 msgid "Failed to update SSH public key." msgstr "更新密钥失败" @@ -4273,7 +4279,7 @@ msgstr "用户组授权资产" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:191 users/views/user.py:526 users/views/user.py:551 +#: users/views/login.py:191 users/views/user.py:531 users/views/user.py:556 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" @@ -4289,10 +4295,6 @@ msgstr "退出登录成功,返回到登录页面" msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:243 -msgid "User auth from {}, go there change password" -msgstr "用户认证源来自 {}, 请去相应系统修改密码" - #: users/views/login.py:256 msgid "Send reset password message" msgstr "发送重置密码邮件" @@ -4310,7 +4312,7 @@ msgstr "重置密码成功" msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:286 users/views/login.py:302 +#: users/views/login.py:286 users/views/login.py:305 msgid "Token invalid or expired" msgstr "Token错误或失效" @@ -4318,11 +4320,11 @@ msgstr "Token错误或失效" msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:308 users/views/user.py:128 users/views/user.py:422 +#: users/views/login.py:311 users/views/user.py:128 users/views/user.py:427 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/login.py:346 +#: users/views/login.py:349 msgid "First login" msgstr "首次登陆" @@ -4350,27 +4352,27 @@ msgstr "个人信息设置" msgid "Password update" msgstr "密码更新" -#: users/views/user.py:440 +#: users/views/user.py:445 msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:481 +#: users/views/user.py:486 msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/user.py:581 +#: users/views/user.py:586 msgid "MFA enable success" msgstr "MFA 绑定成功" -#: users/views/user.py:582 +#: users/views/user.py:587 msgid "MFA enable success, return login page" msgstr "MFA 绑定成功,返回到登录页面" -#: users/views/user.py:584 +#: users/views/user.py:589 msgid "MFA disable success" msgstr "MFA 解绑成功" -#: users/views/user.py:585 +#: users/views/user.py:590 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" @@ -4386,23 +4388,23 @@ msgstr "" msgid "Access Key Secret" msgstr "" -#: xpack/plugins/cloud/forms.py:58 +#: xpack/plugins/cloud/forms.py:52 msgid "Select account" msgstr "选择账户" -#: xpack/plugins/cloud/forms.py:64 +#: xpack/plugins/cloud/forms.py:58 msgid "Select regions" msgstr "选择地域" -#: xpack/plugins/cloud/forms.py:70 +#: xpack/plugins/cloud/forms.py:64 msgid "Select instances" msgstr "选择实例" -#: xpack/plugins/cloud/forms.py:76 +#: xpack/plugins/cloud/forms.py:70 msgid "Select node" msgstr "选择节点" -#: xpack/plugins/cloud/forms.py:82 xpack/plugins/orgs/forms.py:18 +#: xpack/plugins/cloud/forms.py:76 xpack/plugins/orgs/forms.py:18 msgid "Select admins" msgstr "选择管理员" diff --git a/apps/ops/templates/ops/command_execution_create.html b/apps/ops/templates/ops/command_execution_create.html index a832b7fc9..e429e38ba 100644 --- a/apps/ops/templates/ops/command_execution_create.html +++ b/apps/ops/templates/ops/command_execution_create.html @@ -265,7 +265,6 @@ $(document).ready(function(){ systemUserId = $('#system-users-select').val(); $(".select2").select2({ dropdownAutoWidth : true, - width: 'auto' }).on('select2:select', function(evt) { var data = evt.params.data; systemUserId = data.id; diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 9cdbc9f77..851ea2ccc 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -120,7 +120,14 @@ class User(AbstractUser): def set_password(self, raw_password): self._set_password = True - return super().set_password(raw_password) + if self.can_update_password(): + return super().set_password(raw_password) + else: + error = _("User auth from {}, go there change password").format(self.source) + raise PermissionError(error) + + def can_update_password(self): + return self.is_local @property def otp_secret_key(self): diff --git a/apps/users/templates/users/user_detail.html b/apps/users/templates/users/user_detail.html index 0827b9a1f..deb96eb68 100644 --- a/apps/users/templates/users/user_detail.html +++ b/apps/users/templates/users/user_detail.html @@ -177,6 +177,7 @@ + {% if user_object.can_update_password %}
    + {% endif %}
    - - {% trans 'Hostname' %} {% trans 'IP' %} {% trans 'Port' %} {% trans 'Reachable' %}{% trans 'Action' %}
    {% trans 'Send reset password mail' %}: @@ -185,6 +186,7 @@
    {% trans 'Send reset ssh key mail' %}: diff --git a/apps/users/templates/users/user_profile.html b/apps/users/templates/users/user_profile.html index a7e455976..49478de60 100644 --- a/apps/users/templates/users/user_profile.html +++ b/apps/users/templates/users/user_profile.html @@ -148,14 +148,6 @@
    - - - - + {% if request.user.can_update_password %} + + + + + {% endif %} {% if request.user.otp_enabled and request.user.otp_secret_key %} diff --git a/apps/users/templates/users/user_update.html b/apps/users/templates/users/user_update.html index e991af83b..cc4855e1f 100644 --- a/apps/users/templates/users/user_update.html +++ b/apps/users/templates/users/user_update.html @@ -3,6 +3,7 @@ {% load bootstrap3 %} {% block user_template_title %}{% trans "Update user" %}{% endblock %} {% block password %} + {% if object.can_update_password %} {% bootstrap_field form.password layout="horizontal" %} {# 密码popover #}
    @@ -14,13 +15,24 @@
    + {% else %} +
    + +
    + {% trans 'User auth from {}, go there change password' %} +
    +
    + {% endif %} {% bootstrap_field form.public_key layout="horizontal" %} {% endblock %} {% block custom_foot_js %} {{ block.super }} diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 519445895..69e660f11 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -239,7 +239,7 @@ class UserForgotPasswordView(TemplateView): if not user: error = _('Email address invalid, please input again') return self.get(request, errors=error) - elif not user.is_local: + elif not user.can_update_password(): error = _('User auth from {}, go there change password'.format(user.source)) return self.get(request, errors=error) else: @@ -298,6 +298,9 @@ class UserResetPasswordView(TemplateView): return self.get(request, errors=_('Password not same')) user = User.validate_reset_token(token) + if not user.can_update_password(): + error = _('User auth from {}, go there change password'.format(user.source)) + return self.get(request, errors=error) if not user: return self.get(request, errors=_('Token invalid or expired')) diff --git a/apps/users/views/user.py b/apps/users/views/user.py index f58f66392..60fb7cca3 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -414,6 +414,12 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView): return super().get_success_url() def form_valid(self, form): + if not self.request.user.can_update_password(): + error = _("User auth from {}, go there change password").format( + self.request.source_display + ) + form.add_error("password", error) + return self.form_invalid(form) password = form.cleaned_data.get('new_password') is_ok = check_password_rules(password) if not is_ok: From b2a8415f7783babd42f2b853920f3befab3994dd Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 2 Jan 2019 15:33:35 +0800 Subject: [PATCH 79/80] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E5=AD=90=E8=8A=82=E7=82=B9api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/node.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 052d70e31..4fc030e1e 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -134,6 +134,9 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): serializer_class = serializers.NodeSerializer instance = None + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + def post(self, request, *args, **kwargs): instance = self.get_object() if not request.data.get("value"): @@ -154,7 +157,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): def get_object(self): pk = self.kwargs.get('pk') or self.request.query_params.get('id') if not pk: - node = None + node = Node.root() else: node = get_object_or_404(Node, pk=pk) return node From d6567f0e5745b6c0f79c2d932c3f284d6b37a703 Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Thu, 3 Jan 2019 10:39:09 +0800 Subject: [PATCH 80/80] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D-=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E9=A1=B5=E9=9D=A2=E8=B5=84=E4=BA=A7=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E6=98=BE=E7=A4=BAbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/templates/assets/user_asset_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html index a88b9d839..0e39aa899 100644 --- a/apps/assets/templates/assets/user_asset_list.html +++ b/apps/assets/templates/assets/user_asset_list.html @@ -159,7 +159,7 @@ $(document).ready(function () { 'comment': "{% trans 'Comment' %}" {#'date_joined': "{% trans 'Date joined' %}",#} }; - $.each(data, function(index, value){ + $.each(data.results, function(index, value){ if(value.id === asset_id){ for(var i in desc){ trs += "\n" +
    {% trans 'Update password' %}: - - {% trans 'Update' %} - -
    {% trans 'Set MFA' %}: @@ -177,6 +169,16 @@
    {% trans 'Update password' %}: + + {% trans 'Update' %} + +
    {% trans 'Update MFA' %}: