Unverified Commit 3d13f3a1 authored by 老广's avatar 老广 Committed by GitHub

Command (#2134)

* [Update] 任务区分org

* [Update] 修改翻译

* [Update] 使用id而不是hostname

* [Update] 执行命令

* [Update] 修改一些东西

* [Update] 暂存

* [Update] 用户执行命令

* [Update] 添加资产授权模块-tree

* [Update] 暂时这样

* [Update] 批量命令执行

* [Update] 修改表结构

* [Update] 更新翻译

* [Update] 删除cloud模块无效中文翻译
parent d91599ff
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from .user import *
from .label import Label
from .cluster import *
......
......@@ -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:
......
# -*- 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)
......@@ -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))
......
......@@ -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
......
......@@ -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)
......
......@@ -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))
continue
asset = Asset.objects.get_object_by_fullname(hostname)
if not asset:
logger.error(_("Get asset info failed: {}").format(hostname))
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
......
......@@ -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',
......
......@@ -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);
});
}
......
......@@ -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)
......
......@@ -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:
......
......@@ -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'),
]
......@@ -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)
......@@ -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
......@@ -111,3 +111,13 @@ def sort(data):
@register.filter
def subtract(value, arg):
return value - arg
@register.filter
def state_show(state):
success = '<i class ="fa fa-check text-navy"> </i>'
failed = '<i class ="fa fa-times text-danger"> </i>'
if state:
return success
else:
return failed
# -*- 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()
......@@ -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 <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\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: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:135
#: 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><div>如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运</"
"div>"
#: 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\"> 链接 </a> 更新\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/base.py:77
msgid "检测账户有效性: {}"
msgstr ""
#: xpack/plugins/cloud/providers/base.py:80
msgid "账户无效!"
msgstr ""
#: 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/aliyun.py:14
msgid "Aliyun"
msgstr "阿里云"
#: xpack/plugins/cloud/providers/base.py:143
msgid "地域: {}"
msgstr ""
#: xpack/plugins/cloud/providers/aws.py:11
msgid "AWS (China)"
msgstr "AWS (中国)"
#: xpack/plugins/cloud/providers/base.py:154
msgid "实例: {}, 地域: {}"
msgstr ""
#: xpack/plugins/cloud/providers/aws.py:12
msgid "AWS (International)"
msgstr "AWS (国际)"
#: 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"
......
# ~*~ 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 ansible.plugins.callback.minimal import CallbackModule as CMDCallBackModule
from .display import TeeObj
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": "",
......
......@@ -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)
# -*- coding: utf-8 -*-
#
from .adhoc import *
from .celery import *
from .command import *
# ~*~ 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})
# -*- 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)
# -*- 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)
)
......@@ -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))
......
# -*- 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()
......@@ -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:
......
# 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')},
),
]
# 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'),
),
]
......@@ -2,4 +2,5 @@
#
from .adhoc import *
from .celery import *
\ No newline at end of file
from .celery import *
from .command import *
......@@ -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:
......
# -*- 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
# ~*~ 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})
......@@ -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))
......
......@@ -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();
......
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.exhide.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
<script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script>
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<style type="text/css">
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
padding: 10px;
}
.select2-container .select2-selection--single {
height: 34px;
}
</style>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
<div id="assetTree" class="ztree"></div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header" style="padding-top: 5px;">
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="">
<div class="form-group">
<div id="term" style="height: 100%;width: 100%"></div>
</div>
<div class="row">
<div class="col-lg-2">
<select class="select2 form-control" id="system-users-select">
{% for s in system_users %}
{% if s.protocol == 'ssh' and s.login_mode == 'auto' %}
<option value="{{ s.id }}">{{ s }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="col-lg-10">
<div class="input-group" style="height: 100%">
<input type="text" id="command-text" class="form-control">
<span class="input-group-btn">
<button type="button" class="btn btn-primary btn-execute">{% trans 'Go' %}</button>
</span>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var zTree, show = 0;
var systemUserId = null;
function initTree() {
var setting = {
check: {
enable: true
},
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isCopy: true,
isMove: true
}
},
callback: {
onCheck: onCheck
}
};
var url = "{% url 'api-perms:my-nodes-assets-as-tree' %}";
if (systemUserId) {
url += '?system_user=' + systemUserId
}
$.get(url, function(data, status){
$.fn.zTree.init($("#assetTree"), setting, data);
zTree = $.fn.zTree.getZTreeObj("assetTree");
});
}
function getSelectedAssetsNode() {
var nodes = zTree.getCheckedNodes(true);
var assetsNodeId = [];
var assetsNode = [];
nodes.forEach(function (node) {
if (node.meta.type === 'asset' && !node.isHidden && node.meta.asset.protocol === 'ssh') {
if (assetsNodeId.indexOf(node.id) === -1) {
assetsNodeId.push(node.id);
assetsNode.push(node)
}
}
});
return assetsNode;
}
function onCheck(e, treeId, treeNode) {
var nodes = getSelectedAssetsNode();
var nodes_names = nodes.map(function (node) {
return node.name;
});
var message = "已选择资产: ";
message += nodes_names.join(", ");
message += "\r\n";
message += "总共: " + nodes_names.length + "个\r\n";
term.clear();
term.write(message)
}
function toggle() {
if (show === 0) {
$("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-lg-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
show = 1;
});
} else {
$("#split-right").attr("class", "col-lg-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500);
show = 0;
}
}
var term = null;
function initResultTerminal() {
term = new Terminal({
cursorBlink: false,
screenKeys: false,
fontFamily: '"Monaco", "Consolas", "monospace"',
fontSize: 12,
rightClickSelectsWord: true,
disableStdin: true,
theme: {
background: '#1f1b1b'
}
});
term.open(document.getElementById('term'));
term.write("选择左侧资产, 选择运行的系统用户,批量执行命令\r\n")
}
$(document).ready(function(){
systemUserId = $('#system-users-select').val();
$(".select2").select2().on('select2:select', function(evt) {
var data = evt.params.data;
systemUserId = data.id;
initTree();
});
initTree();
initResultTerminal();
}).on('click', '.btn-execute', function () {
if (!term) {
initResultTerminal()
}
var url = '{% url "api-ops:command-execution-list" %}';
var run_as = systemUserId;
var command = $("#command-text").val();
var hosts = getSelectedAssetsNode().map(function (node) {
return node.id;
});
var data = {
hosts: hosts,
run_as: run_as,
command: command
};
var mark = '';
var log_url = null;
var end = false;
var error = false;
var int = null;
var interval = 200;
function writeExecutionOutput() {
if (!end) {
$.ajax({
url: log_url + '?mark=' + mark,
method: "GET",
contentType: "application/json; charset=utf-8"
}).done(function(data, textStatue, jqXHR) {
if (jqXHR.status === 203) {
error = true;
term.write('.');
interval = 500;
}
if (jqXHR.status === 200){
term.write(data.data);
mark = data.mark;
if (data.end){
end = true;
window.clearInterval(int)
}
}
})
}
}
APIUpdateAttr({
url: url,
body: JSON.stringify(data),
method: 'POST',
flash_message: false,
success: function (resp) {
term.write("{% trans 'Pending' %}" + "...\r\n");
log_url = resp.log_url;
int = setInterval(function () {
writeExecutionOutput()
}, interval);
}
})
})
</script>
{% endblock %}
\ No newline at end of file
{% extends '_base_list.html' %}
{% load i18n %}
{% load static %}
{% load common_tags %}
{% block content_left_head %}
{% endblock %}
{% block table_search %}
<form id="search_form" method="get" action="" class="pull-right form-inline">
<div class="form-group" id="date">
<div class="input-daterange input-group" id="datepicker">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from|date:'Y-m-d' }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to|date:'Y-m-d' }}">
</div>
</div>
{% if user_list %}
<div class="input-group">
<select class="select2 form-control" name="user">
<option value="">{% trans 'User' %}</option>
{% for u in user_list %}
<option value="{{ u.id }}" {% if u.id == user_id %} selected {% endif %}>{{ u }}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="input-group">
<input type="text" class="form-control input-sm" name="keyword" placeholder="{% trans 'Search' %}" value="{{ keyword }}">
</div>
<div class="input-group">
<div class="input-group-btn">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
{% trans "Search" %}
</button>
</div>
</div>
</form>
{% endblock %}
{% block table_head %}
<th class="text-center"></th>
<th class="text-center">{% trans 'Hosts' %}</th>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'Command' %}</th>
<th class="text-center">{% trans 'Output' %}</th>
<th class="text-center">{% trans 'Finished' %}</th>
<th class="text-center">{% trans 'Success' %}</th>
<th class="text-center">{% trans 'Date start' %}</th>
<th class="text-center">{% trans 'Date finished' %}</th>
{% endblock %}
{% block table_body %}
{% for object in object_list %}
<tr class="gradeX">
<td class="text-center"><input type="checkbox" class="cbx-term"></td>
<td class="text-center">{{ object.hosts.count }}</td>
<td class="text-center">{{ object.user }}</td>
<td class="text-center">{{ object.command| truncatechars:16 }}</td>
<td class="text-center"><a href="{% url "ops:celery-task-log" pk=object.id %}" target="_blank">查看</a></td>
<td class="text-center">{{ object.is_finished | state_show | safe }}</td>
<td class="text-center">{{ object.is_success | state_show | safe }}</td>
<td class="text-center">{{ object.date_start }}</td>
<td class="text-center">{{ object.date_finished }}</td>
</tr>
{% endfor %}
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
$('table').DataTable({
"searching": false,
"paging": false,
"bInfo" : false,
"order": []
});
$('.select2').select2({
dropdownAutoWidth : true,
width: 'auto'
});
$('#date .input-daterange').datepicker({
format: "yyyy-mm-dd",
todayBtn: "linked",
keyboardNavigation: false,
forceParse: false,
calendarWeeks: true,
autoclose: true
});
})
</script>
{% endblock %}
......@@ -4,10 +4,11 @@
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<link href="{% static 'css/plugins/sweetalert/sweetalert.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
......@@ -25,7 +26,7 @@
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
</li>
<li>
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
<a class="text-center celery-task-log" onclick="window.open("{% url 'ops:celery-task-log' pk=object.latest_history.pk %}",'', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
</li>
</ul>
</div>
......
......@@ -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/<uuid:pk>/run/', api.TaskRun.as_view(), name='task-run'),
path('celery/task/<uuid:pk>/log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
path('celery/task/<uuid:pk>/result/', api.CeleryResultApi.as_view(), name='celery-result'),
]
urlpatterns += router.urls
......@@ -18,4 +18,7 @@ urlpatterns = [
path('adhoc/<uuid:pk>/history/', views.AdHocHistoryView.as_view(), name='adhoc-history'),
path('adhoc/history/<uuid:pk>/', views.AdHocHistoryDetailView.as_view(), name='adhoc-history-detail'),
path('celery/task/<uuid:pk>/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'),
]
# ~*~ 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
......
from .adhoc import *
from .celery import *
from .command import *
\ No newline at end of file
......@@ -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
# -*- 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
# -*- 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()
......@@ -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不同的是,只返回某个节点下的资产
......
......@@ -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'
]
......@@ -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/<uuid:pk>/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/<uuid:pk>/assets/',
......
......@@ -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")):
......
/* 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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");
}
/* 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; }
This source diff could not be displayed because it is too large. You can view the blob instead.
<!doctype html>
<title>CodeMirror: Language Modes</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../doc/docs.css">
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a>
<ul>
<li><a href="../index.html">Home</a>
<li><a href="../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a class=active href="#">Language modes</a>
</ul>
</div>
<article>
<h2>Language modes</h2>
<p>This is a list of every mode in the distribution. Each mode lives
in a subdirectory of the <code>mode/</code> directory, and typically
defines a single JavaScript file that implements the mode. Loading
such file will make the language available to CodeMirror, through
the <a href="manual.html#option_mode"><code>mode</code></a>
option.</p>
<div style="-webkit-columns: 100px 2; -moz-columns: 100px 2; columns: 100px 2;">
<ul style="margin-top: 0">
<li><a href="apl/index.html">APL</a></li>
<li><a href="asterisk/index.html">Asterisk dialplan</a></li>
<li><a href="clike/index.html">C, C++, C#</a></li>
<li><a href="clojure/index.html">Clojure</a></li>
<li><a href="cobol/index.html">COBOL</a></li>
<li><a href="coffeescript/index.html">CoffeeScript</a></li>
<li><a href="commonlisp/index.html">Common Lisp</a></li>
<li><a href="css/index.html">CSS</a></li>
<li><a href="cypher/index.html">Cypher</a></li>
<li><a href="python/index.html">Cython</a></li>
<li><a href="d/index.html">D</a></li>
<li><a href="django/index.html">Django</a> (templating language)</li>
<li><a href="diff/index.html">diff</a></li>
<li><a href="dtd/index.html">DTD</a></li>
<li><a href="dylan/index.html">Dylan</a></li>
<li><a href="ecl/index.html">ECL</a></li>
<li><a href="eiffel/index.html">Eiffel</a></li>
<li><a href="erlang/index.html">Erlang</a></li>
<li><a href="fortran/index.html">Fortran</a></li>
<li><a href="mllike/index.html">F#</a></li>
<li><a href="gas/index.html">Gas</a> (AT&amp;T-style assembly)</li>
<li><a href="gherkin/index.html">Gherkin</a></li>
<li><a href="go/index.html">Go</a></li>
<li><a href="groovy/index.html">Groovy</a></li>
<li><a href="haml/index.html">HAML</a></li>
<li><a href="haskell/index.html">Haskell</a></li>
<li><a href="haxe/index.html">Haxe</a></li>
<li><a href="htmlembedded/index.html">HTML embedded scripts</a></li>
<li><a href="htmlmixed/index.html">HTML mixed-mode</a></li>
<li><a href="http/index.html">HTTP</a></li>
<li><a href="clike/index.html">Java</a></li>
<li><a href="jade/index.html">Jade</a></li>
<li><a href="javascript/index.html">JavaScript</a></li>
<li><a href="jinja2/index.html">Jinja2</a></li>
<li><a href="julia/index.html">Julia</a></li>
<li><a href="kotlin/index.html">Kotlin</a></li>
<li><a href="css/less.html">LESS</a></li>
<li><a href="livescript/index.html">LiveScript</a></li>
<li><a href="lua/index.html">Lua</a></li>
<li><a href="markdown/index.html">Markdown</a> (<a href="gfm/index.html">GitHub-flavour</a>)</li>
<li><a href="mirc/index.html">mIRC</a></li>
<li><a href="modelica/index.html">Modelica</a></li>
<li><a href="nginx/index.html">Nginx</a></li>
<li><a href="ntriples/index.html">NTriples</a></li>
<li><a href="mllike/index.html">OCaml</a></li>
<li><a href="octave/index.html">Octave</a> (MATLAB)</li>
<li><a href="pascal/index.html">Pascal</a></li>
<li><a href="pegjs/index.html">PEG.js</a></li>
<li><a href="perl/index.html">Perl</a></li>
<li><a href="php/index.html">PHP</a></li>
<li><a href="pig/index.html">Pig Latin</a></li>
<li><a href="properties/index.html">Properties files</a></li>
<li><a href="puppet/index.html">Puppet</a></li>
<li><a href="python/index.html">Python</a></li>
<li><a href="q/index.html">Q</a></li>
<li><a href="r/index.html">R</a></li>
<li><a href="rpm/index.html">RPM</a></li>
<li><a href="rst/index.html">reStructuredText</a></li>
<li><a href="ruby/index.html">Ruby</a></li>
<li><a href="rust/index.html">Rust</a></li>
<li><a href="sass/index.html">Sass</a></li>
<li><a href="clike/scala.html">Scala</a></li>
<li><a href="scheme/index.html">Scheme</a></li>
<li><a href="css/scss.html">SCSS</a></li>
<li><a href="shell/index.html">Shell</a></li>
<li><a href="sieve/index.html">Sieve</a></li>
<li><a href="slim/index.html">Slim</a></li>
<li><a href="smalltalk/index.html">Smalltalk</a></li>
<li><a href="smarty/index.html">Smarty</a></li>
<li><a href="smartymixed/index.html">Smarty/HTML mixed</a></li>
<li><a href="solr/index.html">Solr</a></li>
<li><a href="sql/index.html">SQL</a> (several dialects)</li>
<li><a href="sparql/index.html">SPARQL</a></li>
<li><a href="stex/index.html">sTeX, LaTeX</a></li>
<li><a href="tcl/index.html">Tcl</a></li>
<li><a href="textile/index.html">Textile</a></li>
<li><a href="tiddlywiki/index.html">Tiddlywiki</a></li>
<li><a href="tiki/index.html">Tiki wiki</a></li>
<li><a href="toml/index.html">TOML</a></li>
<li><a href="tornado/index.html">Tornado</a> (templating language)</li>
<li><a href="turtle/index.html">Turtle</a></li>
<li><a href="vb/index.html">VB.NET</a></li>
<li><a href="vbscript/index.html">VBScript</a></li>
<li><a href="velocity/index.html">Velocity</a></li>
<li><a href="verilog/index.html">Verilog/SystemVerilog</a></li>
<li><a href="xml/index.html">XML/HTML</a></li>
<li><a href="xquery/index.html">XQuery</a></li>
<li><a href="yaml/index.html">YAML</a></li>
<li><a href="z80/index.html">Z80</a></li>
</ul>
</div>
</article>
// 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;
}
};
});
<!doctype html>
<title>CodeMirror: Shell mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">
<link rel=stylesheet href=../../lib/codemirror.css>
<script src=../../lib/codemirror.js></script>
<script src="../../addon/edit/matchbrackets.js"></script>
<script src=shell.js></script>
<style type=text/css>
.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}
</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">Shell</a>
</ul>
</div>
<article>
<h2>Shell mode</h2>
<textarea id=code>
#!/bin/bash
# clone the repository
git clone http://github.com/garden/tree
# generate HTTPS credentials
cd tree
openssl genrsa -aes256 -out https.key 1024
openssl req -new -nodes -key https.key -out https.csr
openssl x509 -req -days 365 -in https.csr -signkey https.key -out https.crt
cp https.key{,.orig}
openssl rsa -in https.key.orig -out https.key
# start the server in HTTPS mode
cd web
sudo node ../server.js 443 'yes' &gt;&gt; ../node.log &amp;
# here is how to stop the server
for pid in `ps aux | grep 'node ../server.js' | awk '{print $2}'` ; do
sudo kill -9 $pid 2&gt; /dev/null
done
exit 0</textarea>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
mode: 'shell',
lineNumbers: true,
matchBrackets: true
});
</script>
<p><strong>MIME types defined:</strong> <code>text/x-sh</code>.</p>
</article>
// 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');
});
// 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");
})();
(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;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function attach(term, socket, bidirectional, buffered) {
var addonTerminal = term;
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
addonTerminal.__socket = socket;
addonTerminal.__flushBuffer = function () {
addonTerminal.write(addonTerminal.__attachSocketBuffer);
addonTerminal.__attachSocketBuffer = null;
};
addonTerminal.__pushToBuffer = function (data) {
if (addonTerminal.__attachSocketBuffer) {
addonTerminal.__attachSocketBuffer += data;
}
else {
addonTerminal.__attachSocketBuffer = data;
setTimeout(addonTerminal.__flushBuffer, 10);
}
};
var myTextDecoder;
addonTerminal.__getMessage = function (ev) {
var _this = this;
var str;
if (typeof ev.data === 'object') {
if (!myTextDecoder) {
myTextDecoder = new TextDecoder();
}
if (ev.data instanceof ArrayBuffer) {
str = myTextDecoder.decode(ev.data);
displayData(str);
}
else {
var fileReader = new FileReader();
fileReader.addEventListener('load', function () {
str = myTextDecoder.decode(_this.result);
displayData(str);
});
fileReader.readAsArrayBuffer(ev.data);
}
}
else if (typeof ev.data === 'string') {
displayData(ev.data);
}
else {
throw Error("Cannot handle \"" + typeof ev.data + "\" websocket message.");
}
};
function displayData(str, data) {
if (buffered) {
addonTerminal.__pushToBuffer(str || data);
}
else {
addonTerminal.write(str || data);
}
}
addonTerminal.__sendData = function (data) {
if (socket.readyState !== 1) {
return;
}
socket.send(data);
};
addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));
if (bidirectional) {
addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData));
}
addonTerminal._core.register(addSocketListener(socket, 'close', function () { return detach(addonTerminal, socket); }));
addonTerminal._core.register(addSocketListener(socket, 'error', function () { return detach(addonTerminal, socket); }));
}
exports.attach = attach;
function addSocketListener(socket, type, handler) {
socket.addEventListener(type, handler);
return {
dispose: function () {
if (!handler) {
return;
}
socket.removeEventListener(type, handler);
handler = null;
}
};
}
function detach(term, socket) {
var addonTerminal = term;
addonTerminal.off('data', addonTerminal.__sendData);
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
if (socket) {
socket.removeEventListener('message', addonTerminal.__getMessage);
}
delete addonTerminal.__socket;
}
exports.detach = detach;
function apply(terminalConstructor) {
terminalConstructor.prototype.attach = function (socket, bidirectional, buffered) {
attach(this, socket, bidirectional, buffered);
};
terminalConstructor.prototype.detach = function (socket) {
detach(this, socket);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=attach.js.map
{"version":3,"file":"attach.js","sources":["../../../src/addons/attach/attach.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Implements the attach method, that attaches the terminal to a WebSocket stream.\n */\n\nimport { Terminal, IDisposable } from 'xterm';\nimport { IAttachAddonTerminal } from './Interfaces';\n\n/**\n * Attaches the given terminal to the given socket.\n *\n * @param term The terminal to be attached to the given socket.\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 */\nexport function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n const addonTerminal = <IAttachAddonTerminal>term;\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 = <IAttachAddonTerminal>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 (<any>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 (<any>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
(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;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function proposeGeometry(term) {
if (!term.element.parentElement) {
return null;
}
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
var elementStyle = window.getComputedStyle(term.element);
var elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
right: parseInt(elementStyle.getPropertyValue('padding-right')),
left: parseInt(elementStyle.getPropertyValue('padding-left'))
};
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
var elementPaddingHor = elementPadding.right + elementPadding.left;
var availableHeight = parentElementHeight - elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
var geometry = {
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
};
return geometry;
}
exports.proposeGeometry = proposeGeometry;
function fit(term) {
var geometry = proposeGeometry(term);
if (geometry) {
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
term._core.renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}
}
exports.fit = fit;
function apply(terminalConstructor) {
terminalConstructor.prototype.proposeGeometry = function () {
return proposeGeometry(this);
};
terminalConstructor.prototype.fit = function () {
fit(this);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=fit.js.map
{"version":3,"file":"fit.js","sources":["../../../src/addons/fit/fit.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Fit terminal columns and rows to the dimensions of its DOM element.\n *\n * ## Approach\n *\n * Rows: Truncate the division of the terminal parent element height by the\n * terminal row height.\n * Columns: Truncate the division of the terminal parent element width by the\n * terminal character width (apply display: inline at the terminal\n * row and truncate its width with the current number of columns).\n */\n\nimport { Terminal } from 'xterm';\n\nexport interface IGeometry {\n rows: number;\n cols: number;\n}\n\nexport function proposeGeometry(term: Terminal): IGeometry {\n if (!term.element.parentElement) {\n return null;\n }\n const parentElementStyle = window.getComputedStyle(term.element.parentElement);\n const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));\n const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));\n const elementStyle = window.getComputedStyle(term.element);\n const elementPadding = {\n top: parseInt(elementStyle.getPropertyValue('padding-top')),\n bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),\n right: parseInt(elementStyle.getPropertyValue('padding-right')),\n left: parseInt(elementStyle.getPropertyValue('padding-left'))\n };\n const elementPaddingVer = elementPadding.top + elementPadding.bottom;\n const elementPaddingHor = elementPadding.right + elementPadding.left;\n const availableHeight = parentElementHeight - elementPaddingVer;\n const availableWidth = parentElementWidth - elementPaddingHor - (<any>term)._core.viewport.scrollBarWidth;\n const geometry = {\n cols: Math.floor(availableWidth / (<any>term)._core.renderer.dimensions.actualCellWidth),\n rows: Math.floor(availableHeight / (<any>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 (<any>term)._core.renderer.clear();\n term.resize(geometry.cols, geometry.rows);\n }\n }\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).proposeGeometry = function (): IGeometry {\n return proposeGeometry(this);\n };\n\n (<any>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
.xterm.fullscreen {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: auto;
height: auto;
z-index: 255;
}
(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;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function toggleFullScreen(term, fullscreen) {
var fn;
if (typeof fullscreen === 'undefined') {
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
}
else if (!fullscreen) {
fn = 'remove';
}
else {
fn = 'add';
}
term.element.classList[fn]('fullscreen');
}
exports.toggleFullScreen = toggleFullScreen;
function apply(terminalConstructor) {
terminalConstructor.prototype.toggleFullScreen = function (fullscreen) {
toggleFullScreen(this, fullscreen);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=fullscreen.js.map
{"version":3,"file":"fullscreen.js","sources":["../../../src/addons/fullscreen/fullscreen.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\n\n/**\n * Toggle the given terminal's fullscreen mode.\n * @param term The terminal to toggle full screen mode\n * @param fullscreen Toggle fullscreen on (true) or off (false)\n */\nexport function toggleFullScreen(term: Terminal, fullscreen: boolean): void {\n let fn: string;\n\n if (typeof fullscreen === 'undefined') {\n fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';\n } else if (!fullscreen) {\n fn = 'remove';\n } else {\n fn = 'add';\n }\n\n term.element.classList[fn]('fullscreen');\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.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
(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<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var SearchHelper = (function () {
function SearchHelper(_terminal) {
this._terminal = _terminal;
}
SearchHelper.prototype.findNext = function (term) {
if (!term || term.length === 0) {
return false;
}
var result;
var startRow = this._terminal._core.buffer.ydisp;
if (this._terminal._core.selectionManager.selectionEnd) {
startRow = this._terminal._core.selectionManager.selectionEnd[1];
}
for (var y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
result = this._findInLine(term, y);
if (result) {
break;
}
}
if (!result) {
for (var y = 0; y < startRow; y++) {
result = this._findInLine(term, y);
if (result) {
break;
}
}
}
return this._selectResult(result);
};
SearchHelper.prototype.findPrevious = function (term) {
if (!term || term.length === 0) {
return false;
}
var result;
var startRow = this._terminal._core.buffer.ydisp;
if (this._terminal._core.selectionManager.selectionStart) {
startRow = this._terminal._core.selectionManager.selectionStart[1];
}
for (var y = startRow - 1; y >= 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
{"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 = <ISearchAddonTerminal>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 = <ISearchAddonTerminal>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 (<any>terminalConstructor.prototype).findNext = function(term: string): boolean {\n return findNext(this, term);\n };\n\n (<any>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
(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;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function terminadoAttach(term, socket, bidirectional, buffered) {
var addonTerminal = term;
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
addonTerminal.__socket = socket;
addonTerminal.__flushBuffer = function () {
addonTerminal.write(addonTerminal.__attachSocketBuffer);
addonTerminal.__attachSocketBuffer = null;
};
addonTerminal.__pushToBuffer = function (data) {
if (addonTerminal.__attachSocketBuffer) {
addonTerminal.__attachSocketBuffer += data;
}
else {
addonTerminal.__attachSocketBuffer = data;
setTimeout(addonTerminal.__flushBuffer, 10);
}
};
addonTerminal.__getMessage = function (ev) {
var data = JSON.parse(ev.data);
if (data[0] === 'stdout') {
if (buffered) {
addonTerminal.__pushToBuffer(data[1]);
}
else {
addonTerminal.write(data[1]);
}
}
};
addonTerminal.__sendData = function (data) {
socket.send(JSON.stringify(['stdin', data]));
};
addonTerminal.__setSize = function (size) {
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
};
socket.addEventListener('message', addonTerminal.__getMessage);
if (bidirectional) {
addonTerminal.on('data', addonTerminal.__sendData);
}
addonTerminal.on('resize', addonTerminal.__setSize);
socket.addEventListener('close', function () { return terminadoDetach(addonTerminal, socket); });
socket.addEventListener('error', function () { return terminadoDetach(addonTerminal, socket); });
}
exports.terminadoAttach = terminadoAttach;
function terminadoDetach(term, socket) {
var addonTerminal = term;
addonTerminal.off('data', addonTerminal.__sendData);
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
if (socket) {
socket.removeEventListener('message', addonTerminal.__getMessage);
}
delete addonTerminal.__socket;
}
exports.terminadoDetach = terminadoDetach;
function apply(terminalConstructor) {
terminalConstructor.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
return terminadoAttach(this, socket, bidirectional, buffered);
};
terminalConstructor.prototype.terminadoDetach = function (socket) {
return terminadoDetach(this, socket);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=terminado.js.map
{"version":3,"file":"terminado.js","sources":["../../../src/addons/terminado/terminado.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This module provides methods for attaching a terminal to a terminado\n * WebSocket stream.\n */\n\nimport { Terminal } from 'xterm';\nimport { ITerminadoAddonTerminal } from './Interfaces';\n\n/**\n * Attaches the given terminal to the given socket.\n *\n * @param term The terminal to be attached to the given socket.\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 */\nexport function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n const addonTerminal = <ITerminadoAddonTerminal>term;\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 = <ITerminadoAddonTerminal>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 (<any>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 (<any>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
(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<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
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);
function handleLink(event, uri) {
window.open(uri, '_blank');
}
function webLinksInit(term, handler, options) {
if (handler === void 0) { handler = handleLink; }
if (options === void 0) { options = {}; }
options.matchIndex = 1;
term.registerLinkMatcher(strictUrlRegex, handler, options);
}
exports.webLinksInit = webLinksInit;
function apply(terminalConstructor) {
terminalConstructor.prototype.webLinksInit = function (handler, options) {
webLinksInit(this, handler, options);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=webLinks.js.map
{"version":3,"file":"webLinks.js","sources":["../../../src/addons/webLinks/webLinks.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, ILinkMatcherOptions } from 'xterm';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~]*)*';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\nfunction handleLink(event: MouseEvent, uri: string): void {\n window.open(uri, '_blank');\n}\n\n/**\n * Initialize the web links addon, registering the link matcher.\n * @param term The terminal to use web links within.\n * @param handler A custom handler to use.\n * @param options Custom options to use, matchIndex will always be ignored.\n */\nexport function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => 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 (<any>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
(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<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function winptyCompatInit(terminal) {
var addonTerminal = terminal;
var isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 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
{"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 = <IWinptyCompatAddonTerminal>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 (<any>nextLine).isWrapped = true;\n }\n });\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>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
(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<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var zmodem;
function zmodemAttach(ws, opts) {
if (opts === void 0) { opts = {}; }
var term = this;
var senderFunc = function (octets) { return ws.send(new Uint8Array(octets)); };
var zsentry;
function shouldWrite() {
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
}
zsentry = new zmodem.Sentry({
to_terminal: function (octets) {
if (shouldWrite()) {
term.write(String.fromCharCode.apply(String, octets));
}
},
sender: senderFunc,
on_retract: function () { return term.emit('zmodemRetract'); },
on_detect: function (detection) { return term.emit('zmodemDetect', detection); }
});
function handleWSMessage(evt) {
if (typeof evt.data === 'string') {
if (shouldWrite()) {
term.write(evt.data);
}
}
else {
zsentry.consume(evt.data);
}
}
ws.binaryType = 'arraybuffer';
ws.addEventListener('message', handleWSMessage);
}
function apply(terminalConstructor) {
zmodem = (typeof window === 'object') ? window.Zmodem : { Browser: null };
terminalConstructor.prototype.zmodemAttach = zmodemAttach;
terminalConstructor.prototype.zmodemBrowser = zmodem.Browser;
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=zmodem.js.map
{"version":3,"file":"zmodem.js","sources":["../../../src/addons/zmodem/zmodem.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';\n\n/**\n *\n * Allow xterm.js to handle ZMODEM uploads and downloads.\n *\n * This addon is a wrapper around zmodem.js. It adds the following to the\n * Terminal class:\n *\n * - function `zmodemAttach(<WebSocket>, <Object>)` - 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<number>) => 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<number>) => {\n if (shouldWrite()) {\n term.write(\n String.fromCharCode.apply(String, octets)\n );\n }\n },\n sender: senderFunc,\n on_retract: () => (<any>term).emit('zmodemRetract'),\n on_detect: (detection: any) => (<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') ? (<any>window).Zmodem : {Browser: null}; // Nullify browser for tests\n\n (<any>terminalConstructor.prototype).zmodemAttach = zmodemAttach;\n (<any>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
/**
* 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;
left: -9999em;
}
.terminal.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.terminal .xterm-selection {
position: absolute;
top: 0;
left: 0;
z-index: 1;
opacity: 0.3;
pointer-events: none;
}
.terminal .xterm-selection div {
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;
left: -9999em;
line-height: normal;
}
.terminal .xterm-color-44 {
color: #00d7d7;
.xterm {
cursor: text;
}
.terminal .xterm-bg-color-44 {
background-color: #00d7d7;
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.terminal .xterm-color-45 {
color: #00d7ff;
.xterm.xterm-cursor-pointer {
cursor: pointer;
}
.terminal .xterm-bg-color-45 {
background-color: #00d7ff;
.xterm.xterm-cursor-crosshair {
/* Column selection mode */
cursor: crosshair;
}
.terminal .xterm-color-46 {
color: #00ff00;
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 100;
color: transparent;
}
.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;
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* 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("<li ",a?"style='display:none;' ":"","id='",b.tId,"' class='",
l.className.LEVEL,b.level,"' tabindex='0' hidefocus='true' treenode>")},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;g<i;g++){var h=a[g];if(!f[h.parentTId]){var u=h.getParentNode();f[h.parentTId]=u===null?d.getRoot(c):h.getParentNode()}e.showNode(c,h,b)}for(var j in f)a=d.nodeChildren(c,f[j]),e.setFirstNodeForShow(c,a),e.setLastNodeForShow(c,a)}},hideNode:function(c,a){d.isHidden(c,a,
!0);a.isFirstNode=!1;a.isLastNode=!1;d.initHideForExCheck(c,a);e.cancelPreSelectedNode(c,a);k(a,c).hide()},hideNodes:function(c,a,b){if(a&&a.length!=0){var f={},g,i;for(g=0,i=a.length;g<i;g++){var h=a[g];if((h.isFirstNode||h.isLastNode)&&!f[h.parentTId]){var j=h.getParentNode();f[h.parentTId]=j===null?d.getRoot(c):h.getParentNode()}e.hideNode(c,h,b)}for(var k in f)a=d.nodeChildren(c,f[k]),e.setFirstNodeForHide(c,a),e.setLastNodeForHide(c,a)}},setFirstNode:function(c,a){var b=d.nodeChildren(c,a),f=
d.isHidden(c,b[0],!1);b.length>0&&!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<g;f++){b=a[f];if(b.isFirstNode)break;if(!d.isHidden(c,b)&&!b.isFirstNode){b.isFirstNode=!0;e.setNodeLineIcos(c,b);break}else b=null}return b},setFirstNodeForShow:function(c,a){var b,f,
g,i,h;for(f=0,g=a.length;f<g;f++){b=a[f];var j=d.isHidden(c,b);if(!i&&!j&&b.isFirstNode){i=b;break}else if(!i&&!j&&!b.isFirstNode)b.isFirstNode=!0,i=b,e.setNodeLineIcos(c,b);else if(i&&b.isFirstNode){b.isFirstNode=!1;h=b;e.setNodeLineIcos(c,b);break}}return{"new":i,old:h}},setLastNodeForHide:function(c,a){var b,f;for(f=a.length-1;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);
......@@ -58,7 +58,6 @@
{% endif %}
</ul>
</li>
{% if request.user.is_superuser %}
<li id="ops">
<a>
<i class="fa fa-coffee" style="width: 14px"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
......@@ -67,7 +66,6 @@
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
</ul>
</li>
{% endif %}
<li id="audits">
<a>
<i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span>
......@@ -77,17 +75,9 @@
<li id="ftp-log"><a href="{% url 'audits:ftp-log-list' %}">{% trans 'FTP log' %}</a></li>
<li id="operate-log"><a href="{% url 'audits:operate-log-list' %}">{% trans 'Operate log' %}</a></li>
<li id="password-change-log"><a href="{% url 'audits:password-change-log-list' %}">{% trans 'Password change log' %}</a></li>
<li id="command-execution-log"><a href="{% url 'audits:command-execution-log-list' %}">{% trans 'Command execution' %}</a></li>
</ul>
</li>
{#<li id="">#}
{# <a href="#">#}
{# <i class="fa fa-download"></i> <span class="nav-label">{% trans 'File' %}</span><span class="fa arrow"></span>#}
{# </a>#}
{# <ul class="nav nav-second-level">#}
{# <li id="upload"><a href="">{% trans 'File upload' %}</a></li>#}
{# <li id="download"><a href="">{% trans 'File download' %}</a></li>#}
{# </ul>#}
{#</li>#}
{% if XPACK_PLUGINS %}
<li id="xpack">
<a>
......
......@@ -4,6 +4,11 @@
<i class="fa fa-files-o" style="width: 14px"></i><span class="nav-label">{% trans 'My assets' %}</span><span class="label label-info pull-right"></span>
</a>
</li>
<li id="ops">
<a href="{% url 'ops:command-execution-start' %}">
<i class="fa fa-terminal" style="width: 14px"></i> <span class="nav-label">{% trans 'Command execution' %}</span><span class="label label-info pull-right"></span>
</a>
</li>
<li id="users">
<a href="{% url 'users:user-profile' %}">
<i class="fa fa-user" style="width: 14px"></i> <span class="nav-label">{% trans 'Profile' %}</span><span class="label label-info pull-right"></span>
......
......@@ -57,7 +57,6 @@
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
......@@ -108,7 +107,8 @@ function initTable() {
function onSelected(event, treeNode) {
url = '{% url "api-perms:user-node-assets" pk=object.id 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);
asset_table.ajax.url(url);
asset_table.ajax.reload();
}
......@@ -129,23 +129,9 @@ function initTree() {
}
};
var zNodes = [];
$.get("{% url 'api-perms:user-nodes' pk=object.id %}", 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'];
value["iconSkin"] = value["is_node"] ? null : 'file';
});
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
$.get("{% url 'api-perms:user-nodes-assets-as-tree' pk=object.id %}?show_assets=0", function(data, status) {
$.fn.zTree.init($("#assetTree"), setting, data);
zTree = $.fn.zTree.getZTreeObj("assetTree");
var root = zTree.getNodes()[0];
zTree.expandNode(root);
});
}
......
......@@ -43,7 +43,7 @@ jmespath==0.9.3
kombu==4.0.2
ldap3==2.4
MarkupSafe==1.0
mysqlclient==1.3.12
mysqlclient==1.3.14
olefile==0.44
openapi-codec==1.3.2
paramiko==2.4.1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment