mirror of https://github.com/jumpserver/jumpserver
Bugfix (#3232)
* [Update] 拆分user permission * [Update] 修改 ops command * [Update] 修改setting无法生效的问题 * [Update] 修复授权详情-授权资产或节点添加资产失败 * [Bugfix] 修复组织管理员运行命令时的问题 * [Update] 修复命令执行左侧树点击问题pull/3236/head
parent
4931737164
commit
1457281b73
|
@ -92,7 +92,7 @@ function syncSelectedAssetsToModalTable(assetModalTable) {
|
|||
}
|
||||
|
||||
// input assets有,table assets没选,则选中(click)
|
||||
if (inputAssets !== null) {
|
||||
if (inputAssets) {
|
||||
assetModalTable.selected = inputAssets;
|
||||
$.each(inputAssets, function (index, assetId) {
|
||||
var dom = document.getElementById(assetId);
|
||||
|
|
|
@ -2,16 +2,6 @@ from __future__ import unicode_literals
|
|||
|
||||
import sys
|
||||
from django.apps import AppConfig
|
||||
from django.dispatch import receiver
|
||||
from django.db.backends.signals import connection_created
|
||||
|
||||
|
||||
@receiver(connection_created)
|
||||
def on_db_connection_ready(sender, **kwargs):
|
||||
from .signals import django_ready
|
||||
if 'migrate' not in sys.argv:
|
||||
django_ready.send(CommonConfig)
|
||||
connection_created.disconnect(on_db_connection_ready)
|
||||
|
||||
|
||||
class CommonConfig(AppConfig):
|
||||
|
@ -19,3 +9,6 @@ class CommonConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
from . import signals_handlers
|
||||
from .signals import django_ready
|
||||
if 'migrate' not in sys.argv:
|
||||
django_ready.send(CommonConfig)
|
||||
|
|
|
@ -30,10 +30,10 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet):
|
|||
util = AssetPermissionUtilV2(self.request.user)
|
||||
util.filter_permissions(system_users=system_user.id)
|
||||
permed_assets = util.get_assets().filter(id__in=[a.id for a in assets])
|
||||
unpermed_assets = set(assets) - set(permed_assets)
|
||||
if unpermed_assets:
|
||||
invalid_assets = set(assets) - set(permed_assets)
|
||||
if invalid_assets:
|
||||
msg = _("Not has host {} permission").format(
|
||||
[str(a.id) for a in unpermed_assets]
|
||||
[str(a.id) for a in invalid_assets]
|
||||
)
|
||||
raise ValidationError({"hosts": msg})
|
||||
|
||||
|
|
|
@ -4,347 +4,365 @@
|
|||
{% load bootstrap3 %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
|
||||
<link href="{% static 'css/plugins/codemirror/codemirror.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/codemirror/ambiance.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.exhide.min.js' %}"></script>
|
||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}"/>
|
||||
<link href="{% static 'css/plugins/codemirror/codemirror.css' %}"
|
||||
rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/codemirror/ambiance.css' %}"
|
||||
rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}"
|
||||
rel="stylesheet">
|
||||
<script type="text/javascript"
|
||||
src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||
<script type="text/javascript"
|
||||
src="{% static 'js/plugins/ztree/jquery.ztree.exhide.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/xterm/addons/fit/fit.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/codemirror/codemirror.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/codemirror/mode/shell/shell.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
<style type="text/css">
|
||||
<style type="text/css">
|
||||
.xterm .xterm-screen canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--single {
|
||||
height: 34px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
||||
<div class="file-manager ">
|
||||
<div id="assetTree" class="ztree"></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
||||
<div class="tree-toggle">
|
||||
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
|
||||
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mail-box-header" style="padding-top: 5px;">
|
||||
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" onsubmit="return execute()">
|
||||
<div class="form-group">
|
||||
<div id="term" style="height: 100%;width: 100%"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-10">
|
||||
<div class="input-group" style="height: 100%; width: 100%">
|
||||
<textarea class="form-control" id="command-text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="select2 form-control" id="system-users-select">
|
||||
{% for s in system_users %}
|
||||
{% if s.protocol == 'ssh' and s.login_mode == 'auto' %}
|
||||
<option value="{{ s.id }}">{{ s }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="button" class="btn btn-primary btn-execute" style="margin-top: 30px; width: 100%">{% trans 'Go' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper wrapper-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-content mailbox-content"
|
||||
style="padding-top: 0;padding-left: 1px">
|
||||
<div class="file-manager ">
|
||||
<div id="assetTree" class="ztree">
|
||||
{% trans 'Loading' %} ..
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
||||
<div class="tree-toggle">
|
||||
<div class="btn btn-sm btn-primary tree-toggle-btn"
|
||||
onclick="toggle()">
|
||||
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mail-box-header" style="padding-top: 5px;">
|
||||
<form enctype="multipart/form-data" method="post"
|
||||
class="form-horizontal" action=""
|
||||
onsubmit="return execute()">
|
||||
<div class="form-group">
|
||||
<div id="term"
|
||||
style="height: 100%;width: 100%"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-10">
|
||||
<div class="input-group"
|
||||
style="height: 100%; width: 100%">
|
||||
<textarea class="form-control"
|
||||
id="command-text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2">
|
||||
<select class="select2 form-control"
|
||||
id="system-users-select">
|
||||
{% for s in system_users %}
|
||||
{% if s.protocol == 'ssh' and s.login_mode == 'auto' %}
|
||||
<option value="{{ s.id }}">{{ s }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="button"
|
||||
class="btn btn-primary btn-execute"
|
||||
style="margin-top: 30px; width: 100%">{% trans 'Go' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var zTree, show = 0;
|
||||
var systemUserId = null;
|
||||
var url = null;
|
||||
var treeUrl = "{% url 'api-perms:my-nodes-children-with-assets-as-tree' %}?cache_policy=1";
|
||||
<script>
|
||||
var zTree, show = 0;
|
||||
var systemUserId = null;
|
||||
var url = null;
|
||||
var treeUrl = "{% url 'api-perms:my-nodes-with-assets-as-tree' %}?cache_policy=1";
|
||||
|
||||
function proposeGeometry(term) {
|
||||
if (!term.element.parentElement) {
|
||||
return null;
|
||||
}
|
||||
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
|
||||
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||
var elementStyle = window.getComputedStyle(term.element);
|
||||
var elementPadding = {
|
||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||
};
|
||||
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||
var elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||
var availableHeight = parentElementHeight - elementPaddingVer;
|
||||
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
|
||||
var geometry = {
|
||||
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
|
||||
};
|
||||
return geometry;
|
||||
}
|
||||
|
||||
function fit(term) {
|
||||
var geometry = proposeGeometry(term);
|
||||
if (geometry) {
|
||||
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
|
||||
term._core.renderer.clear();
|
||||
term.resize(geometry.cols, geometry.rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initTree() {
|
||||
if (systemUserId) {
|
||||
url = treeUrl + '&system_user=' + systemUserId
|
||||
}
|
||||
else{
|
||||
url = treeUrl
|
||||
}
|
||||
var setting = {
|
||||
check: {
|
||||
enable: true
|
||||
},
|
||||
view: {
|
||||
dblClickExpand: false,
|
||||
showLine: true
|
||||
},
|
||||
data: {
|
||||
simpleData: {
|
||||
enable: true
|
||||
function proposeGeometry(term) {
|
||||
if (!term.element.parentElement) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async: {
|
||||
enable: true,
|
||||
url: url,
|
||||
autoParam: ["id=key", "name=n", "level=lv"],
|
||||
type: 'get'
|
||||
},
|
||||
edit: {
|
||||
enable: true,
|
||||
showRemoveBtn: false,
|
||||
showRenameBtn: false,
|
||||
drag: {
|
||||
isCopy: true,
|
||||
isMove: true
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
onCheck: onCheck
|
||||
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
|
||||
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||
var elementStyle = window.getComputedStyle(term.element);
|
||||
var elementPadding = {
|
||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||
};
|
||||
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||
var elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||
var availableHeight = parentElementHeight - elementPaddingVer;
|
||||
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
|
||||
var geometry = {
|
||||
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
|
||||
};
|
||||
return geometry;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$.get(url, function(data, status){
|
||||
$.fn.zTree.init($("#assetTree"), setting, data);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
rootNodeAddDom(zTree, function () {
|
||||
treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
|
||||
initTree();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getSelectedAssetsNode() {
|
||||
var nodes = zTree.getCheckedNodes(true);
|
||||
var assetsNodeId = [];
|
||||
var assetsNode = [];
|
||||
nodes.forEach(function (node) {
|
||||
if (node.meta.type === 'asset' && !node.isHidden) {
|
||||
var protocols = node.meta.asset.protocols;
|
||||
protocols.forEach(function (val) {
|
||||
if (assetsNodeId.indexOf(node.id) === -1 && val.indexOf("ssh") > -1) {
|
||||
assetsNodeId.push(node.id);
|
||||
assetsNode.push(node)
|
||||
function fit(term) {
|
||||
var geometry = proposeGeometry(term);
|
||||
if (geometry) {
|
||||
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
|
||||
term._core.renderer.clear();
|
||||
term.resize(geometry.cols, geometry.rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initTree() {
|
||||
if (systemUserId) {
|
||||
url = treeUrl + '&system_user=' + systemUserId
|
||||
} else {
|
||||
url = treeUrl
|
||||
}
|
||||
var setting = {
|
||||
check: {
|
||||
enable: true
|
||||
},
|
||||
view: {
|
||||
dblClickExpand: false,
|
||||
showLine: true
|
||||
},
|
||||
data: {
|
||||
simpleData: {
|
||||
enable: true
|
||||
}
|
||||
},
|
||||
async: {
|
||||
enable: true,
|
||||
url: url,
|
||||
autoParam: ["id=key", "name=n", "level=lv"],
|
||||
type: 'get'
|
||||
},
|
||||
edit: {
|
||||
enable: true,
|
||||
showRemoveBtn: false,
|
||||
showRenameBtn: false,
|
||||
drag: {
|
||||
isCopy: true,
|
||||
isMove: true
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
onCheck: onCheck
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$.get(url, function (data, status) {
|
||||
$.fn.zTree.init($("#assetTree"), setting, data);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
rootNodeAddDom(zTree, function () {
|
||||
treeUrl = treeUrl.replace('cache_policy=1', 'cache_policy=2');
|
||||
initTree();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
return assetsNode;
|
||||
}
|
||||
|
||||
function onCheck(e, treeId, treeNode) {
|
||||
var nodes = getSelectedAssetsNode();
|
||||
var nodes_names = nodes.map(function (node) {
|
||||
return node.name;
|
||||
});
|
||||
var message = "{% trans 'Selected assets' %}" + ': ';
|
||||
message += nodes_names.join(", ");
|
||||
message += "\r\n";
|
||||
message += "{% trans 'In total' %}" + ': ' + nodes_names.length + "个\r\n";
|
||||
term.clear();
|
||||
term.write(message)
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (show === 0) {
|
||||
$("#split-left").hide(500, function () {
|
||||
$("#split-right").attr("class", "col-lg-12");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
|
||||
show = 1;
|
||||
});
|
||||
} else {
|
||||
$("#split-right").attr("class", "col-lg-9");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
|
||||
$("#split-left").show(500);
|
||||
show = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var term = null;
|
||||
|
||||
function initResultTerminal() {
|
||||
term = new Terminal({
|
||||
cursorBlink: false,
|
||||
screenKeys: false,
|
||||
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
|
||||
fontSize: 14,
|
||||
lineHeight: 1,
|
||||
rightClickSelectsWord: true,
|
||||
disableStdin: true,
|
||||
theme: {
|
||||
background: '#1f1b1b'
|
||||
}
|
||||
});
|
||||
term.open(document.getElementById('term'));
|
||||
var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
|
||||
fit(term);
|
||||
term.write(msg)
|
||||
}
|
||||
|
||||
function wrapperError(msg) {
|
||||
return '\033[31m' + msg + '\033[0m' + '\r\n';
|
||||
}
|
||||
|
||||
function execute() {
|
||||
if (!term) {
|
||||
initResultTerminal()
|
||||
}
|
||||
var size = 'rows=' + term.rows + '&cols=' + term.cols;
|
||||
var url = '{% url "api-ops:command-execution-list" %}?' + size;
|
||||
var run_as = systemUserId;
|
||||
var command = editor.getValue();
|
||||
var hosts = getSelectedAssetsNode().map(function (node) {
|
||||
return node.id;
|
||||
});
|
||||
if (hosts.length === 0) {
|
||||
term.write(wrapperError("{% trans 'Unselected assets' %}"));
|
||||
return
|
||||
}
|
||||
if (!command) {
|
||||
term.write(wrapperError("{% trans 'No input command' %}"));
|
||||
return
|
||||
}
|
||||
if (!run_as) {
|
||||
term.write(wrapperError("{% trans 'No system user was selected' %}"));
|
||||
return
|
||||
}
|
||||
var data = {
|
||||
hosts: hosts,
|
||||
run_as: run_as,
|
||||
command: command
|
||||
};
|
||||
var mark = '';
|
||||
var log_url = null;
|
||||
var end = false;
|
||||
var error = false;
|
||||
var int = null;
|
||||
var interval = 200;
|
||||
|
||||
function writeExecutionOutput() {
|
||||
if (!end) {
|
||||
$.ajax({
|
||||
url: log_url + '?mark=' + mark,
|
||||
method: "GET",
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function(data, textStatue, jqXHR) {
|
||||
if (jqXHR.status === 203) {
|
||||
error = true;
|
||||
term.write('.');
|
||||
interval = 500;
|
||||
function getSelectedAssetsNode() {
|
||||
var nodes = zTree.getCheckedNodes(true);
|
||||
var assetsNodeId = [];
|
||||
var assetsNode = [];
|
||||
nodes.forEach(function (node) {
|
||||
if (node.meta.type === 'asset' && !node.isHidden) {
|
||||
var protocols = node.meta.asset.protocols;
|
||||
protocols.forEach(function (val) {
|
||||
if (assetsNodeId.indexOf(node.id) === -1 && val.indexOf("ssh") > -1) {
|
||||
assetsNodeId.push(node.id);
|
||||
assetsNode.push(node)
|
||||
}
|
||||
});
|
||||
}
|
||||
if (jqXHR.status === 200){
|
||||
term.write(data.data);
|
||||
mark = data.mark;
|
||||
if (data.end){
|
||||
end = true;
|
||||
window.clearInterval(int)
|
||||
}
|
||||
});
|
||||
return assetsNode;
|
||||
}
|
||||
|
||||
function onCheck(e, treeId, treeNode) {
|
||||
var nodes = getSelectedAssetsNode();
|
||||
var nodes_names = nodes.map(function (node) {
|
||||
return node.name;
|
||||
});
|
||||
var message = "{% trans 'Selected assets' %}" + ': ';
|
||||
message += nodes_names.join(", ");
|
||||
message += "\r\n";
|
||||
message += "{% trans 'In total' %}" + ': ' + nodes_names.length + "个\r\n";
|
||||
term.clear();
|
||||
term.write(message)
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (show === 0) {
|
||||
$("#split-left").hide(500, function () {
|
||||
$("#split-right").attr("class", "col-lg-12");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
|
||||
show = 1;
|
||||
});
|
||||
} else {
|
||||
$("#split-right").attr("class", "col-lg-9");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
|
||||
$("#split-left").show(500);
|
||||
show = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var term = null;
|
||||
|
||||
function initResultTerminal() {
|
||||
term = new Terminal({
|
||||
cursorBlink: false,
|
||||
screenKeys: false,
|
||||
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
|
||||
fontSize: 14,
|
||||
lineHeight: 1,
|
||||
rightClickSelectsWord: true,
|
||||
disableStdin: true,
|
||||
theme: {
|
||||
background: '#1f1b1b'
|
||||
}
|
||||
})
|
||||
});
|
||||
term.open(document.getElementById('term'));
|
||||
var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
|
||||
fit(term);
|
||||
term.write(msg)
|
||||
}
|
||||
}
|
||||
|
||||
requestApi({
|
||||
url: url,
|
||||
body: JSON.stringify(data),
|
||||
method: 'POST',
|
||||
flash_message: false,
|
||||
success: function (resp) {
|
||||
var msg = "{% trans 'Pending' %}";
|
||||
term.write(msg + "...\r\n");
|
||||
log_url = resp.log_url;
|
||||
int = setInterval(function () {
|
||||
writeExecutionOutput()
|
||||
}, interval);
|
||||
function wrapperError(msg) {
|
||||
return '\033[31m' + msg + '\033[0m' + '\r\n';
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
var editor;
|
||||
$(document).ready(function(){
|
||||
systemUserId = $('#system-users-select').val();
|
||||
$(".select2").select2({
|
||||
dropdownAutoWidth : true,
|
||||
}).on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
systemUserId = data.id;
|
||||
initTree();
|
||||
});
|
||||
editor = CodeMirror.fromTextArea(document.getElementById("command-text"), {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
mode: "shell"
|
||||
});
|
||||
editor.setSize(600, 100);
|
||||
var charWidth = editor.defaultCharWidth(), basePadding = 4;
|
||||
editor.on("renderLine", function(cm, line, elt) {
|
||||
var off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth;
|
||||
elt.style.textIndent = "-" + off + "px";
|
||||
elt.style.paddingLeft = (basePadding + off) + "px";
|
||||
});
|
||||
editor.refresh();
|
||||
initTree();
|
||||
initResultTerminal();
|
||||
}).on('click', '.btn-execute', function () {
|
||||
execute()
|
||||
})
|
||||
</script>
|
||||
function execute() {
|
||||
if (!term) {
|
||||
initResultTerminal()
|
||||
}
|
||||
var size = 'rows=' + term.rows + '&cols=' + term.cols;
|
||||
var url = '{% url "api-ops:command-execution-list" %}?' + size;
|
||||
var run_as = systemUserId;
|
||||
var command = editor.getValue();
|
||||
var hosts = getSelectedAssetsNode().map(function (node) {
|
||||
return node.id;
|
||||
});
|
||||
if (hosts.length === 0) {
|
||||
term.write(wrapperError("{% trans 'Unselected assets' %}"));
|
||||
return
|
||||
}
|
||||
if (!command) {
|
||||
term.write(wrapperError("{% trans 'No input command' %}"));
|
||||
return
|
||||
}
|
||||
if (!run_as) {
|
||||
term.write(wrapperError("{% trans 'No system user was selected' %}"));
|
||||
return
|
||||
}
|
||||
var data = {
|
||||
hosts: hosts,
|
||||
run_as: run_as,
|
||||
command: command
|
||||
};
|
||||
var mark = '';
|
||||
var log_url = null;
|
||||
var end = false;
|
||||
var error = false;
|
||||
var int = null;
|
||||
var interval = 200;
|
||||
|
||||
function writeExecutionOutput() {
|
||||
if (!end) {
|
||||
$.ajax({
|
||||
url: log_url + '?mark=' + mark,
|
||||
method: "GET",
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function (data, textStatue, jqXHR) {
|
||||
if (jqXHR.status === 203) {
|
||||
error = true;
|
||||
term.write('.');
|
||||
interval = 500;
|
||||
}
|
||||
if (jqXHR.status === 200) {
|
||||
term.write(data.data);
|
||||
mark = data.mark;
|
||||
if (data.end) {
|
||||
end = true;
|
||||
window.clearInterval(int)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
requestApi({
|
||||
url: url,
|
||||
body: JSON.stringify(data),
|
||||
method: 'POST',
|
||||
flash_message: false,
|
||||
success: function (resp) {
|
||||
var msg = "{% trans 'Pending' %}";
|
||||
term.write(msg + "...\r\n");
|
||||
log_url = resp.log_url;
|
||||
int = setInterval(function () {
|
||||
writeExecutionOutput()
|
||||
}, interval);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
var editor;
|
||||
$(document).ready(function () {
|
||||
systemUserId = $('#system-users-select').val();
|
||||
$(".select2").select2({
|
||||
dropdownAutoWidth: true,
|
||||
}).on('select2:select', function (evt) {
|
||||
var data = evt.params.data;
|
||||
systemUserId = data.id;
|
||||
initTree();
|
||||
});
|
||||
editor = CodeMirror.fromTextArea(document.getElementById("command-text"), {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
mode: "shell"
|
||||
});
|
||||
editor.setSize(600, 100);
|
||||
var charWidth = editor.defaultCharWidth(), basePadding = 4;
|
||||
editor.on("renderLine", function (cm, line, elt) {
|
||||
var off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth;
|
||||
elt.style.textIndent = "-" + off + "px";
|
||||
elt.style.paddingLeft = (basePadding + off) + "px";
|
||||
});
|
||||
editor.refresh();
|
||||
initTree();
|
||||
initResultTerminal();
|
||||
}).on('click', '.btn-execute', function () {
|
||||
execute()
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -9,6 +9,7 @@ from common.permissions import (
|
|||
PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor
|
||||
)
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from ..models import CommandExecution
|
||||
from ..forms import CommandExecutionForm
|
||||
|
||||
|
@ -67,8 +68,9 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
|
|||
def get_user_system_users(self):
|
||||
from perms.utils import AssetPermissionUtilV2
|
||||
user = self.request.user
|
||||
util = AssetPermissionUtilV2(user)
|
||||
system_users = [s for s in util.get_system_users() if s.protocol == 'ssh']
|
||||
with tmp_to_root_org():
|
||||
util = AssetPermissionUtilV2(user)
|
||||
system_users = [s for s in util.get_system_users() if s.protocol == 'ssh']
|
||||
return system_users
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
|
@ -1,29 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
from hashlib import md5
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from rest_framework.views import Response
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.http import condition
|
||||
|
||||
from rest_framework.generics import get_object_or_404
|
||||
from django.utils.translation import ugettext as _
|
||||
from assets.utils import LabelFilterMixin
|
||||
from common.permissions import IsValidUser, IsOrgAdminOrAppUser, IsOrgAdmin
|
||||
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
|
||||
from common.utils import get_logger
|
||||
from orgs.utils import set_to_root_org
|
||||
from ..hands import User, Asset, Node, SystemUser
|
||||
from ..hands import User, Asset, SystemUser
|
||||
from .. import serializers
|
||||
from .. import const
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
__all__ = [
|
||||
'UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin',
|
||||
'UserPermissionMixin',
|
||||
]
|
||||
|
||||
|
@ -54,147 +45,6 @@ class UserPermissionMixin:
|
|||
return super().get_permissions()
|
||||
|
||||
|
||||
# def get_etag(request, *args, **kwargs):
|
||||
# cache_policy = request.GET.get("cache_policy")
|
||||
# if cache_policy != '1':
|
||||
# return None
|
||||
# if not UserPermissionCacheMixin.CACHE_ENABLE:
|
||||
# return None
|
||||
# view = request.parser_context.get("view")
|
||||
# if not view:
|
||||
# return None
|
||||
# etag = view.get_meta_cache_id()
|
||||
# return etag
|
||||
|
||||
|
||||
class UserPermissionCacheMixin:
|
||||
pass
|
||||
# cache_policy = '0'
|
||||
# RESP_CACHE_KEY = '_PERMISSION_RESPONSE_CACHE_V2_{}'
|
||||
# CACHE_ENABLE = settings.ASSETS_PERM_CACHE_ENABLE
|
||||
# CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
||||
# _object = None
|
||||
#
|
||||
# def get_object(self):
|
||||
# return None
|
||||
#
|
||||
# # 内部使用可控制缓存
|
||||
# def _get_object(self):
|
||||
# if not self._object:
|
||||
# self._object = self.get_object()
|
||||
# return self._object
|
||||
#
|
||||
# def get_object_id(self):
|
||||
# obj = self._get_object()
|
||||
# if obj:
|
||||
# return str(obj.id)
|
||||
# return None
|
||||
#
|
||||
# def get_request_md5(self):
|
||||
# path = self.request.path
|
||||
# query = {k: v for k, v in self.request.GET.items()}
|
||||
# query.pop("_", None)
|
||||
# query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
|
||||
# full_path = "{}?{}".format(path, query)
|
||||
# return md5(full_path.encode()).hexdigest()
|
||||
#
|
||||
# def get_meta_cache_id(self):
|
||||
# obj = self._get_object()
|
||||
# util = AssetPermissionUtil(obj, cache_policy=self.cache_policy)
|
||||
# meta_cache_id = util.cache_meta.get('id')
|
||||
# return meta_cache_id
|
||||
#
|
||||
# def get_response_cache_id(self):
|
||||
# obj_id = self.get_object_id()
|
||||
# request_md5 = self.get_request_md5()
|
||||
# meta_cache_id = self.get_meta_cache_id()
|
||||
# resp_cache_id = '{}_{}_{}'.format(obj_id, request_md5, meta_cache_id)
|
||||
# return resp_cache_id
|
||||
#
|
||||
# def get_response_from_cache(self):
|
||||
# # 没有数据缓冲
|
||||
# meta_cache_id = self.get_meta_cache_id()
|
||||
# if not meta_cache_id:
|
||||
# logger.debug("Not get meta id: {}".format(meta_cache_id))
|
||||
# return None
|
||||
# # 从响应缓冲里获取响应
|
||||
# key = self.get_response_key()
|
||||
# data = cache.get(key)
|
||||
# if not data:
|
||||
# logger.debug("Not get response from cache: {}".format(key))
|
||||
# return None
|
||||
# logger.debug("Get user permission from cache: {}".format(self.get_object()))
|
||||
# response = Response(data)
|
||||
# return response
|
||||
#
|
||||
# def expire_response_cache(self):
|
||||
# obj_id = self.get_object_id()
|
||||
# expire_cache_id = '{}_{}'.format(obj_id, '*')
|
||||
# key = self.RESP_CACHE_KEY.format(expire_cache_id)
|
||||
# cache.delete_pattern(key)
|
||||
#
|
||||
# def get_response_key(self):
|
||||
# resp_cache_id = self.get_response_cache_id()
|
||||
# key = self.RESP_CACHE_KEY.format(resp_cache_id)
|
||||
# return key
|
||||
#
|
||||
# def set_response_to_cache(self, response):
|
||||
# key = self.get_response_key()
|
||||
# cache.set(key, response.data, self.CACHE_TIME)
|
||||
# logger.debug("Set response to cache: {}".format(key))
|
||||
#
|
||||
# @method_decorator(condition(etag_func=get_etag))
|
||||
# def get(self, request, *args, **kwargs):
|
||||
# if not self.CACHE_ENABLE:
|
||||
# self.cache_policy = '0'
|
||||
# else:
|
||||
# self.cache_policy = request.GET.get('cache_policy', '0')
|
||||
#
|
||||
# obj = self._get_object()
|
||||
# if obj is None:
|
||||
# logger.debug("Not get response from cache: obj is none")
|
||||
# return super().get(request, *args, **kwargs)
|
||||
#
|
||||
# if AssetPermissionUtil.is_not_using_cache(self.cache_policy):
|
||||
# logger.debug("Not get resp from cache: {}".format(self.cache_policy))
|
||||
# return super().get(request, *args, **kwargs)
|
||||
# elif AssetPermissionUtil.is_refresh_cache(self.cache_policy):
|
||||
# logger.debug("Not get resp from cache: {}".format(self.cache_policy))
|
||||
# self.expire_response_cache()
|
||||
#
|
||||
# logger.debug("Try get response from cache")
|
||||
# resp = self.get_response_from_cache()
|
||||
# if not resp:
|
||||
# resp = super().get(request, *args, **kwargs)
|
||||
# self.set_response_to_cache(resp)
|
||||
# return resp
|
||||
|
||||
|
||||
class NodesWithUngroupMixin:
|
||||
util = None
|
||||
|
||||
@staticmethod
|
||||
def get_ungrouped_node(ungroup_key):
|
||||
return Node(key=ungroup_key, id=const.UNGROUPED_NODE_ID,
|
||||
value=_("ungrouped"))
|
||||
|
||||
@staticmethod
|
||||
def get_empty_node():
|
||||
return Node(key=const.EMPTY_NODE_KEY, id=const.EMPTY_NODE_ID,
|
||||
value=_("empty"))
|
||||
|
||||
def add_ungrouped_nodes(self, node_map, node_keys):
|
||||
ungroup_key = '1:-1'
|
||||
for key in node_keys:
|
||||
if key.endswith('-1'):
|
||||
ungroup_key = key
|
||||
break
|
||||
ungroup_node = self.get_ungrouped_node(ungroup_key)
|
||||
empty_node = self.get_empty_node()
|
||||
node_map[ungroup_key] = ungroup_node
|
||||
node_map[const.EMPTY_NODE_KEY] = empty_node
|
||||
|
||||
|
||||
class GrantAssetsMixin(LabelFilterMixin):
|
||||
serializer_class = serializers.AssetGrantedSerializer
|
||||
|
||||
|
|
|
@ -1,321 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import (
|
||||
ListAPIView, get_object_or_404, RetrieveAPIView
|
||||
)
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin
|
||||
from common.tree import TreeNodeSerializer
|
||||
from common.utils import get_logger
|
||||
from ..utils import (
|
||||
ParserNode, AssetPermissionUtilV2
|
||||
)
|
||||
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
|
||||
from .. import serializers
|
||||
from ..models import Action
|
||||
from .mixin import UserPermissionMixin
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
__all__ = [
|
||||
'UserGrantedAssetsApi',
|
||||
'UserGrantedAssetsAsTreeApi',
|
||||
'UserGrantedNodeAssetsApi',
|
||||
'UserGrantedNodesApi',
|
||||
'UserGrantedNodesAsTreeApi',
|
||||
'UserGrantedNodesWithAssetsAsTreeApi',
|
||||
'UserGrantedNodeChildrenApi',
|
||||
'UserGrantedNodeChildrenAsTreeApi',
|
||||
'UserGrantedNodeChildrenWithAssetsAsTreeApi',
|
||||
'RefreshAssetPermissionCacheApi',
|
||||
'UserGrantedAssetSystemUsersApi',
|
||||
'ValidateUserAssetPermissionApi',
|
||||
'GetUserAssetPermissionActionsApi',
|
||||
]
|
||||
|
||||
|
||||
class UserAssetPermissionMixin(UserPermissionMixin):
|
||||
util = None
|
||||
|
||||
def initial(self, *args, **kwargs):
|
||||
super().initial(*args, *kwargs)
|
||||
cache_policy = self.request.query_params.get('cache_policy', '0')
|
||||
self.util = AssetPermissionUtilV2(self.obj, cache_policy=cache_policy)
|
||||
|
||||
|
||||
class UserNodeTreeMixin:
|
||||
serializer_class = TreeNodeSerializer
|
||||
nodes_only_fields = ParserNode.nodes_only_fields
|
||||
tree = None
|
||||
|
||||
def parse_nodes_to_queryset(self, nodes):
|
||||
nodes = nodes.only(*self.nodes_only_fields)
|
||||
_queryset = []
|
||||
|
||||
tree = self.util.get_user_tree()
|
||||
for node in nodes:
|
||||
assets_amount = tree.assets_amount(node.key)
|
||||
if assets_amount == 0:
|
||||
continue
|
||||
node._assets_amount = assets_amount
|
||||
data = ParserNode.parse_node_to_tree_node(node)
|
||||
_queryset.append(data)
|
||||
return _queryset
|
||||
|
||||
def get_serializer_queryset(self, queryset):
|
||||
queryset = self.parse_nodes_to_queryset(queryset)
|
||||
return queryset
|
||||
|
||||
def get_serializer(self, queryset, many=True, **kwargs):
|
||||
queryset = self.get_serializer_queryset(queryset)
|
||||
queryset.sort()
|
||||
return super().get_serializer(queryset, many=many, **kwargs)
|
||||
|
||||
|
||||
class UserAssetTreeMixin:
|
||||
serializer_class = TreeNodeSerializer
|
||||
nodes_only_fields = ParserNode.assets_only_fields
|
||||
|
||||
@staticmethod
|
||||
def parse_assets_to_queryset(assets, node):
|
||||
_queryset = []
|
||||
for asset in assets:
|
||||
data = ParserNode.parse_asset_to_tree_node(node, asset)
|
||||
_queryset.append(data)
|
||||
return _queryset
|
||||
|
||||
def get_serializer_queryset(self, queryset):
|
||||
queryset = queryset.only(*self.nodes_only_fields)
|
||||
_queryset = self.parse_assets_to_queryset(queryset, None)
|
||||
return _queryset
|
||||
|
||||
def get_serializer(self, queryset, many=True, **kwargs):
|
||||
queryset = self.get_serializer_queryset(queryset)
|
||||
queryset.sort()
|
||||
return super().get_serializer(queryset, many=many, **kwargs)
|
||||
|
||||
|
||||
class UserGrantedAssetsApi(UserAssetPermissionMixin, ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.AssetGrantedSerializer
|
||||
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
|
||||
filter_fields = ['hostname', 'ip', 'id', 'comment']
|
||||
search_fields = ['hostname', 'ip', 'comment']
|
||||
|
||||
def filter_by_nodes(self, queryset):
|
||||
node_id = self.request.query_params.get("node")
|
||||
if not node_id:
|
||||
return queryset
|
||||
node = get_object_or_404(Node, pk=node_id)
|
||||
query_all = self.request.query_params.get("all", "0") in ["1", "true"]
|
||||
if query_all:
|
||||
pattern = '^{0}$|^{0}:'.format(node.key)
|
||||
queryset = queryset.filter(nodes__key__regex=pattern).distinct()
|
||||
else:
|
||||
queryset = queryset.filter(nodes=node)
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
queryset = self.filter_by_nodes(queryset)
|
||||
return queryset
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.util.get_assets().only(*self.only_fields)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGrantedAssetsAsTreeApi(UserAssetTreeMixin, UserGrantedAssetsApi):
|
||||
pass
|
||||
|
||||
|
||||
class UserGrantedNodeAssetsApi(UserGrantedAssetsApi):
|
||||
def get_queryset(self):
|
||||
node_id = self.kwargs.get("node_id")
|
||||
node = get_object_or_404(Node, pk=node_id)
|
||||
deep = self.request.query_params.get("all", "0") == "1"
|
||||
queryset = self.util.get_nodes_assets(node, deep=deep)\
|
||||
.only(*self.only_fields)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGrantedNodesApi(UserAssetPermissionMixin, ListAPIView):
|
||||
"""
|
||||
查询用户授权的所有节点的API
|
||||
"""
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.NodeGrantedSerializer
|
||||
nodes_only_fields = NodeSerializer.Meta.only_fields
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context["tree"] = self.util.user_tree
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
node_keys = self.util.get_nodes()
|
||||
queryset = Node.objects.filter(key__in=node_keys)\
|
||||
.only(*self.nodes_only_fields)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGrantedNodesAsTreeApi(UserNodeTreeMixin, UserGrantedNodesApi):
|
||||
pass
|
||||
|
||||
|
||||
class UserGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesAsTreeApi):
|
||||
def get_serializer_queryset(self, queryset):
|
||||
_queryset = super().get_serializer_queryset(queryset)
|
||||
for node in queryset:
|
||||
assets = self.util.get_nodes_assets(node)
|
||||
_queryset.extend(
|
||||
UserAssetTreeMixin.parse_assets_to_queryset(assets, node)
|
||||
)
|
||||
return _queryset
|
||||
|
||||
|
||||
class UserGrantedNodeChildrenApi(UserGrantedNodesApi):
|
||||
node = None
|
||||
tree = None
|
||||
root_keys = None # 如果是第一次访问,则需要把二级节点添加进去,这个 roots_keys
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
key = self.request.query_params.get("key")
|
||||
pk = self.request.query_params.get("id")
|
||||
system_user_id = self.request.query_params.get("system_user")
|
||||
if system_user_id:
|
||||
self.util.filter_permissions(system_users=system_user_id)
|
||||
self.tree = self.util.get_user_tree()
|
||||
|
||||
node = None
|
||||
if pk is not None:
|
||||
node = get_object_or_404(Node, id=pk)
|
||||
elif key is not None:
|
||||
node = get_object_or_404(Node, key=key)
|
||||
self.node = node
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.node:
|
||||
children = self.tree.children(self.node.key)
|
||||
else:
|
||||
children = self.tree.children(self.tree.root)
|
||||
# 默认打开组织节点下的的节点
|
||||
self.root_keys = [child.identifier for child in children]
|
||||
for key in self.root_keys:
|
||||
children.extend(self.tree.children(key))
|
||||
node_keys = [n.identifier for n in children]
|
||||
queryset = Node.objects.filter(key__in=node_keys)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGrantedNodeChildrenAsTreeApi(UserNodeTreeMixin, UserGrantedNodeChildrenApi):
|
||||
pass
|
||||
|
||||
|
||||
class UserGrantedNodeChildrenWithAssetsAsTreeApi(UserGrantedNodeChildrenAsTreeApi):
|
||||
nodes_only_fields = ParserNode.nodes_only_fields
|
||||
assets_only_fields = ParserNode.assets_only_fields
|
||||
|
||||
def get_serializer_queryset(self, queryset):
|
||||
_queryset = super().get_serializer_queryset(queryset)
|
||||
nodes = []
|
||||
if self.node:
|
||||
nodes.append(self.node)
|
||||
elif self.root_keys:
|
||||
nodes = Node.objects.filter(key__in=self.root_keys)
|
||||
|
||||
for node in nodes:
|
||||
assets = self.util.get_nodes_assets(node).only(
|
||||
*self.assets_only_fields
|
||||
)
|
||||
_queryset.extend(
|
||||
UserAssetTreeMixin.parse_assets_to_queryset(assets, node)
|
||||
)
|
||||
return _queryset
|
||||
|
||||
|
||||
class GetUserAssetPermissionActionsApi(UserAssetPermissionMixin, RetrieveAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.ActionsSerializer
|
||||
|
||||
def get_obj(self):
|
||||
user_id = self.request.query_params.get('user_id', '')
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
return user
|
||||
|
||||
def get_object(self):
|
||||
asset_id = self.request.query_params.get('asset_id', '')
|
||||
system_id = self.request.query_params.get('system_user_id', '')
|
||||
|
||||
try:
|
||||
asset_id = uuid.UUID(asset_id)
|
||||
system_id = uuid.UUID(system_id)
|
||||
except ValueError:
|
||||
return Response({'msg': False}, status=403)
|
||||
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
system_user = get_object_or_404(SystemUser, id=system_id)
|
||||
|
||||
system_users_actions = self.util.get_asset_system_users_with_actions(asset)
|
||||
actions = system_users_actions.get(system_user)
|
||||
return {"actions": actions}
|
||||
|
||||
|
||||
class ValidateUserAssetPermissionApi(UserAssetPermissionMixin, APIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_obj(self):
|
||||
user_id = self.request.query_params.get('user_id', '')
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
return user
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
asset_id = request.query_params.get('asset_id', '')
|
||||
system_id = request.query_params.get('system_user_id', '')
|
||||
action_name = request.query_params.get('action_name', '')
|
||||
|
||||
try:
|
||||
asset_id = uuid.UUID(asset_id)
|
||||
system_id = uuid.UUID(system_id)
|
||||
except ValueError:
|
||||
return Response({'msg': False}, status=403)
|
||||
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
system_user = get_object_or_404(SystemUser, id=system_id)
|
||||
|
||||
system_users_actions = self.util.get_asset_system_users_with_actions(asset)
|
||||
actions = system_users_actions.get(system_user)
|
||||
if action_name in Action.value_to_choices(actions):
|
||||
return Response({'msg': True}, status=200)
|
||||
return Response({'msg': False}, status=403)
|
||||
|
||||
|
||||
class RefreshAssetPermissionCacheApi(RetrieveAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
AssetPermissionUtilV2.expire_all_user_tree_cache()
|
||||
return Response({'msg': True}, status=200)
|
||||
|
||||
|
||||
class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.AssetSystemUserSerializer
|
||||
only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields
|
||||
|
||||
def get_queryset(self):
|
||||
asset_id = self.kwargs.get('asset_id')
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
system_users_with_actions = self.util.get_asset_system_users_with_actions(asset)
|
||||
system_users = []
|
||||
for system_user, actions in system_users_with_actions.items():
|
||||
system_user.actions = actions
|
||||
system_users.append(system_user)
|
||||
system_users.sort(key=lambda x: x.priority)
|
||||
return system_users
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .common import *
|
||||
from .user_permission_nodes import *
|
||||
from .user_permission_assets import *
|
||||
from .user_permission_nodes_with_assets import *
|
|
@ -0,0 +1,113 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import (
|
||||
ListAPIView, get_object_or_404, RetrieveAPIView
|
||||
)
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin
|
||||
from common.utils import get_logger
|
||||
from ...utils import (
|
||||
AssetPermissionUtilV2
|
||||
)
|
||||
from ...hands import User, Asset, SystemUser
|
||||
from ... import serializers
|
||||
from ...models import Action
|
||||
from .mixin import UserAssetPermissionMixin
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
__all__ = [
|
||||
'RefreshAssetPermissionCacheApi',
|
||||
'UserGrantedAssetSystemUsersApi',
|
||||
'ValidateUserAssetPermissionApi',
|
||||
'GetUserAssetPermissionActionsApi',
|
||||
]
|
||||
|
||||
|
||||
class GetUserAssetPermissionActionsApi(UserAssetPermissionMixin,
|
||||
RetrieveAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.ActionsSerializer
|
||||
|
||||
def get_obj(self):
|
||||
user_id = self.request.query_params.get('user_id', '')
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
return user
|
||||
|
||||
def get_object(self):
|
||||
asset_id = self.request.query_params.get('asset_id', '')
|
||||
system_id = self.request.query_params.get('system_user_id', '')
|
||||
|
||||
try:
|
||||
asset_id = uuid.UUID(asset_id)
|
||||
system_id = uuid.UUID(system_id)
|
||||
except ValueError:
|
||||
return Response({'msg': False}, status=403)
|
||||
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
system_user = get_object_or_404(SystemUser, id=system_id)
|
||||
|
||||
system_users_actions = self.util.get_asset_system_users_with_actions(
|
||||
asset)
|
||||
actions = system_users_actions.get(system_user)
|
||||
return {"actions": actions}
|
||||
|
||||
|
||||
class ValidateUserAssetPermissionApi(UserAssetPermissionMixin, APIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_obj(self):
|
||||
user_id = self.request.query_params.get('user_id', '')
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
return user
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
asset_id = request.query_params.get('asset_id', '')
|
||||
system_id = request.query_params.get('system_user_id', '')
|
||||
action_name = request.query_params.get('action_name', '')
|
||||
|
||||
try:
|
||||
asset_id = uuid.UUID(asset_id)
|
||||
system_id = uuid.UUID(system_id)
|
||||
except ValueError:
|
||||
return Response({'msg': False}, status=403)
|
||||
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
system_user = get_object_or_404(SystemUser, id=system_id)
|
||||
|
||||
system_users_actions = self.util.get_asset_system_users_with_actions(
|
||||
asset)
|
||||
actions = system_users_actions.get(system_user)
|
||||
if action_name in Action.value_to_choices(actions):
|
||||
return Response({'msg': True}, status=200)
|
||||
return Response({'msg': False}, status=403)
|
||||
|
||||
|
||||
class RefreshAssetPermissionCacheApi(RetrieveAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
AssetPermissionUtilV2.expire_all_user_tree_cache()
|
||||
return Response({'msg': True}, status=200)
|
||||
|
||||
|
||||
class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.AssetSystemUserSerializer
|
||||
only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields
|
||||
|
||||
def get_queryset(self):
|
||||
asset_id = self.kwargs.get('asset_id')
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
system_users_with_actions = self.util.get_asset_system_users_with_actions(
|
||||
asset)
|
||||
system_users = []
|
||||
for system_user, actions in system_users_with_actions.items():
|
||||
system_user.actions = actions
|
||||
system_users.append(system_user)
|
||||
system_users.sort(key=lambda x: x.priority)
|
||||
return system_users
|
|
@ -0,0 +1,70 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from ..mixin import UserPermissionMixin
|
||||
from ...utils import AssetPermissionUtilV2, ParserNode
|
||||
from ...hands import Node
|
||||
from common.tree import TreeNodeSerializer
|
||||
|
||||
|
||||
class UserAssetPermissionMixin(UserPermissionMixin):
|
||||
util = None
|
||||
tree = None
|
||||
|
||||
def initial(self, *args, **kwargs):
|
||||
super().initial(*args, *kwargs)
|
||||
cache_policy = self.request.query_params.get('cache_policy', '0')
|
||||
system_user_id = self.request.query_params.get("system_user")
|
||||
self.util = AssetPermissionUtilV2(self.obj, cache_policy=cache_policy)
|
||||
if system_user_id:
|
||||
self.util.filter_permissions(system_users=system_user_id)
|
||||
self.tree = self.util.get_user_tree()
|
||||
|
||||
|
||||
class UserNodeTreeMixin:
|
||||
serializer_class = TreeNodeSerializer
|
||||
nodes_only_fields = ParserNode.nodes_only_fields
|
||||
|
||||
def parse_nodes_to_queryset(self, nodes):
|
||||
nodes = nodes.only(*self.nodes_only_fields)
|
||||
_queryset = []
|
||||
|
||||
for node in nodes:
|
||||
assets_amount = self.tree.assets_amount(node.key)
|
||||
if assets_amount == 0 and node.key != Node.empty_key:
|
||||
continue
|
||||
node._assets_amount = assets_amount
|
||||
data = ParserNode.parse_node_to_tree_node(node)
|
||||
_queryset.append(data)
|
||||
return _queryset
|
||||
|
||||
def get_serializer_queryset(self, queryset):
|
||||
queryset = self.parse_nodes_to_queryset(queryset)
|
||||
return queryset
|
||||
|
||||
def get_serializer(self, queryset, many=True, **kwargs):
|
||||
queryset = self.get_serializer_queryset(queryset)
|
||||
queryset.sort()
|
||||
return super().get_serializer(queryset, many=many, **kwargs)
|
||||
|
||||
|
||||
class UserAssetTreeMixin:
|
||||
serializer_class = TreeNodeSerializer
|
||||
nodes_only_fields = ParserNode.assets_only_fields
|
||||
|
||||
@staticmethod
|
||||
def parse_assets_to_queryset(assets, node):
|
||||
_queryset = []
|
||||
for asset in assets:
|
||||
data = ParserNode.parse_asset_to_tree_node(node, asset)
|
||||
_queryset.append(data)
|
||||
return _queryset
|
||||
|
||||
def get_serializer_queryset(self, queryset):
|
||||
queryset = queryset.only(*self.nodes_only_fields)
|
||||
_queryset = self.parse_assets_to_queryset(queryset, None)
|
||||
return _queryset
|
||||
|
||||
def get_serializer(self, queryset, many=True, **kwargs):
|
||||
queryset = self.get_serializer_queryset(queryset)
|
||||
queryset.sort()
|
||||
return super().get_serializer(queryset, many=many, **kwargs)
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.generics import (
|
||||
ListAPIView, get_object_or_404
|
||||
)
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from common.utils import get_logger
|
||||
from ...hands import Node
|
||||
from ... import serializers
|
||||
from .mixin import UserAssetPermissionMixin, UserAssetTreeMixin
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
__all__ = [
|
||||
'UserGrantedAssetsApi',
|
||||
'UserGrantedAssetsAsTreeApi',
|
||||
'UserGrantedNodeAssetsApi',
|
||||
]
|
||||
|
||||
|
||||
class UserGrantedAssetsApi(UserAssetPermissionMixin, ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.AssetGrantedSerializer
|
||||
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
|
||||
filter_fields = ['hostname', 'ip', 'id', 'comment']
|
||||
search_fields = ['hostname', 'ip', 'comment']
|
||||
|
||||
def filter_by_nodes(self, queryset):
|
||||
node_id = self.request.query_params.get("node")
|
||||
if not node_id:
|
||||
return queryset
|
||||
node = get_object_or_404(Node, pk=node_id)
|
||||
query_all = self.request.query_params.get("all", "0") in ["1", "true"]
|
||||
if query_all:
|
||||
pattern = '^{0}$|^{0}:'.format(node.key)
|
||||
queryset = queryset.filter(nodes__key__regex=pattern).distinct()
|
||||
else:
|
||||
queryset = queryset.filter(nodes=node)
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
queryset = self.filter_by_nodes(queryset)
|
||||
return queryset
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.util.get_assets().only(*self.only_fields)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGrantedAssetsAsTreeApi(UserAssetTreeMixin, UserGrantedAssetsApi):
|
||||
pass
|
||||
|
||||
|
||||
class UserGrantedNodeAssetsApi(UserGrantedAssetsApi):
|
||||
def get_queryset(self):
|
||||
node_id = self.kwargs.get("node_id")
|
||||
node = get_object_or_404(Node, pk=node_id)
|
||||
deep = self.request.query_params.get("all", "0") == "1"
|
||||
queryset = self.util.get_nodes_assets(node, deep=deep)\
|
||||
.only(*self.only_fields)
|
||||
return queryset
|
|
@ -0,0 +1,82 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.generics import (
|
||||
ListAPIView, get_object_or_404
|
||||
)
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from common.utils import get_logger
|
||||
from ...hands import Node, NodeSerializer
|
||||
from ... import serializers
|
||||
from .mixin import UserNodeTreeMixin, UserAssetPermissionMixin
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
__all__ = [
|
||||
'UserGrantedNodesApi',
|
||||
'UserGrantedNodesAsTreeApi',
|
||||
'UserGrantedNodeChildrenApi',
|
||||
'UserGrantedNodeChildrenAsTreeApi',
|
||||
]
|
||||
|
||||
|
||||
class UserGrantedNodesApi(UserAssetPermissionMixin, ListAPIView):
|
||||
"""
|
||||
查询用户授权的所有节点的API
|
||||
"""
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.NodeGrantedSerializer
|
||||
nodes_only_fields = NodeSerializer.Meta.only_fields
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
if self.serializer_class == serializers.NodeGrantedSerializer:
|
||||
context["tree"] = self.tree
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
node_keys = self.util.get_nodes()
|
||||
queryset = Node.objects.filter(key__in=node_keys)\
|
||||
.only(*self.nodes_only_fields)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGrantedNodesAsTreeApi(UserNodeTreeMixin, UserGrantedNodesApi):
|
||||
pass
|
||||
|
||||
|
||||
class UserGrantedNodeChildrenApi(UserGrantedNodesApi):
|
||||
node = None
|
||||
root_keys = None # 如果是第一次访问,则需要把二级节点添加进去,这个 roots_keys
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
key = self.request.query_params.get("key")
|
||||
pk = self.request.query_params.get("id")
|
||||
|
||||
node = None
|
||||
if pk is not None:
|
||||
node = get_object_or_404(Node, id=pk)
|
||||
elif key is not None:
|
||||
node = get_object_or_404(Node, key=key)
|
||||
self.node = node
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.node:
|
||||
children = self.tree.children(self.node.key)
|
||||
else:
|
||||
children = self.tree.children(self.tree.root)
|
||||
# 默认打开组织节点下的节点
|
||||
self.root_keys = [child.identifier for child in children]
|
||||
for key in self.root_keys:
|
||||
children.extend(self.tree.children(key))
|
||||
node_keys = [n.identifier for n in children]
|
||||
queryset = Node.objects.filter(key__in=node_keys)
|
||||
return queryset
|
||||
|
||||
|
||||
class UserGrantedNodeChildrenAsTreeApi(UserNodeTreeMixin, UserGrantedNodeChildrenApi):
|
||||
pass
|
|
@ -0,0 +1,56 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from common.utils import get_logger
|
||||
from ...utils import ParserNode
|
||||
from .mixin import UserAssetTreeMixin
|
||||
from ...hands import Node
|
||||
from .user_permission_nodes import UserGrantedNodesAsTreeApi
|
||||
from .user_permission_nodes import UserGrantedNodeChildrenAsTreeApi
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
__all__ = [
|
||||
'UserGrantedNodesAsTreeApi',
|
||||
'UserGrantedNodesWithAssetsAsTreeApi',
|
||||
'UserGrantedNodeChildrenAsTreeApi',
|
||||
'UserGrantedNodeChildrenWithAssetsAsTreeApi',
|
||||
]
|
||||
|
||||
|
||||
class UserGrantedNodesWithAssetsAsTreeApi(UserGrantedNodesAsTreeApi):
|
||||
assets_only_fields = ParserNode.assets_only_fields
|
||||
|
||||
def get_serializer_queryset(self, queryset):
|
||||
_queryset = super().get_serializer_queryset(queryset)
|
||||
_all_assets = self.util.get_assets().only(*self.assets_only_fields)
|
||||
_all_assets_map = {a.id: a for a in _all_assets}
|
||||
for node in queryset:
|
||||
assets_ids = self.tree.assets(node.key)
|
||||
assets = [_all_assets_map[_id] for _id in assets_ids if _id in _all_assets_map]
|
||||
_queryset.extend(
|
||||
UserAssetTreeMixin.parse_assets_to_queryset(assets, node)
|
||||
)
|
||||
return _queryset
|
||||
|
||||
|
||||
class UserGrantedNodeChildrenWithAssetsAsTreeApi(UserGrantedNodeChildrenAsTreeApi):
|
||||
nodes_only_fields = ParserNode.nodes_only_fields
|
||||
assets_only_fields = ParserNode.assets_only_fields
|
||||
|
||||
def get_serializer_queryset(self, queryset):
|
||||
_queryset = super().get_serializer_queryset(queryset)
|
||||
nodes = []
|
||||
if self.node:
|
||||
nodes.append(self.node)
|
||||
elif self.root_keys:
|
||||
nodes = Node.objects.filter(key__in=self.root_keys)
|
||||
|
||||
for node in nodes:
|
||||
assets = self.util.get_nodes_assets(node).only(
|
||||
*self.assets_only_fields
|
||||
)
|
||||
_queryset.extend(
|
||||
UserAssetTreeMixin.parse_assets_to_queryset(assets, node)
|
||||
)
|
||||
return _queryset
|
|
@ -75,7 +75,7 @@
|
|||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select assets' %}" class="select2" id="asset_select2" style="width: 100%" multiple="" tabindex="4">
|
||||
<select data-placeholder="{% trans 'Select assets' %}" class="select2" id="id_assets" style="width: 100%" multiple="" tabindex="4">
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -207,7 +207,7 @@ $(document).ready(function () {
|
|||
var nodeListUrl = "{% url 'api-assets:node-list' %}";
|
||||
nodesSelect2Init(".nodes-select2", nodeListUrl);
|
||||
|
||||
$("#asset_select2").parent().find(".select2-selection").on('click', function (e) {
|
||||
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
|
||||
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -216,7 +216,7 @@ $(document).ready(function () {
|
|||
})
|
||||
})
|
||||
.on('click', '.btn-add-assets', function () {
|
||||
var assets_selected = $("#asset_select2 option:selected").map(function () {
|
||||
var assets_selected = $("#id_assets option:selected").map(function () {
|
||||
return $(this).attr('value');
|
||||
}).get();
|
||||
if (assets_selected.length === 0) {
|
||||
|
|
|
@ -4,6 +4,8 @@ import pickle
|
|||
import threading
|
||||
from collections import defaultdict
|
||||
from functools import reduce
|
||||
from hashlib import md5
|
||||
import json
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Q
|
||||
|
@ -60,29 +62,33 @@ def get_system_user_permissions(system_user):
|
|||
|
||||
|
||||
class AssetPermissionUtilCacheMixin:
|
||||
user_tree_cache_key = 'USER_PERM_TREE_{}'
|
||||
user_tree_cache_key = 'USER_PERM_TREE_{}_{}'
|
||||
user_tree_cache_ttl = settings.ASSETS_PERM_CACHE_TIME
|
||||
user_tree_cache_enable = settings.ASSETS_PERM_CACHE_ENABLE
|
||||
cache_policy = '0'
|
||||
obj_id = ''
|
||||
_filter_id = 'None'
|
||||
|
||||
@property
|
||||
def cache_key(self):
|
||||
return self.user_tree_cache_key.format(self.obj_id, self._filter_id)
|
||||
|
||||
def expire_user_tree_cache(self):
|
||||
key = self.user_tree_cache_key.format(self.obj_id)
|
||||
cache.delete(key)
|
||||
cache.delete(self.cache_key)
|
||||
|
||||
@classmethod
|
||||
def expire_all_user_tree_cache(cls):
|
||||
key = cls.user_tree_cache_key.format('*')
|
||||
key = cls.user_tree_cache_key.format('*', '*')
|
||||
key = key.split('_')[:-1]
|
||||
key = '_'.join(key)
|
||||
cache.delete_pattern(key)
|
||||
|
||||
def set_user_tree_to_cache(self, user_tree):
|
||||
data = pickle.dumps(user_tree)
|
||||
key = self.user_tree_cache_key.format(self.obj_id)
|
||||
cache.set(key, data, self.user_tree_cache_ttl)
|
||||
cache.set(self.cache_key, data, self.user_tree_cache_ttl)
|
||||
|
||||
def get_user_tree_from_cache(self):
|
||||
key = self.user_tree_cache_key.format(self.obj_id)
|
||||
data = cache.get(key)
|
||||
data = cache.get(self.cache_key)
|
||||
if not data:
|
||||
return None
|
||||
user_tree = pickle.loads(data)
|
||||
|
@ -129,6 +135,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
|
|||
self._filter_id = 'None' # 当通过filter更改 permission是标记
|
||||
self.change_org_if_need()
|
||||
self._user_tree = None
|
||||
self._user_tree_filter_id = 'None'
|
||||
self.full_tree = Node.tree()
|
||||
self.mutex = threading.Lock()
|
||||
|
||||
|
@ -148,7 +155,9 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
|
|||
|
||||
@timeit
|
||||
def filter_permissions(self, **filters):
|
||||
filters_json = json.dumps(filters, sort_keys=True)
|
||||
self._permissions = self.permissions.filter(**filters)
|
||||
self._filter_id = md5(filters_json.encode()).hexdigest()
|
||||
|
||||
@property
|
||||
def user_tree(self):
|
||||
|
@ -282,15 +291,25 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
|
|||
parent=user_tree.root,
|
||||
)
|
||||
|
||||
def set_user_tree_to_local(self, user_tree):
|
||||
self._user_tree = user_tree
|
||||
self._user_tree_filter_id = self._filter_id
|
||||
|
||||
def get_user_tree_from_local(self):
|
||||
if self._user_tree and self._user_tree_filter_id == self._filter_id:
|
||||
return self._user_tree
|
||||
return None
|
||||
|
||||
@timeit
|
||||
def get_user_tree(self):
|
||||
# 使用锁,保证多次获取tree的时候顺序执行,可以使用缓存
|
||||
with self.mutex:
|
||||
if self._user_tree:
|
||||
return self._user_tree
|
||||
user_tree = self.get_user_tree_from_local()
|
||||
if user_tree:
|
||||
return user_tree
|
||||
user_tree = self.get_user_tree_from_cache_if_need()
|
||||
if user_tree:
|
||||
self._user_tree = user_tree
|
||||
self.set_user_tree_to_local(user_tree)
|
||||
return user_tree
|
||||
user_tree = TreeService()
|
||||
full_tree_root = self.full_tree.root_node()
|
||||
|
@ -303,7 +322,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
|
|||
self.parse_user_tree_to_full_tree(user_tree)
|
||||
self.add_empty_node_if_need(user_tree)
|
||||
self.set_user_tree_to_cache_if_need(user_tree)
|
||||
self._user_tree = user_tree
|
||||
self.set_user_tree_to_local(user_tree)
|
||||
return user_tree
|
||||
|
||||
# Todo: 是否可以获取多个资产的系统用户
|
||||
|
|
|
@ -22,7 +22,7 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
|
|||
instance.refresh_setting()
|
||||
|
||||
|
||||
@receiver(django_ready, dispatch_uid="my_unique_identifier")
|
||||
@receiver(django_ready)
|
||||
def monkey_patch_settings(sender, **kwargs):
|
||||
logger.debug("Monkey patch settings")
|
||||
cache_key_prefix = '_SETTING_'
|
||||
|
|
Loading…
Reference in New Issue