mirror of https://github.com/jumpserver/jumpserver
				
				
				
			Merge branch 'dev' of github.com:jumpserver/jumpserver into dev
						commit
						d1a2fe145d
					
				| 
						 | 
				
			
			@ -30,3 +30,4 @@ celerybeat.pid
 | 
			
		|||
django.db
 | 
			
		||||
celerybeat-schedule.db
 | 
			
		||||
data/static
 | 
			
		||||
_build/
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ __all__ = ['Node']
 | 
			
		|||
class Node(models.Model):
 | 
			
		||||
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
 | 
			
		||||
    key = models.CharField(unique=True, max_length=64, verbose_name=_("Key"))  # '1:1:1:1'
 | 
			
		||||
    value = models.CharField(max_length=128, unique=True, verbose_name=_("Value"))
 | 
			
		||||
    value = models.CharField(max_length=128, verbose_name=_("Value"))
 | 
			
		||||
    child_mark = models.IntegerField(default=0)
 | 
			
		||||
    date_create = models.DateTimeField(auto_now_add=True)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,14 +26,14 @@ signer = get_signer()
 | 
			
		|||
class AssetUser(models.Model):
 | 
			
		||||
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
 | 
			
		||||
    name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
 | 
			
		||||
    username = models.CharField(max_length=16, verbose_name=_('Username'))
 | 
			
		||||
    username = models.CharField(max_length=128, verbose_name=_('Username'))
 | 
			
		||||
    _password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
 | 
			
		||||
    _private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
 | 
			
		||||
    _public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
 | 
			
		||||
    comment = models.TextField(blank=True, verbose_name=_('Comment'))
 | 
			
		||||
    date_created = models.DateTimeField(auto_now_add=True)
 | 
			
		||||
    date_updated = models.DateTimeField(auto_now=True)
 | 
			
		||||
    created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
 | 
			
		||||
    created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def password(self):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -91,7 +91,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
 | 
			
		|||
    if task_name is None:
 | 
			
		||||
        task_name = _("Update some assets hardware info")
 | 
			
		||||
    tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
 | 
			
		||||
    hostname_list = [asset.hostname for asset in assets]
 | 
			
		||||
    hostname_list = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
 | 
			
		||||
    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',
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +120,10 @@ def update_assets_hardware_info_period():
 | 
			
		|||
    """
 | 
			
		||||
    from ops.utils import update_or_create_ansible_task
 | 
			
		||||
    task_name = _("Update assets hardware info period")
 | 
			
		||||
    hostname_list = [asset.hostname for asset in Asset.objects.all()]
 | 
			
		||||
    hostname_list = [
 | 
			
		||||
        asset.hostname 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
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +168,8 @@ 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.hostname for asset in assets]
 | 
			
		||||
    hosts = [asset.hostname for asset in assets
 | 
			
		||||
             if asset.is_active and asset.is_unixlike()]
 | 
			
		||||
    if not hosts:
 | 
			
		||||
        return
 | 
			
		||||
    tasks = const.TEST_ADMIN_USER_CONN_TASKS
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +261,7 @@ def test_system_user_connectability_util(system_user, task_name):
 | 
			
		|||
    """
 | 
			
		||||
    from ops.utils import update_or_create_ansible_task
 | 
			
		||||
    assets = system_user.assets
 | 
			
		||||
    hosts = [asset.hostname for asset in assets]
 | 
			
		||||
    hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
 | 
			
		||||
    tasks = const.TEST_SYSTEM_USER_CONN_TASKS
 | 
			
		||||
    if not hosts:
 | 
			
		||||
        logger.info("No hosts, passed")
 | 
			
		||||
| 
						 | 
				
			
			@ -346,7 +350,7 @@ def push_system_user_util(system_users, assets, task_name):
 | 
			
		|||
        logger.info("Not tasks, passed")
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    hosts = [asset.hostname for asset in assets]
 | 
			
		||||
    hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
 | 
			
		||||
    if not hosts:
 | 
			
		||||
        logger.info("Not hosts, passed")
 | 
			
		||||
        return {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@
 | 
			
		|||
            <div class="col-sm-12">
 | 
			
		||||
                <div class="ibox float-e-margins">
 | 
			
		||||
                    <div class="ibox-title">
 | 
			
		||||
                        <h5>{% trans 'Create system user' %}</h5>
 | 
			
		||||
                        <h5>{{ action }}</h5>
 | 
			
		||||
                        <div class="ibox-tools">
 | 
			
		||||
                            <a class="collapse-link">
 | 
			
		||||
                                <i class="fa fa-chevron-up"></i>
 | 
			
		||||
| 
						 | 
				
			
			@ -81,6 +81,14 @@
 | 
			
		|||
{% block custom_foot_js %}
 | 
			
		||||
    <script>
 | 
			
		||||
        var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
 | 
			
		||||
        var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
 | 
			
		||||
        var password_id = '#' + '{{ form.password.id_for_label }}';
 | 
			
		||||
        var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
 | 
			
		||||
        var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
 | 
			
		||||
        var shell_id = '#' + '{{ form.shell.id_for_label }}';
 | 
			
		||||
 | 
			
		||||
        var need_change_field = [auto_generate_key, private_key_id, sudo_id, shell_id] ;
 | 
			
		||||
 | 
			
		||||
        function authFieldsDisplay() {
 | 
			
		||||
            if ($(auto_generate_key).prop('checked')) {
 | 
			
		||||
                $('.auth-fields').addClass('hidden');
 | 
			
		||||
| 
						 | 
				
			
			@ -88,9 +96,23 @@
 | 
			
		|||
                $('.auth-fields').removeClass('hidden');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function protocolChange() {
 | 
			
		||||
            if ($(protocol_id).attr('value') === 'rdp') {
 | 
			
		||||
                $.each(need_change_field, function (index, value) {
 | 
			
		||||
                    $(value).addClass('hidden')
 | 
			
		||||
                });
 | 
			
		||||
                $(password_id).removeClass('hidden')
 | 
			
		||||
            } else {
 | 
			
		||||
                $.each(need_change_field, function (index, value) {
 | 
			
		||||
                    $(value).removeClass('hidden')
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $(document).ready(function () {
 | 
			
		||||
            $('.select2').select2();
 | 
			
		||||
            authFieldsDisplay();
 | 
			
		||||
            protocolChange();
 | 
			
		||||
            $(auto_generate_key).change(function () {
 | 
			
		||||
                authFieldsDisplay();
 | 
			
		||||
            });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@
 | 
			
		|||
            <div class="col-sm-12">
 | 
			
		||||
                <div class="ibox float-e-margins">
 | 
			
		||||
                    <div class="ibox-title">
 | 
			
		||||
                        <h5>{% trans 'Create admin user' %}</h5>
 | 
			
		||||
                        <h5>{{ action }}</h5>
 | 
			
		||||
                        <div class="ibox-tools">
 | 
			
		||||
                            <a class="collapse-link">
 | 
			
		||||
                                <i class="fa fa-chevron-up"></i>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,12 @@
 | 
			
		|||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block help_message %}
 | 
			
		||||
    <div class="alert alert-info help-message">
 | 
			
		||||
    左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% 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>
 | 
			
		||||
| 
						 | 
				
			
			@ -224,7 +230,9 @@ function editTreeNode() {
 | 
			
		|||
	if (!current_node){
 | 
			
		||||
	    return
 | 
			
		||||
    }
 | 
			
		||||
    current_node.name = current_node.value;
 | 
			
		||||
    if (current_node.value) {
 | 
			
		||||
        current_node.name = current_node.value;
 | 
			
		||||
    }
 | 
			
		||||
    zTree.editName(current_node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -313,38 +321,36 @@ function beforeDrag() {
 | 
			
		|||
    return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function beforeDrop() {
 | 
			
		||||
    return true
 | 
			
		||||
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
 | 
			
		||||
    var treeNodesNames = [];
 | 
			
		||||
    $.each(treeNodes, function (index, value) {
 | 
			
		||||
        treeNodesNames.push(value.value);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
 | 
			
		||||
    if (confirm(msg)){
 | 
			
		||||
        return true
 | 
			
		||||
    } else {
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onDrag(event, treeId, treeNodes) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
 | 
			
		||||
    console.log("DROP");
 | 
			
		||||
    console.log(event);
 | 
			
		||||
    console.log(treeNodes);
 | 
			
		||||
    console.log(targetNode);
 | 
			
		||||
    console.log(moveType);
 | 
			
		||||
 | 
			
		||||
    var treeNodesNames = [];
 | 
			
		||||
    var treeNodesIds = [];
 | 
			
		||||
    $.each(treeNodes, function (index, value) {
 | 
			
		||||
        treeNodesNames.push(value.value);
 | 
			
		||||
        treeNodesIds.push(value.id);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
 | 
			
		||||
    var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.id);
 | 
			
		||||
    var body = {nodes: treeNodesIds};
 | 
			
		||||
    if (confirm(msg)){
 | 
			
		||||
        APIUpdateAttr({
 | 
			
		||||
            url: the_url,
 | 
			
		||||
            method: "PUT",
 | 
			
		||||
            body: JSON.stringify(body)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    APIUpdateAttr({
 | 
			
		||||
        url: the_url,
 | 
			
		||||
        method: "PUT",
 | 
			
		||||
        body: JSON.stringify(body)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initTree() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -178,7 +178,7 @@
 | 
			
		|||
                                            </tr>
 | 
			
		||||
                                            <tr>
 | 
			
		||||
                                                <td colspan="2" class="no-borders">
 | 
			
		||||
                                                    <button type="button" class="btn btn-info btn-sm" id="btn-add-to-cluster">{% trans 'Confirm' %}</button>
 | 
			
		||||
                                                    <button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
 | 
			
		||||
                                                </td>
 | 
			
		||||
                                            </tr>
 | 
			
		||||
                                        </form>
 | 
			
		||||
| 
						 | 
				
			
			@ -187,7 +187,7 @@
 | 
			
		|||
                                        <tr>
 | 
			
		||||
                                          <td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</b></td>
 | 
			
		||||
                                          <td>
 | 
			
		||||
                                              <button class="btn btn-danger pull-right btn-xs btn-remove-from-cluster" type="button"><i class="fa fa-minus"></i></button>
 | 
			
		||||
                                              <button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
 | 
			
		||||
                                          </td>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
| 
						 | 
				
			
			@ -204,27 +204,27 @@
 | 
			
		|||
{% endblock %}
 | 
			
		||||
{% block custom_foot_js %}
 | 
			
		||||
<script>
 | 
			
		||||
function updateSystemUserCluster(clusters) {
 | 
			
		||||
function updateSystemUserCluster(nodes) {
 | 
			
		||||
    var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
 | 
			
		||||
    var body = {
 | 
			
		||||
        cluster: Object.assign([], clusters)
 | 
			
		||||
        nodes: Object.assign([], nodes)
 | 
			
		||||
    };
 | 
			
		||||
    var success = function(data) {
 | 
			
		||||
        // remove all the selected groups from select > option and rendered ul element;
 | 
			
		||||
        $('.select2-selection__rendered').empty();
 | 
			
		||||
        $('#cluster_selected').val('');
 | 
			
		||||
        $.map(jumpserver.cluster_selected, function(cluster_name, index) {
 | 
			
		||||
        $.map(jumpserver.nodes_selected, function(cluster_name, index) {
 | 
			
		||||
            $('#opt_' + index).remove();
 | 
			
		||||
            // change tr html of user groups.
 | 
			
		||||
            $('.cluster_edit tbody').append(
 | 
			
		||||
                '<tr>' +
 | 
			
		||||
                '<td><b class="bdg_node" data-gid="' + index + '">' + cluster_name + '</b></td>' +
 | 
			
		||||
                '<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-cluster" type="button"><i class="fa fa-minus"></i></button></td>' +
 | 
			
		||||
                '<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
 | 
			
		||||
                '</tr>'
 | 
			
		||||
            )
 | 
			
		||||
        });
 | 
			
		||||
        // clear jumpserver.groups_selected
 | 
			
		||||
        jumpserver.cluster_selected = {};
 | 
			
		||||
        jumpserver.nodes_selected = {};
 | 
			
		||||
    };
 | 
			
		||||
    APIUpdateAttr({
 | 
			
		||||
        url: the_url,
 | 
			
		||||
| 
						 | 
				
			
			@ -232,16 +232,16 @@ function updateSystemUserCluster(clusters) {
 | 
			
		|||
        success: success
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
jumpserver.cluster_selected = {};
 | 
			
		||||
jumpserver.nodes_selected = {};
 | 
			
		||||
$(document).ready(function () {
 | 
			
		||||
    $('.select2').select2()
 | 
			
		||||
    .on('select2:select', function(evt) {
 | 
			
		||||
            var data = evt.params.data;
 | 
			
		||||
            jumpserver.cluster_selected[data.id] = data.text;
 | 
			
		||||
            jumpserver.nodes_selected[data.id] = data.text;
 | 
			
		||||
    })
 | 
			
		||||
    .on('select2:unselect', function(evt) {
 | 
			
		||||
        var data = evt.params.data;
 | 
			
		||||
        delete jumpserver.cluster_selected[data.id];
 | 
			
		||||
        delete jumpserver.nodes_selected[data.id];
 | 
			
		||||
    });
 | 
			
		||||
})
 | 
			
		||||
.on('click', '#btn-auto-push', function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -255,26 +255,26 @@ $(document).ready(function () {
 | 
			
		|||
        body: JSON.stringify(body)
 | 
			
		||||
    });
 | 
			
		||||
})
 | 
			
		||||
.on('click', '#btn-add-to-cluster', function() {
 | 
			
		||||
    if (Object.keys(jumpserver.cluster_selected).length === 0) {
 | 
			
		||||
.on('click', '#btn-add-to-node', function() {
 | 
			
		||||
    if (Object.keys(jumpserver.nodes_selected).length === 0) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    var clusters = $('.bdg_node').map(function() {
 | 
			
		||||
    var nodes = $('.bdg_node').map(function() {
 | 
			
		||||
        return $(this).data('gid');
 | 
			
		||||
    }).get();
 | 
			
		||||
    $.map(jumpserver.cluster_selected, function(value, index) {
 | 
			
		||||
        clusters.push(index);
 | 
			
		||||
    $.map(jumpserver.nodes_selected, function(value, index) {
 | 
			
		||||
        nodes.push(index);
 | 
			
		||||
    });
 | 
			
		||||
    updateSystemUserCluster(clusters);
 | 
			
		||||
    updateSystemUserCluster(nodes);
 | 
			
		||||
})
 | 
			
		||||
.on('click', '.btn-remove-from-cluster', function() {
 | 
			
		||||
.on('click', '.btn-remove-from-node', function() {
 | 
			
		||||
    var $this = $(this);
 | 
			
		||||
    var $tr = $this.closest('tr');
 | 
			
		||||
    var $badge = $tr.find('.bdg_node');
 | 
			
		||||
    var gid = $badge.data('gid');
 | 
			
		||||
    var cluster_name = $badge.html() || $badge.text();
 | 
			
		||||
    var node_name = $badge.html() || $badge.text();
 | 
			
		||||
    $('#groups_selected').append(
 | 
			
		||||
        '<option value="' + gid + '" id="opt_' + gid + '">' + cluster_name + '</option>'
 | 
			
		||||
        '<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
 | 
			
		||||
    );
 | 
			
		||||
    $tr.remove();
 | 
			
		||||
    var clusters = $('.bdg_node').map(function () {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,8 +58,7 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
 | 
			
		|||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = {
 | 
			
		||||
            'app': _('Assets'),
 | 
			
		||||
            'action': _('Asset list'),
 | 
			
		||||
            'action': _('My assets'),
 | 
			
		||||
            'system_users': SystemUser.objects.all(),
 | 
			
		||||
        }
 | 
			
		||||
        kwargs.update(context)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -97,6 +97,9 @@ class LDAPTestingAPI(APIView):
 | 
			
		|||
 | 
			
		||||
class DjangoSettingsAPI(APIView):
 | 
			
		||||
    def get(self, request):
 | 
			
		||||
        if not settings.DEBUG:
 | 
			
		||||
            return Response('Only debug mode support')
 | 
			
		||||
 | 
			
		||||
        configs = {}
 | 
			
		||||
        for i in dir(settings):
 | 
			
		||||
            if i.isupper():
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,9 +99,8 @@ class DatetimeSearchMixin:
 | 
			
		|||
 | 
			
		||||
        if date_from_s:
 | 
			
		||||
            date_from = timezone.datetime.strptime(date_from_s, self.date_format)
 | 
			
		||||
            self.date_from = date_from.replace(
 | 
			
		||||
                tzinfo=timezone.get_current_timezone()
 | 
			
		||||
            )
 | 
			
		||||
            tz = timezone.get_current_timezone()
 | 
			
		||||
            self.date_from = tz.localize(date_from)
 | 
			
		||||
        else:
 | 
			
		||||
            self.date_from = timezone.now() - timezone.timedelta(7)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,17 +73,20 @@ def to_html(s):
 | 
			
		|||
 | 
			
		||||
@register.filter
 | 
			
		||||
def time_util_with_seconds(date_from, date_to):
 | 
			
		||||
    if date_from and date_to:
 | 
			
		||||
        delta = date_to - date_from
 | 
			
		||||
        seconds = delta.seconds
 | 
			
		||||
        if seconds < 60:
 | 
			
		||||
            return '{} s'.format(seconds)
 | 
			
		||||
        elif seconds < 60*60:
 | 
			
		||||
            return '{} m'.format(seconds//60)
 | 
			
		||||
        else:
 | 
			
		||||
            return '{} h'.format(seconds//3600)
 | 
			
		||||
    else:
 | 
			
		||||
    if not date_from:
 | 
			
		||||
        return ''
 | 
			
		||||
    if not date_to:
 | 
			
		||||
        return ''
 | 
			
		||||
        date_to = timezone.now()
 | 
			
		||||
 | 
			
		||||
    delta = date_to - date_from
 | 
			
		||||
    seconds = delta.seconds
 | 
			
		||||
    if seconds < 60:
 | 
			
		||||
        return '{} s'.format(seconds)
 | 
			
		||||
    elif seconds < 60*60:
 | 
			
		||||
        return '{} m'.format(seconds//60)
 | 
			
		||||
    else:
 | 
			
		||||
        return '{} h'.format(seconds//3600)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -397,6 +397,6 @@ BOOTSTRAP3 = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600
 | 
			
		||||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE
 | 
			
		||||
DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25
 | 
			
		||||
DEFAULT_EXPIRED_YEARS = 70
 | 
			
		||||
USER_GUIDE_URL = ""
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,16 +4,16 @@ from __future__ import unicode_literals
 | 
			
		|||
from django.conf.urls import url, include
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.conf.urls.static import static
 | 
			
		||||
from django.views.static import serve as static_serve
 | 
			
		||||
 | 
			
		||||
from rest_framework.schemas import get_schema_view
 | 
			
		||||
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer
 | 
			
		||||
 | 
			
		||||
from .views import IndexView
 | 
			
		||||
from .views import IndexView, LunaView
 | 
			
		||||
 | 
			
		||||
schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer])
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    url(r'^$', IndexView.as_view(), name='index'),
 | 
			
		||||
    url(r'^luna/', LunaView.as_view(), name='luna-error'),
 | 
			
		||||
    url(r'^users/', include('users.urls.views_urls', namespace='users')),
 | 
			
		||||
    url(r'^assets/', include('assets.urls.views_urls', namespace='assets')),
 | 
			
		||||
    url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,7 @@
 | 
			
		|||
from django.views.generic import TemplateView
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
from django.http import HttpResponse
 | 
			
		||||
from django.views.generic import TemplateView, View
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.db.models import Count
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
| 
						 | 
				
			
			@ -45,15 +48,22 @@ class IndexView(LoginRequiredMixin, TemplateView):
 | 
			
		|||
        return self.session_week.values('user').distinct().count()
 | 
			
		||||
 | 
			
		||||
    def get_week_login_asset_count(self):
 | 
			
		||||
        return self.session_week.values('asset').distinct().count()
 | 
			
		||||
        return self.session_week.count()
 | 
			
		||||
        # return self.session_week.values('asset').distinct().count()
 | 
			
		||||
 | 
			
		||||
    def get_month_day_metrics(self):
 | 
			
		||||
        month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
 | 
			
		||||
        return month_str
 | 
			
		||||
 | 
			
		||||
    def get_month_login_metrics(self):
 | 
			
		||||
        return [self.session_month.filter(date_start__date=d).count()
 | 
			
		||||
                for d in self.session_month_dates]
 | 
			
		||||
        data = []
 | 
			
		||||
        time_min = datetime.datetime.min.time()
 | 
			
		||||
        time_max = datetime.datetime.max.time()
 | 
			
		||||
        for d in self.session_month_dates:
 | 
			
		||||
            ds = datetime.datetime.combine(d, time_min).replace(tzinfo=timezone.get_current_timezone())
 | 
			
		||||
            de = datetime.datetime.combine(d, time_max).replace(tzinfo=timezone.get_current_timezone())
 | 
			
		||||
            data.append(self.session_month.filter(date_start__range=(ds, de)).count())
 | 
			
		||||
        return data
 | 
			
		||||
 | 
			
		||||
    def get_month_active_user_metrics(self):
 | 
			
		||||
        if self.session_month_dates_archive:
 | 
			
		||||
| 
						 | 
				
			
			@ -119,10 +129,18 @@ class IndexView(LoginRequiredMixin, TemplateView):
 | 
			
		|||
        self.session_week = Session.objects.filter(date_start__gt=week_ago)
 | 
			
		||||
        self.session_month = Session.objects.filter(date_start__gt=month_ago)
 | 
			
		||||
        self.session_month_dates = self.session_month.dates('date_start', 'day')
 | 
			
		||||
        self.session_month_dates_archive = [
 | 
			
		||||
            self.session_month.filter(date_start__date=d)
 | 
			
		||||
            for d in self.session_month_dates
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.session_month_dates_archive = []
 | 
			
		||||
        time_min = datetime.datetime.min.time()
 | 
			
		||||
        time_max = datetime.datetime.max.time()
 | 
			
		||||
 | 
			
		||||
        for d in self.session_month_dates:
 | 
			
		||||
            ds = datetime.datetime.combine(d, time_min).replace(
 | 
			
		||||
                tzinfo=timezone.get_current_timezone())
 | 
			
		||||
            de = datetime.datetime.combine(d, time_max).replace(
 | 
			
		||||
                tzinfo=timezone.get_current_timezone())
 | 
			
		||||
            self.session_month_dates_archive.append(
 | 
			
		||||
                self.session_month.filter(date_start__range=(ds, de)))
 | 
			
		||||
 | 
			
		||||
        context = {
 | 
			
		||||
            'assets_count': self.get_asset_count(),
 | 
			
		||||
| 
						 | 
				
			
			@ -149,3 +167,12 @@ class IndexView(LoginRequiredMixin, TemplateView):
 | 
			
		|||
 | 
			
		||||
        kwargs.update(context)
 | 
			
		||||
        return super(IndexView, self).get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LunaView(View):
 | 
			
		||||
    def get(self, request):
 | 
			
		||||
        msg = """
 | 
			
		||||
        Luna是单独部署的一个程序,你需要部署luna,coco,配置nginx做url分发, 
 | 
			
		||||
        如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运
 | 
			
		||||
        """
 | 
			
		||||
        return HttpResponse(msg)
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
 | 
			
		|||
    class Meta:
 | 
			
		||||
        model = NodePermission
 | 
			
		||||
        fields = [
 | 
			
		||||
            'node', 'user_group', 'system_user',
 | 
			
		||||
            'id', 'node', 'user_group', 'system_user',
 | 
			
		||||
            'is_active', 'date_expired'
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
            <div class="col-sm-12">
 | 
			
		||||
                <div class="ibox float-e-margins">
 | 
			
		||||
                    <div class="ibox-title">
 | 
			
		||||
                        <h5>{% trans 'Create asset permission ' %}</h5>
 | 
			
		||||
                        <h5>{{ action }}</h5>
 | 
			
		||||
                        <div class="ibox-tools">
 | 
			
		||||
                            <a class="collapse-link">
 | 
			
		||||
                                <i class="fa fa-chevron-up"></i>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -186,7 +186,7 @@ function initTree() {
 | 
			
		|||
            {#if (value["key"] === "0") {#}
 | 
			
		||||
            value["open"] = true;
 | 
			
		||||
            {# }#}
 | 
			
		||||
            value["name"] = value["value"] + ' (' + value['assets_amount'] + ')'
 | 
			
		||||
            value["name"] = value["value"]
 | 
			
		||||
        });
 | 
			
		||||
        zNodes = data;
 | 
			
		||||
        $.fn.zTree.init($("#assetTree"), setting, zNodes);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -427,3 +427,9 @@ div.dataTables_wrapper div.dataTables_filter {
 | 
			
		|||
    text-align: center;
 | 
			
		||||
    padding: 5px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.profile-dropdown li a {
 | 
			
		||||
    font-size: 12px !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3299,7 +3299,7 @@ body.tour-open .animated {
 | 
			
		|||
  border-bottom: 1px solid #e7eaec;
 | 
			
		||||
}
 | 
			
		||||
body {
 | 
			
		||||
  font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
 | 
			
		||||
  font-family: "open sans", "Helvetica Neue", "微软雅黑", Helvetica, Arial, sans-serif;
 | 
			
		||||
  background-color: #2f4050;
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  color: #676a6c;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 18 KiB  | 
| 
						 | 
				
			
			@ -14,8 +14,13 @@
 | 
			
		|||
{#                <span class="m-r-sm text-muted welcome-message">{% trans 'Welcome to use Jumpserver system' %}</span>#}
 | 
			
		||||
{#            </li>#}
 | 
			
		||||
            <li class="dropdown">
 | 
			
		||||
                <a class="dropdown-toggle count-info" data-toggle="dropdown" href="#">
 | 
			
		||||
                     <span class="m-r-sm text-muted welcome-message">{% trans 'Help' %}</span>
 | 
			
		||||
                <a class="count-info" href="https://market.aliyun.com/products/53690006/cmgj026011.html?spm=5176.730005.0.0.cY2io1" target="_blank">
 | 
			
		||||
                    <span class="m-r-sm text-muted welcome-message">{% trans 'Supports' %}</span>
 | 
			
		||||
                </a>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li class="dropdown">
 | 
			
		||||
                <a class="count-info"  href="http://jumpserver.readthedocs.io/" target="_blank">
 | 
			
		||||
                     <span class="m-r-sm text-muted welcome-message">{% trans 'Docs' %}</span>
 | 
			
		||||
                </a>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li class="dropdown">
 | 
			
		||||
| 
						 | 
				
			
			@ -28,9 +33,8 @@
 | 
			
		|||
                        </span>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </a>
 | 
			
		||||
                <ul class="dropdown-menu animated fadeInRight m-t-xs">
 | 
			
		||||
                    <li><a href="{% url 'users:user-profile' %}"><i class="fa fa-cogs">  </i><span> {% trans 'Profile' %}</span></a></li>
 | 
			
		||||
                    <li class="divider"></li>
 | 
			
		||||
                <ul class="dropdown-menu animated fadeInRight m-t-xs profile-dropdown">
 | 
			
		||||
                    <li><a href="{% url 'users:user-profile' %}"><i class="fa fa-cogs"> </i><span> {% trans 'Profile' %}</span></a></li>
 | 
			
		||||
                    {% if request.user.is_superuser %}
 | 
			
		||||
                        {% if request.COOKIES.IN_ADMIN_PAGE == 'No' %}
 | 
			
		||||
                            <li><a id="switch_admin"><i class="fa fa-exchange"></i><span> {% trans 'Admin page' %}</span></a></li>
 | 
			
		||||
| 
						 | 
				
			
			@ -57,11 +61,11 @@
 | 
			
		|||
            <li>
 | 
			
		||||
                <a href="">{% trans 'Dashboard' %}</a>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li>
 | 
			
		||||
            {% if app %}
 | 
			
		||||
            <li>
 | 
			
		||||
                <a>{{ app }}</a>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            </li>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% if action %}
 | 
			
		||||
            <li class="active">
 | 
			
		||||
                <strong>{{ action }}</strong>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,6 +42,11 @@
 | 
			
		|||
        <li id="session-online"><a href="{% url 'terminal:session-online-list' %}">{% trans 'Session online' %}</a></li>
 | 
			
		||||
        <li id="session-offline"><a href="{% url 'terminal:session-offline-list' %}">{% trans 'Session offline' %}</a></li>
 | 
			
		||||
        <li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li>
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="{% url 'terminal:web-terminal' %}" target="_blank">
 | 
			
		||||
            <span class="nav-label">{% trans 'Web terminal' %}</span>
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li>
 | 
			
		||||
    </ul>
 | 
			
		||||
</li>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
<li class="nav-header">
 | 
			
		||||
    <div class="dropdown profile-element">
 | 
			
		||||
        <div href="http://www.jumpserver.org" target="_blank">
 | 
			
		||||
            <img alt="image" height="55" src="/static/img/logo-text.png" style="margin-left: 10px"/>
 | 
			
		||||
            <img alt="logo" height="55" width="185" src="/static/img/logo-text.png" style="margin-left: 20px"/>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="clearfix"></div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,7 @@
 | 
			
		|||
    <div class="row">
 | 
			
		||||
        <div class="col-sm-2 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px">
 | 
			
		||||
            <h2>活跃用户TOP5</h2>
 | 
			
		||||
            <small>过去一周共有<span class="text-info">{{ user_visit_count_weekly }}</span>位用户登录<span class="text-success">{{ asset_visit_count_weekly }}</span>次服务器.</small>
 | 
			
		||||
            <small>过去一周共有<span class="text-info">{{ user_visit_count_weekly }}</span>位用户登录<span class="text-success">{{ asset_visit_count_weekly }}</span>次资产.</small>
 | 
			
		||||
            <ul class="list-group clear-list m-t">
 | 
			
		||||
                {% for data in user_visit_count_top_five %}
 | 
			
		||||
                    <li class="list-group-item fist-item">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,14 +5,15 @@ import logging
 | 
			
		|||
import os
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
from rest_framework import viewsets, serializers
 | 
			
		||||
from rest_framework.views import APIView, Response
 | 
			
		||||
from rest_framework.permissions import AllowAny
 | 
			
		||||
from django.core.cache import cache
 | 
			
		||||
from django.shortcuts import get_object_or_404, redirect
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.core.files.storage import default_storage
 | 
			
		||||
from django.http import HttpResponseNotFound
 | 
			
		||||
from rest_framework import viewsets, serializers
 | 
			
		||||
from rest_framework.views import APIView, Response
 | 
			
		||||
from rest_framework.permissions import AllowAny
 | 
			
		||||
from rest_framework_bulk import BulkModelViewSet
 | 
			
		||||
 | 
			
		||||
from common.utils import get_object_or_none
 | 
			
		||||
from .models import Terminal, Status, Session, Task
 | 
			
		||||
| 
						 | 
				
			
			@ -179,12 +180,29 @@ class SessionViewSet(viewsets.ModelViewSet):
 | 
			
		|||
        return self.queryset
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TaskViewSet(viewsets.ModelViewSet):
 | 
			
		||||
class TaskViewSet(BulkModelViewSet):
 | 
			
		||||
    queryset = Task.objects.all()
 | 
			
		||||
    serializer_class = TaskSerializer
 | 
			
		||||
    permission_classes = (IsSuperUserOrAppUser,)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class KillSessionAPI(APIView):
 | 
			
		||||
    permission_classes = (IsSuperUserOrAppUser,)
 | 
			
		||||
    model = Task
 | 
			
		||||
 | 
			
		||||
    def post(self, request, *args, **kwargs):
 | 
			
		||||
        validated_session = []
 | 
			
		||||
        for session_id in request.data:
 | 
			
		||||
            session = get_object_or_none(Session, id=session_id)
 | 
			
		||||
            if session and not session.is_finished:
 | 
			
		||||
                validated_session.append(session_id)
 | 
			
		||||
                self.model.objects.create(
 | 
			
		||||
                    name="kill_session", args=session.id,
 | 
			
		||||
                    terminal=session.terminal,
 | 
			
		||||
                )
 | 
			
		||||
        return Response({"ok": validated_session})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommandViewSet(viewsets.ViewSet):
 | 
			
		||||
    """接受app发送来的command log, 格式如下
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -129,7 +129,7 @@ class Session(models.Model):
 | 
			
		|||
    has_command = models.BooleanField(default=False, verbose_name=_("Command"))
 | 
			
		||||
    terminal = models.ForeignKey(Terminal, null=True, on_delete=models.CASCADE)
 | 
			
		||||
    date_last_active = models.DateTimeField(verbose_name=_("Date last active"), default=timezone.now)
 | 
			
		||||
    date_start = models.DateTimeField(verbose_name=_("Date start"))
 | 
			
		||||
    date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True)
 | 
			
		||||
    date_end = models.DateTimeField(verbose_name=_("Date end"), null=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,11 @@
 | 
			
		|||
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from rest_framework import serializers
 | 
			
		||||
from rest_framework_bulk.serializers import BulkListSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from common.mixins import BulkSerializerMixin
 | 
			
		||||
from common.utils import get_object_or_none
 | 
			
		||||
from .models import Terminal, Status, Session, Task
 | 
			
		||||
from .backends import get_multi_command_store
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +51,7 @@ class SessionSerializer(serializers.ModelSerializer):
 | 
			
		|||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Session
 | 
			
		||||
        list_serializer_class = BulkListSerializer
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
 | 
			
		||||
    def get_command_amount(self, obj):
 | 
			
		||||
| 
						 | 
				
			
			@ -60,11 +65,12 @@ class StatusSerializer(serializers.ModelSerializer):
 | 
			
		|||
        model = Status
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TaskSerializer(serializers.ModelSerializer):
 | 
			
		||||
class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        model = Task
 | 
			
		||||
        list_serializer_class = BulkListSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ReplaySerializer(serializers.Serializer):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ from django.utils import timezone
 | 
			
		|||
 | 
			
		||||
from common.celery import register_as_period_task, after_app_ready_start, \
 | 
			
		||||
    after_app_shutdown_clean
 | 
			
		||||
from .models import Status
 | 
			
		||||
from .models import Status, Session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CACHE_REFRESH_INTERVAL = 10
 | 
			
		||||
| 
						 | 
				
			
			@ -20,11 +20,17 @@ RUNNING = False
 | 
			
		|||
@after_app_ready_start
 | 
			
		||||
@after_app_shutdown_clean
 | 
			
		||||
def delete_terminal_status_period():
 | 
			
		||||
    yesterday = timezone.now() - datetime.timedelta(days=1)
 | 
			
		||||
    yesterday = timezone.now() - datetime.timedelta(days=3)
 | 
			
		||||
    Status.objects.filter(date_created__lt=yesterday).delete()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task
 | 
			
		||||
@register_as_period_task(interval=3600)
 | 
			
		||||
@after_app_ready_start
 | 
			
		||||
@after_app_shutdown_clean
 | 
			
		||||
def clean_orphan_session():
 | 
			
		||||
    active_sessions = Session.objects.filter(is_finished=False)
 | 
			
		||||
    for session in active_sessions:
 | 
			
		||||
        if not session.terminal.is_active:
 | 
			
		||||
            session.is_finished = True
 | 
			
		||||
            session.save()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -109,6 +109,21 @@
 | 
			
		|||
    {% endfor %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content_bottom_left %}
 | 
			
		||||
    <div id="actions" {% if type != "online" %} style="display: none" {% endif %}>
 | 
			
		||||
        <div class="input-group">
 | 
			
		||||
            <select class="form-control m-b" style="width: auto" id="slct_bulk_update">
 | 
			
		||||
                <option value="terminate">{% trans 'Terminate selected' %}</option>
 | 
			
		||||
            </select>
 | 
			
		||||
            <div class="input-group-btn pull-left" style="padding-left: 5px;">
 | 
			
		||||
                <button id='btn_bulk_update' style="height: 32px;"  class="btn btn-sm btn-primary">
 | 
			
		||||
                 {% trans 'Submit' %}
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block custom_foot_js %}
 | 
			
		||||
    <script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
 | 
			
		||||
    <script>
 | 
			
		||||
| 
						 | 
				
			
			@ -119,7 +134,7 @@
 | 
			
		|||
                }, 1000)
 | 
			
		||||
            }
 | 
			
		||||
            var success_message = '{% trans "Terminate task send, waiting ..." %}';
 | 
			
		||||
            var the_url = "{% url 'api-terminal:tasks-list' %}";
 | 
			
		||||
            var the_url = "{% url 'api-terminal:kill-session' %}";
 | 
			
		||||
            APIUpdateAttr({
 | 
			
		||||
                url: the_url,
 | 
			
		||||
                method: 'POST',
 | 
			
		||||
| 
						 | 
				
			
			@ -150,14 +165,31 @@
 | 
			
		|||
        }).on('click', '.btn-term', function () {
 | 
			
		||||
            var $this = $(this);
 | 
			
		||||
            var session_id = $this.attr('value');
 | 
			
		||||
            var terminal_id = $this.attr('terminal');
 | 
			
		||||
            var data = {
 | 
			
		||||
                name: "kill_session",
 | 
			
		||||
                args: session_id,
 | 
			
		||||
                terminal: terminal_id
 | 
			
		||||
            };
 | 
			
		||||
            var data = [
 | 
			
		||||
                session_id
 | 
			
		||||
            ];
 | 
			
		||||
            terminateSession(data)
 | 
			
		||||
        })
 | 
			
		||||
        }).on('click', '#btn_bulk_update', function () {
 | 
			
		||||
            var action = $('#slct_bulk_update').val();
 | 
			
		||||
            var id_list = [];
 | 
			
		||||
            $(".cbx-term:checked").each(function (index, data) {
 | 
			
		||||
                id_list.push($(data).attr("value"))
 | 
			
		||||
            });
 | 
			
		||||
            if (id_list.length === 0) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function doTerminate() {
 | 
			
		||||
                terminateSession(id_list)
 | 
			
		||||
            }
 | 
			
		||||
            switch(action) {
 | 
			
		||||
                case 'terminate':
 | 
			
		||||
                    doTerminate();
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    </script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ urlpatterns = [
 | 
			
		|||
    url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$',
 | 
			
		||||
        api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
 | 
			
		||||
        name='session-replay'),
 | 
			
		||||
    url(r'^v1/tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'),
 | 
			
		||||
    url(r'^v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})/access-key', api.TerminalTokenApi.as_view(), name='terminal-access-key'),
 | 
			
		||||
    url(r'^v1/terminal/config', api.TerminalConfig.as_view(), name='terminal-config'),
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ from django.conf import settings
 | 
			
		|||
from django.utils import timezone
 | 
			
		||||
from django.utils.translation import ugettext as _
 | 
			
		||||
 | 
			
		||||
from common.mixins import DatetimeSearchMixin
 | 
			
		||||
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
 | 
			
		||||
from ..models import Command
 | 
			
		||||
from .. import utils
 | 
			
		||||
from ..backends import get_multi_command_store
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ __all__ = ['CommandListView']
 | 
			
		|||
common_storage = get_multi_command_store()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommandListView(DatetimeSearchMixin, ListView):
 | 
			
		||||
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
 | 
			
		||||
    model = Command
 | 
			
		||||
    template_name = "terminal/command_list.html"
 | 
			
		||||
    context_object_name = 'command_list'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,6 +74,7 @@ class SessionOnlineListView(SessionListView):
 | 
			
		|||
        context = {
 | 
			
		||||
            'app': _('Terminal'),
 | 
			
		||||
            'action': _('Session online list'),
 | 
			
		||||
            'type': 'online',
 | 
			
		||||
            'now': timezone.now(),
 | 
			
		||||
        }
 | 
			
		||||
        kwargs.update(context)
 | 
			
		||||
| 
						 | 
				
			
			@ -97,7 +98,7 @@ class SessionOfflineListView(SessionListView):
 | 
			
		|||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SessionDetailView(SingleObjectMixin, ListView):
 | 
			
		||||
class SessionDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView):
 | 
			
		||||
    template_name = 'terminal/session_detail.html'
 | 
			
		||||
    model = Session
 | 
			
		||||
    object = None
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -145,7 +145,8 @@ class UserAuthApi(APIView):
 | 
			
		|||
 | 
			
		||||
        if not login_ip:
 | 
			
		||||
            x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
 | 
			
		||||
            if x_forwarded_for:
 | 
			
		||||
 | 
			
		||||
            if x_forwarded_for and x_forwarded_for[0]:
 | 
			
		||||
                login_ip = x_forwarded_for[0]
 | 
			
		||||
            else:
 | 
			
		||||
                login_ip = request.META.get("REMOTE_ADDR")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,8 @@ class AccessKey(models.Model):
 | 
			
		|||
                          default=uuid.uuid4, editable=False)
 | 
			
		||||
    secret = models.UUIDField(verbose_name='AccessKeySecret',
 | 
			
		||||
                              default=uuid.uuid4, editable=False)
 | 
			
		||||
    user = models.ForeignKey(User, verbose_name='User', on_delete=models.CASCADE, related_name='access_key')
 | 
			
		||||
    user = models.ForeignKey(User, verbose_name='User',
 | 
			
		||||
                             on_delete=models.CASCADE, related_name='access_key')
 | 
			
		||||
 | 
			
		||||
    def get_id(self):
 | 
			
		||||
        return str(self.id)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ class UserGroup(NoDeleteModelMixin):
 | 
			
		|||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        ordering = ['name']
 | 
			
		||||
        verbose_name = _("User group")
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def initial(cls):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -251,6 +251,7 @@ class User(AbstractUser):
 | 
			
		|||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        ordering = ['username']
 | 
			
		||||
        verbose_name = _("User")
 | 
			
		||||
 | 
			
		||||
    #: Use this method initial user
 | 
			
		||||
    @classmethod
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,24 +22,27 @@
 | 
			
		|||
    <div class="loginColumns animated fadeInDown">
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-md-6">
 | 
			
		||||
                <h2 class="font-bold">欢迎使用Jumpserver开源跳板机</h2>
 | 
			
		||||
                <h2 class="font-bold">欢迎使用Jumpserver开源堡垒机</h2>
 | 
			
		||||
                <p>
 | 
			
		||||
                    符合4A规范的专业运维审计系统:拥有跳板机的所有功能,认证,授权,审计,文件上传;
 | 
			
		||||
                    全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
 | 
			
		||||
                </p>
 | 
			
		||||
                <p>
 | 
			
		||||
                    极致的用户使用体验:拥有时尚外观是区别与以往版本和其他软件的铭牌,高雅的气质让你爱不释手;
 | 
			
		||||
                    使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
 | 
			
		||||
                </p>
 | 
			
		||||
                <p>
 | 
			
		||||
                    混合云环境下的堡垒机:怎么能容忍传统堡垒机的繁琐步骤,Jumpserver让你极致省力;
 | 
			
		||||
                    采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。
 | 
			
		||||
                </p>
 | 
			
		||||
                <p>
 | 
			
		||||
                    <small>改变世界,从一点点开始。</small>
 | 
			
		||||
                    改变世界,从一点点开始。
 | 
			
		||||
                </p>
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-md-6">
 | 
			
		||||
                <div class="ibox-content">
 | 
			
		||||
                    <div><img src="{% static 'img/logo.png' %}" width="82" height="82"> <span class="font-bold text-center" style="font-size: 32px; font-family: inherit">{% trans 'Login' %}</span></div>
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <img src="{% static 'img/logo.png' %}" width="60" height="60">
 | 
			
		||||
                        <span class="font-bold text-center" style="font-size: 24px; font-family: inherit; margin-left: 20px">{% trans 'Login' %}</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <form class="m-t" role="form" method="post" action="">
 | 
			
		||||
                        {% csrf_token %}
 | 
			
		||||
                        {% if form.errors %}
 | 
			
		||||
| 
						 | 
				
			
			@ -60,12 +63,16 @@
 | 
			
		|||
                        </div>
 | 
			
		||||
                        <button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Login' %}</button>
 | 
			
		||||
 | 
			
		||||
                        {% if demo_mode %}
 | 
			
		||||
                        <p class="text-muted font-bold" style="color: red">
 | 
			
		||||
                            Demo账号: admin 密码: admin
 | 
			
		||||
                        </p>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
 | 
			
		||||
                        <a href="{% url 'users:forgot-password' %}">
 | 
			
		||||
                            <small>{% trans 'Forgot password' %}?</small>
 | 
			
		||||
                        </a>
 | 
			
		||||
 | 
			
		||||
                        <p class="text-muted text-center">
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </form>
 | 
			
		||||
                    <p class="m-t">
 | 
			
		||||
                    </p>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -92,8 +92,8 @@ class UserGroupGrantedAssetView(AdminUserRequiredMixin, DetailView):
 | 
			
		|||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = {
 | 
			
		||||
            'app': 'User',
 | 
			
		||||
            'action': 'User group granted asset',
 | 
			
		||||
            'app': _('Users'),
 | 
			
		||||
            'action': _('User group granted asset'),
 | 
			
		||||
        }
 | 
			
		||||
        kwargs.update(context)
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
# ~*~ coding: utf-8 ~*~
 | 
			
		||||
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
import os
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
from django.contrib.auth import login as auth_login, logout as auth_logout
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +57,7 @@ class UserLoginView(FormView):
 | 
			
		|||
            return HttpResponse(_("Please enable cookies and try again."))
 | 
			
		||||
        auth_login(self.request, form.get_user())
 | 
			
		||||
        x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
 | 
			
		||||
 | 
			
		||||
        if x_forwarded_for and x_forwarded_for[0]:
 | 
			
		||||
            login_ip = x_forwarded_for[0]
 | 
			
		||||
        else:
 | 
			
		||||
| 
						 | 
				
			
			@ -75,6 +77,13 @@ class UserLoginView(FormView):
 | 
			
		|||
            self.redirect_field_name,
 | 
			
		||||
            self.request.GET.get(self.redirect_field_name, reverse('index')))
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = {
 | 
			
		||||
            'demo_mode': os.environ.get("DEMO_MODE"),
 | 
			
		||||
        }
 | 
			
		||||
        kwargs.update(context)
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@method_decorator(never_cache, name='dispatch')
 | 
			
		||||
class UserLogoutView(TemplateView):
 | 
			
		||||
| 
						 | 
				
			
			@ -237,7 +246,7 @@ class LoginLogListView(DatetimeSearchMixin, ListView):
 | 
			
		|||
        if self.user:
 | 
			
		||||
            queryset = queryset.filter(username=self.user)
 | 
			
		||||
        if self.keyword:
 | 
			
		||||
            queryset = self.queryset.filter(
 | 
			
		||||
            queryset = queryset.filter(
 | 
			
		||||
                Q(ip__contains=self.keyword) |
 | 
			
		||||
                Q(city__contains=self.keyword) |
 | 
			
		||||
                Q(username__contains=self.keyword)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -313,7 +313,6 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
 | 
			
		|||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = {
 | 
			
		||||
            'app': _('Users'),
 | 
			
		||||
            'action': _('Profile'),
 | 
			
		||||
        }
 | 
			
		||||
        kwargs.update(context)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
# Minimal makefile for Sphinx documentation
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# You can set these variables from the command line.
 | 
			
		||||
SPHINXOPTS    =
 | 
			
		||||
SPHINXBUILD   = sphinx-build
 | 
			
		||||
SPHINXPROJ    = Jumpserver
 | 
			
		||||
SOURCEDIR     = .
 | 
			
		||||
BUILDDIR      = _build
 | 
			
		||||
 | 
			
		||||
# Put it first so that "make" without argument is like "make help".
 | 
			
		||||
help:
 | 
			
		||||
	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
 | 
			
		||||
 | 
			
		||||
.PHONY: help Makefile
 | 
			
		||||
 | 
			
		||||
# Catch-all target: route all unknown targets to Sphinx using the new
 | 
			
		||||
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
 | 
			
		||||
%: Makefile
 | 
			
		||||
	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 12 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 29 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
资产管理模块
 | 
			
		||||
=============
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
管理文档
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
这里介绍管理员功能。
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 1
 | 
			
		||||
 | 
			
		||||
   admin_user
 | 
			
		||||
   admin_asset
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
用户管理
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
这里介绍用户管理模块的功能。
 | 
			
		||||
 | 
			
		||||
点击页面左侧“用户列表”菜单下的“用户列表,进入用户列表页面。
 | 
			
		||||
 | 
			
		||||
.. contents:: Topics
 | 
			
		||||
 | 
			
		||||
.. _create_user:
 | 
			
		||||
 | 
			
		||||
创建用户
 | 
			
		||||
````````
 | 
			
		||||
 | 
			
		||||
点击页面左上角“创建用户”按钮,进入创建用户页面,填写账户,角色安全,个人等信息,点击“提交”按钮,用户创建完成。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _update_user:
 | 
			
		||||
 | 
			
		||||
更新用户
 | 
			
		||||
````````
 | 
			
		||||
 | 
			
		||||
点击页面右边的“更新”按钮,进入编辑用户页面,编辑用户信息,点击“提交”按钮,更新用户完成。
 | 
			
		||||
 | 
			
		||||
.. _delete_user:
 | 
			
		||||
 | 
			
		||||
删除用户
 | 
			
		||||
````````
 | 
			
		||||
 | 
			
		||||
点击页面右边的“删除”按钮,弹出是否删除确认框,点击“确定”按钮,删除用户完成。
 | 
			
		||||
 | 
			
		||||
.. _export_user:
 | 
			
		||||
 | 
			
		||||
导出用户
 | 
			
		||||
````````
 | 
			
		||||
 | 
			
		||||
选中用户,点击右上角的“导出”按钮,导出用户完成。
 | 
			
		||||
 | 
			
		||||
.. _inport_user:
 | 
			
		||||
 | 
			
		||||
导入用户
 | 
			
		||||
````````
 | 
			
		||||
 | 
			
		||||
点击右上角的“导入”按钮,弹出导入对话框,选择要导入的CSV格式文件,点击“确认”按钮,导入用户完成。
 | 
			
		||||
 | 
			
		||||
.. _batch_operation:
 | 
			
		||||
 | 
			
		||||
批量操作
 | 
			
		||||
````````
 | 
			
		||||
 | 
			
		||||
选中用户,选择页面左下角的批量操作选项,点击”提交“按钮,批量操作完成。
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,166 @@
 | 
			
		|||
REST API规范约定
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
这里仅考虑REST API的基本情况。参考
 | 
			
		||||
 | 
			
		||||
`RESTful API 设计指南`_
 | 
			
		||||
 | 
			
		||||
`github api文档`_
 | 
			
		||||
 | 
			
		||||
协议
 | 
			
		||||
~~~~
 | 
			
		||||
 | 
			
		||||
API与用户的通信协议,总是使用HTTPs协议。
 | 
			
		||||
 | 
			
		||||
域名
 | 
			
		||||
~~~~
 | 
			
		||||
 | 
			
		||||
这版api相对简单, 没有前后端分离, 没有独立app, 所以放在主域名下
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    https://example.org/api/
 | 
			
		||||
 | 
			
		||||
版本
 | 
			
		||||
~~~~
 | 
			
		||||
 | 
			
		||||
将API的版本号放入URL中, 由于一个项目多个app所以Jumpserver使用以下风格,
 | 
			
		||||
将版本号放到app后面
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    https://example.com/api/:app:/:version:/:resource:
 | 
			
		||||
    https://example.com/api/assets/v1.0/assets [GET, POST]
 | 
			
		||||
    https://example.com/api/assets/v1.0/assets/1 [GET, PUT, DELETE]
 | 
			
		||||
 | 
			
		||||
路径
 | 
			
		||||
~~~~
 | 
			
		||||
 | 
			
		||||
路径又称“终点”(endpoint),表示API的具体网址。
 | 
			
		||||
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的“集合”(collection),所以API中的名词也应该使用复数。
 | 
			
		||||
举例来说 cmdb中的assets列表, idc列表
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    https://example.com/api/:app:/:version:/:resource:
 | 
			
		||||
 | 
			
		||||
    https://example.com/api/assets/v1.0/assets [GET, POST]
 | 
			
		||||
    https://example.com/api/assets/v1.0/assets/1 [GET, PUT, DELETE]
 | 
			
		||||
    https://example.com/api/assets/v1.0/idcs [GET, POST]
 | 
			
		||||
 | 
			
		||||
一般性的增删查改(CRUD)API,完全使用HTTP
 | 
			
		||||
method加上url提供的语义,url中的可变部分(比如上面提到的)
 | 
			
		||||
一般用来传递该API操作的核心实体对象的唯一ID,如果有更多的参数需要提供,GET方法请使用url
 | 
			
		||||
parameter
 | 
			
		||||
(例如:“?client_id=xxxxx&app_id=xxxxxx”),PUT/POST/DELETE方法请使用请求体传递参数。
 | 
			
		||||
 | 
			
		||||
HTTP Method
 | 
			
		||||
~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
对于资源的具体操作类型,由HTTP动词表示。
 | 
			
		||||
 | 
			
		||||
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
 | 
			
		||||
 | 
			
		||||
-  GET(SELECT):从服务器取出资源(一项或多项)。
 | 
			
		||||
-  POST(CREATE):在服务器新建一个资源。
 | 
			
		||||
-  PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源, 幂等
 | 
			
		||||
-  PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
 | 
			
		||||
-  DELETE(DELETE):从服务器删除资源。
 | 
			
		||||
 | 
			
		||||
.. _RESTful API 设计指南: http://www.ruanyifeng.com/blog/2014/05/restful_api.html
 | 
			
		||||
.. _github api文档: https://developer.github.com/v3/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
过滤信息
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
常见参数约定
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    ?keyword=localhost 模糊搜索
 | 
			
		||||
    ?limit=10:指定返回记录的数量
 | 
			
		||||
    ?offset=10:指定返回记录的开始位置。
 | 
			
		||||
    ?page=2&per_page=100:指定第几页,以及每页的记录数。
 | 
			
		||||
    ?sort=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
 | 
			
		||||
    ?asset_id=1:指定筛选条件
 | 
			
		||||
 | 
			
		||||
状态码
 | 
			
		||||
~~~~~~
 | 
			
		||||
 | 
			
		||||
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。
 | 
			
		||||
 | 
			
		||||
-  200 OK -
 | 
			
		||||
   [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
 | 
			
		||||
-  201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
 | 
			
		||||
-  202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
 | 
			
		||||
-  204 NO CONTENT - [DELETE]:用户删除数据成功。
 | 
			
		||||
-  400 INVALID REQUEST -
 | 
			
		||||
   [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
 | 
			
		||||
-  401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
 | 
			
		||||
-  403 Forbidden - [*]
 | 
			
		||||
   表示用户得到授权(与401错误相对),但是访问是被禁止的。
 | 
			
		||||
-  404 NOT FOUND -
 | 
			
		||||
   [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
 | 
			
		||||
-  406 Not Acceptable -
 | 
			
		||||
   [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
 | 
			
		||||
-  410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
 | 
			
		||||
-  422 Unprocesable entity - [POST/PUT/PATCH]
 | 
			
		||||
   当创建一个对象时,发生一个验证错误。
 | 
			
		||||
-  500 INTERNAL SERVER ERROR -
 | 
			
		||||
   [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
 | 
			
		||||
 | 
			
		||||
错误处理
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        error: "Invalid API key"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
返回结果
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
针对不同操作,服务器向用户返回的结果应该符合以下规范。
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    GET /collection:返回资源对象的列表(数组)
 | 
			
		||||
    GET /collection/resource:返回单个资源对象
 | 
			
		||||
    POST /collection:返回新生成的资源对象
 | 
			
		||||
    PUT /collection/resource:返回完整的资源对象
 | 
			
		||||
    PATCH /collection/resource:返回完整的资源对象
 | 
			
		||||
    DELETE /collection/resource:返回一个空文档
 | 
			
		||||
 | 
			
		||||
Hypermedia API
 | 
			
		||||
~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
RESTful
 | 
			
		||||
API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
 | 
			
		||||
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    {"link": {
 | 
			
		||||
      "rel":   "collection https://www.example.com/zoos",
 | 
			
		||||
      "href":  "https://api.example.com/zoos",
 | 
			
		||||
      "title": "List of zoos",
 | 
			
		||||
      "type":  "application/vnd.yourformat+json"
 | 
			
		||||
    }}
 | 
			
		||||
 | 
			
		||||
上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。
 | 
			
		||||
 | 
			
		||||
rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),
 | 
			
		||||
 | 
			
		||||
href表示API的路径,title表示API的标题,type表示返回类型。 Hypermedia
 | 
			
		||||
API的设计被称为HATEOAS。 Github的API就是这种设计.
 | 
			
		||||
 | 
			
		||||
其它
 | 
			
		||||
~~~~
 | 
			
		||||
 | 
			
		||||
(1)API的身份认证应该使用OAuth 2.0框架。
 | 
			
		||||
(2)服务器返回的数据格式,应该尽量使用JSON
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,168 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Configuration file for the Sphinx documentation builder.
 | 
			
		||||
#
 | 
			
		||||
# This file does only contain a selection of the most common options. For a
 | 
			
		||||
# full list see the documentation:
 | 
			
		||||
# http://www.sphinx-doc.org/en/stable/config
 | 
			
		||||
 | 
			
		||||
# -- Path setup --------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# If extensions (or modules to document with autodoc) are in another directory,
 | 
			
		||||
# add these directories to sys.path here. If the directory is relative to the
 | 
			
		||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
 | 
			
		||||
#
 | 
			
		||||
# import os
 | 
			
		||||
# import sys
 | 
			
		||||
# sys.path.insert(0, os.path.abspath('.'))
 | 
			
		||||
import sphinx_rtd_theme
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Project information -----------------------------------------------------
 | 
			
		||||
 | 
			
		||||
project = 'jumpserver'
 | 
			
		||||
copyright = '北京堆栈科技有限公司 © 2014-2018'
 | 
			
		||||
author = 'Jumpserver team'
 | 
			
		||||
 | 
			
		||||
# The short X.Y version
 | 
			
		||||
version = ''
 | 
			
		||||
# The full version, including alpha/beta/rc tags
 | 
			
		||||
release = '0.5.0'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- General configuration ---------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# If your documentation needs a minimal Sphinx version, state it here.
 | 
			
		||||
#
 | 
			
		||||
# needs_sphinx = '1.0'
 | 
			
		||||
 | 
			
		||||
# Add any Sphinx extension module names here, as strings. They can be
 | 
			
		||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 | 
			
		||||
# ones.
 | 
			
		||||
extensions = [
 | 
			
		||||
    'sphinx.ext.viewcode',
 | 
			
		||||
    'sphinx.ext.githubpages',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain templates here, relative to this directory.
 | 
			
		||||
templates_path = ['_templates']
 | 
			
		||||
 | 
			
		||||
# The suffix(es) of source filenames.
 | 
			
		||||
# You can specify multiple suffix as a list of string:
 | 
			
		||||
#
 | 
			
		||||
# source_suffix = ['.rst', '.md']
 | 
			
		||||
source_suffix = '.rst'
 | 
			
		||||
 | 
			
		||||
# The master toctree document.
 | 
			
		||||
master_doc = 'index'
 | 
			
		||||
 | 
			
		||||
# The language for content autogenerated by Sphinx. Refer to documentation
 | 
			
		||||
# for a list of supported languages.
 | 
			
		||||
#
 | 
			
		||||
# This is also used if you do content translation via gettext catalogs.
 | 
			
		||||
# Usually you set "language" from the command line for these cases.
 | 
			
		||||
language = 'zh_CN'
 | 
			
		||||
 | 
			
		||||
# List of patterns, relative to source directory, that match files and
 | 
			
		||||
# directories to ignore when looking for source files.
 | 
			
		||||
# This pattern also affects html_static_path and html_extra_path .
 | 
			
		||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
 | 
			
		||||
 | 
			
		||||
# The name of the Pygments (syntax highlighting) style to use.
 | 
			
		||||
pygments_style = 'sphinx'
 | 
			
		||||
html_show_sourcelink = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for HTML output -------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# The theme to use for HTML and HTML Help pages.  See the documentation for
 | 
			
		||||
# a list of builtin themes.
 | 
			
		||||
#
 | 
			
		||||
# html_theme = 'alabaster'
 | 
			
		||||
html_theme = "sphinx_rtd_theme"
 | 
			
		||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
 | 
			
		||||
 | 
			
		||||
# Theme options are theme-specific and customize the look and feel of a theme
 | 
			
		||||
# further.  For a list of options available for each theme, see the
 | 
			
		||||
# documentation.
 | 
			
		||||
#
 | 
			
		||||
html_theme_options = {
 | 
			
		||||
    'logo_only': True,
 | 
			
		||||
    'display_version': False
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain custom static files (such as style sheets) here,
 | 
			
		||||
# relative to this directory. They are copied after the builtin static files,
 | 
			
		||||
# so a file named "default.css" will overwrite the builtin "default.css".
 | 
			
		||||
html_static_path = ['_static']
 | 
			
		||||
 | 
			
		||||
# Custom sidebar templates, must be a dictionary that maps document names
 | 
			
		||||
# to template names.
 | 
			
		||||
#
 | 
			
		||||
# The default sidebars (for documents that don't match any pattern) are
 | 
			
		||||
# defined by theme itself.  Builtin themes are using these templates by
 | 
			
		||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
 | 
			
		||||
# 'searchbox.html']``.
 | 
			
		||||
#
 | 
			
		||||
# html_sidebars = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for HTMLHelp output ---------------------------------------------
 | 
			
		||||
 | 
			
		||||
# Output file base name for HTML help builder.
 | 
			
		||||
htmlhelp_basename = 'Jumpserver 文档'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for LaTeX output ------------------------------------------------
 | 
			
		||||
 | 
			
		||||
latex_elements = {
 | 
			
		||||
    # The paper size ('letterpaper' or 'a4paper').
 | 
			
		||||
    #
 | 
			
		||||
    # 'papersize': 'letterpaper',
 | 
			
		||||
 | 
			
		||||
    # The font size ('10pt', '11pt' or '12pt').
 | 
			
		||||
    #
 | 
			
		||||
    # 'pointsize': '10pt',
 | 
			
		||||
 | 
			
		||||
    # Additional stuff for the LaTeX preamble.
 | 
			
		||||
    #
 | 
			
		||||
    # 'preamble': '',
 | 
			
		||||
 | 
			
		||||
    # Latex figure (float) alignment
 | 
			
		||||
    #
 | 
			
		||||
    # 'figure_align': 'htbp',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Grouping the document tree into LaTeX files. List of tuples
 | 
			
		||||
# (source start file, target name, title,
 | 
			
		||||
#  author, documentclass [howto, manual, or own class]).
 | 
			
		||||
latex_documents = [
 | 
			
		||||
    (master_doc, 'jumpserver.tex', 'jumpserver Documentation',
 | 
			
		||||
     'Jumpserver team', 'manual'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for manual page output ------------------------------------------
 | 
			
		||||
 | 
			
		||||
# One entry per manual page. List of tuples
 | 
			
		||||
# (source start file, name, description, authors, manual section).
 | 
			
		||||
man_pages = [
 | 
			
		||||
    (master_doc, 'jumpserver', 'jumpserver Documentation',
 | 
			
		||||
     [author], 1)
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for Texinfo output ----------------------------------------------
 | 
			
		||||
 | 
			
		||||
# Grouping the document tree into Texinfo files. List of tuples
 | 
			
		||||
# (source start file, target name, title, author,
 | 
			
		||||
#  dir menu entry, description, category)
 | 
			
		||||
texinfo_documents = [
 | 
			
		||||
    (master_doc, 'jumpserver', 'jumpserver Documentation',
 | 
			
		||||
     author, 'jumpserver', 'One line description of project.',
 | 
			
		||||
     'Miscellaneous'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Extension configuration -------------------------------------------------
 | 
			
		||||
html_logo = '_static/img/logo-text.png'
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
联系方式
 | 
			
		||||
+++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
QQ群
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
群1: 390139816
 | 
			
		||||
群2: 399218702
 | 
			
		||||
群3: 552054376
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Github
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
https://github.com/jumpserver/jumpserver.git
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
官网
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
http://www.jumpserver.org
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Demo
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
http://demo.jumpserver.org:8080
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
邮件
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
ibuler#fit2cloud.com (#替换为@)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
贡献者
 | 
			
		||||
++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
感谢一下朋友为Jumpserver做出的贡献,世界因你们而不同,排名不分先后
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
- **小彧 <李磊>** Django资深开发者,为用户模块贡献了很多代码
 | 
			
		||||
- **sofia <周小侠>** 资深前端工程师, 前端代码贡献者
 | 
			
		||||
- **liuz <刘正> 全栈工程师** 编写了Web terminal大部分代码
 | 
			
		||||
- **jiaxiangkong <陈尚委>** Jumpserver测试运营
 | 
			
		||||
- **halcyon <王墉>** DevOps 资深开发者, 0.3.2 核心开发者之一
 | 
			
		||||
- **yumaojun03 <喻茂峻>** DevOps 资深开发者,擅长Python, Go以及PAAS平台开发
 | 
			
		||||
- **kelianchun <柯连春>** DevOps 资产开发者,fix了很多bug
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
开发文档
 | 
			
		||||
======================================
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 1
 | 
			
		||||
   :caption: 开发文档
 | 
			
		||||
 | 
			
		||||
   api_style_guide
 | 
			
		||||
   python_style_guide
 | 
			
		||||
   project_structure
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
FAQ
 | 
			
		||||
+++++++++++++++++++++
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
.. jumpserver documentation master file, created by
 | 
			
		||||
   sphinx-quickstart on Mon Feb 26 23:28:27 2018.
 | 
			
		||||
   You can adapt this file completely to your liking, but it should at least
 | 
			
		||||
   contain the root `toctree` directive.
 | 
			
		||||
 | 
			
		||||
Jumpserver 文档
 | 
			
		||||
======================================
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 2
 | 
			
		||||
   :caption: 文档:
 | 
			
		||||
 | 
			
		||||
   intro
 | 
			
		||||
   installation
 | 
			
		||||
   admin_guide
 | 
			
		||||
   user_guide
 | 
			
		||||
   development
 | 
			
		||||
   contributor
 | 
			
		||||
   contact
 | 
			
		||||
   faq
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
索引
 | 
			
		||||
==================
 | 
			
		||||
 | 
			
		||||
* :ref:`genindex`
 | 
			
		||||
* :ref:`modindex`
 | 
			
		||||
* :ref:`search`
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
安装文档
 | 
			
		||||
++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 1
 | 
			
		||||
 | 
			
		||||
   quickstart
 | 
			
		||||
   step_by_step
 | 
			
		||||
   upgrade
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
简介
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
Jumpserver是混合云下更好用的堡垒机, 分布式架构设计无限扩展,轻松对接混合云资产,支持使用云存储(AWS S3, ES等)存储录像、命令
 | 
			
		||||
 | 
			
		||||
Jumpserver颠覆传统堡垒机, 无主机和并发数量限制,支持水平扩容,FIT2CLOUD提供完备的商业服务支持,用户无后顾之忧
 | 
			
		||||
 | 
			
		||||
Jumpserver拥有极致的用户体验, 极致UI体验,容器化的部署方式,部署过程方便快捷,可持续升级
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
组件说明
 | 
			
		||||
++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
Jumpserver
 | 
			
		||||
```````````
 | 
			
		||||
现指Jumpserver管理后台,是核心组件(Core), 使用 Django Class Based View 风格开发,支持Restful API。
 | 
			
		||||
 | 
			
		||||
`Github <https://github.com/jumpserver/jumpserver.git>`_
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Coco
 | 
			
		||||
````````
 | 
			
		||||
实现了SSH Server 和 Web Terminal Server的组件,提供ssh和websocket接口, 使用 Paramiko 和 Flask 开发。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
`Github <https://github.com/jumpserver/coco.git>`__
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Luna
 | 
			
		||||
````````
 | 
			
		||||
现在是Web Terminal前端,计划前端页面都由该项目提供,Jumpserver只提供API,不再负责后台渲染html等。
 | 
			
		||||
 | 
			
		||||
`Github <https://github.com/jumpserver/luna.git>`__
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Guacamole
 | 
			
		||||
```````````
 | 
			
		||||
Apache 跳板机项目,Jumpserver使用其组件实现RDP功能,Jumpserver并没有修改其代码而是添加了额外的插件,支持Jumpserver调用
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Jumpserver-python-sdk
 | 
			
		||||
```````````````````````
 | 
			
		||||
Jumpserver API Python SDK,Coco目前使用该SDK与Jumpserver API交互
 | 
			
		||||
 | 
			
		||||
`Github <https://github.com/jumpserver/jumpserver-python-sdk.git>`__
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
组件架构图
 | 
			
		||||
++++++++++++++++++++++++
 | 
			
		||||
.. image:: _static/img/structure.png
 | 
			
		||||
    :alt: 组件架构图
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
项目骨架
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
说明如下:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    .
 | 
			
		||||
    ├── config-example.py               // 配置文件样例
 | 
			
		||||
    ├── docs                            // 所有doc文件放到该目录
 | 
			
		||||
    │   └── README.md
 | 
			
		||||
    ├── LICENSE
 | 
			
		||||
    ├── README.md
 | 
			
		||||
    ├── install                         // 安装说明
 | 
			
		||||
    ├── logs                            // 日志目录
 | 
			
		||||
    ├── apps                            // 管理后台目录,也是各app所在目录
 | 
			
		||||
    │   └── assets                      // app目录
 | 
			
		||||
    │   │   ├── admin.py
 | 
			
		||||
    │   │   ├── apps.py                 // 新版本django app设置文件
 | 
			
		||||
    │   │   ├── api.py                  // api文件
 | 
			
		||||
    │   │   ├── __init__.py             // 对外暴露的接口,放到该文件中,方便别的app引用
 | 
			
		||||
    │   │   ├── migrations              // models Migrations版本控制目录
 | 
			
		||||
    │   │   │   └── __init__.py
 | 
			
		||||
    │   │   ├── models.py               // 数据模型目录
 | 
			
		||||
    │   │   ├── static                  // app下静态资源目录,如果需要
 | 
			
		||||
    │   │   │   └── assets              // 多一层目录,防止资源重名
 | 
			
		||||
    │   │   │       └── some_image.png
 | 
			
		||||
    │   │   ├── templates               // app下模板目录
 | 
			
		||||
    │   │   │   └── assets              // 多一层目录,防止资源重名
 | 
			
		||||
    │   │   │       └── asset_list.html
 | 
			
		||||
    │   │   ├── templatetags            // 模板标签目录
 | 
			
		||||
    │   │   ├── tests.py                // 测试用例文件
 | 
			
		||||
    │   │   ├── urls.py                 // urlconf文件
 | 
			
		||||
    │   │   ├── utils.py                // 将views和api可复用的代码放在这里, api和views只是请求和返回不同
 | 
			
		||||
    │   │   └── views.py                // views文件
 | 
			
		||||
    │   ├── common
 | 
			
		||||
    │   │   ├── templatetags            // 通用template tag
 | 
			
		||||
    │   │   ├── utils.py                // 通用的函数方法
 | 
			
		||||
    │   │   └── views.py
 | 
			
		||||
    │   ├── fixtures                    // 初始化数据目录
 | 
			
		||||
    │   │   ├── init.json               // 初始化项目数据库
 | 
			
		||||
    │   │   └── fake.json               // 生成大量测试数据
 | 
			
		||||
    │   ├── jumpserver                  // 项目设置目录
 | 
			
		||||
    │   │    ├── __init__.py
 | 
			
		||||
    │   │    ├── settings.py            // 项目设置文件
 | 
			
		||||
    │   │    ├── urls.py                // 项目入口urlconf
 | 
			
		||||
    │   │    └── wsgi.py
 | 
			
		||||
    │   ├── manage.py
 | 
			
		||||
    │   ├── static                      // 项目静态资源目录
 | 
			
		||||
    │   ├── i18n                        // 项目多语言目录
 | 
			
		||||
    │   └── templates                   // 项目模板目录
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,216 @@
 | 
			
		|||
Jumpserver 项目规范(Draft)
 | 
			
		||||
============================
 | 
			
		||||
 | 
			
		||||
语言框架
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
1. Python 3.6.1 (当前最新)
 | 
			
		||||
2. Django 1.11 (当前最新)
 | 
			
		||||
3. Flask 0.12 Luna (当前最新)
 | 
			
		||||
4. Paramiko 2.12 Coco (当前最新)
 | 
			
		||||
 | 
			
		||||
Django规范
 | 
			
		||||
----------
 | 
			
		||||
 | 
			
		||||
1. 尽量使用Class Base View编程,更少代码
 | 
			
		||||
2. 使用Django Form
 | 
			
		||||
3. 每个url独立命名,不要硬编码,同理static也是
 | 
			
		||||
4. 数据库表名手动指定,不要使用默认
 | 
			
		||||
5. 代码优雅简洁
 | 
			
		||||
6. 注释明确优美
 | 
			
		||||
7. 测试案例尽可能完整
 | 
			
		||||
8. 尽可能利用Django造好的轮子
 | 
			
		||||
 | 
			
		||||
代码风格
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
Python方面大致的风格,我们采用pocoo的\ `Style
 | 
			
		||||
Guidance`_\ ,但是有些细节部分会尽量放开 参考国内翻译
 | 
			
		||||
 | 
			
		||||
基本的代码布局
 | 
			
		||||
~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
缩进
 | 
			
		||||
^^^^
 | 
			
		||||
 | 
			
		||||
1. Python严格采用4个空格的缩进,任何python代码都都必须遵守此规定。
 | 
			
		||||
2. web部分代码(HTML, CSS,
 | 
			
		||||
   JavaScript),Node.js采用2空格缩进,同样不使用tab (:raw-latex:`\t`)。
 | 
			
		||||
   之所以与Python不同,是因为js中有大量回调式的写法,2空格可以显著降低视觉上的负担。
 | 
			
		||||
 | 
			
		||||
最大行长度
 | 
			
		||||
^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
按PEP8规范,Python一般限制最大79个字符,
 | 
			
		||||
但是Django的命名,url等通常比较长,
 | 
			
		||||
而且21世纪都是宽屏了,所以我们限制最大120字符
 | 
			
		||||
 | 
			
		||||
**补充说明:HTML代码不受此规范约束。**
 | 
			
		||||
 | 
			
		||||
长语句缩进
 | 
			
		||||
^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
编写长语句时,可以使用换行符()换行。在这种情况下,下一行应该与上一行的最后
 | 
			
		||||
一个“.”句点或“=”对齐,或者是缩进4个空格符
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    this_is_a_very_long(function_call, 'with many parameters') \
 | 
			
		||||
        .that_returns_an_object_with_an_attribute
 | 
			
		||||
 | 
			
		||||
    MyModel.query.filter(MyModel.scalar > 120) \
 | 
			
		||||
                 .order_by(MyModel.name.desc()) \
 | 
			
		||||
                 .limit(10)
 | 
			
		||||
 | 
			
		||||
如果你使用括号“()”或花括号“{}”为长语句换行,那么下一行应与括号或花括号对齐:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    this_is_a_very_long(function_call, 'with many parameters',
 | 
			
		||||
                        23, 42, 'and even more')
 | 
			
		||||
 | 
			
		||||
对于元素众多的列表或元组,在第一个“[”或“(”之后马上换行:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    items = [
 | 
			
		||||
        'this is the first', 'set of items', 'with more items',
 | 
			
		||||
        'to come in this line', 'like this'
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
.. _Style Guidance: http://www.pocoo.org/internal/styleguide/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
空行
 | 
			
		||||
^^^^
 | 
			
		||||
 | 
			
		||||
顶层函数与类之间空两行,此外都只空一行。不要在代码中使用太多的空行来区分不同的逻辑模块。
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    def hello(name):
 | 
			
		||||
        print 'Hello %s!' % name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def goodbye(name):
 | 
			
		||||
        print 'See you %s.' % name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    class MyClass(object):
 | 
			
		||||
        """This is a simple docstring."""
 | 
			
		||||
 | 
			
		||||
        def __init__(self, name):
 | 
			
		||||
            self.name = name
 | 
			
		||||
 | 
			
		||||
        def get_annoying_name(self):
 | 
			
		||||
            return self.name.upper() + '!!!!111'
 | 
			
		||||
 | 
			
		||||
语句和表达式
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
一般空格规则
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
1. 单目运算符与运算对象之间不空格(例如,-,~等),即使单目运算符位于括号内部也一样。
 | 
			
		||||
2. 双目运算符与运算对象之间要空格。
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    exp = -1.05
 | 
			
		||||
    value = (item_value / item_count) * offset / exp
 | 
			
		||||
    value = my_list[index]
 | 
			
		||||
    value = my_dict['key']
 | 
			
		||||
 | 
			
		||||
比较
 | 
			
		||||
^^^^
 | 
			
		||||
 | 
			
		||||
1. 任意类型之间的比较,使用“==”和“!=”。
 | 
			
		||||
2. 与单例(singletons)进行比较时,使用is和is not。
 | 
			
		||||
3. 永远不要与True或False进行比较(例如,不要这样写:foo ==
 | 
			
		||||
   False,而应该这样写:not foo)。
 | 
			
		||||
 | 
			
		||||
否定成员关系检查
 | 
			
		||||
^^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
使用foo not in bar,而不是not foo in bar。
 | 
			
		||||
 | 
			
		||||
命名约定
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
1. 类名称:采用骆驼拼写法(CamelCase),首字母缩略词保持大写不变(HTTPWriter,而不是HttpWriter)。
 | 
			
		||||
2. 变量名:小写_以及_下划线(lowercase_with_underscores)。
 | 
			
		||||
3. 方法与函数名:小写_以及_下划线(lowercase_with_underscores)。
 | 
			
		||||
4. 常量:大写_以及_下划线(UPPERCASE_WITH_UNDERSCORES)。
 | 
			
		||||
5. 预编译的正则表达式:name_re。
 | 
			
		||||
6. 受保护的元素以一个下划线为前缀。双下划线前缀只有定义混入类(mixin
 | 
			
		||||
   classes)时才使用。
 | 
			
		||||
7. 如果使用关键词(keywords)作为类名称,应在名称后添加后置下划线(trailing
 | 
			
		||||
   underscore)。
 | 
			
		||||
   允许与内建变量重名,不要在变量名后添加下划线进行区分。如果函数需要访问重名的内建变量,请将内建变量重新绑定为其他名称。
 | 
			
		||||
8. 命名要有寓意, 不使用拼音,不使用无意义简单字母命名 (循环中计数例外 for
 | 
			
		||||
   i in)
 | 
			
		||||
9. 命名缩写要谨慎, 尽量是大家认可的缩写
 | 
			
		||||
 | 
			
		||||
函数和方法的参数:
 | 
			
		||||
^^^^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
1. 类方法:cls为第一个参数。
 | 
			
		||||
2. 实例方法:self为第一个参数。
 | 
			
		||||
3. property函数中使用匿名函数(lambdas)时,匿名函数的第一个参数可以用x替代,
 | 
			
		||||
   例如:display_name = property(lambda x: x.real_name or x.username)。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
文档注释(Docstring,即各方法,类的说明文档注释)
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
所有文档字符串均以reStructuredText格式编写,方便Sphinx处理。文档字符串的行数不同,布局也不一样。
 | 
			
		||||
如果只有一行,代表字符串结束的三个引号与代表字符串开始的三个引号在同一行。
 | 
			
		||||
如果为多行,文档字符串中的文本紧接着代表字符串开始的三个引号编写,代表字符串结束的三个引号则自己独立成一行。
 | 
			
		||||
(有能力尽可能用英文, 否则请中文优雅注释)
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    def foo():
 | 
			
		||||
        """This is a simple docstring."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def bar():
 | 
			
		||||
        """This is a longer docstring with so much information in there
 | 
			
		||||
        that it spans three lines.  In this case, the closing triple quote
 | 
			
		||||
        is on its own line.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
文档字符串应分成简短摘要(尽量一行)和详细介绍。如果必要的话,摘要与详细介绍之间空一行。
 | 
			
		||||
 | 
			
		||||
模块头部
 | 
			
		||||
~~~~~~~~
 | 
			
		||||
 | 
			
		||||
模块文件的头部包含有utf-8编码声明(如果模块中使用了非ASCII编码的字符,建议进行声明),以及标准的文档字符串。
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    # -*- coding: utf-8 -*-
 | 
			
		||||
    """
 | 
			
		||||
        package.module
 | 
			
		||||
        ~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
        A brief description goes here.
 | 
			
		||||
 | 
			
		||||
        :copyright: (c) YEAR by AUTHOR.
 | 
			
		||||
        :license: LICENSE_NAME, see LICENSE_FILE for more details.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
注释(comment)
 | 
			
		||||
~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
注释的规范与文档字符串编写规范类似。二者均以reStructuredText格式编写。
 | 
			
		||||
如果使用注释来编写类属性的文档,请在#符号后添加一个冒号“:”。
 | 
			
		||||
(有能力尽可能用英文, 否则请中文优雅注释)
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    class User(object):
 | 
			
		||||
        #: the name of the user as unicode string
 | 
			
		||||
        name = Column(String)
 | 
			
		||||
        #: the sha1 hash of the password + inline salt
 | 
			
		||||
        pw_hash = Column(String)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
快速安装
 | 
			
		||||
==========================
 | 
			
		||||
 | 
			
		||||
Jumpserver 封装了一个All in one Docker,可以快速启动。该镜像集成了所有需要的组件,可以使用外置db和redis
 | 
			
		||||
 | 
			
		||||
Tips: 不建议在生产中使用
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Docker 安装见: `Docker官方安装文档 <https://docs.docker.com/install/>`_
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
快速启动
 | 
			
		||||
```````````````
 | 
			
		||||
使用root命令行输入::
 | 
			
		||||
 | 
			
		||||
    $ docker run -p 8080:80 -p 2222:2222 jumpserver/jumpserver:0.5.0-beta2
 | 
			
		||||
 | 
			
		||||
访问
 | 
			
		||||
```````````````
 | 
			
		||||
 | 
			
		||||
浏览器访问: http://localhost:8080
 | 
			
		||||
 | 
			
		||||
ssh访问: ssh -p 2222 localhost
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
额外环境变量
 | 
			
		||||
```````````````
 | 
			
		||||
 | 
			
		||||
- DB_ENGINE = mysql
 | 
			
		||||
- DB_HOST = mysql_host
 | 
			
		||||
- DB_PORT = 3306
 | 
			
		||||
- DB_USER = xxx
 | 
			
		||||
- DB_PASSWORD = xxxx
 | 
			
		||||
- DB_NAME = jumpserver
 | 
			
		||||
 | 
			
		||||
- REDIS_HOST = ''
 | 
			
		||||
- REDIS_PORT = ''
 | 
			
		||||
- REDIS_PASSWORD = ''
 | 
			
		||||
 | 
			
		||||
 ::
 | 
			
		||||
 | 
			
		||||
   docker run -p 8080:80 -p 2222:2222 -e DB_ENGINE=mysql -e DB_HOST=192.168.1.1 -e DB_PORT=3306 -e DB_USER=root -e DB_PASSWORD=xxx -e DB_NAME=jumpserver  jumpserver/jumpserver:0.5.0-beta2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
仓库地址
 | 
			
		||||
```````````````
 | 
			
		||||
 | 
			
		||||
https://github.com/jumpserver/Dockerfile
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,294 @@
 | 
			
		|||
一步一步安装
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
环境
 | 
			
		||||
~~~~
 | 
			
		||||
 | 
			
		||||
-  系统: CentOS 7
 | 
			
		||||
-  IP: 192.168.244.144
 | 
			
		||||
-  关闭 selinux和防火墙
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    # CentOS 7
 | 
			
		||||
    $ setenforce 0  # 可以设置配置文件永久关闭
 | 
			
		||||
    $ systemctl stop iptables.service
 | 
			
		||||
    $ systemctl stop firewalld.service
 | 
			
		||||
 | 
			
		||||
    # CentOS6
 | 
			
		||||
    $ setenforce 0
 | 
			
		||||
    $ service iptables stop
 | 
			
		||||
 | 
			
		||||
一. 准备Python3和Python虚拟环境
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
**1.1 安装依赖包**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ yum -y install wget sqlite-devel xz gcc automake zlib-devel openssl-devel epel-release
 | 
			
		||||
 | 
			
		||||
**1.2 编译安装**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tar.xz
 | 
			
		||||
    $ tar xvf Python-3.6.1.tar.xz  && cd Python-3.6.1
 | 
			
		||||
    $ ./configure && make && make install
 | 
			
		||||
 | 
			
		||||
**1.3 建立python虚拟环境**
 | 
			
		||||
 | 
			
		||||
因为CentOS
 | 
			
		||||
6/7自带的是Python2,而Yum等工具依赖原来的Python,为了不扰乱原来的环境我们来使用Python虚拟环境
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt
 | 
			
		||||
    $ python3 -m venv py3
 | 
			
		||||
    $ source /opt/py3/bin/activate
 | 
			
		||||
 | 
			
		||||
    # 看到下面的提示符代表成功,以后运行jumpserver都要先运行以上source命令,以下所有命令均在该虚拟环境中运行
 | 
			
		||||
    (py3) [root@localhost py3]#
 | 
			
		||||
 | 
			
		||||
二. 安装Jumpserver 0.5.0
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
**2.1 下载或clone项目**
 | 
			
		||||
 | 
			
		||||
项目提交较多git clone时较大,你可以选择去github项目页面直接下载
 | 
			
		||||
zip包,我的网速好,我直接clone了
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt/
 | 
			
		||||
    $ git clone --depth=1 https://github.com/jumpserver/jumpserver.git && cd jumpserver && git checkout dev
 | 
			
		||||
 | 
			
		||||
**2.2 安装依赖rpm包**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt/jumpserver/requirements
 | 
			
		||||
    $ yum -y install $(cat rpm_requirements.txt)  # 如果没有任何报错请继续
 | 
			
		||||
 | 
			
		||||
**2.3 安装python库依赖**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ pip install -r requirements.txt  # 不要指定-i参数,因为镜像上可能没有最新的包,如果没有任何报错请继续
 | 
			
		||||
 | 
			
		||||
**2.4 安装Redis, jumpserver使用redis做cache和celery broker**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ yum -y install redis
 | 
			
		||||
    $ service redis start
 | 
			
		||||
 | 
			
		||||
**2.5 安装MySQL**
 | 
			
		||||
 | 
			
		||||
本教程使用mysql作为数据库,如果不使用mysql可以跳过相关mysql安装和配置
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    # centos7
 | 
			
		||||
    $ yum -y install mariadb mariadb-devel mariadb-server # centos7下安装的是mariadb
 | 
			
		||||
    $ service mariadb start
 | 
			
		||||
 | 
			
		||||
    # centos6
 | 
			
		||||
    $ yum -y install mysql mysql-devel mysql-server
 | 
			
		||||
    $ service mysqld start
 | 
			
		||||
 | 
			
		||||
**2.6 创建数据库 jumpserver并授权**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ mysql
 | 
			
		||||
    > create database jumpserver default charset 'utf8';
 | 
			
		||||
    > grant all on jumpserver.* to 'jumpserver'@'127.0.0.1' identified by 'somepassword';
 | 
			
		||||
 | 
			
		||||
**2.7 修改jumpserver配置文件**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt/jumpserver
 | 
			
		||||
    $ cp config_example.py config.py
 | 
			
		||||
    $ vi config.py  # 我们计划修改 DevelopmentConfig中的配置,因为默认jumpserver是使用该配置,它继承自Config
 | 
			
		||||
 | 
			
		||||
**注意: 配置文件是python格式,不要用tab,而要用空格** **注意:
 | 
			
		||||
配置文件是python格式,不要用tab,而要用空格** **注意:
 | 
			
		||||
配置文件是python格式,不要用tab,而要用空格**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    class DevelopmentConfig(Config):
 | 
			
		||||
        DEBUG = True
 | 
			
		||||
        DB_ENGINE = 'mysql'
 | 
			
		||||
        DB_HOST = '127.0.0.1'
 | 
			
		||||
        DB_PORT = 3306
 | 
			
		||||
        DB_USER = 'jumpserver'
 | 
			
		||||
        DB_PASSWORD = 'somepassword'
 | 
			
		||||
        DB_NAME = 'jumpserver'
 | 
			
		||||
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
    config = DevelopmentConfig()  # 确保使用的是刚才设置的配置文件
 | 
			
		||||
 | 
			
		||||
**2.8 生成数据库表结构和初始化数据**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt/jumpserver/utils
 | 
			
		||||
    $ bash make_migrations.sh
 | 
			
		||||
 | 
			
		||||
**2.9 运行Jumpserver**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt/jumpserver
 | 
			
		||||
    $ python run_server.py all
 | 
			
		||||
 | 
			
		||||
运行不报错,请浏览器访问 http://192.168.244.144:8080/
 | 
			
		||||
(这里只是jumpserver, 没有web terminal,所以访问web terminal会报错)
 | 
			
		||||
 | 
			
		||||
账号:admin 密码: admin
 | 
			
		||||
 | 
			
		||||
三. 安装 SSH Server和Web Socket Server: Coco
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
**3.1 下载clone项目**
 | 
			
		||||
 | 
			
		||||
新开一个终端,连接测试机,别忘了 source /opt/py3/bin/activate
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt
 | 
			
		||||
    $ git clone https://github.com/jumpserver/coco.git && cd coco && git checkout dev
 | 
			
		||||
 | 
			
		||||
**3.2 安装依赖**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt/coco/requirements $ yum -y install $(cat rpm_requirements.txt) $ pip install requirements.txt
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**3.2 安装依赖**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt/coco/requirements
 | 
			
		||||
    $ yum -y  install $(cat rpm_requirements.txt)
 | 
			
		||||
    $ pip install -r requirements.txt
 | 
			
		||||
 | 
			
		||||
**3.3 查看配置文件并运行**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ cd /opt/coco
 | 
			
		||||
    $ cp conf_example.py conf.py
 | 
			
		||||
    $ python run_server.py
 | 
			
		||||
 | 
			
		||||
这时需要去
 | 
			
		||||
jumpserver管理后台-终端-终端(http://192.168.244.144:8080/terminal/terminal/)接受coco的注册
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    Coco version 0.4.0, more see https://www.jumpserver.org
 | 
			
		||||
    Starting ssh server at 0.0.0.0:2222
 | 
			
		||||
    Quit the server with CONTROL-C.
 | 
			
		||||
 | 
			
		||||
**3.4 测试连接**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ ssh -p2222 admin@192.168.244.144
 | 
			
		||||
    密码: admin
 | 
			
		||||
 | 
			
		||||
    如果是用在windows下,Xshell terminal登录语法如下
 | 
			
		||||
    $ssh admin@192.168.244.144 2222
 | 
			
		||||
    密码: admin
 | 
			
		||||
    如果能登陆代表部署成功
 | 
			
		||||
 | 
			
		||||
四. 安装 Web Terminal 前端: Luna
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
Luna已改为纯前端,需要nginx来运行访问
 | 
			
		||||
 | 
			
		||||
下载 release包,直接解压,不需要编译
 | 
			
		||||
 | 
			
		||||
访问 https://github.com/jumpserver/luna/releases,下载对应release包
 | 
			
		||||
 | 
			
		||||
4.1 解压luna
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ pwd
 | 
			
		||||
    /opt/
 | 
			
		||||
 | 
			
		||||
    $ tar xvf luna.tar.gz
 | 
			
		||||
    $ ls /opt/luna
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
五. 安装Windows支持组件
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
使用docker启动 guacamole
 | 
			
		||||
 | 
			
		||||
.. code:: shell
 | 
			
		||||
 | 
			
		||||
    docker run \
 | 
			
		||||
      -p 8080:8080 \
 | 
			
		||||
      -e JUMPSERVER_SERVER=http://<jumpserver>:8080 \
 | 
			
		||||
      jumpserver/guacamole
 | 
			
		||||
 | 
			
		||||
这里所需要注意的是guacamole暴露出来的端口是8080,若与jumpserver部署在同一主机上自定义一下。
 | 
			
		||||
 | 
			
		||||
修改JUMPSERVER_SERVER的配置,填上jumpserver的内网地址
 | 
			
		||||
 | 
			
		||||
六. 配置 nginx 整合各组件
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
6.1 安装nginx 根据喜好选择安装方式和版本
 | 
			
		||||
 | 
			
		||||
6.2 配置文件
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    server {
 | 
			
		||||
        listen 80;
 | 
			
		||||
 | 
			
		||||
        proxy_set_header X-Real-IP $remote_addr;
 | 
			
		||||
        proxy_set_header Host $host;
 | 
			
		||||
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
			
		||||
 | 
			
		||||
        location /luna/ {
 | 
			
		||||
            try_files $uri / /index.html;
 | 
			
		||||
            alias /opt/luna/;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        location /media/ {
 | 
			
		||||
            add_header Content-Encoding gzip;
 | 
			
		||||
            root /opt/jumpserver/data/;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        location /static/ {
 | 
			
		||||
            root /opt/jumpserver/data/;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        location /socket.io/ {
 | 
			
		||||
            proxy_pass       http://localhost:5000/socket.io/;
 | 
			
		||||
            proxy_http_version 1.1;
 | 
			
		||||
            proxy_set_header Upgrade $http_upgrade;
 | 
			
		||||
            proxy_set_header Connection "upgrade";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        location /guacamole/ {
 | 
			
		||||
            proxy_pass http://<guacamole>:8080/;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        location / {
 | 
			
		||||
            proxy_pass http://localhost:8080;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
6.3 运行 nginx
 | 
			
		||||
 | 
			
		||||
6.4 访问 http://192.168.244.144
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
升级
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
1. 升级 jumpserver
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ git pull && pip install -r requirements/requirements.txt && cd utils && sh make_migrations.sh
 | 
			
		||||
 | 
			
		||||
2. 升级 coco
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    $ git pull && cd requirements && pip install -r requirements.txt   # 不要指定 -i参数
 | 
			
		||||
 | 
			
		||||
3. 升级 luna
 | 
			
		||||
 | 
			
		||||
重新下载release包
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
个人资产
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
这里介绍用户个人资产相关的功能。
 | 
			
		||||
 | 
			
		||||
.. contents:: Topics
 | 
			
		||||
 | 
			
		||||
.. _view_personal_assets:
 | 
			
		||||
 | 
			
		||||
查看个人资产
 | 
			
		||||
````````````
 | 
			
		||||
 | 
			
		||||
登录个人用户,默认展示个人资产列表。点击主机名,查看资产的详细信息。
 | 
			
		||||
 | 
			
		||||
.. _host_login:
 | 
			
		||||
 | 
			
		||||
主机登录
 | 
			
		||||
`````````
 | 
			
		||||
 | 
			
		||||
点解页面左侧的"Web终端",进入主机登录页,然后点击页面右侧的主机IP地址,连接主机,页面右侧会展示当前连接的终端信息。
 | 
			
		||||
 | 
			
		||||
.. _host_logout:
 | 
			
		||||
 | 
			
		||||
主机登出
 | 
			
		||||
`````````
 | 
			
		||||
 | 
			
		||||
在主机登录页面,选择左上角的“服务器”按钮,出现两个选项,一个“断开链接“按钮,断开当前连接的主机;另一个”断开所有链接“,断开当前所有连接的主机。
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
用户使用文档
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
这部分给您介绍Jumpserver的用户管理模块的使用方法。
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 1
 | 
			
		||||
 | 
			
		||||
   user_asset
 | 
			
		||||
   user_info
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
个人信息
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
这里介绍个人信息相关的功能。
 | 
			
		||||
 | 
			
		||||
.. contents:: Topics
 | 
			
		||||
 | 
			
		||||
.. _view_personal_info:
 | 
			
		||||
 | 
			
		||||
查看个人信息
 | 
			
		||||
````````````
 | 
			
		||||
 | 
			
		||||
点击页面左侧的“个人信息”,查看用户的个人信息、SSH密钥。
 | 
			
		||||
 | 
			
		||||
.. _modify_personal_info:
 | 
			
		||||
 | 
			
		||||
修改个人信息
 | 
			
		||||
````````````
 | 
			
		||||
 | 
			
		||||
在个人信息页,点击页面右上角的“设置”按钮,进入个人信息修改页面,填写个人信息,点击“提交”按钮,完成个人信息修改。
 | 
			
		||||
 | 
			
		||||
.. _update_password:
 | 
			
		||||
 | 
			
		||||
更新密码
 | 
			
		||||
`````````
 | 
			
		||||
 | 
			
		||||
在个人信息页,点击页面右上角的“重置密码“按钮,进入密码更新页面,填写原来密码、新密码等信息,点击“提交”按钮,完成密码更新。
 | 
			
		||||
 | 
			
		||||
.. _update_ssh_key:
 | 
			
		||||
 | 
			
		||||
密钥更新
 | 
			
		||||
`````````
 | 
			
		||||
 | 
			
		||||
在个人信息页,点击页面左上角的“重置SSH密钥“按钮,进入密钥更新页面,填写SSH公钥,点击“提交”按钮,完成密钥更新。
 | 
			
		||||
| 
						 | 
				
			
			@ -56,8 +56,8 @@ uritemplate==3.0.0
 | 
			
		|||
urllib3==1.22
 | 
			
		||||
vine==1.1.4
 | 
			
		||||
gunicorn==19.7.1
 | 
			
		||||
https://github.com/celery/django-celery-beat/zipball/master#egg=django-celery-beat
 | 
			
		||||
#django_celery_beat==1.1.0
 | 
			
		||||
#https://github.com/celery/django-celery-beat/zipball/master#egg=django-celery-beat
 | 
			
		||||
django_celery_beat==1.1.1
 | 
			
		||||
ephem==3.7.6.0
 | 
			
		||||
python-gssapi==0.6.4
 | 
			
		||||
jms-es-sdk
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue