Merge branch 'v3' of github.com:jumpserver/jumpserver into v3

pull/9246/head
ibuler 2022-12-23 16:00:11 +08:00
commit 4bd913b585
21 changed files with 1987 additions and 1910 deletions

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.16 on 2022-12-23 07:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0116_alter_automationexecution_options'),
]
operations = [
migrations.AlterModelOptions(
name='gateway',
options={'verbose_name': 'Gateway'},
),
]

View File

@ -37,6 +37,7 @@ class Gateway(Host):
class Meta: class Meta:
proxy = True proxy = True
verbose_name = _("Gateway")
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.platform = self.default_platform() self.platform = self.default_platform()

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5c366d6b10c4ce62bd8ed7c69ecaec5533f1a178b3cc7db4e6008769a6c8bb1f oid sha256:7f83a00d90fe74749386ecd1f64d507b135e8c4d35acea1c5cd56bba3387e834
size 119897 size 119674

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:eaeedc4823f9b7e236b36a169b5587ef5988d3c3e529d6cbede6bae5e2b57ab8 oid sha256:b02c5d36ea6ea96590be9a25dc6d3f1340a5af2aa940764243f85da1c756c732
size 106349 size 104221

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
# Generated by Django 3.2.16 on 2022-12-23 07:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ops', '0032_auto_20221221_1513'),
]
operations = [
migrations.AlterModelOptions(
name='adhoc',
options={'verbose_name': 'AdHoc'},
),
migrations.AlterModelOptions(
name='celerytask',
options={'ordering': ('name',), 'verbose_name': 'Celery Task'},
),
migrations.AlterModelOptions(
name='celerytaskexecution',
options={'verbose_name': 'Celery Task Execution'},
),
migrations.AlterModelOptions(
name='historicaljob',
options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical Job', 'verbose_name_plural': 'historical Jobs'},
),
migrations.AlterModelOptions(
name='job',
options={'ordering': ['date_created'], 'verbose_name': 'Job'},
),
migrations.AlterModelOptions(
name='jobauditlog',
options={'verbose_name': 'Job audit log'},
),
migrations.AlterModelOptions(
name='jobexecution',
options={'ordering': ['-date_created'], 'verbose_name': 'Job Execution'},
),
]

View File

@ -41,3 +41,6 @@ class AdHoc(JMSOrgBaseModel):
def __str__(self): def __str__(self):
return "{}: {}".format(self.module, self.args) return "{}: {}".format(self.module, self.args)
class Meta:
verbose_name = _("AdHoc")

View File

@ -44,6 +44,7 @@ class CeleryTask(models.Model):
return "green" return "green"
class Meta: class Meta:
verbose_name = _("Celery Task")
ordering = ('name',) ordering = ('name',)
@ -77,3 +78,6 @@ class CeleryTaskExecution(models.Model):
def __str__(self): def __str__(self):
return "{}: {}".format(self.name, self.id) return "{}: {}".format(self.name, self.id)
class Meta:
verbose_name = _("Celery Task Execution")

View File

@ -88,6 +88,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
return self.executions.create(job_version=self.version) return self.executions.create(job_version=self.version)
class Meta: class Meta:
verbose_name = _("Job")
ordering = ['date_created'] ordering = ['date_created']
@ -284,6 +285,7 @@ class JobExecution(JMSOrgBaseModel):
self.set_error(e) self.set_error(e)
class Meta: class Meta:
verbose_name = _("Job Execution")
ordering = ['-date_created'] ordering = ['-date_created']
@ -294,3 +296,4 @@ class JobAuditLog(JobExecution):
class Meta: class Meta:
proxy = True proxy = True
verbose_name = _("Job audit log")

View File

@ -1,6 +1,9 @@
from django.core.cache import cache
from rest_framework.request import Request from rest_framework.request import Request
from common.http import is_true from common.http import is_true
from common.utils import lazyproperty
from perms.utils import UserPermTreeRefreshUtil from perms.utils import UserPermTreeRefreshUtil
from users.models import User from users.models import User
@ -9,8 +12,29 @@ __all__ = ['RebuildTreeMixin']
class RebuildTreeMixin: class RebuildTreeMixin:
user: User user: User
request: Request
def get(self, request: Request, *args, **kwargs): def get(self, request, *args, **kwargs):
force = is_true(request.query_params.get('rebuild_tree')) UserPermTreeRefreshUtil(self.user).refresh_if_need(force=self.is_force_refresh_tree)
UserPermTreeRefreshUtil(self.user).refresh_if_need(force)
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
@lazyproperty
def is_force_refresh_tree(self):
force = is_true(self.request.query_params.get('rebuild_tree'))
if not force:
force = self.compute_is_force_refresh()
return force
def compute_is_force_refresh(self):
""" 5s 内连续刷新三次转为强制刷新 """
force_timeout = 5
force_max_count = 3
force_cache_key = '{user_id}:{path}'.format(user_id=self.user.id, path=self.request.path)
count = cache.get(force_cache_key, 1)
if count >= force_max_count:
force = True
cache.delete(force_cache_key)
else:
force = False
cache.set(force_cache_key, count+1, force_timeout)
return force

View File

@ -37,7 +37,7 @@ class BaseUserNodeWithAssetAsTreeApi(
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
nodes, assets = self.get_nodes_assets() nodes, assets = self.get_nodes_assets()
tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True) tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True)
tree_assets = self.serialize_assets(assets, node_key=self.node_key_for_serializer_assets) tree_assets = self.serialize_assets(assets, node_key=self.node_key_for_serialize_assets)
data = list(tree_nodes) + list(tree_assets) data = list(tree_nodes) + list(tree_assets)
return Response(data=data) return Response(data=data)
@ -46,7 +46,7 @@ class BaseUserNodeWithAssetAsTreeApi(
return [], [] return [], []
@lazyproperty @lazyproperty
def node_key_for_serializer_assets(self): def node_key_for_serialize_assets(self):
return None return None
@ -95,19 +95,21 @@ class UserPermedNodesWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi):
class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi):
""" 用户授权的节点的子节点与资产树 """ """ 用户授权的节点的子节点与资产树 """
# 默认展开的节点key
default_unfolded_node_key = None
def get_nodes_assets(self): def get_nodes_assets(self):
query_node_util = UserPermNodeUtil(self.user) query_node_util = UserPermNodeUtil(self.user)
query_asset_util = UserPermAssetUtil(self.user) query_asset_util = UserPermAssetUtil(self.user)
node_key = self.query_node_key node_key = self.query_node_key
if not node_key: if not node_key:
nodes = query_node_util.get_top_level_nodes() nodes, unfolded_node = query_node_util.get_top_level_nodes(with_unfolded_node=True)
assets = Asset.objects.none() if unfolded_node:
# 获取根节点下的资产 """ 默认展开的节点, 获取根节点下的资产 """
for node in nodes: assets = query_asset_util.get_node_assets(key=unfolded_node.key)
if not node.key.isdigit(): self.default_unfolded_node_key = unfolded_node.key
continue else:
assets = query_asset_util.get_node_assets(key=node.key) assets = Asset.objects.none()
break
elif node_key == PermNode.UNGROUPED_NODE_KEY: elif node_key == PermNode.UNGROUPED_NODE_KEY:
nodes = PermNode.objects.none() nodes = PermNode.objects.none()
assets = query_asset_util.get_ungroup_assets() assets = query_asset_util.get_ungroup_assets()
@ -123,16 +125,15 @@ class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi):
@lazyproperty @lazyproperty
def query_node_key(self): def query_node_key(self):
node_key = self.request.query_params.get('key', None) node_key = self.request.query_params.get('key', None)
if node_key is not None: if node_key is None:
return node_key node_id = self.request.query_params.get('id', None)
node_id = self.request.query_params.get('id', None) node = get_object_or_none(Node, id=node_id)
node = get_object_or_none(Node, id=node_id) node_key = getattr(node, 'key', None)
node_key = getattr(node, 'key', None)
return node_key return node_key
@lazyproperty @lazyproperty
def node_key_for_serializer_assets(self): def node_key_for_serialize_assets(self):
return self.query_node_key return self.query_node_key or self.default_unfolded_node_key
class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView): class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView):

View File

@ -148,15 +148,20 @@ class UserPermNodeUtil:
assets_amount = UserPermAssetUtil(self.user).get_direct_assets().count() assets_amount = UserPermAssetUtil(self.user).get_direct_assets().count()
return PermNode.get_favorite_node(assets_amount) return PermNode.get_favorite_node(assets_amount)
def get_top_level_nodes(self): def get_top_level_nodes(self, with_unfolded_node=False):
# 是否有节点展开, 展开的节点
unfolded_node = None
nodes = self.get_special_nodes() nodes = self.get_special_nodes()
# 获取组织下的根节点 real_nodes = self._get_perm_node_children_from_relation(key='')
real_nodes = self._get_indirect_perm_node_children(key='')
nodes.extend(real_nodes) nodes.extend(real_nodes)
if len(real_nodes) == 1: if len(real_nodes) == 1:
children = self.get_node_children(real_nodes[0].key) unfolded_node = real_nodes[0]
children = self.get_node_children(unfolded_node.key)
nodes.extend(children) nodes.extend(children)
return nodes if with_unfolded_node:
return nodes, unfolded_node
else:
return nodes
def get_special_nodes(self): def get_special_nodes(self):
nodes = [] nodes = []
@ -177,16 +182,18 @@ class UserPermNodeUtil:
node = PermNode.objects.get(key=key) node = PermNode.objects.get(key=key)
node.compute_node_from_and_assets_amount(self.user) node.compute_node_from_and_assets_amount(self.user)
if node.node_from == node.NodeFrom.granted: if node.node_from == node.NodeFrom.granted:
""" 直接授权的节点, 直接从完整资产树获取子节点 """
children = PermNode.objects.filter(parent_key=key) children = PermNode.objects.filter(parent_key=key)
elif node.node_from in (node.NodeFrom.asset, node.NodeFrom.child): elif node.node_from in (node.NodeFrom.asset, node.NodeFrom.child):
children = self._get_indirect_perm_node_children(key) """ 间接授权的节点, 从 Relation 表中获取子节点 """
children = self._get_perm_node_children_from_relation(key)
else: else:
children = PermNode.objects.none() children = PermNode.objects.none()
children = sorted(children, key=lambda x: x.value) children = sorted(children, key=lambda x: x.value)
return children return children
def _get_indirect_perm_node_children(self, key): def _get_perm_node_children_from_relation(self, key):
""" 获取未直接授权节点的子节点 """ """ 获取授权节点的子节点, 从用户授权节点关系表中获取 """
children = PermNode.objects.filter(granted_node_rels__user=self.user, parent_key=key) children = PermNode.objects.filter(granted_node_rels__user=self.user, parent_key=key)
children = children.annotate(**PermNode.annotate_granted_node_rel_fields).distinct() children = children.annotate(**PermNode.annotate_granted_node_rel_fields).distinct()
for node in children: for node in children:

View File

@ -52,8 +52,8 @@ extra_nodes_data = [
{"id": "terminal_node", "name": _("Terminal setting"), "pId": "view_setting"}, {"id": "terminal_node", "name": _("Terminal setting"), "pId": "view_setting"},
{'id': "task_center", "name": _("Task Center"), "pId": "view_console"}, {'id': "task_center", "name": _("Task Center"), "pId": "view_console"},
{'id': "my_assets", "name": _("My assets"), "pId": "view_workbench"}, {'id': "my_assets", "name": _("My assets"), "pId": "view_workbench"},
{'id': "operation_center", "name": _('Operation Center'), "pId": "view_workbench"}, {'id': "operation_center", "name": _('App ops'), "pId": "view_workbench"},
{'id': "remote_application", "name": _("Remote application"), "pId": "view_setting"}, {'id': "remote_application", "name": _("Applet"), "pId": "view_setting"},
] ]
# 将 model 放到其它节点下,而不是本来的 app 中 # 将 model 放到其它节点下,而不是本来的 app 中

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.16 on 2022-12-23 07:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('terminal', '0064_auto_20221220_1956'),
]
operations = [
migrations.AlterModelOptions(
name='applet',
options={'verbose_name': 'Applet'},
),
migrations.AlterModelOptions(
name='applethost',
options={'verbose_name': 'Applet host'},
),
]

View File

@ -34,6 +34,9 @@ class Applet(JMSBaseModel):
to='AppletHost', verbose_name=_('Hosts') to='AppletHost', verbose_name=_('Hosts')
) )
class Meta:
verbose_name = _("Applet")
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -29,6 +29,9 @@ class AppletHost(Host):
) )
LOCKING_ORG = '00000000-0000-0000-0000-000000000004' LOCKING_ORG = '00000000-0000-0000-0000-000000000004'
class Meta:
verbose_name = _("Applet host")
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2022-12-23 07:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tickets', '0026_auto_20221220_1956'),
]
operations = [
migrations.AlterField(
model_name='applycommandticket',
name='apply_run_account',
field=models.CharField(default='', max_length=128, verbose_name='Account'),
),
]

View File

@ -10,7 +10,7 @@ class ApplyCommandTicket(Ticket):
) )
apply_run_asset = models.CharField(max_length=128, verbose_name=_('Run asset')) apply_run_asset = models.CharField(max_length=128, verbose_name=_('Run asset'))
apply_run_command = models.CharField(max_length=4096, verbose_name=_('Run command')) apply_run_command = models.CharField(max_length=4096, verbose_name=_('Run command'))
apply_run_account = models.CharField(max_length=128, default='', verbose_name=_('Run account')) apply_run_account = models.CharField(max_length=128, default='', verbose_name=_('Account'))
apply_from_session = models.ForeignKey( apply_from_session = models.ForeignKey(
'terminal.Session', on_delete=models.SET_NULL, null=True, verbose_name=_("Session") 'terminal.Session', on_delete=models.SET_NULL, null=True, verbose_name=_("Session")
) )

View File

@ -1,7 +1,8 @@
#!/bin/bash #!/bin/bash
if [[ "$(ps axu | grep 'celery' | grep -v 'grep' | grep -cv 'defunct')" -gt "2" ]];then set -e
exit 0
else test -e /tmp/worker_ready_ansible
exit 1 test -e /tmp/worker_ready_celery
fi test -e /tmp/worker_heartbeat_ansible && test $(($(date +%s) - $(stat -c %Y /tmp/worker_heartbeat_ansible))) -lt 10
test -e /tmp/worker_heartbeat_celery && test $(($(date +%s) - $(stat -c %Y /tmp/worker_heartbeat_celery))) -lt 10

View File

@ -1,45 +0,0 @@
#!/bin/bash
if grep -q 'source /opt/autoenv/activate.sh' ~/.bashrc; then
echo -e "\033[31m 正在自动载入 python 环境 \033[0m"
else
echo -e "\033[31m 不支持自动升级,请参考 http://docs.jumpserver.org/zh/docs/upgrade.html 手动升级 \033[0m"
exit 0
fi
source ~/.bashrc
cd `dirname $0`/ && cd .. && ./jms stop
jumpserver_backup=/tmp/jumpserver_backup$(date -d "today" +"%Y%m%d_%H%M%S")
mkdir -p $jumpserver_backup
cp -r ./* $jumpserver_backup
echo -e "\033[31m 是否需要备份Jumpserver数据库 \033[0m"
stty erase ^H
read -p "确认备份请按Y否则按其他键跳过备份 " a
if [ "$a" == y -o "$a" == Y ];then
echo -e "\033[31m 正在备份数据库 \033[0m"
echo -e "\033[31m 请手动输入数据库信息 \033[0m"
read -p '请输入Jumpserver数据库ip:' DB_HOST
read -p '请输入Jumpserver数据库端口:' DB_PORT
read -p '请输入Jumpserver数据库名称:' DB_NAME
read -p '请输入有权限导出数据库的用户:' DB_USER
read -p '请输入该用户的密码:' DB_PASSWORD
mysqldump -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASSWORD $DB_NAME > /$jumpserver_backup/$DB_NAME$(date -d "today" +"%Y%m%d_%H%M%S").sql || {
echo -e "\033[31m 备份数据库失败,请检查输入是否有误 \033[0m"
exit 1
}
echo -e "\033[31m 备份数据库完成 \033[0m"
else
echo -e "\033[31m 已取消备份数据库操作 \033[0m"
fi
git pull && pip install -r requirements/requirements.txt && cd utils && sh make_migrations.sh
cd .. && ./jms start all -d
echo -e "\033[31m 请检查jumpserver是否启动成功 \033[0m"
echo -e "\033[31m 备份文件存放于$jumpserver_backup目录 \033[0m"
stty erase ^?
exit 0