mirror of https://github.com/jumpserver/jumpserver
commit
3b56027edc
|
@ -17,6 +17,7 @@ dump.rdb
|
|||
.idea/
|
||||
db.sqlite3
|
||||
config.py
|
||||
config.yml
|
||||
*.log
|
||||
host_rsa_key
|
||||
*.bat
|
||||
|
|
|
@ -6,13 +6,13 @@ RUN useradd jumpserver
|
|||
|
||||
COPY ./requirements /tmp/requirements
|
||||
|
||||
RUN yum -y install epel-release && cd /tmp/requirements && \
|
||||
RUN yum -y install epel-release openldap-clients telnet && cd /tmp/requirements && \
|
||||
yum -y install $(cat rpm_requirements.txt)
|
||||
|
||||
RUN cd /tmp/requirements && pip install -r requirements.txt
|
||||
|
||||
COPY . /opt/jumpserver
|
||||
COPY config_docker.py /opt/jumpserver/config.py
|
||||
RUN echo > config.yml
|
||||
VOLUME /opt/jumpserver/data
|
||||
VOLUME /opt/jumpserver/logs
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
|||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
|
|
|
@ -113,6 +113,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
|||
"""
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
|
@ -124,6 +125,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
|||
class AssetGatewayApi(generics.RetrieveAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.GatewayWithAuthSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
|
|
|
@ -43,6 +43,23 @@ class NodeViewSet(viewsets.ModelViewSet):
|
|||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
child_key = Node.root().get_next_child_key()
|
||||
serializer.validated_data["key"] = child_key
|
||||
serializer.save()
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
node = self.get_object()
|
||||
if node.is_root():
|
||||
node_value = node.value
|
||||
post_value = request.data.get('value')
|
||||
if node_value != post_value:
|
||||
return Response(
|
||||
{"msg": _("You can't update the root node name")},
|
||||
status=400
|
||||
)
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
|
||||
class NodeListAsTreeApi(generics.ListAPIView):
|
||||
"""
|
||||
|
@ -259,7 +276,7 @@ class RefreshNodeHardwareInfoApi(APIView):
|
|||
def get(self, request, *args, **kwargs):
|
||||
node_id = kwargs.get('pk')
|
||||
node = get_object_or_404(self.model, id=node_id)
|
||||
assets = node.assets.all()
|
||||
assets = node.get_all_assets()
|
||||
# task_name = _("更新节点资产硬件信息: {}".format(node.name))
|
||||
task_name = _("Update node asset hardware information: {}").format(node.name)
|
||||
task = update_assets_hardware_info_util.delay(assets, task_name=task_name)
|
||||
|
@ -273,7 +290,7 @@ class TestNodeConnectiveApi(APIView):
|
|||
def get(self, request, *args, **kwargs):
|
||||
node_id = kwargs.get('pk')
|
||||
node = get_object_or_404(self.model, id=node_id)
|
||||
assets = node.assets.all()
|
||||
assets = node.get_all_assets()
|
||||
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
|
||||
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
|
||||
task = test_asset_connectivity_util.delay(assets, task_name=task_name)
|
||||
|
|
|
@ -117,6 +117,7 @@ class SystemUserAssetsListView(generics.ListAPIView):
|
|||
class SystemUserPushToAssetApi(generics.RetrieveAPIView):
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
|
@ -129,6 +130,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView):
|
|||
class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView):
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
|
|
|
@ -185,7 +185,7 @@ class Asset(OrgModelMixin):
|
|||
@property
|
||||
def connectivity(self):
|
||||
if not self.is_unixlike():
|
||||
return self.UNKNOWN
|
||||
return self.REACHABLE
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cached = cache.get(key, None)
|
||||
return cached if cached is not None else self.UNKNOWN
|
||||
|
|
|
@ -58,7 +58,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
|
|||
管理用户更新关联到的集群
|
||||
"""
|
||||
nodes = serializers.PrimaryKeyRelatedField(
|
||||
many=True, queryset = Node.objects.all()
|
||||
many=True, queryset=Node.objects.all()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -66,4 +66,5 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
|
|||
fields = ['id', 'nodes']
|
||||
|
||||
|
||||
|
||||
class TaskIDSerializer(serializers.Serializer):
|
||||
task = serializers.CharField(read_only=True)
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import os
|
||||
|
||||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.cache import cache
|
||||
|
||||
from common.utils import capacity_convert, \
|
||||
sum_capacity, encrypt_password, get_logger
|
||||
from ops.celery.utils import register_as_period_task, after_app_shutdown_clean
|
||||
from common.utils import (
|
||||
capacity_convert, sum_capacity, encrypt_password, get_logger
|
||||
)
|
||||
from ops.celery.decorator import (
|
||||
register_as_period_task, after_app_shutdown_clean_periodic
|
||||
)
|
||||
|
||||
from .models import SystemUser, AdminUser, Asset
|
||||
from . import const
|
||||
|
@ -132,7 +134,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
|||
@shared_task
|
||||
def update_asset_hardware_info_manual(asset):
|
||||
task_name = _("Update asset hardware info: {}").format(asset.hostname)
|
||||
return update_assets_hardware_info_util(
|
||||
update_assets_hardware_info_util(
|
||||
[asset], task_name=task_name
|
||||
)
|
||||
|
||||
|
@ -221,12 +223,14 @@ def test_admin_user_connectivity_period():
|
|||
for admin_user in admin_users:
|
||||
task_name = _("Test admin user connectivity period: {}").format(admin_user.name)
|
||||
test_admin_user_connectivity_util(admin_user, task_name)
|
||||
cache.set(key, 1, 60*40)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_admin_user_connectivity_manual(admin_user):
|
||||
task_name = _("Test admin user connectivity: {}").format(admin_user.name)
|
||||
return test_admin_user_connectivity_util(admin_user, task_name)
|
||||
test_admin_user_connectivity_util(admin_user, task_name)
|
||||
return True
|
||||
|
||||
|
||||
## System user connective ##
|
||||
|
@ -394,13 +398,13 @@ def push_system_user_to_assets(system_user, assets):
|
|||
|
||||
|
||||
@shared_task
|
||||
@after_app_shutdown_clean
|
||||
@after_app_shutdown_clean_periodic
|
||||
def test_system_user_connectability_period():
|
||||
pass
|
||||
|
||||
|
||||
@shared_task
|
||||
@after_app_shutdown_clean
|
||||
@after_app_shutdown_clean_periodic
|
||||
def test_admin_user_connectability_period():
|
||||
pass
|
||||
|
||||
|
@ -408,7 +412,7 @@ def test_admin_user_connectability_period():
|
|||
# @shared_task
|
||||
# @register_as_period_task(interval=3600)
|
||||
# @after_app_ready_start
|
||||
# # @after_app_shutdown_clean
|
||||
# @after_app_shutdown_clean_periodic
|
||||
# def push_system_user_period():
|
||||
# for system_user in SystemUser.objects.all():
|
||||
# push_system_user_related_nodes(system_user)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -0,0 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from radiusauth.backends import RADIUSBackend, RADIUSRealmBackend
|
||||
from django.conf import settings
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class CreateUserMixin:
|
||||
def get_django_user(self, username, password=None):
|
||||
if isinstance(username, bytes):
|
||||
username = username.decode()
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
if '@' in username:
|
||||
email = username
|
||||
else:
|
||||
email_suffix = settings.EMAIL_SUFFIX
|
||||
email = '{}@{}'.format(username, email_suffix)
|
||||
user = User(username=username, name=username, email=email)
|
||||
user.source = user.SOURCE_RADIUS
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class RadiusBackend(CreateUserMixin, RADIUSBackend):
|
||||
pass
|
||||
|
||||
|
||||
class RadiusRealmBackend(CreateUserMixin, RADIUSRealmBackend):
|
||||
pass
|
|
@ -4,15 +4,20 @@
|
|||
import os
|
||||
import json
|
||||
import jms_storage
|
||||
import uuid
|
||||
|
||||
from rest_framework.views import Response, APIView
|
||||
from rest_framework import generics
|
||||
from ldap3 import Server, Connection
|
||||
from django.core.mail import get_connection, send_mail
|
||||
from django.core.mail import send_mail
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from .permissions import IsOrgAdmin, IsSuperUser
|
||||
from .serializers import MailTestSerializer, LDAPTestSerializer
|
||||
from .serializers import (
|
||||
MailTestSerializer, LDAPTestSerializer, OutputSerializer
|
||||
)
|
||||
from .models import Setting
|
||||
|
||||
|
||||
|
@ -189,4 +194,39 @@ class DjangoSettingsAPI(APIView):
|
|||
return Response(data)
|
||||
|
||||
|
||||
class LogTailApi(generics.RetrieveAPIView):
|
||||
permission_classes = ()
|
||||
buff_size = 1024 * 10
|
||||
serializer_class = OutputSerializer
|
||||
end = False
|
||||
|
||||
def is_file_finish_write(self):
|
||||
return True
|
||||
|
||||
def get_log_path(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
mark = request.query_params.get("mark") or str(uuid.uuid4())
|
||||
log_path = self.get_log_path()
|
||||
|
||||
if not log_path or not os.path.isfile(log_path):
|
||||
if self.is_file_finish_write():
|
||||
return Response({
|
||||
"data": 'Not found the log',
|
||||
'end': True,
|
||||
'mark': mark
|
||||
})
|
||||
else:
|
||||
return Response({"data": "Waiting...\r\n"}, status=200)
|
||||
|
||||
with open(log_path, 'r') as f:
|
||||
offset = cache.get(mark, 0)
|
||||
f.seek(offset)
|
||||
data = f.read(self.buff_size).replace('\n', '\r\n')
|
||||
mark = str(uuid.uuid4())
|
||||
cache.set(mark, f.tell(), 5)
|
||||
|
||||
if data == '' and self.is_file_finish_write():
|
||||
self.end = True
|
||||
return Response({"data": data, 'end': self.end, 'mark': mark})
|
||||
|
|
|
@ -19,3 +19,8 @@ class LDAPTestSerializer(serializers.Serializer):
|
|||
AUTH_LDAP_USER_ATTR_MAP = serializers.CharField()
|
||||
AUTH_LDAP_START_TLS = serializers.BooleanField(required=False)
|
||||
|
||||
|
||||
class OutputSerializer(serializers.Serializer):
|
||||
output = serializers.CharField()
|
||||
is_end = serializers.BooleanField()
|
||||
mark = serializers.CharField()
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import json
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import post_save, pre_save
|
||||
from django.conf import LazySettings, empty
|
||||
|
@ -8,7 +10,7 @@ from django.core.cache import cache
|
|||
|
||||
from jumpserver.utils import current_request
|
||||
from .models import Setting
|
||||
from .utils import get_logger
|
||||
from .utils import get_logger, ssh_key_gen
|
||||
from .signals import django_ready
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -16,23 +18,25 @@ logger = get_logger(__file__)
|
|||
|
||||
@receiver(post_save, sender=Setting, dispatch_uid="my_unique_identifier")
|
||||
def refresh_settings_on_changed(sender, instance=None, **kwargs):
|
||||
logger.debug("Receive setting item change")
|
||||
logger.debug(" - refresh setting: {}".format(instance.name))
|
||||
if instance:
|
||||
instance.refresh_setting()
|
||||
|
||||
|
||||
@receiver(django_ready, dispatch_uid="my_unique_identifier")
|
||||
def refresh_all_settings_on_django_ready(sender, **kwargs):
|
||||
logger.debug("Receive django ready signal")
|
||||
logger.debug(" - fresh all settings")
|
||||
def monkey_patch_settings(sender, **kwargs):
|
||||
cache_key_prefix = '_SETTING_'
|
||||
uncached_settings = [
|
||||
'CACHES', 'DEBUG', 'SECRET_KEY', 'INSTALLED_APPS',
|
||||
'ROOT_URLCONF', 'TEMPLATES', 'DATABASES', '_wrapped',
|
||||
'CELERY_LOG_DIR'
|
||||
]
|
||||
|
||||
def monkey_patch_getattr(self, name):
|
||||
key = cache_key_prefix + name
|
||||
cached = cache.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
if name not in uncached_settings:
|
||||
key = cache_key_prefix + name
|
||||
cached = cache.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
if self._wrapped is empty:
|
||||
self._setup(name)
|
||||
val = getattr(self._wrapped, name)
|
||||
|
@ -62,6 +66,18 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
|
|||
pass
|
||||
|
||||
|
||||
@receiver(django_ready)
|
||||
def auto_generate_terminal_host_key(sender, **kwargs):
|
||||
try:
|
||||
if Setting.objects.filter(name='TERMINAL_HOST_KEY').exists():
|
||||
return
|
||||
private_key, public_key = ssh_key_gen()
|
||||
value = json.dumps(private_key)
|
||||
Setting.objects.create(name='TERMINAL_HOST_KEY', value=value)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@receiver(pre_save, dispatch_uid="my_unique_identifier")
|
||||
def on_create_set_created_by(sender, instance=None, **kwargs):
|
||||
if getattr(instance, '_ignore_auto_created_by', False) is True:
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<option value ="s3">s3</option>
|
||||
<option value="oss">oss</option>
|
||||
<option value ="azure">azure</option>
|
||||
<option value="ceph">ceph</option>
|
||||
{# <option value="ceph">ceph</option>#}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -108,15 +108,21 @@
|
|||
<label class="col-md-2 control-label" for="id_endpoint">{% trans "Endpoint" %}</label>
|
||||
<div class="col-md-9">
|
||||
<input id="id_endpoint" class="form-control" type="text" name="ENDPOINT" value="" placeholder="Endpoint">
|
||||
<div class="help-block">
|
||||
<span class="oss">
|
||||
{% trans 'OSS: http://{REGION_NAME}.aliyuncs.com' %}
|
||||
<br>
|
||||
{% trans 'Example: http://oss-cn-hangzhou.aliyuncs.com' %}
|
||||
</span>
|
||||
<span class="s3">{% trans 'S3: http://s3.{REGION_NAME}.amazonaws.com' %}<br></span>
|
||||
<span class="s3">{% trans 'S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn' %}<br></span>
|
||||
<span class="s3">{% trans 'Example: http://s3.cn-north-1.amazonaws.com.cn' %}<br></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="display: none;" >
|
||||
<label class="col-md-2 control-label" for="id_endpoint_suffix">{% trans "Endpoint suffix" %}</label>
|
||||
{# <div class="col-md-9">#}
|
||||
{# <input id="id_endpoint_suffix" class="form-control" type="text" name="ENDPOINT_SUFFIX" value="">#}
|
||||
{# <div class="help-block">{% trans '' %}</div>#}
|
||||
{# </div>#}
|
||||
<div class="col-md-9">
|
||||
<select id="id_endpoint_suffix" name="ENDPOINT_SUFFIX" class="endpoint-suffix-selector form-control">
|
||||
<option value="core.chinacloudapi.cn" selected="selected">core.chinacloudapi.cn</option>
|
||||
|
@ -129,6 +135,13 @@
|
|||
<label class="col-md-2 control-label" for="id_region">{% trans "Region" %}</label>
|
||||
<div class="col-md-9">
|
||||
<input id="id_region" class="form-control" type="text" name="REGION" value="" placeholder="">
|
||||
<div class="help-block">
|
||||
<span class="s3">
|
||||
{% trans 'Beijing: cn-north-1' %}
|
||||
{% trans 'Ningxia: cn-northwest-1' %}
|
||||
<a href="https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html">{% trans 'More' %}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -166,7 +179,6 @@ function hiddenField(field){
|
|||
}
|
||||
|
||||
function getFieldByType(type){
|
||||
|
||||
if(type === 'server'){
|
||||
return need_get_field_of_server
|
||||
}
|
||||
|
@ -211,15 +223,17 @@ $(document).ready(function() {
|
|||
|
||||
field_of_all = [name_id, host_id, port_id, bucket_id, access_key_id, secret_key_id, container_name_id, account_name_id, account_key_id, endpoint_id, endpoint_suffix_id, region_id];
|
||||
need_get_field_of_server = [name_id];
|
||||
need_get_field_of_s3 = [name_id, bucket_id, access_key_id, secret_key_id, region_id];
|
||||
need_get_field_of_s3 = [name_id, bucket_id, access_key_id, secret_key_id, region_id, endpoint_id];
|
||||
need_get_field_of_oss = [name_id, bucket_id, access_key_id, secret_key_id, endpoint_id];
|
||||
need_get_field_of_azure = [name_id, container_name_id, account_name_id, account_key_id, endpoint_suffix_id];
|
||||
need_get_field_of_ceph = [name_id, host_id, port_id, bucket_id, access_key_id, secret_key_id, region_id];
|
||||
})
|
||||
.on('change', '.selector', function(){
|
||||
var type = $('.selector').val();
|
||||
console.log(type);
|
||||
$("." + type).show();
|
||||
hiddenField(field_of_all);
|
||||
$('.help-block').children().hide();
|
||||
$('.help-block ' + '.' + type).show();
|
||||
var field = getFieldByType(type);
|
||||
showField(field)
|
||||
})
|
||||
|
|
|
@ -406,24 +406,6 @@ def get_replay_storage_setting():
|
|||
return value
|
||||
|
||||
|
||||
class TeeObj:
|
||||
origin_stdout = sys.stdout
|
||||
|
||||
def __init__(self, file_obj):
|
||||
self.file_obj = file_obj
|
||||
|
||||
def write(self, msg):
|
||||
self.origin_stdout.write(msg)
|
||||
self.file_obj.write(msg.replace('*', ''))
|
||||
|
||||
def flush(self):
|
||||
self.origin_stdout.flush()
|
||||
self.file_obj.flush()
|
||||
|
||||
def close(self):
|
||||
self.file_obj.close()
|
||||
|
||||
|
||||
def with_cache(func):
|
||||
cache = {}
|
||||
key = "_{}.{}".format(func.__module__, func.__name__)
|
||||
|
|
|
@ -193,14 +193,16 @@ class Config(dict):
|
|||
if self.root_path:
|
||||
filename = os.path.join(self.root_path, filename)
|
||||
try:
|
||||
with open(filename) as json_file:
|
||||
obj = yaml.load(json_file)
|
||||
with open(filename) as f:
|
||||
obj = yaml.load(f)
|
||||
except IOError as e:
|
||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||
return False
|
||||
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
|
||||
raise
|
||||
return self.from_mapping(obj)
|
||||
if obj:
|
||||
return self.from_mapping(obj)
|
||||
return True
|
||||
|
||||
def from_mapping(self, *mapping, **kwargs):
|
||||
"""Updates the config like :meth:`update` ignoring items with non-upper
|
||||
|
@ -278,6 +280,8 @@ class Config(dict):
|
|||
return value
|
||||
value = os.environ.get(item, None)
|
||||
if value is not None:
|
||||
if value.isdigit():
|
||||
value = int(value)
|
||||
return value
|
||||
return self.defaults.get(item)
|
||||
|
||||
|
@ -286,8 +290,8 @@ class Config(dict):
|
|||
|
||||
|
||||
defaults = {
|
||||
'SECRET_KEY': '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x',
|
||||
'BOOTSTRAP_TOKEN': 'PleaseChangeMe',
|
||||
'SECRET_KEY': '',
|
||||
'BOOTSTRAP_TOKEN': '',
|
||||
'DEBUG': True,
|
||||
'SITE_URL': 'http://localhost',
|
||||
'LOG_LEVEL': 'DEBUG',
|
||||
|
@ -312,6 +316,7 @@ defaults = {
|
|||
'SESSION_COOKIE_AGE': 3600 * 24,
|
||||
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
|
||||
'AUTH_OPENID': False,
|
||||
'OTP_VALID_WINDOW': 0,
|
||||
'OTP_ISSUER_NAME': 'Jumpserver',
|
||||
'EMAIL_SUFFIX': 'jumpserver.org',
|
||||
'TERMINAL_PASSWORD_AUTH': True,
|
||||
|
@ -320,6 +325,7 @@ defaults = {
|
|||
'TERMINAL_ASSET_LIST_SORT_BY': 'hostname',
|
||||
'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto',
|
||||
'TERMINAL_SESSION_KEEP_DURATION': 9999,
|
||||
'TERMINAL_HOST_KEY': '',
|
||||
'SECURITY_MFA_AUTH': False,
|
||||
'SECURITY_LOGIN_LIMIT_COUNT': 7,
|
||||
'SECURITY_LOGIN_LIMIT_TIME': 30,
|
||||
|
@ -330,21 +336,48 @@ defaults = {
|
|||
'SECURITY_PASSWORD_LOWER_CASE': False,
|
||||
'SECURITY_PASSWORD_NUMBER': False,
|
||||
'SECURITY_PASSWORD_SPECIAL_CHAR': False,
|
||||
'AUTH_RADIUS': False,
|
||||
'RADIUS_SERVER': 'localhost',
|
||||
'RADIUS_PORT': 1812,
|
||||
'RADIUS_SECRET': '',
|
||||
'HTTP_BIND_HOST': '0.0.0.0',
|
||||
'HTTP_LISTEN_PORT': 8080,
|
||||
}
|
||||
|
||||
|
||||
def load_from_object(config):
|
||||
try:
|
||||
from config import config as c
|
||||
config.from_object(c)
|
||||
return True
|
||||
except ImportError:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def load_from_yml(config):
|
||||
for i in ['config.yml', 'config.yaml']:
|
||||
if not os.path.isfile(os.path.join(config.root_path, i)):
|
||||
continue
|
||||
loaded = config.from_yaml(i)
|
||||
if loaded:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def load_user_config():
|
||||
sys.path.insert(0, PROJECT_DIR)
|
||||
config = Config(PROJECT_DIR, defaults)
|
||||
try:
|
||||
from config import config as c
|
||||
config.from_object(c)
|
||||
except ImportError:
|
||||
|
||||
loaded = load_from_object(config)
|
||||
if not loaded:
|
||||
loaded = load_from_yml(config)
|
||||
if not loaded:
|
||||
msg = """
|
||||
|
||||
Error: No config file found.
|
||||
|
||||
You can run `cp config_example.py config.py`, and edit it.
|
||||
You can run `cp config_example.yml config.yml`, and edit it.
|
||||
"""
|
||||
raise ImportError(msg)
|
||||
return config
|
||||
|
|
|
@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
|
|||
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
|
||||
import ldap
|
||||
# from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
||||
|
@ -23,6 +24,12 @@ from .conf import load_user_config
|
|||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
||||
CONFIG = load_user_config()
|
||||
LOG_DIR = os.path.join(PROJECT_DIR, 'logs')
|
||||
JUMPSERVER_LOG_FILE = os.path.join(LOG_DIR, 'jumpserver.log')
|
||||
ANSIBLE_LOG_FILE = os.path.join(LOG_DIR, 'ansible.log')
|
||||
|
||||
if not os.path.isdir(LOG_DIR):
|
||||
os.makedirs(LOG_DIR)
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
||||
|
@ -209,19 +216,21 @@ LOGGING = {
|
|||
'formatter': 'main'
|
||||
},
|
||||
'file': {
|
||||
'encoding': 'utf8',
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.TimedRotatingFileHandler',
|
||||
'when': "D",
|
||||
'interval': 1,
|
||||
"backupCount": 7,
|
||||
'formatter': 'main',
|
||||
'filename': os.path.join(PROJECT_DIR, 'logs', 'jumpserver.log')
|
||||
'filename': JUMPSERVER_LOG_FILE,
|
||||
},
|
||||
'ansible_logs': {
|
||||
'encoding': 'utf8',
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.FileHandler',
|
||||
'formatter': 'main',
|
||||
'filename': os.path.join(PROJECT_DIR, 'logs', 'ansible.log')
|
||||
'filename': ANSIBLE_LOG_FILE,
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
|
@ -400,6 +409,19 @@ if AUTH_OPENID:
|
|||
AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[0])
|
||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[1])
|
||||
|
||||
# Radius Auth
|
||||
AUTH_RADIUS = CONFIG.AUTH_RADIUS
|
||||
AUTH_RADIUS_BACKEND = 'authentication.radius.backends.RadiusBackend'
|
||||
RADIUS_SERVER = CONFIG.RADIUS_SERVER
|
||||
RADIUS_PORT = CONFIG.RADIUS_PORT
|
||||
RADIUS_SECRET = CONFIG.RADIUS_SECRET
|
||||
|
||||
if AUTH_RADIUS:
|
||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_RADIUS_BACKEND)
|
||||
|
||||
# Dump all celery log to here
|
||||
CELERY_LOG_DIR = os.path.join(PROJECT_DIR, 'data', 'celery')
|
||||
|
||||
# Celery using redis as broker
|
||||
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
|
||||
'password': CONFIG.REDIS_PASSWORD,
|
||||
|
@ -413,14 +435,16 @@ CELERY_RESULT_BACKEND = CELERY_BROKER_URL
|
|||
CELERY_ACCEPT_CONTENT = ['json', 'pickle']
|
||||
CELERY_RESULT_EXPIRES = 3600
|
||||
# CELERY_WORKER_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
|
||||
CELERY_WORKER_LOG_FORMAT = '%(message)s'
|
||||
# CELERY_WORKER_TASK_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
|
||||
CELERY_WORKER_TASK_LOG_FORMAT = '%(message)s'
|
||||
# CELERY_WORKER_LOG_FORMAT = '%(message)s'
|
||||
CELERY_WORKER_TASK_LOG_FORMAT = '%(task_id)s %(task_name)s %(message)s'
|
||||
# CELERY_WORKER_TASK_LOG_FORMAT = '%(message)s'
|
||||
# CELERY_WORKER_LOG_FORMAT = '%(asctime)s [%(module)s %(levelname)s] %(message)s'
|
||||
CELERY_WORKER_LOG_FORMAT = '%(message)s'
|
||||
CELERY_TASK_EAGER_PROPAGATES = True
|
||||
CELERY_REDIRECT_STDOUTS = True
|
||||
CELERY_REDIRECT_STDOUTS_LEVEL = "INFO"
|
||||
CELERY_WORKER_HIJACK_ROOT_LOGGER = False
|
||||
CELERY_WORKER_REDIRECT_STDOUTS = True
|
||||
CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO"
|
||||
# CELERY_WORKER_HIJACK_ROOT_LOGGER = False
|
||||
CELERY_WORKER_MAX_TASKS_PER_CHILD = 40
|
||||
|
||||
# Cache use redis
|
||||
CACHES = {
|
||||
|
@ -492,6 +516,7 @@ TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL
|
|||
TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY
|
||||
TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE
|
||||
TERMINAL_SESSION_KEEP_DURATION = CONFIG.TERMINAL_SESSION_KEEP_DURATION
|
||||
TERMINAL_HOST_KEY = CONFIG.TERMINAL_HOST_KEY
|
||||
|
||||
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
||||
BOOTSTRAP3 = {
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Jumpserver 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-12-27 15:48+0800\n"
|
||||
"POT-Creation-Date: 2019-01-28 12:56+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
||||
|
@ -17,11 +17,15 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: assets/api/node.py:261
|
||||
#: assets/api/node.py:58
|
||||
msgid "You can't update the root node name"
|
||||
msgstr "不能修改根节点名称"
|
||||
|
||||
#: assets/api/node.py:281
|
||||
msgid "Update node asset hardware information: {}"
|
||||
msgstr "更新节点资产硬件信息: {}"
|
||||
|
||||
#: assets/api/node.py:275
|
||||
#: assets/api/node.py:295
|
||||
msgid "Test if the assets under the node are connectable: {}"
|
||||
msgstr "测试节点下资产是否可连接: {}"
|
||||
|
||||
|
@ -65,9 +69,10 @@ msgstr "网域"
|
|||
#: assets/forms/asset.py:124 assets/models/node.py:31
|
||||
#: assets/templates/assets/asset_create.html:30
|
||||
#: assets/templates/assets/asset_update.html:35 perms/forms.py:45
|
||||
#: perms/forms.py:52 perms/models.py:79
|
||||
#: perms/forms.py:52 perms/models.py:85
|
||||
#: perms/templates/perms/asset_permission_list.html:57
|
||||
#: perms/templates/perms/asset_permission_list.html:117
|
||||
#: perms/templates/perms/asset_permission_list.html:78
|
||||
#: perms/templates/perms/asset_permission_list.html:128
|
||||
#: xpack/plugins/cloud/models.py:123
|
||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:63
|
||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66
|
||||
|
@ -118,8 +123,8 @@ msgstr "端口"
|
|||
#: perms/models.py:31
|
||||
#: perms/templates/perms/asset_permission_create_update.html:45
|
||||
#: perms/templates/perms/asset_permission_list.html:56
|
||||
#: perms/templates/perms/asset_permission_list.html:114
|
||||
#: terminal/backends/command/models.py:13 terminal/models.py:141
|
||||
#: perms/templates/perms/asset_permission_list.html:125
|
||||
#: terminal/backends/command/models.py:13 terminal/models.py:155
|
||||
#: terminal/templates/terminal/command_list.html:40
|
||||
#: terminal/templates/terminal/command_list.html:73
|
||||
#: terminal/templates/terminal/session_list.html:41
|
||||
|
@ -156,10 +161,11 @@ msgstr "不能包含特殊字符"
|
|||
#: orgs/models.py:12 perms/models.py:28
|
||||
#: perms/templates/perms/asset_permission_detail.html:62
|
||||
#: perms/templates/perms/asset_permission_list.html:53
|
||||
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:20
|
||||
#: terminal/models.py:198 terminal/templates/terminal/terminal_detail.html:43
|
||||
#: perms/templates/perms/asset_permission_list.html:72
|
||||
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:22
|
||||
#: terminal/models.py:233 terminal/templates/terminal/terminal_detail.html:43
|
||||
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
|
||||
#: users/models/user.py:53 users/templates/users/_select_user_modal.html:13
|
||||
#: users/models/user.py:55 users/templates/users/_select_user_modal.html:13
|
||||
#: users/templates/users/user_detail.html:63
|
||||
#: users/templates/users/user_group_detail.html:55
|
||||
#: users/templates/users/user_group_list.html:12
|
||||
|
@ -183,8 +189,9 @@ msgstr "名称"
|
|||
#: assets/templates/assets/system_user_detail.html:62
|
||||
#: assets/templates/assets/system_user_list.html:30
|
||||
#: audits/templates/audits/login_log_list.html:49
|
||||
#: perms/templates/perms/asset_permission_list.html:74
|
||||
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15
|
||||
#: users/forms.py:33 users/models/authentication.py:77 users/models/user.py:51
|
||||
#: users/forms.py:33 users/models/authentication.py:77 users/models/user.py:53
|
||||
#: users/templates/users/_select_user_modal.html:14
|
||||
#: users/templates/users/login.html:64
|
||||
#: users/templates/users/user_detail.html:67
|
||||
|
@ -210,7 +217,7 @@ msgstr "密码或密钥密码"
|
|||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
#: assets/forms/user.py:29 users/models/user.py:80
|
||||
#: assets/forms/user.py:29 users/models/user.py:82
|
||||
msgid "Private key"
|
||||
msgstr "ssh私钥"
|
||||
|
||||
|
@ -274,6 +281,7 @@ msgstr "IP"
|
|||
#: assets/templates/assets/user_asset_list.html:45
|
||||
#: assets/templates/assets/user_asset_list.html:150 common/forms.py:130
|
||||
#: perms/templates/perms/asset_permission_asset.html:54
|
||||
#: perms/templates/perms/asset_permission_list.html:77
|
||||
#: users/templates/users/user_granted_asset.html:44
|
||||
#: users/templates/users/user_group_granted_asset.html:44
|
||||
msgid "Hostname"
|
||||
|
@ -381,8 +389,8 @@ msgstr "标签管理"
|
|||
#: assets/templates/assets/domain_detail.html:72
|
||||
#: assets/templates/assets/system_user_detail.html:100
|
||||
#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:37
|
||||
#: perms/models.py:84 perms/templates/perms/asset_permission_detail.html:98
|
||||
#: users/models/user.py:94 users/templates/users/user_detail.html:111
|
||||
#: perms/models.py:90 perms/templates/perms/asset_permission_detail.html:98
|
||||
#: users/models/user.py:96 users/templates/users/user_detail.html:111
|
||||
#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127
|
||||
msgid "Created by"
|
||||
msgstr "创建者"
|
||||
|
@ -394,7 +402,7 @@ msgstr "创建者"
|
|||
#: assets/templates/assets/domain_detail.html:68
|
||||
#: assets/templates/assets/system_user_detail.html:96
|
||||
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64
|
||||
#: orgs/models.py:16 perms/models.py:38 perms/models.py:85
|
||||
#: orgs/models.py:16 perms/models.py:38 perms/models.py:91
|
||||
#: perms/templates/perms/asset_permission_detail.html:94
|
||||
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
|
||||
#: users/templates/users/user_group_detail.html:63
|
||||
|
@ -422,9 +430,9 @@ msgstr "创建日期"
|
|||
#: assets/templates/assets/system_user_list.html:37
|
||||
#: assets/templates/assets/user_asset_list.html:159 common/models.py:34
|
||||
#: ops/models/adhoc.py:43 orgs/models.py:17 perms/models.py:39
|
||||
#: perms/models.py:86 perms/templates/perms/asset_permission_detail.html:102
|
||||
#: terminal/models.py:30 terminal/templates/terminal/terminal_detail.html:63
|
||||
#: users/models/group.py:15 users/models/user.py:86
|
||||
#: perms/models.py:92 perms/templates/perms/asset_permission_detail.html:102
|
||||
#: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63
|
||||
#: users/models/group.py:15 users/models/user.py:88
|
||||
#: users/templates/users/user_detail.html:127
|
||||
#: users/templates/users/user_group_detail.html:67
|
||||
#: users/templates/users/user_group_list.html:14
|
||||
|
@ -457,7 +465,7 @@ msgstr "可连接"
|
|||
|
||||
#: assets/models/asset.py:119 assets/models/base.py:36
|
||||
msgid "Unknown"
|
||||
msgstr ""
|
||||
msgstr "未知"
|
||||
|
||||
#: assets/models/base.py:25
|
||||
msgid "SSH private key"
|
||||
|
@ -475,7 +483,7 @@ msgstr "带宽"
|
|||
msgid "Contact"
|
||||
msgstr "联系人"
|
||||
|
||||
#: assets/models/cluster.py:22 users/models/user.py:72
|
||||
#: assets/models/cluster.py:22 users/models/user.py:74
|
||||
#: users/templates/users/user_detail.html:76
|
||||
msgid "Phone"
|
||||
msgstr "手机"
|
||||
|
@ -501,7 +509,7 @@ msgid "Default"
|
|||
msgstr "默认"
|
||||
|
||||
#: assets/models/cluster.py:36 assets/models/label.py:14
|
||||
#: users/models/user.py:439
|
||||
#: users/models/user.py:441
|
||||
msgid "System"
|
||||
msgstr "系统"
|
||||
|
||||
|
@ -529,8 +537,8 @@ msgstr "BGP全网通"
|
|||
msgid "Regex"
|
||||
msgstr "正则表达式"
|
||||
|
||||
#: assets/models/cmd_filter.py:36 ops/models/command.py:19
|
||||
#: ops/templates/ops/command_execution_list.html:60 terminal/models.py:147
|
||||
#: assets/models/cmd_filter.py:36 ops/models/command.py:21
|
||||
#: ops/templates/ops/command_execution_list.html:60 terminal/models.py:161
|
||||
#: terminal/templates/terminal/command_list.html:55
|
||||
#: terminal/templates/terminal/command_list.html:71
|
||||
#: terminal/templates/terminal/session_detail.html:48
|
||||
|
@ -633,13 +641,13 @@ msgstr "默认资产组"
|
|||
#: perms/models.py:29
|
||||
#: perms/templates/perms/asset_permission_create_update.html:41
|
||||
#: perms/templates/perms/asset_permission_list.html:54
|
||||
#: perms/templates/perms/asset_permission_list.html:108 templates/index.html:87
|
||||
#: terminal/backends/command/models.py:12 terminal/models.py:140
|
||||
#: perms/templates/perms/asset_permission_list.html:119 templates/index.html:87
|
||||
#: terminal/backends/command/models.py:12 terminal/models.py:154
|
||||
#: terminal/templates/terminal/command_list.html:32
|
||||
#: terminal/templates/terminal/command_list.html:72
|
||||
#: terminal/templates/terminal/session_list.html:33
|
||||
#: terminal/templates/terminal/session_list.html:71 users/forms.py:303
|
||||
#: users/models/user.py:33 users/models/user.py:427
|
||||
#: users/models/user.py:33 users/models/user.py:429
|
||||
#: users/templates/users/user_group_detail.html:78
|
||||
#: users/templates/users/user_group_list.html:13 users/views/user.py:386
|
||||
#: xpack/plugins/orgs/forms.py:26
|
||||
|
@ -717,11 +725,12 @@ msgstr "登录模式"
|
|||
#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:156
|
||||
#: audits/models.py:19 audits/templates/audits/ftp_log_list.html:49
|
||||
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:48
|
||||
#: perms/models.py:33 perms/models.py:81
|
||||
#: perms/models.py:33 perms/models.py:87
|
||||
#: perms/templates/perms/asset_permission_detail.html:140
|
||||
#: perms/templates/perms/asset_permission_list.html:58
|
||||
#: perms/templates/perms/asset_permission_list.html:120 templates/_nav.html:25
|
||||
#: terminal/backends/command/models.py:14 terminal/models.py:142
|
||||
#: perms/templates/perms/asset_permission_list.html:79
|
||||
#: perms/templates/perms/asset_permission_list.html:131 templates/_nav.html:25
|
||||
#: terminal/backends/command/models.py:14 terminal/models.py:156
|
||||
#: terminal/templates/terminal/command_list.html:48
|
||||
#: terminal/templates/terminal/command_list.html:74
|
||||
#: terminal/templates/terminal/session_list.html:49
|
||||
|
@ -735,68 +744,68 @@ msgstr "系统用户"
|
|||
msgid "%(value)s is not an even number"
|
||||
msgstr "%(value)s is not an even number"
|
||||
|
||||
#: assets/tasks.py:31
|
||||
#: assets/tasks.py:33
|
||||
msgid "Asset has been disabled, skipped: {}"
|
||||
msgstr "资产或许不支持ansible, 跳过: {}"
|
||||
|
||||
#: assets/tasks.py:35
|
||||
#: assets/tasks.py:37
|
||||
msgid "Asset may not be support ansible, skipped: {}"
|
||||
msgstr "资产或许不支持ansible, 跳过: {}"
|
||||
|
||||
#: assets/tasks.py:40
|
||||
#: assets/tasks.py:42
|
||||
msgid "No assets matched, stop task"
|
||||
msgstr "没有匹配到资产,结束任务"
|
||||
|
||||
#: assets/tasks.py:65
|
||||
#: assets/tasks.py:67
|
||||
msgid "Get asset info failed: {}"
|
||||
msgstr "获取资产信息失败:{}"
|
||||
|
||||
#: assets/tasks.py:115
|
||||
#: assets/tasks.py:117
|
||||
msgid "Update some assets hardware info"
|
||||
msgstr "更新资产硬件信息"
|
||||
|
||||
#: assets/tasks.py:134
|
||||
#: assets/tasks.py:136
|
||||
msgid "Update asset hardware info: {}"
|
||||
msgstr "更新资产硬件信息: {}"
|
||||
|
||||
#: assets/tasks.py:159
|
||||
#: assets/tasks.py:161
|
||||
msgid "Test assets connectivity"
|
||||
msgstr "测试资产可连接性"
|
||||
|
||||
#: assets/tasks.py:183
|
||||
#: assets/tasks.py:185
|
||||
msgid "Test assets connectivity: {}"
|
||||
msgstr "测试资产可连接性: {}"
|
||||
|
||||
#: assets/tasks.py:222
|
||||
#: assets/tasks.py:224
|
||||
msgid "Test admin user connectivity period: {}"
|
||||
msgstr "定期测试管理账号可连接性: {}"
|
||||
|
||||
#: assets/tasks.py:228
|
||||
#: assets/tasks.py:231
|
||||
msgid "Test admin user connectivity: {}"
|
||||
msgstr "测试管理行号可连接性: {}"
|
||||
|
||||
#: assets/tasks.py:266
|
||||
#: assets/tasks.py:270
|
||||
msgid "Test system user connectivity: {}"
|
||||
msgstr "测试系统用户可连接性: {}"
|
||||
|
||||
#: assets/tasks.py:273
|
||||
#: assets/tasks.py:277
|
||||
msgid "Test system user connectivity: {} => {}"
|
||||
msgstr "测试系统用户可连接性: {} => {}"
|
||||
|
||||
#: assets/tasks.py:286
|
||||
#: assets/tasks.py:290
|
||||
msgid "Test system user connectivity period: {}"
|
||||
msgstr "定期测试系统用户可连接性: {}"
|
||||
|
||||
#: assets/tasks.py:358
|
||||
#: assets/tasks.py:362
|
||||
msgid ""
|
||||
"Push system user task skip, auto push not enable or protocol is not ssh: {}"
|
||||
msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh: {}"
|
||||
|
||||
#: assets/tasks.py:378 assets/tasks.py:392
|
||||
#: assets/tasks.py:382 assets/tasks.py:396
|
||||
msgid "Push system users to assets: {}"
|
||||
msgstr "推送系统用户到入资产: {}"
|
||||
|
||||
#: assets/tasks.py:384
|
||||
#: assets/tasks.py:388
|
||||
msgid "Push system users to asset: {} => {}"
|
||||
msgstr "推送系统用户到入资产: {} => {}"
|
||||
|
||||
|
@ -880,7 +889,7 @@ msgstr "自动生成密钥"
|
|||
#: assets/templates/assets/asset_update.html:64
|
||||
#: assets/templates/assets/gateway_create_update.html:53
|
||||
#: perms/templates/perms/asset_permission_create_update.html:50
|
||||
#: terminal/templates/terminal/terminal_update.html:42
|
||||
#: terminal/templates/terminal/terminal_update.html:40
|
||||
msgid "Other"
|
||||
msgstr "其它"
|
||||
|
||||
|
@ -898,11 +907,11 @@ msgstr "其它"
|
|||
#: common/templates/common/command_storage_create.html:79
|
||||
#: common/templates/common/email_setting.html:62
|
||||
#: common/templates/common/ldap_setting.html:62
|
||||
#: common/templates/common/replay_storage_create.html:138
|
||||
#: common/templates/common/replay_storage_create.html:151
|
||||
#: common/templates/common/security_setting.html:70
|
||||
#: common/templates/common/terminal_setting.html:68
|
||||
#: perms/templates/perms/asset_permission_create_update.html:80
|
||||
#: terminal/templates/terminal/terminal_update.html:47
|
||||
#: terminal/templates/terminal/terminal_update.html:45
|
||||
#: users/templates/users/_user.html:50
|
||||
#: users/templates/users/user_bulk_update.html:23
|
||||
#: users/templates/users/user_detail.html:176
|
||||
|
@ -931,13 +940,13 @@ msgstr "重置"
|
|||
#: common/templates/common/command_storage_create.html:80
|
||||
#: common/templates/common/email_setting.html:63
|
||||
#: common/templates/common/ldap_setting.html:63
|
||||
#: common/templates/common/replay_storage_create.html:139
|
||||
#: common/templates/common/replay_storage_create.html:152
|
||||
#: common/templates/common/security_setting.html:71
|
||||
#: common/templates/common/terminal_setting.html:70
|
||||
#: perms/templates/perms/asset_permission_create_update.html:81
|
||||
#: terminal/templates/terminal/command_list.html:103
|
||||
#: terminal/templates/terminal/session_list.html:127
|
||||
#: terminal/templates/terminal/terminal_update.html:48
|
||||
#: terminal/templates/terminal/terminal_update.html:46
|
||||
#: users/templates/users/_user.html:51
|
||||
#: users/templates/users/forgot_password.html:45
|
||||
#: users/templates/users/user_bulk_update.html:24
|
||||
|
@ -1021,7 +1030,7 @@ msgstr "测试"
|
|||
#: assets/templates/assets/system_user_detail.html:26
|
||||
#: assets/templates/assets/system_user_list.html:92 audits/models.py:32
|
||||
#: perms/templates/perms/asset_permission_detail.html:30
|
||||
#: perms/templates/perms/asset_permission_list.html:166
|
||||
#: perms/templates/perms/asset_permission_list.html:177
|
||||
#: terminal/templates/terminal/terminal_detail.html:16
|
||||
#: terminal/templates/terminal/terminal_list.html:71
|
||||
#: users/templates/users/user_detail.html:25
|
||||
|
@ -1056,7 +1065,7 @@ msgstr "更新"
|
|||
#: common/templates/common/terminal_setting.html:112
|
||||
#: ops/templates/ops/task_list.html:72
|
||||
#: perms/templates/perms/asset_permission_detail.html:34
|
||||
#: perms/templates/perms/asset_permission_list.html:167
|
||||
#: perms/templates/perms/asset_permission_list.html:178
|
||||
#: terminal/templates/terminal/terminal_list.html:73
|
||||
#: users/templates/users/user_detail.html:30
|
||||
#: users/templates/users/user_group_detail.html:32
|
||||
|
@ -1166,10 +1175,9 @@ msgstr "快速修改"
|
|||
|
||||
#: assets/templates/assets/asset_detail.html:151
|
||||
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:34
|
||||
#: perms/models.py:82
|
||||
#: perms/models.py:88
|
||||
#: perms/templates/perms/asset_permission_create_update.html:52
|
||||
#: perms/templates/perms/asset_permission_detail.html:120
|
||||
#: perms/templates/perms/asset_permission_list.html:59
|
||||
#: terminal/templates/terminal/terminal_list.html:34
|
||||
#: users/templates/users/_select_user_modal.html:18
|
||||
#: users/templates/users/user_detail.html:144
|
||||
|
@ -1657,7 +1665,7 @@ msgstr "系统用户资产"
|
|||
#: audits/templates/audits/ftp_log_list.html:73
|
||||
#: audits/templates/audits/operate_log_list.html:70
|
||||
#: audits/templates/audits/password_change_log_list.html:52
|
||||
#: terminal/models.py:144 terminal/templates/terminal/session_list.html:74
|
||||
#: terminal/models.py:158 terminal/templates/terminal/session_list.html:74
|
||||
#: terminal/templates/terminal/terminal_detail.html:47
|
||||
msgid "Remote addr"
|
||||
msgstr "远端地址"
|
||||
|
@ -1700,7 +1708,7 @@ msgstr "修改者"
|
|||
#: ops/templates/ops/adhoc_history_detail.html:61
|
||||
#: ops/templates/ops/command_execution_list.html:65
|
||||
#: ops/templates/ops/task_history.html:58 perms/models.py:35
|
||||
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:151
|
||||
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:165
|
||||
#: terminal/templates/terminal/session_list.html:78
|
||||
msgid "Date start"
|
||||
msgstr "开始日期"
|
||||
|
@ -1744,7 +1752,7 @@ msgid "City"
|
|||
msgstr "城市"
|
||||
|
||||
#: audits/templates/audits/login_log_list.html:54 users/forms.py:162
|
||||
#: users/models/authentication.py:82 users/models/user.py:75
|
||||
#: users/models/authentication.py:82 users/models/user.py:77
|
||||
#: users/templates/users/first_login.html:45
|
||||
msgid "MFA"
|
||||
msgstr "MFA"
|
||||
|
@ -1801,33 +1809,33 @@ msgstr "登录日志"
|
|||
msgid "Command execution list"
|
||||
msgstr "命令执行列表"
|
||||
|
||||
#: common/api.py:22
|
||||
#: common/api.py:27
|
||||
msgid "Test mail sent to {}, please check"
|
||||
msgstr "邮件已经发送{}, 请检查"
|
||||
|
||||
#: common/api.py:46
|
||||
#: common/api.py:51
|
||||
msgid "Test ldap success"
|
||||
msgstr "连接LDAP成功"
|
||||
|
||||
#: common/api.py:76
|
||||
#: common/api.py:81
|
||||
msgid "Search no entry matched in ou {}"
|
||||
msgstr "在ou:{}中没有匹配条目"
|
||||
|
||||
#: common/api.py:85
|
||||
#: common/api.py:90
|
||||
msgid "Match {} s users"
|
||||
msgstr "匹配 {} 个用户"
|
||||
|
||||
#: common/api.py:108 common/api.py:144
|
||||
#: common/api.py:113 common/api.py:149
|
||||
msgid ""
|
||||
"Error: Account invalid (Please make sure the information such as Access key "
|
||||
"or Secret key is correct)"
|
||||
msgstr "错误:账户无效 (请确保 Access key 或 Secret key 等信息正确)"
|
||||
|
||||
#: common/api.py:114 common/api.py:150
|
||||
#: common/api.py:119 common/api.py:155
|
||||
msgid "Create succeed"
|
||||
msgstr "创建成功"
|
||||
|
||||
#: common/api.py:132 common/api.py:170
|
||||
#: common/api.py:137 common/api.py:175
|
||||
#: common/templates/common/terminal_setting.html:151
|
||||
msgid "Delete succeed"
|
||||
msgstr "删除成功"
|
||||
|
@ -2194,23 +2202,58 @@ msgstr "账户密钥"
|
|||
msgid "Endpoint"
|
||||
msgstr "端点"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:113
|
||||
#, python-brace-format
|
||||
msgid "OSS: http://{REGION_NAME}.aliyuncs.com"
|
||||
msgstr "OSS: http://{REGION_NAME}.aliyuncs.com"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:115
|
||||
msgid "Example: http://oss-cn-hangzhou.aliyuncs.com"
|
||||
msgstr "如: http://oss-cn-hangzhou.aliyuncs.com"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:117
|
||||
#, python-brace-format
|
||||
msgid "S3: http://s3.{REGION_NAME}.amazonaws.com"
|
||||
msgstr "S3: http://s3.{REGION_NAME}.amazonaws.com"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:118
|
||||
#, python-brace-format
|
||||
msgid "S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn"
|
||||
msgstr "S3(中国): http://s3.{REGION_NAME}.amazonaws.com.cn"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:119
|
||||
msgid "Example: http://s3.cn-north-1.amazonaws.com.cn"
|
||||
msgstr "如: http://s3.cn-north-1.amazonaws.com.cn"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:125
|
||||
msgid "Endpoint suffix"
|
||||
msgstr "端点后缀"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:129
|
||||
#: common/templates/common/replay_storage_create.html:135
|
||||
#: xpack/plugins/cloud/models.py:186
|
||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83
|
||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64
|
||||
msgid "Region"
|
||||
msgstr "地域"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:140
|
||||
msgid "Beijing: cn-north-1"
|
||||
msgstr "北京: cn-north-1"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:141
|
||||
msgid "Ningxia: cn-northwest-1"
|
||||
msgstr "宁夏: cn-northwest-1"
|
||||
|
||||
#: common/templates/common/replay_storage_create.html:142
|
||||
msgid "More"
|
||||
msgstr "更多"
|
||||
|
||||
#: common/templates/common/security_setting.html:46
|
||||
msgid "Password check rule"
|
||||
msgstr "密码校验规则"
|
||||
|
||||
#: common/templates/common/terminal_setting.html:76 terminal/forms.py:27
|
||||
#: terminal/models.py:24
|
||||
#: terminal/models.py:26
|
||||
msgid "Command storage"
|
||||
msgstr "命令存储"
|
||||
|
||||
|
@ -2227,7 +2270,7 @@ msgid "Add"
|
|||
msgstr "添加"
|
||||
|
||||
#: common/templates/common/terminal_setting.html:98 terminal/forms.py:32
|
||||
#: terminal/models.py:25
|
||||
#: terminal/models.py:27
|
||||
msgid "Replay storage"
|
||||
msgstr "录像存储"
|
||||
|
||||
|
@ -2272,10 +2315,6 @@ msgstr ""
|
|||
"div><div>如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运</"
|
||||
"div>"
|
||||
|
||||
#: ops/api/celery.py:32
|
||||
msgid "Waiting ..."
|
||||
msgstr ""
|
||||
|
||||
#: ops/models/adhoc.py:38
|
||||
msgid "Interval"
|
||||
msgstr "间隔"
|
||||
|
@ -2320,52 +2359,60 @@ msgstr "Become"
|
|||
msgid "Create by"
|
||||
msgstr "创建者"
|
||||
|
||||
#: ops/models/adhoc.py:324
|
||||
#: ops/models/adhoc.py:223
|
||||
msgid "{} Start task: {}"
|
||||
msgstr "{} 任务开始: {}"
|
||||
|
||||
#: ops/models/adhoc.py:226
|
||||
msgid "{} Task finish"
|
||||
msgstr "{} 任务结束"
|
||||
|
||||
#: ops/models/adhoc.py:323
|
||||
msgid "Start time"
|
||||
msgstr "开始时间"
|
||||
|
||||
#: ops/models/adhoc.py:325
|
||||
#: ops/models/adhoc.py:324
|
||||
msgid "End time"
|
||||
msgstr "完成时间"
|
||||
|
||||
#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_history.html:57
|
||||
#: ops/models/adhoc.py:325 ops/templates/ops/adhoc_history.html:57
|
||||
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41
|
||||
msgid "Time"
|
||||
msgstr "时间"
|
||||
|
||||
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_detail.html:106
|
||||
#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_detail.html:106
|
||||
#: ops/templates/ops/adhoc_history.html:55
|
||||
#: ops/templates/ops/adhoc_history_detail.html:69
|
||||
#: ops/templates/ops/task_detail.html:84 ops/templates/ops/task_history.html:61
|
||||
msgid "Is finished"
|
||||
msgstr "是否完成"
|
||||
|
||||
#: ops/models/adhoc.py:328 ops/templates/ops/adhoc_history.html:56
|
||||
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_history.html:56
|
||||
#: ops/templates/ops/task_history.html:62
|
||||
msgid "Is success"
|
||||
msgstr "是否成功"
|
||||
|
||||
#: ops/models/adhoc.py:329
|
||||
#: ops/models/adhoc.py:328
|
||||
msgid "Adhoc raw result"
|
||||
msgstr "结果"
|
||||
|
||||
#: ops/models/adhoc.py:330
|
||||
#: ops/models/adhoc.py:329
|
||||
msgid "Adhoc result summary"
|
||||
msgstr "汇总"
|
||||
|
||||
#: ops/models/command.py:20 xpack/plugins/cloud/models.py:170
|
||||
#: ops/models/command.py:22 xpack/plugins/cloud/models.py:170
|
||||
msgid "Result"
|
||||
msgstr "结果"
|
||||
|
||||
#: ops/models/command.py:55
|
||||
#: ops/models/command.py:57
|
||||
msgid "Task start"
|
||||
msgstr "任务开始"
|
||||
|
||||
#: ops/models/command.py:67
|
||||
#: ops/models/command.py:71
|
||||
msgid "Command `{}` is forbidden ........"
|
||||
msgstr "命令 `{}` 不允许被执行 ......."
|
||||
|
||||
#: ops/models/command.py:73
|
||||
#: ops/models/command.py:77
|
||||
msgid "Task end"
|
||||
msgstr "任务结束"
|
||||
|
||||
|
@ -2470,12 +2517,42 @@ msgstr "没有资产"
|
|||
msgid "Success assets"
|
||||
msgstr "成功资产"
|
||||
|
||||
#: ops/templates/ops/celery_task_log.html:4
|
||||
msgid "Task log"
|
||||
msgstr "任务列表"
|
||||
|
||||
#: ops/templates/ops/command_execution_create.html:71
|
||||
#: terminal/templates/terminal/session_detail.html:91
|
||||
#: terminal/templates/terminal/session_detail.html:100
|
||||
msgid "Go"
|
||||
msgstr ""
|
||||
|
||||
#: ops/templates/ops/command_execution_create.html:144
|
||||
msgid "Selected assets"
|
||||
msgstr "已选择资产"
|
||||
|
||||
#: ops/templates/ops/command_execution_create.html:147
|
||||
msgid "In total"
|
||||
msgstr "总共"
|
||||
|
||||
#: ops/templates/ops/command_execution_create.html:182
|
||||
msgid ""
|
||||
"Select the left asset, select the running system user, execute command in "
|
||||
"batch"
|
||||
msgstr "选择左侧资产, 选择运行的系统用户,批量执行命令"
|
||||
|
||||
#: ops/templates/ops/command_execution_create.html:200
|
||||
msgid "Unselected assets"
|
||||
msgstr "没有选中资产"
|
||||
|
||||
#: ops/templates/ops/command_execution_create.html:204
|
||||
msgid "No input command"
|
||||
msgstr "没有输入命令"
|
||||
|
||||
#: ops/templates/ops/command_execution_create.html:208
|
||||
msgid "No system user was selected"
|
||||
msgstr "没有选择系统用户"
|
||||
|
||||
#: ops/templates/ops/command_execution_create.html:253
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
@ -2560,10 +2637,11 @@ msgstr "命令执行"
|
|||
msgid "Organization"
|
||||
msgstr "组织管理"
|
||||
|
||||
#: perms/forms.py:39 perms/models.py:30 perms/models.py:80
|
||||
#: perms/forms.py:39 perms/models.py:30 perms/models.py:86
|
||||
#: perms/templates/perms/asset_permission_list.html:55
|
||||
#: perms/templates/perms/asset_permission_list.html:111 templates/_nav.html:14
|
||||
#: users/forms.py:273 users/models/group.py:26 users/models/user.py:59
|
||||
#: perms/templates/perms/asset_permission_list.html:75
|
||||
#: perms/templates/perms/asset_permission_list.html:122 templates/_nav.html:14
|
||||
#: users/forms.py:273 users/models/group.py:26 users/models/user.py:61
|
||||
#: users/templates/users/_select_user_modal.html:16
|
||||
#: users/templates/users/user_detail.html:213
|
||||
#: users/templates/users/user_list.html:26
|
||||
|
@ -2579,14 +2657,14 @@ msgstr "用户和用户组至少选一个"
|
|||
msgid "Asset or group at least one required"
|
||||
msgstr "资产和节点至少选一个"
|
||||
|
||||
#: perms/models.py:36 perms/models.py:83
|
||||
#: perms/models.py:36 perms/models.py:89
|
||||
#: perms/templates/perms/asset_permission_detail.html:90
|
||||
#: users/models/user.py:91 users/templates/users/user_detail.html:107
|
||||
#: users/models/user.py:93 users/templates/users/user_detail.html:107
|
||||
#: users/templates/users/user_profile.html:116
|
||||
msgid "Date expired"
|
||||
msgstr "失效日期"
|
||||
|
||||
#: perms/models.py:45 perms/models.py:92 templates/_nav.html:34
|
||||
#: perms/models.py:45 perms/models.py:98 templates/_nav.html:34
|
||||
msgid "Asset permission"
|
||||
msgstr "资产授权"
|
||||
|
||||
|
@ -2647,6 +2725,14 @@ msgstr "选择系统用户"
|
|||
msgid "Create permission"
|
||||
msgstr "创建授权规则"
|
||||
|
||||
#: perms/templates/perms/asset_permission_list.html:59
|
||||
#: perms/templates/perms/asset_permission_list.html:73
|
||||
#: users/templates/users/user_list.html:28 xpack/plugins/cloud/models.py:53
|
||||
#: xpack/plugins/cloud/templates/cloud/account_detail.html:60
|
||||
#: xpack/plugins/cloud/templates/cloud/account_list.html:14
|
||||
msgid "Validity"
|
||||
msgstr "有效"
|
||||
|
||||
#: perms/templates/perms/asset_permission_user.html:35
|
||||
msgid "User list of "
|
||||
msgstr "用户列表"
|
||||
|
@ -2801,7 +2887,7 @@ msgstr ""
|
|||
#: users/views/group.py:60 users/views/group.py:76 users/views/group.py:92
|
||||
#: users/views/login.py:349 users/views/user.py:68 users/views/user.py:83
|
||||
#: users/views/user.py:113 users/views/user.py:194 users/views/user.py:355
|
||||
#: users/views/user.py:405 users/views/user.py:444
|
||||
#: users/views/user.py:405 users/views/user.py:445
|
||||
msgid "Users"
|
||||
msgstr "用户管理"
|
||||
|
||||
|
@ -2989,7 +3075,7 @@ msgstr "一个月内历史汇总"
|
|||
|
||||
#: templates/index.html:277 templates/index.html:301
|
||||
msgid "Login count"
|
||||
msgstr "登陆次数"
|
||||
msgstr "登录次数"
|
||||
|
||||
#: templates/index.html:277 templates/index.html:308
|
||||
msgid "Active users"
|
||||
|
@ -3009,7 +3095,7 @@ msgstr "禁用用户"
|
|||
|
||||
#: templates/index.html:342 templates/index.html:394
|
||||
msgid "Month not logged in user"
|
||||
msgstr "月未登陆用户"
|
||||
msgstr "月未登录用户"
|
||||
|
||||
#: templates/index.html:368 templates/index.html:444
|
||||
msgid "Access to the source"
|
||||
|
@ -3017,7 +3103,7 @@ msgstr "访问来源"
|
|||
|
||||
#: templates/index.html:418 templates/index.html:468
|
||||
msgid "Month is logged into the host"
|
||||
msgstr "月被登陆主机"
|
||||
msgstr "月被登录主机"
|
||||
|
||||
#: templates/index.html:418 templates/index.html:469
|
||||
msgid "Disable host"
|
||||
|
@ -3025,7 +3111,7 @@ msgstr "禁用主机"
|
|||
|
||||
#: templates/index.html:418 templates/index.html:470
|
||||
msgid "Month not logged on host"
|
||||
msgstr "月未登陆主机"
|
||||
msgstr "月未登录主机"
|
||||
|
||||
#: templates/rest_framework/base.html:128
|
||||
msgid "Filters"
|
||||
|
@ -3054,55 +3140,55 @@ msgstr ""
|
|||
"录像文件支持存储到服务器端硬盘、AWS S3、 阿里云 OSS 中,默认存储到服务器端硬"
|
||||
"盘, 更多查看文档"
|
||||
|
||||
#: terminal/models.py:21
|
||||
#: terminal/models.py:23
|
||||
msgid "Remote Address"
|
||||
msgstr "远端地址"
|
||||
|
||||
#: terminal/models.py:22
|
||||
#: terminal/models.py:24
|
||||
msgid "SSH Port"
|
||||
msgstr "SSH端口"
|
||||
|
||||
#: terminal/models.py:23
|
||||
#: terminal/models.py:25
|
||||
msgid "HTTP Port"
|
||||
msgstr "HTTP端口"
|
||||
|
||||
#: terminal/models.py:111
|
||||
#: terminal/models.py:125
|
||||
msgid "Session Online"
|
||||
msgstr "在线会话"
|
||||
|
||||
#: terminal/models.py:112
|
||||
#: terminal/models.py:126
|
||||
msgid "CPU Usage"
|
||||
msgstr "CPU使用"
|
||||
|
||||
#: terminal/models.py:113
|
||||
#: terminal/models.py:127
|
||||
msgid "Memory Used"
|
||||
msgstr "内存使用"
|
||||
|
||||
#: terminal/models.py:114
|
||||
#: terminal/models.py:128
|
||||
msgid "Connections"
|
||||
msgstr "连接数"
|
||||
|
||||
#: terminal/models.py:115
|
||||
#: terminal/models.py:129
|
||||
msgid "Threads"
|
||||
msgstr "线程数"
|
||||
|
||||
#: terminal/models.py:116
|
||||
#: terminal/models.py:130
|
||||
msgid "Boot Time"
|
||||
msgstr "运行时间"
|
||||
|
||||
#: terminal/models.py:146 terminal/templates/terminal/session_list.html:104
|
||||
#: terminal/models.py:160 terminal/templates/terminal/session_list.html:104
|
||||
msgid "Replay"
|
||||
msgstr "回放"
|
||||
|
||||
#: terminal/models.py:150
|
||||
#: terminal/models.py:164
|
||||
msgid "Date last active"
|
||||
msgstr "最后活跃日期"
|
||||
|
||||
#: terminal/models.py:152
|
||||
#: terminal/models.py:166
|
||||
msgid "Date end"
|
||||
msgstr "结束日期"
|
||||
|
||||
#: terminal/models.py:199
|
||||
#: terminal/models.py:234
|
||||
msgid "Args"
|
||||
msgstr "参数"
|
||||
|
||||
|
@ -3259,7 +3345,7 @@ msgstr "请先进行用户名和密码验证"
|
|||
msgid "MFA certification failed"
|
||||
msgstr "MFA认证失败"
|
||||
|
||||
#: users/api/user.py:140
|
||||
#: users/api/user.py:145
|
||||
msgid "Could not reset self otp, use profile reset instead"
|
||||
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
|
||||
|
||||
|
@ -3318,7 +3404,7 @@ msgstr ""
|
|||
msgid "MFA code"
|
||||
msgstr "MFA 验证码"
|
||||
|
||||
#: users/forms.py:52 users/models/user.py:63
|
||||
#: users/forms.py:52 users/models/user.py:65
|
||||
#: users/templates/users/_select_user_modal.html:15
|
||||
#: users/templates/users/user_detail.html:87
|
||||
#: users/templates/users/user_list.html:25
|
||||
|
@ -3406,7 +3492,7 @@ msgstr "自动配置并下载SSH密钥"
|
|||
msgid "Paste your id_rsa.pub here."
|
||||
msgstr "复制你的公钥到这里"
|
||||
|
||||
#: users/forms.py:250 users/models/user.py:83
|
||||
#: users/forms.py:250 users/models/user.py:85
|
||||
#: users/templates/users/first_login.html:42
|
||||
#: users/templates/users/user_password_update.html:46
|
||||
#: users/templates/users/user_profile.html:68
|
||||
|
@ -3473,7 +3559,7 @@ msgstr "Agent"
|
|||
msgid "Date login"
|
||||
msgstr "登录日期"
|
||||
|
||||
#: users/models/user.py:32 users/models/user.py:435
|
||||
#: users/models/user.py:32 users/models/user.py:437
|
||||
msgid "Administrator"
|
||||
msgstr "管理员"
|
||||
|
||||
|
@ -3496,38 +3582,42 @@ msgstr "启用"
|
|||
msgid "Force enable"
|
||||
msgstr "强制启用"
|
||||
|
||||
#: users/models/user.py:55 users/templates/users/user_detail.html:71
|
||||
#: users/models/user.py:57 users/templates/users/user_detail.html:71
|
||||
#: users/templates/users/user_profile.html:59
|
||||
msgid "Email"
|
||||
msgstr "邮件"
|
||||
|
||||
#: users/models/user.py:66
|
||||
#: users/models/user.py:68
|
||||
msgid "Avatar"
|
||||
msgstr "头像"
|
||||
|
||||
#: users/models/user.py:69 users/templates/users/user_detail.html:82
|
||||
#: users/models/user.py:71 users/templates/users/user_detail.html:82
|
||||
msgid "Wechat"
|
||||
msgstr "微信"
|
||||
|
||||
#: users/models/user.py:98 users/templates/users/user_detail.html:103
|
||||
#: users/models/user.py:100 users/templates/users/user_detail.html:103
|
||||
#: users/templates/users/user_list.html:27
|
||||
#: users/templates/users/user_profile.html:100
|
||||
msgid "Source"
|
||||
msgstr "用户来源"
|
||||
|
||||
#: users/models/user.py:102
|
||||
#: users/models/user.py:104
|
||||
msgid "Date password last updated"
|
||||
msgstr "最后更新密码日期"
|
||||
|
||||
#: users/models/user.py:126 users/templates/users/user_update.html:22
|
||||
#: users/models/user.py:128 users/templates/users/user_update.html:22
|
||||
#: users/views/login.py:243 users/views/login.py:302 users/views/user.py:418
|
||||
msgid "User auth from {}, go there change password"
|
||||
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
|
||||
|
||||
#: users/models/user.py:438
|
||||
#: users/models/user.py:440
|
||||
msgid "Administrator is the super user of system"
|
||||
msgstr "Administrator是初始的超级管理员"
|
||||
|
||||
#: users/serializers/v2.py:40
|
||||
msgid "name not unique"
|
||||
msgstr "名称重复"
|
||||
|
||||
#: users/templates/users/_base_otp.html:27
|
||||
msgid "Home page"
|
||||
msgstr "首页"
|
||||
|
@ -3583,7 +3673,7 @@ msgstr "更新ssh密钥"
|
|||
#: users/templates/users/first_login.html:19
|
||||
#: users/templates/users/first_login_done.html:19
|
||||
msgid "First Login"
|
||||
msgstr "首次登陆"
|
||||
msgstr "首次登录"
|
||||
|
||||
#: users/templates/users/first_login.html:72
|
||||
msgid "I agree with the terms and conditions."
|
||||
|
@ -3945,12 +4035,6 @@ msgstr "用户组删除"
|
|||
msgid "UserGroup Deleting failed."
|
||||
msgstr "用户组删除失败"
|
||||
|
||||
#: users/templates/users/user_list.html:28 xpack/plugins/cloud/models.py:53
|
||||
#: xpack/plugins/cloud/templates/cloud/account_detail.html:60
|
||||
#: xpack/plugins/cloud/templates/cloud/account_list.html:14
|
||||
msgid "Validity"
|
||||
msgstr "账户状态"
|
||||
|
||||
#: users/templates/users/user_list.html:203
|
||||
msgid "This will delete the selected users !!!"
|
||||
msgstr "删除选中用户 !!!"
|
||||
|
@ -4279,7 +4363,7 @@ msgstr "用户组授权资产"
|
|||
msgid "Please enable cookies and try again."
|
||||
msgstr "设置你的浏览器支持cookie"
|
||||
|
||||
#: users/views/login.py:191 users/views/user.py:531 users/views/user.py:556
|
||||
#: users/views/login.py:191 users/views/user.py:532 users/views/user.py:557
|
||||
msgid "MFA code invalid, or ntp sync server time"
|
||||
msgstr "MFA验证码不正确,或者服务器端时间不对"
|
||||
|
||||
|
@ -4320,13 +4404,13 @@ msgstr "Token错误或失效"
|
|||
msgid "Password not same"
|
||||
msgstr "密码不一致"
|
||||
|
||||
#: users/views/login.py:311 users/views/user.py:128 users/views/user.py:427
|
||||
#: users/views/login.py:311 users/views/user.py:128 users/views/user.py:428
|
||||
msgid "* Your password does not meet the requirements"
|
||||
msgstr "* 您的密码不符合要求"
|
||||
|
||||
#: users/views/login.py:349
|
||||
msgid "First login"
|
||||
msgstr "首次登陆"
|
||||
msgstr "首次登录"
|
||||
|
||||
#: users/views/user.py:145
|
||||
msgid "Bulk update user success"
|
||||
|
@ -4352,27 +4436,27 @@ msgstr "个人信息设置"
|
|||
msgid "Password update"
|
||||
msgstr "密码更新"
|
||||
|
||||
#: users/views/user.py:445
|
||||
#: users/views/user.py:446
|
||||
msgid "Public key update"
|
||||
msgstr "密钥更新"
|
||||
|
||||
#: users/views/user.py:486
|
||||
#: users/views/user.py:487
|
||||
msgid "Password invalid"
|
||||
msgstr "用户名或密码无效"
|
||||
|
||||
#: users/views/user.py:586
|
||||
#: users/views/user.py:587
|
||||
msgid "MFA enable success"
|
||||
msgstr "MFA 绑定成功"
|
||||
|
||||
#: users/views/user.py:587
|
||||
#: users/views/user.py:588
|
||||
msgid "MFA enable success, return login page"
|
||||
msgstr "MFA 绑定成功,返回到登录页面"
|
||||
|
||||
#: users/views/user.py:589
|
||||
#: users/views/user.py:590
|
||||
msgid "MFA disable success"
|
||||
msgstr "MFA 解绑成功"
|
||||
|
||||
#: users/views/user.py:590
|
||||
#: users/views/user.py:591
|
||||
msgid "MFA disable success, return login page"
|
||||
msgstr "MFA 解绑成功,返回登录页面"
|
||||
|
||||
|
@ -4631,6 +4715,9 @@ msgstr "创建组织"
|
|||
msgid "Update org"
|
||||
msgstr "更新组织"
|
||||
|
||||
#~ msgid "Valid"
|
||||
#~ msgstr "账户状态"
|
||||
|
||||
#~ msgid "Error: Account invalid"
|
||||
#~ msgstr "错误: 账户无效"
|
||||
|
||||
|
@ -4643,14 +4730,6 @@ msgstr "更新组织"
|
|||
#~ msgid "No assets, task stop"
|
||||
#~ msgstr "没有匹配到资产,结束任务"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Validity"
|
||||
#~ msgid "Valid"
|
||||
#~ msgstr "账户状态"
|
||||
|
||||
#~ msgid "You can't update the root node name"
|
||||
#~ msgstr "不能修改根节点名称"
|
||||
|
||||
#~ msgid "Update assets hardware info period"
|
||||
#~ msgstr "定期更新资产硬件信息"
|
||||
|
||||
|
|
|
@ -118,18 +118,6 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule):
|
|||
self.gather_result("unreachable", result)
|
||||
super().v2_runner_on_unreachable(result)
|
||||
|
||||
def on_playbook_start(self, name):
|
||||
date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.display(
|
||||
"{} Start task: {}\r\n".format(date_start, name)
|
||||
)
|
||||
|
||||
def on_playbook_end(self, name):
|
||||
date_finished = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.display(
|
||||
"{} Task finish\r\n".format(date_finished)
|
||||
)
|
||||
|
||||
def display_skipped_hosts(self):
|
||||
pass
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class TeeObj:
|
||||
origin_stdout = sys.stdout
|
||||
|
||||
def __init__(self, file_obj):
|
||||
self.file_obj = file_obj
|
||||
|
||||
def write(self, msg):
|
||||
self.origin_stdout.write(msg)
|
||||
self.file_obj.write(msg.replace('*', ''))
|
||||
|
||||
def flush(self):
|
||||
self.origin_stdout.flush()
|
||||
self.file_obj.flush()
|
|
@ -9,10 +9,10 @@ from ansible.parsing.dataloader import DataLoader
|
|||
from ansible.executor.playbook_executor import PlaybookExecutor
|
||||
from ansible.playbook.play import Play
|
||||
import ansible.constants as C
|
||||
from ansible.utils.display import Display
|
||||
|
||||
from .callback import AdHocResultCallback, PlaybookResultCallBack, \
|
||||
CommandResultCallback
|
||||
from .callback import (
|
||||
AdHocResultCallback, PlaybookResultCallBack, CommandResultCallback
|
||||
)
|
||||
from common.utils import get_logger
|
||||
from .exceptions import AnsibleError
|
||||
|
||||
|
@ -22,13 +22,6 @@ C.HOST_KEY_CHECKING = False
|
|||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class CustomDisplay(Display):
|
||||
def display(self, msg, color=None, stderr=False, screen_only=False, log_only=False):
|
||||
pass
|
||||
|
||||
display = CustomDisplay()
|
||||
|
||||
|
||||
Options = namedtuple('Options', [
|
||||
'listtags', 'listtasks', 'listhosts', 'syntax', 'connection',
|
||||
'module_path', 'forks', 'remote_user', 'private_key_file', 'timeout',
|
||||
|
|
|
@ -1,46 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
|
||||
import os
|
||||
|
||||
from celery.result import AsyncResult
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import generics
|
||||
from rest_framework.views import Response
|
||||
|
||||
from common.permissions import IsOrgAdmin, IsValidUser
|
||||
from common.permissions import IsValidUser
|
||||
from common.api import LogTailApi
|
||||
from ..models import CeleryTask
|
||||
from ..serializers import CeleryResultSerializer
|
||||
from ..celery.utils import get_celery_task_log_path
|
||||
|
||||
|
||||
__all__ = ['CeleryTaskLogApi', 'CeleryResultApi']
|
||||
|
||||
|
||||
class CeleryTaskLogApi(generics.RetrieveAPIView):
|
||||
class CeleryTaskLogApi(LogTailApi):
|
||||
permission_classes = (IsValidUser,)
|
||||
buff_size = 1024 * 10
|
||||
end = False
|
||||
queryset = CeleryTask.objects.all()
|
||||
task = None
|
||||
task_id = ''
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
mark = request.query_params.get("mark") or str(uuid.uuid4())
|
||||
task = self.get_object()
|
||||
log_path = task.full_log_path
|
||||
self.task_id = str(kwargs.get('pk'))
|
||||
self.task = AsyncResult(self.task_id)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
if not log_path or not os.path.isfile(log_path):
|
||||
return Response({"data": _("Waiting ...")}, status=203)
|
||||
def get_log_path(self):
|
||||
new_path = get_celery_task_log_path(self.task_id)
|
||||
if new_path and os.path.isfile(new_path):
|
||||
return new_path
|
||||
try:
|
||||
task = CeleryTask.objects.get(id=self.task_id)
|
||||
except CeleryTask.DoesNotExist:
|
||||
return None
|
||||
return task.full_log_path
|
||||
|
||||
with open(log_path, 'r') as f:
|
||||
offset = cache.get(mark, 0)
|
||||
f.seek(offset)
|
||||
data = f.read(self.buff_size).replace('\n', '\r\n')
|
||||
mark = str(uuid.uuid4())
|
||||
cache.set(mark, f.tell(), 5)
|
||||
|
||||
if data == '' and task.is_finished():
|
||||
self.end = True
|
||||
return Response({"data": data, 'end': self.end, 'mark': mark})
|
||||
def is_file_finish_write(self):
|
||||
return self.task.ready()
|
||||
|
||||
|
||||
class CeleryResultApi(generics.RetrieveAPIView):
|
||||
|
|
|
@ -10,6 +10,5 @@ class OpsConfig(AppConfig):
|
|||
from orgs.models import Organization
|
||||
from orgs.utils import set_current_org
|
||||
set_current_org(Organization.root())
|
||||
|
||||
super().ready()
|
||||
from .celery import signal_handler
|
||||
super().ready()
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from functools import wraps
|
||||
|
||||
_need_registered_period_tasks = []
|
||||
_after_app_ready_start_tasks = []
|
||||
_after_app_shutdown_clean_periodic_tasks = []
|
||||
|
||||
|
||||
def add_register_period_task(task):
|
||||
_need_registered_period_tasks.append(task)
|
||||
# key = "__REGISTER_PERIODIC_TASKS"
|
||||
# value = cache.get(key, [])
|
||||
# value.append(name)
|
||||
# cache.set(key, value)
|
||||
|
||||
|
||||
def get_register_period_tasks():
|
||||
# key = "__REGISTER_PERIODIC_TASKS"
|
||||
# return cache.get(key, [])
|
||||
return _need_registered_period_tasks
|
||||
|
||||
|
||||
def add_after_app_shutdown_clean_task(name):
|
||||
# key = "__AFTER_APP_SHUTDOWN_CLEAN_TASKS"
|
||||
# value = cache.get(key, [])
|
||||
# value.append(name)
|
||||
# cache.set(key, value)
|
||||
_after_app_shutdown_clean_periodic_tasks.append(name)
|
||||
|
||||
|
||||
def get_after_app_shutdown_clean_tasks():
|
||||
# key = "__AFTER_APP_SHUTDOWN_CLEAN_TASKS"
|
||||
# return cache.get(key, [])
|
||||
return _after_app_shutdown_clean_periodic_tasks
|
||||
|
||||
|
||||
def add_after_app_ready_task(name):
|
||||
# key = "__AFTER_APP_READY_RUN_TASKS"
|
||||
# value = cache.get(key, [])
|
||||
# value.append(name)
|
||||
# cache.set(key, value)
|
||||
_after_app_ready_start_tasks.append(name)
|
||||
|
||||
|
||||
def get_after_app_ready_tasks():
|
||||
# key = "__AFTER_APP_READY_RUN_TASKS"
|
||||
# return cache.get(key, [])
|
||||
return _after_app_ready_start_tasks
|
||||
|
||||
|
||||
def register_as_period_task(crontab=None, interval=None):
|
||||
"""
|
||||
Warning: Task must be have not any args and kwargs
|
||||
:param crontab: "* * * * *"
|
||||
:param interval: 60*60*60
|
||||
:return:
|
||||
"""
|
||||
if crontab is None and interval is None:
|
||||
raise SyntaxError("Must set crontab or interval one")
|
||||
|
||||
def decorate(func):
|
||||
if crontab is None and interval is None:
|
||||
raise SyntaxError("Interval and crontab must set one")
|
||||
|
||||
# Because when this decorator run, the task was not created,
|
||||
# So we can't use func.name
|
||||
name = '{func.__module__}.{func.__name__}'.format(func=func)
|
||||
add_register_period_task({
|
||||
name: {
|
||||
'task': name,
|
||||
'interval': interval,
|
||||
'crontab': crontab,
|
||||
'args': (),
|
||||
'enabled': True,
|
||||
}
|
||||
})
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorate
|
||||
|
||||
|
||||
def after_app_ready_start(func):
|
||||
# Because when this decorator run, the task was not created,
|
||||
# So we can't use func.name
|
||||
name = '{func.__module__}.{func.__name__}'.format(func=func)
|
||||
if name not in _after_app_ready_start_tasks:
|
||||
add_after_app_ready_task(name)
|
||||
|
||||
@wraps(func)
|
||||
def decorate(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return decorate
|
||||
|
||||
|
||||
def after_app_shutdown_clean_periodic(func):
|
||||
# Because when this decorator run, the task was not created,
|
||||
# So we can't use func.name
|
||||
name = '{func.__module__}.{func.__name__}'.format(func=func)
|
||||
if name not in _after_app_shutdown_clean_periodic_tasks:
|
||||
add_after_app_shutdown_clean_task(name)
|
||||
|
||||
@wraps(func)
|
||||
def decorate(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return decorate
|
|
@ -0,0 +1,160 @@
|
|||
from logging import StreamHandler
|
||||
|
||||
from django.conf import settings
|
||||
from celery import current_task
|
||||
from celery.signals import task_prerun, task_postrun
|
||||
from kombu import Connection, Exchange, Queue, Producer
|
||||
from kombu.mixins import ConsumerMixin
|
||||
|
||||
from .utils import get_celery_task_log_path
|
||||
|
||||
routing_key = 'celery_log'
|
||||
celery_log_exchange = Exchange('celery_log_exchange', type='direct')
|
||||
celery_log_queue = [Queue('celery_log', celery_log_exchange, routing_key=routing_key)]
|
||||
|
||||
|
||||
class CeleryLoggerConsumer(ConsumerMixin):
|
||||
def __init__(self):
|
||||
self.connection = Connection(settings.CELERY_LOG_BROKER_URL)
|
||||
|
||||
def get_consumers(self, Consumer, channel):
|
||||
return [Consumer(queues=celery_log_queue,
|
||||
accept=['pickle', 'json'],
|
||||
callbacks=[self.process_task])
|
||||
]
|
||||
|
||||
def handle_task_start(self, task_id, message):
|
||||
pass
|
||||
|
||||
def handle_task_end(self, task_id, message):
|
||||
pass
|
||||
|
||||
def handle_task_log(self, task_id, msg, message):
|
||||
pass
|
||||
|
||||
def process_task(self, body, message):
|
||||
action = body.get('action')
|
||||
task_id = body.get('task_id')
|
||||
msg = body.get('msg')
|
||||
if action == CeleryLoggerProducer.ACTION_TASK_LOG:
|
||||
self.handle_task_log(task_id, msg, message)
|
||||
elif action == CeleryLoggerProducer.ACTION_TASK_START:
|
||||
self.handle_task_start(task_id, message)
|
||||
elif action == CeleryLoggerProducer.ACTION_TASK_END:
|
||||
self.handle_task_end(task_id, message)
|
||||
|
||||
|
||||
class CeleryLoggerProducer:
|
||||
ACTION_TASK_START, ACTION_TASK_LOG, ACTION_TASK_END = range(3)
|
||||
|
||||
def __init__(self):
|
||||
self.connection = Connection(settings.CELERY_LOG_BROKER_URL)
|
||||
|
||||
@property
|
||||
def producer(self):
|
||||
return Producer(self.connection)
|
||||
|
||||
def publish(self, payload):
|
||||
self.producer.publish(
|
||||
payload, serializer='json', exchange=celery_log_exchange,
|
||||
declare=[celery_log_exchange], routing_key=routing_key
|
||||
)
|
||||
|
||||
def log(self, task_id, msg):
|
||||
payload = {'task_id': task_id, 'msg': msg, 'action': self.ACTION_TASK_LOG}
|
||||
return self.publish(payload)
|
||||
|
||||
def read(self):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def task_end(self, task_id):
|
||||
payload = {'task_id': task_id, 'action': self.ACTION_TASK_END}
|
||||
return self.publish(payload)
|
||||
|
||||
def task_start(self, task_id):
|
||||
payload = {'task_id': task_id, 'action': self.ACTION_TASK_START}
|
||||
return self.publish(payload)
|
||||
|
||||
|
||||
class CeleryTaskLoggerHandler(StreamHandler):
|
||||
terminator = '\r\n'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
task_prerun.connect(self.on_task_start)
|
||||
task_postrun.connect(self.on_start_end)
|
||||
|
||||
@staticmethod
|
||||
def get_current_task_id():
|
||||
if not current_task:
|
||||
return
|
||||
task_id = current_task.request.root_id
|
||||
return task_id
|
||||
|
||||
def on_task_start(self, sender, task_id, **kwargs):
|
||||
return self.handle_task_start(task_id)
|
||||
|
||||
def on_start_end(self, sender, task_id, **kwargs):
|
||||
return self.handle_task_end(task_id)
|
||||
|
||||
def after_task_publish(self, sender, body, **kwargs):
|
||||
pass
|
||||
|
||||
def emit(self, record):
|
||||
task_id = self.get_current_task_id()
|
||||
if not task_id:
|
||||
return
|
||||
try:
|
||||
self.write_task_log(task_id, record)
|
||||
self.flush()
|
||||
except Exception:
|
||||
self.handleError(record)
|
||||
|
||||
def write_task_log(self, task_id, msg):
|
||||
pass
|
||||
|
||||
def handle_task_start(self, task_id):
|
||||
pass
|
||||
|
||||
def handle_task_end(self, task_id):
|
||||
pass
|
||||
|
||||
|
||||
class CeleryTaskMQLoggerHandler(CeleryTaskLoggerHandler):
|
||||
def __init__(self):
|
||||
self.producer = CeleryLoggerProducer()
|
||||
super().__init__(stream=None)
|
||||
|
||||
def write_task_log(self, task_id, record):
|
||||
msg = self.format(record)
|
||||
self.producer.log(task_id, msg)
|
||||
|
||||
def flush(self):
|
||||
self.producer.flush()
|
||||
|
||||
|
||||
class CeleryTaskFileHandler(CeleryTaskLoggerHandler):
|
||||
def __init__(self):
|
||||
self.f = None
|
||||
super().__init__(stream=None)
|
||||
|
||||
def emit(self, record):
|
||||
msg = self.format(record)
|
||||
if not self.f:
|
||||
return
|
||||
self.f.write(msg)
|
||||
self.f.write(self.terminator)
|
||||
self.flush()
|
||||
|
||||
def flush(self):
|
||||
self.f and self.f.flush()
|
||||
|
||||
def handle_task_start(self, task_id):
|
||||
log_path = get_celery_task_log_path(task_id)
|
||||
self.f = open(log_path, 'a')
|
||||
|
||||
def handle_task_end(self, task_id):
|
||||
self.f and self.f.close()
|
|
@ -1,103 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import os
|
||||
import datetime
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.db import transaction
|
||||
from celery import subtask
|
||||
from celery.signals import worker_ready, worker_shutdown, task_prerun, \
|
||||
task_postrun, after_task_publish
|
||||
from celery.signals import (
|
||||
worker_ready, worker_shutdown, after_setup_logger
|
||||
)
|
||||
from kombu.utils.encoding import safe_str
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
|
||||
from common.utils import get_logger, TeeObj, get_object_or_none
|
||||
from common.const import celery_task_pre_key
|
||||
from .utils import get_after_app_ready_tasks, get_after_app_shutdown_clean_tasks
|
||||
from ..models import CeleryTask
|
||||
from common.utils import get_logger
|
||||
from .decorator import get_after_app_ready_tasks, get_after_app_shutdown_clean_tasks
|
||||
from .logger import CeleryTaskFileHandler
|
||||
|
||||
logger = get_logger(__file__)
|
||||
safe_str = lambda x: x
|
||||
|
||||
|
||||
@worker_ready.connect
|
||||
def on_app_ready(sender=None, headers=None, body=None, **kwargs):
|
||||
def on_app_ready(sender=None, headers=None, **kwargs):
|
||||
if cache.get("CELERY_APP_READY", 0) == 1:
|
||||
return
|
||||
cache.set("CELERY_APP_READY", 1, 10)
|
||||
tasks = get_after_app_ready_tasks()
|
||||
logger.debug("Start need start task: [{}]".format(
|
||||
", ".join(tasks))
|
||||
)
|
||||
logger.debug("Work ready signal recv")
|
||||
logger.debug("Start need start task: [{}]".format(", ".join(tasks)))
|
||||
for task in tasks:
|
||||
subtask(task).delay()
|
||||
|
||||
|
||||
@worker_shutdown.connect
|
||||
def after_app_shutdown(sender=None, headers=None, body=None, **kwargs):
|
||||
def after_app_shutdown_periodic_tasks(sender=None, **kwargs):
|
||||
if cache.get("CELERY_APP_SHUTDOWN", 0) == 1:
|
||||
return
|
||||
cache.set("CELERY_APP_SHUTDOWN", 1, 10)
|
||||
tasks = get_after_app_shutdown_clean_tasks()
|
||||
logger.debug("App shutdown signal recv")
|
||||
logger.debug("Clean need cleaned period tasks: [{}]".format(
|
||||
', '.join(tasks))
|
||||
)
|
||||
logger.debug("Worker shutdown signal recv")
|
||||
logger.debug("Clean period tasks: [{}]".format(', '.join(tasks)))
|
||||
PeriodicTask.objects.filter(name__in=tasks).delete()
|
||||
|
||||
|
||||
@after_task_publish.connect
|
||||
def after_task_publish_signal_handler(sender, headers=None, **kwargs):
|
||||
CeleryTask.objects.create(
|
||||
id=headers["id"], status=CeleryTask.WAITING, name=headers["task"]
|
||||
)
|
||||
cache.set(headers["id"], True, 3600)
|
||||
|
||||
|
||||
@task_prerun.connect
|
||||
def pre_run_task_signal_handler(sender, task_id=None, task=None, **kwargs):
|
||||
time.sleep(0.1)
|
||||
for i in range(5):
|
||||
if cache.get(task_id, False):
|
||||
break
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
t = get_object_or_none(CeleryTask, id=task_id)
|
||||
if t is None:
|
||||
logger.warn("Not get the task: {}".format(task_id))
|
||||
@after_setup_logger.connect
|
||||
def add_celery_logger_handler(sender=None, logger=None, loglevel=None, format=None, **kwargs):
|
||||
if not logger:
|
||||
return
|
||||
now = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
log_path = os.path.join(now, task_id + '.log')
|
||||
full_path = os.path.join(CeleryTask.LOG_DIR, log_path)
|
||||
|
||||
if not os.path.exists(os.path.dirname(full_path)):
|
||||
os.makedirs(os.path.dirname(full_path))
|
||||
with transaction.atomic():
|
||||
t.date_start = timezone.now()
|
||||
t.status = CeleryTask.RUNNING
|
||||
t.log_path = log_path
|
||||
t.save()
|
||||
f = open(full_path, 'w')
|
||||
tee = TeeObj(f)
|
||||
sys.stdout = tee
|
||||
task.log_f = tee
|
||||
|
||||
|
||||
@task_postrun.connect
|
||||
def post_run_task_signal_handler(sender, task_id=None, task=None, **kwargs):
|
||||
t = get_object_or_none(CeleryTask, id=task_id)
|
||||
if t is None:
|
||||
logger.warn("Not get the task: {}".format(task_id))
|
||||
return
|
||||
with transaction.atomic():
|
||||
t.status = CeleryTask.FINISHED
|
||||
t.date_finished = timezone.now()
|
||||
t.save()
|
||||
task.log_f.flush()
|
||||
sys.stdout = task.log_f.origin_stdout
|
||||
task.log_f.close()
|
||||
|
||||
handler = CeleryTaskFileHandler()
|
||||
handler.setLevel(loglevel)
|
||||
formatter = logging.Formatter(format)
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
|
|
@ -1,49 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import json
|
||||
from functools import wraps
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.utils import ProgrammingError, OperationalError
|
||||
from django.core.cache import cache
|
||||
from django_celery_beat.models import PeriodicTask, IntervalSchedule, CrontabSchedule
|
||||
|
||||
|
||||
def add_register_period_task(name):
|
||||
key = "__REGISTER_PERIODIC_TASKS"
|
||||
value = cache.get(key, [])
|
||||
value.append(name)
|
||||
cache.set(key, value)
|
||||
|
||||
|
||||
def get_register_period_tasks():
|
||||
key = "__REGISTER_PERIODIC_TASKS"
|
||||
return cache.get(key, [])
|
||||
|
||||
|
||||
def add_after_app_shutdown_clean_task(name):
|
||||
key = "__AFTER_APP_SHUTDOWN_CLEAN_TASKS"
|
||||
value = cache.get(key, [])
|
||||
value.append(name)
|
||||
cache.set(key, value)
|
||||
|
||||
|
||||
def get_after_app_shutdown_clean_tasks():
|
||||
key = "__AFTER_APP_SHUTDOWN_CLEAN_TASKS"
|
||||
return cache.get(key, [])
|
||||
|
||||
|
||||
def add_after_app_ready_task(name):
|
||||
key = "__AFTER_APP_READY_RUN_TASKS"
|
||||
value = cache.get(key, [])
|
||||
value.append(name)
|
||||
cache.set(key, value)
|
||||
|
||||
|
||||
def get_after_app_ready_tasks():
|
||||
key = "__AFTER_APP_READY_RUN_TASKS"
|
||||
return cache.get(key, [])
|
||||
|
||||
|
||||
def create_or_update_celery_periodic_tasks(tasks):
|
||||
"""
|
||||
:param tasks: {
|
||||
|
@ -123,63 +87,10 @@ def delete_celery_periodic_task(task_name):
|
|||
PeriodicTask.objects.filter(name=task_name).delete()
|
||||
|
||||
|
||||
def register_as_period_task(crontab=None, interval=None):
|
||||
"""
|
||||
Warning: Task must be have not any args and kwargs
|
||||
:param crontab: "* * * * *"
|
||||
:param interval: 60*60*60
|
||||
:return:
|
||||
"""
|
||||
if crontab is None and interval is None:
|
||||
raise SyntaxError("Must set crontab or interval one")
|
||||
def get_celery_task_log_path(task_id):
|
||||
task_id = str(task_id)
|
||||
rel_path = os.path.join(task_id[0], task_id[1], task_id + '.log')
|
||||
path = os.path.join(settings.CELERY_LOG_DIR, rel_path)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
return path
|
||||
|
||||
def decorate(func):
|
||||
if crontab is None and interval is None:
|
||||
raise SyntaxError("Interval and crontab must set one")
|
||||
|
||||
# Because when this decorator run, the task was not created,
|
||||
# So we can't use func.name
|
||||
name = '{func.__module__}.{func.__name__}'.format(func=func)
|
||||
if name not in get_register_period_tasks():
|
||||
create_or_update_celery_periodic_tasks({
|
||||
name: {
|
||||
'task': name,
|
||||
'interval': interval,
|
||||
'crontab': crontab,
|
||||
'args': (),
|
||||
'enabled': True,
|
||||
}
|
||||
})
|
||||
add_register_period_task(name)
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorate
|
||||
|
||||
|
||||
def after_app_ready_start(func):
|
||||
# Because when this decorator run, the task was not created,
|
||||
# So we can't use func.name
|
||||
name = '{func.__module__}.{func.__name__}'.format(func=func)
|
||||
if name not in get_after_app_ready_tasks():
|
||||
add_after_app_ready_task(name)
|
||||
|
||||
@wraps(func)
|
||||
def decorate(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return decorate
|
||||
|
||||
|
||||
def after_app_shutdown_clean(func):
|
||||
# Because when this decorator run, the task was not created,
|
||||
# So we can't use func.name
|
||||
name = '{func.__module__}.{func.__name__}'.format(func=func)
|
||||
if name not in get_after_app_shutdown_clean_tasks():
|
||||
add_after_app_shutdown_clean_task(name)
|
||||
|
||||
@wraps(func)
|
||||
def decorate(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return decorate
|
||||
|
|
|
@ -220,10 +220,10 @@ class AdHoc(models.Model):
|
|||
time_start = time.time()
|
||||
try:
|
||||
date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
print("{} Start task: {}\r\n".format(date_start, self.task.name))
|
||||
print(_("{} Start task: {}").format(date_start, self.task.name))
|
||||
raw, summary = self._run_only()
|
||||
date_end = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
print("\r\n{} Task finished".format(date_end))
|
||||
print(_("{} Task finish").format(date_end))
|
||||
history.is_finished = True
|
||||
if summary.get('dark'):
|
||||
history.is_success = False
|
||||
|
@ -235,7 +235,6 @@ class AdHoc(models.Model):
|
|||
except Exception as e:
|
||||
return {}, {"dark": {"all": str(e)}, "contacted": []}
|
||||
finally:
|
||||
# f.close()
|
||||
history.date_finished = timezone.now()
|
||||
history.timedelta = time.time() - time_start
|
||||
history.save()
|
||||
|
|
|
@ -8,6 +8,8 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.utils.translation import ugettext
|
||||
from django.db import models
|
||||
|
||||
|
||||
from orgs.models import Organization
|
||||
from ..ansible.runner import CommandRunner
|
||||
from ..inventory import JMSInventory
|
||||
|
||||
|
@ -53,6 +55,8 @@ class CommandExecution(models.Model):
|
|||
|
||||
def run(self):
|
||||
print('-'*10 + ' ' + ugettext('Task start') + ' ' + '-'*10)
|
||||
org = Organization.get_instance(self.run_as.org_id)
|
||||
org.change_to()
|
||||
self.date_start = timezone.now()
|
||||
ok, msg = self.run_as.is_command_can_run(self.command)
|
||||
if ok:
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
# coding: utf-8
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from django.conf import settings
|
||||
from celery import shared_task, subtask
|
||||
from django.utils import timezone
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from .celery.utils import register_as_period_task, after_app_shutdown_clean
|
||||
from .models import Task, CommandExecution
|
||||
from .celery.decorator import (
|
||||
register_as_period_task, after_app_shutdown_clean_periodic,
|
||||
after_app_ready_start
|
||||
)
|
||||
from .celery.utils import create_or_update_celery_periodic_tasks
|
||||
from .models import Task, CommandExecution, CeleryTask
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -36,8 +45,8 @@ def run_command_execution(cid, **kwargs):
|
|||
|
||||
|
||||
@shared_task
|
||||
@after_app_shutdown_clean_periodic
|
||||
@register_as_period_task(interval=3600*24)
|
||||
@after_app_shutdown_clean
|
||||
def clean_tasks_adhoc_period():
|
||||
logger.debug("Start clean task adhoc and run history")
|
||||
tasks = Task.objects.all()
|
||||
|
@ -48,11 +57,42 @@ def clean_tasks_adhoc_period():
|
|||
ad.delete()
|
||||
|
||||
|
||||
@shared_task
|
||||
@after_app_shutdown_clean_periodic
|
||||
@register_as_period_task(interval=3600*24)
|
||||
def clean_celery_tasks_period():
|
||||
expire_days = 30
|
||||
logger.debug("Start clean celery task history")
|
||||
one_month_ago = timezone.now() - timezone.timedelta(days=expire_days)
|
||||
tasks = CeleryTask.objects.filter(date_start__lt=one_month_ago)
|
||||
for task in tasks:
|
||||
if os.path.isfile(task.full_log_path):
|
||||
try:
|
||||
os.remove(task.full_log_path)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
pass
|
||||
task.delete()
|
||||
tasks = CeleryTask.objects.filter(date_start__isnull=True)
|
||||
tasks.delete()
|
||||
command = "find %s -mtime +%s -name '*.log' -type f -exec rm -f {} \\;" % (
|
||||
settings.CELERY_LOG_DIR, expire_days
|
||||
)
|
||||
subprocess.call(command, shell=True)
|
||||
|
||||
|
||||
@shared_task
|
||||
@after_app_ready_start
|
||||
def create_or_update_registered_periodic_tasks():
|
||||
from .celery.decorator import get_register_period_tasks
|
||||
for task in get_register_period_tasks():
|
||||
create_or_update_celery_periodic_tasks(task)
|
||||
|
||||
|
||||
@shared_task
|
||||
def hello(name, callback=None):
|
||||
import time
|
||||
time.sleep(10)
|
||||
print("Hello {}".format(name))
|
||||
if callback is not None:
|
||||
subtask(callback).delay("Guahongwei")
|
||||
|
||||
|
||||
@shared_task
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
<head>
|
||||
<title>term.js</title>
|
||||
<title>{% trans 'Task log' %}</title>
|
||||
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
|
||||
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
|
||||
|
@ -15,14 +16,14 @@
|
|||
}
|
||||
</style>
|
||||
</head>
|
||||
<div id="term" style="height: 100%;width: 100%">
|
||||
</div>
|
||||
<div id="term" style="height: 100%;width: 100%">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var rowHeight = 18;
|
||||
var colWidth = 10;
|
||||
var mark = '';
|
||||
var url = "{% url 'api-ops:celery-task-log' pk=object.id %}";
|
||||
var url = "{% url 'api-ops:celery-task-log' pk=task_id %}";
|
||||
var term;
|
||||
var end = false;
|
||||
var error = false;
|
||||
|
@ -35,9 +36,9 @@
|
|||
{#colWidth = 1.00 * t.width() / 6;#}
|
||||
}
|
||||
function resize() {
|
||||
var rows = Math.floor(window.innerHeight / rowHeight) - 1;
|
||||
var cols = Math.floor(window.innerWidth / colWidth) - 2;
|
||||
term.resize(cols, rows);
|
||||
{#var rows = Math.floor(window.innerHeight / rowHeight) - 1;#}
|
||||
{#var cols = Math.floor(window.innerWidth / colWidth) - 2;#}
|
||||
{#term.resize(cols, rows);#}
|
||||
}
|
||||
function requestAndWrite() {
|
||||
if (!end && success) {
|
||||
|
@ -73,7 +74,7 @@
|
|||
disableStdin: true
|
||||
});
|
||||
term.open(document.getElementById('term'));
|
||||
term.resize(80, 24);
|
||||
term.resize(90, 32);
|
||||
resize();
|
||||
term.on('data', function (data) {
|
||||
{#term.write(data.replace('\r', '\r\n'))#}
|
||||
|
|
|
@ -141,10 +141,10 @@ function onCheck(e, treeId, treeNode) {
|
|||
var nodes_names = nodes.map(function (node) {
|
||||
return node.name;
|
||||
});
|
||||
var message = "已选择资产: ";
|
||||
var message = "{% trans 'Selected assets' %}" + ': ';
|
||||
message += nodes_names.join(", ");
|
||||
message += "\r\n";
|
||||
message += "总共: " + nodes_names.length + "个\r\n";
|
||||
message += "{% trans 'In total' %}" + ': ' + nodes_names.length + "个\r\n";
|
||||
term.clear();
|
||||
term.write(message)
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ function initResultTerminal() {
|
|||
}
|
||||
});
|
||||
term.open(document.getElementById('term'));
|
||||
term.write("选择左侧资产, 选择运行的系统用户,批量执行命令\r\n")
|
||||
term.write("{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n")
|
||||
}
|
||||
|
||||
function wrapperError(msg) {
|
||||
|
@ -197,15 +197,15 @@ function execute() {
|
|||
return node.id;
|
||||
});
|
||||
if (hosts.length === 0) {
|
||||
term.write(wrapperError('没有选中资产'));
|
||||
term.write(wrapperError("{% trans 'Unselected assets' %}"));
|
||||
return
|
||||
}
|
||||
if (!command) {
|
||||
term.write(wrapperError('没有输入命令'));
|
||||
term.write(wrapperError("{% trans 'No input command' %}"));
|
||||
return
|
||||
}
|
||||
if (!run_as) {
|
||||
term.write(wrapperError('没有选择运行用户'));
|
||||
term.write(wrapperError("{% trans 'No system user was selected' %}"));
|
||||
return
|
||||
}
|
||||
var data = {
|
||||
|
|
|
@ -48,7 +48,7 @@ def update_or_create_ansible_task(
|
|||
hosts_same = old_hosts == new_hosts
|
||||
|
||||
if not adhoc or adhoc != new_adhoc or not hosts_same:
|
||||
logger.info(_("Update task content: {}").format(task_name))
|
||||
logger.debug(_("Update task content: {}").format(task_name))
|
||||
new_adhoc.save()
|
||||
new_adhoc.hosts.set(hosts)
|
||||
task.latest_adhoc = new_adhoc
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.views.generic import DetailView
|
||||
from django.views.generic import DetailView, TemplateView
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from ..models import CeleryTask
|
||||
|
@ -9,6 +9,10 @@ from ..models import CeleryTask
|
|||
__all__ = ['CeleryTaskLogView']
|
||||
|
||||
|
||||
class CeleryTaskLogView(AdminUserRequiredMixin, DetailView):
|
||||
class CeleryTaskLogView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'ops/celery_task_log.html'
|
||||
model = CeleryTask
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'task_id': self.kwargs.get('pk')})
|
||||
return context
|
||||
|
|
|
@ -66,3 +66,4 @@ class OrgMembershipUsersViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet
|
|||
serializer_class = OrgMembershipUserSerializer
|
||||
membership_class = Organization.users.through
|
||||
permission_classes = (IsSuperUserOrAppUser, )
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from werkzeug.local import Local
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import redirect
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.forms import ModelForm
|
||||
from django.http.response import HttpResponseForbidden
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -191,7 +191,7 @@ class OrgMembershipModelViewSetMixin:
|
|||
http_method_names = ['get', 'post', 'delete', 'head', 'options']
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.org = Organization.objects.get(pk=kwargs.get('org_id'))
|
||||
self.org = get_object_or_404(Organization, pk=kwargs.get('org_id'))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_serializer_context(self):
|
||||
|
@ -200,4 +200,5 @@ class OrgMembershipModelViewSetMixin:
|
|||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
return self.membership_class.objects.filter(organization=self.org)
|
||||
queryset = self.membership_class.objects.filter(organization=self.org)
|
||||
return queryset
|
||||
|
|
|
@ -122,3 +122,7 @@ class Organization(models.Model):
|
|||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def change_to(self):
|
||||
from .utils import set_current_org
|
||||
set_current_org(self)
|
||||
|
|
|
@ -9,11 +9,16 @@ from .. import api
|
|||
app_name = 'orgs'
|
||||
router = DefaultRouter()
|
||||
|
||||
# 将会删除
|
||||
router.register(r'org/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
|
||||
api.OrgMembershipAdminsViewSet, 'membership-admins')
|
||||
|
||||
router.register(r'org/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
|
||||
api.OrgMembershipUsersViewSet, 'membership-users'),
|
||||
# 替换为这个
|
||||
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
|
||||
api.OrgMembershipAdminsViewSet, 'membership-admins-2')
|
||||
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
|
||||
api.OrgMembershipUsersViewSet, 'membership-users-2'),
|
||||
|
||||
router.register(r'orgs', api.OrgViewSet, 'org')
|
||||
|
||||
|
|
|
@ -2,21 +2,26 @@
|
|||
#
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.db.models import Q
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import ListAPIView, get_object_or_404, \
|
||||
RetrieveUpdateAPIView
|
||||
from rest_framework.generics import (
|
||||
ListAPIView, get_object_or_404, RetrieveUpdateAPIView
|
||||
)
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.utils import set_or_append_attr_bulk
|
||||
from common.permissions import IsValidUser, IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from common.tree import TreeNode, TreeNodeSerializer
|
||||
from common.utils import get_object_or_none
|
||||
from orgs.mixins import RootOrgViewMixin
|
||||
from orgs.utils import set_to_root_org
|
||||
from .utils import AssetPermissionUtil
|
||||
from .models import AssetPermission
|
||||
from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \
|
||||
from .hands import (
|
||||
AssetGrantedSerializer, User, UserGroup, Asset, Node,
|
||||
SystemUser, NodeSerializer
|
||||
)
|
||||
from . import serializers
|
||||
from .mixins import AssetsFilterMixin
|
||||
|
||||
|
@ -38,6 +43,7 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
|||
queryset = AssetPermission.objects.all()
|
||||
serializer_class = serializers.AssetPermissionCreateUpdateSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_fields = ['name']
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
@ -45,36 +51,122 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
|||
return serializers.AssetPermissionListSerializer
|
||||
return self.serializer_class
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().all()
|
||||
search = self.request.query_params.get('search')
|
||||
asset_id = self.request.query_params.get('asset')
|
||||
node_id = self.request.query_params.get('node')
|
||||
inherit_nodes = set()
|
||||
|
||||
if search:
|
||||
queryset = queryset.filter(name__icontains=search)
|
||||
|
||||
if not asset_id and not node_id:
|
||||
def filter_valid(self, queryset):
|
||||
valid = self.request.query_params.get('is_valid', None)
|
||||
if valid is None:
|
||||
return queryset
|
||||
if valid in ['0', 'N', 'false', 'False']:
|
||||
valid = False
|
||||
else:
|
||||
valid = True
|
||||
now = timezone.now()
|
||||
if valid:
|
||||
queryset = queryset.filter(is_active=True).filter(
|
||||
date_start__lt=now, date_expired__gt=now,
|
||||
)
|
||||
else:
|
||||
queryset = queryset.filter(
|
||||
Q(is_active=False) |
|
||||
Q(date_start__gt=now) |
|
||||
Q(date_expired__lt=now)
|
||||
)
|
||||
return queryset
|
||||
|
||||
permissions = set()
|
||||
def filter_system_user(self, queryset):
|
||||
system_user_id = self.request.query_params.get('system_user_id')
|
||||
system_user_name = self.request.query_params.get('system_user')
|
||||
if system_user_id:
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
elif system_user_name:
|
||||
system_user = get_object_or_none(SystemUser, name=system_user_name)
|
||||
else:
|
||||
return queryset
|
||||
if not system_user:
|
||||
return queryset.none()
|
||||
queryset = queryset.filter(system_users=system_user)
|
||||
return queryset
|
||||
|
||||
def filter_node(self, queryset):
|
||||
node_id = self.request.query_params.get('node_id')
|
||||
node_name = self.request.query_params.get('node')
|
||||
if node_id:
|
||||
node = get_object_or_none(Node, pk=node_id)
|
||||
elif node_name:
|
||||
node = get_object_or_none(Node, name=node_name)
|
||||
else:
|
||||
return queryset
|
||||
if not node:
|
||||
return queryset.none()
|
||||
nodes = node.get_ancestor(with_self=True)
|
||||
queryset = queryset.filter(nodes__in=nodes)
|
||||
return queryset
|
||||
|
||||
def filter_asset(self, queryset):
|
||||
asset_id = self.request.query_params.get('asset_id')
|
||||
hostname = self.request.query_params.get('hostname')
|
||||
ip = self.request.query_params.get('ip')
|
||||
if asset_id:
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
permissions = set(queryset.filter(assets=asset))
|
||||
assets = Asset.objects.filter(pk=asset_id)
|
||||
elif hostname:
|
||||
assets = Asset.objects.filter(hostname=hostname)
|
||||
elif ip:
|
||||
assets = Asset.objects.filter(ip=ip)
|
||||
else:
|
||||
return queryset
|
||||
if not assets:
|
||||
return queryset.none()
|
||||
inherit_nodes = set()
|
||||
for asset in assets:
|
||||
for node in asset.nodes.all():
|
||||
inherit_nodes.update(set(node.get_ancestor(with_self=True)))
|
||||
elif node_id:
|
||||
node = get_object_or_404(Node, pk=node_id)
|
||||
permissions = set(queryset.filter(nodes=node))
|
||||
inherit_nodes = node.get_ancestor()
|
||||
queryset = queryset.filter(Q(assets__in=assets) | Q(nodes__in=inherit_nodes))
|
||||
return queryset
|
||||
|
||||
for n in inherit_nodes:
|
||||
_permissions = queryset.filter(nodes=n)
|
||||
set_or_append_attr_bulk(_permissions, "inherit", n.value)
|
||||
permissions.update(_permissions)
|
||||
def filter_user(self, queryset):
|
||||
user_id = self.request.query_params.get('user_id')
|
||||
username = self.request.query_params.get('username')
|
||||
if user_id:
|
||||
user = get_object_or_none(User, pk=user_id)
|
||||
elif username:
|
||||
user = get_object_or_none(User, username=username)
|
||||
else:
|
||||
return queryset
|
||||
if not user:
|
||||
return queryset.none()
|
||||
|
||||
return list(permissions)
|
||||
def filter_user_group(self, queryset):
|
||||
user_group_id = self.request.query_params.get('user_group_id')
|
||||
user_group_name = self.request.query_params.get('user_group')
|
||||
if user_group_id:
|
||||
group = get_object_or_none(UserGroup, pk=user_group_id)
|
||||
elif user_group_name:
|
||||
group = get_object_or_none(UserGroup, name=user_group_name)
|
||||
else:
|
||||
return queryset
|
||||
if not group:
|
||||
return queryset.none()
|
||||
queryset = queryset.filter(user_groups=group)
|
||||
return queryset
|
||||
|
||||
def filter_keyword(self, queryset):
|
||||
keyword = self.request.query_params.get('search')
|
||||
if not keyword:
|
||||
return queryset
|
||||
queryset = queryset.filter(name__icontains=keyword)
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
queryset = self.filter_valid(queryset)
|
||||
queryset = self.filter_keyword(queryset)
|
||||
queryset = self.filter_asset(queryset)
|
||||
queryset = self.filter_node(queryset)
|
||||
queryset = self.filter_system_user(queryset)
|
||||
queryset = self.filter_user_group(queryset)
|
||||
return queryset
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset.all()
|
||||
|
||||
|
||||
class UserGrantedAssetsApi(AssetsFilterMixin, ListAPIView):
|
||||
|
|
|
@ -51,9 +51,15 @@ class AssetPermission(OrgModelMixin):
|
|||
def id_str(self):
|
||||
return str(self.id)
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
if self.date_expired > timezone.now() > self.date_start:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
if self.date_expired > timezone.now() > self.date_start and self.is_active:
|
||||
if not self.is_expired and self.is_active:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
|
@ -28,19 +28,13 @@ class AssetPermissionListSerializer(serializers.ModelSerializer):
|
|||
assets = StringManyToManyField(many=True, read_only=True)
|
||||
nodes = StringManyToManyField(many=True, read_only=True)
|
||||
system_users = StringManyToManyField(many=True, read_only=True)
|
||||
inherit = serializers.SerializerMethodField()
|
||||
is_valid = serializers.BooleanField()
|
||||
is_expired = serializers.BooleanField()
|
||||
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_inherit(obj):
|
||||
if hasattr(obj, 'inherit'):
|
||||
return obj.inherit
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class AssetPermissionUpdateUserSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'Node'%}</th>
|
||||
<th class="text-center">{% trans 'System user' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Validity' %}</th>
|
||||
<th class="text-center" >{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -67,6 +67,17 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="dropdown-menu search-help">
|
||||
<li><a class="search-item" data-value="name">{% trans 'Name' %}</a></li>
|
||||
<li><a class="search-item" data-value="is_valid">{% trans 'Validity' %}</a></li>
|
||||
<li><a class="search-item" data-value="username">{% trans 'Username' %}</a></li>
|
||||
<li><a class="search-item" data-value="user_group">{% trans 'User group' %}</a></li>
|
||||
<li><a class="search-item" data-value="ip">IP</a></li>
|
||||
<li><a class="search-item" data-value="hostname">{% trans 'Hostname' %}</a></li>
|
||||
<li><a class="search-item" data-value="node">{% trans 'Node' %}</a></li>
|
||||
<li><a class="search-item" data-value="system_user">{% trans 'System user' %}</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
|
@ -79,11 +90,11 @@ function onSelected(event, treeNode) {
|
|||
setCookie('node_selected', treeNode.id);
|
||||
var url = table.ajax.url();
|
||||
if (treeNode.meta.type === 'node') {
|
||||
url = setUrlParam(url, 'asset', "");
|
||||
url = setUrlParam(url, 'node', treeNode.meta.node.id)
|
||||
url = setUrlParam(url, 'asset_id', "");
|
||||
url = setUrlParam(url, 'node_id', treeNode.meta.node.id)
|
||||
} else {
|
||||
url = setUrlParam(url, 'node', "");
|
||||
url = setUrlParam(url, 'asset', treeNode.meta.asset.id)
|
||||
url = setUrlParam(url, 'node_id', "");
|
||||
url = setUrlParam(url, 'asset_id', treeNode.meta.asset.id)
|
||||
}
|
||||
setCookie('node_selected', treeNode.node_id);
|
||||
table.ajax.url(url);
|
||||
|
@ -178,7 +189,7 @@ function initTable() {
|
|||
{data: "id"}, {data: "name"}, {data: "users"},
|
||||
{data: "user_groups"}, {data: "assets"},
|
||||
{data: "nodes"}, {data: "system_users"},
|
||||
{data: "is_active", orderable: false}, {data: "id", orderable: false}
|
||||
{data: "is_valid", orderable: false}, {data: "id", orderable: false}
|
||||
],
|
||||
select: {},
|
||||
op_html: $('#actions').html()
|
||||
|
@ -231,6 +242,7 @@ function toggle() {
|
|||
$(document).ready(function(){
|
||||
initTable();
|
||||
initTree();
|
||||
|
||||
})
|
||||
.on('click', '.btn-del', function () {
|
||||
var $this = $(this);
|
||||
|
@ -279,6 +291,28 @@ $(document).ready(function(){
|
|||
}
|
||||
}
|
||||
|
||||
}).on('click', '#permission_list_table_filter input', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var position = $('#permission_list_table_filter input').offset();
|
||||
var y = position['top'];
|
||||
var x = position['left'];
|
||||
x -= 220;
|
||||
y += 30;
|
||||
|
||||
$('.search-help').css({"top":y+"px", "left":x+"px", "position": "absolute"});
|
||||
$('.dropdown-menu.search-help').show();
|
||||
}).on('click', '.search-item', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var value = $(this).data('value');
|
||||
var old_value = $('#permission_list_table_filter input').val();
|
||||
var new_value = old_value + ' ' + value + ':';
|
||||
$('#permission_list_table_filter input').val(new_value.trim());
|
||||
$('.dropdown-menu.search-help').hide();
|
||||
$('#permission_list_table_filter input').focus()
|
||||
}).on('click', 'body', function (e) {
|
||||
$('.dropdown-menu.search-help').hide()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -161,6 +161,87 @@ function activeNav() {
|
|||
}
|
||||
}
|
||||
|
||||
function formSubmit(props) {
|
||||
/*
|
||||
{
|
||||
"form": $("form"),
|
||||
"url": "",
|
||||
"method": "POST",
|
||||
"redirect_to": "",
|
||||
"success": function(data, textStatue, jqXHR){},
|
||||
"error": function(jqXHR, textStatus, errorThrown) {}
|
||||
}
|
||||
*/
|
||||
props = props || {};
|
||||
var data = props.data || props.form.serializeObject();
|
||||
var redirect_to = props.redirect_to;
|
||||
$.ajax({
|
||||
url: props.url,
|
||||
type: props.method || 'POST',
|
||||
data: JSON.stringify(data),
|
||||
contentType: props.content_type || "application/json; charset=utf-8",
|
||||
dataType: props.data_type || "json"
|
||||
}).done(function (data, textState, jqXHR) {
|
||||
if (redirect_to) {
|
||||
location.href = redirect_to;
|
||||
} else if (typeof props.success === 'function') {
|
||||
return props.success(data, textState, jqXHR);
|
||||
}
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (typeof props.error === 'function') {
|
||||
return props.error(jqXHR, textStatus, errorThrown)
|
||||
}
|
||||
if (!props.form) {
|
||||
alert(jqXHR.responseText);
|
||||
return
|
||||
}
|
||||
if (jqXHR.status === 400) {
|
||||
var errors = jqXHR.responseJSON;
|
||||
var noneFieldErrorRef = props.form.children('.alert-danger');
|
||||
if (noneFieldErrorRef.length !== 1) {
|
||||
props.form.prepend('<div class="alert alert-danger" style="display: none"></div>');
|
||||
noneFieldErrorRef = props.form.children('.alert-danger');
|
||||
}
|
||||
var noneFieldErrorMsg = "";
|
||||
noneFieldErrorRef.css("display", "none");
|
||||
noneFieldErrorRef.html("");
|
||||
props.form.find(".help-block.error").html("");
|
||||
props.form.find(".form-group.has-error").removeClass("has-error");
|
||||
|
||||
if (typeof errors !== "object") {
|
||||
noneFieldErrorMsg = errors;
|
||||
if (noneFieldErrorRef.length === 1) {
|
||||
noneFieldErrorRef.css('display', 'block');
|
||||
noneFieldErrorRef.html(noneFieldErrorMsg);
|
||||
}
|
||||
return
|
||||
}
|
||||
$.each(errors, function (k, v) {
|
||||
var fieldRef = props.form.find('input[name="' + k + '"]');
|
||||
var formGroupRef = fieldRef.parents('.form-group');
|
||||
var parentRef = fieldRef.parent();
|
||||
var helpBlockRef = parentRef.children('.help-block.error');
|
||||
if (helpBlockRef.length === 0) {
|
||||
parentRef.append('<div class="help-block error"></div>');
|
||||
helpBlockRef = parentRef.children('.help-block.error');
|
||||
}
|
||||
if (fieldRef.length === 1 && formGroupRef.length === 1) {
|
||||
formGroupRef.addClass('has-error');
|
||||
var help_msg = v.join("<br/>") ;
|
||||
helpBlockRef.html(help_msg);
|
||||
} else {
|
||||
noneFieldErrorMsg += v + '<br/>';
|
||||
}
|
||||
});
|
||||
if (noneFieldErrorRef.length === 1 && noneFieldErrorMsg !== '') {
|
||||
noneFieldErrorRef.css('display', 'block');
|
||||
noneFieldErrorRef.html(noneFieldErrorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function APIUpdateAttr(props) {
|
||||
// props = {url: .., body: , success: , error: , method: ,}
|
||||
props = props || {};
|
||||
|
@ -195,9 +276,6 @@ function APIUpdateAttr(props) {
|
|||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (flash_message) {
|
||||
var msg = "";
|
||||
console.log(jqXHR);
|
||||
console.log(textStatus);
|
||||
console.log(errorThrown);
|
||||
if (user_fail_message) {
|
||||
msg = user_fail_message;
|
||||
} else if (jqXHR.responseJSON) {
|
||||
|
@ -213,6 +291,7 @@ function APIUpdateAttr(props) {
|
|||
toastr.error(msg);
|
||||
}
|
||||
if (typeof props.error === 'function') {
|
||||
console.log(jqXHR);
|
||||
return props.error(jqXHR.responseText, jqXHR.status);
|
||||
}
|
||||
});
|
||||
|
@ -478,7 +557,7 @@ jumpserver.initServerSideDataTable = function (options) {
|
|||
url: options.ajax_url ,
|
||||
data: function (data) {
|
||||
delete data.columns;
|
||||
if (data.length !== null ){
|
||||
if (data.length !== null){
|
||||
data.limit = data.length;
|
||||
delete data.length;
|
||||
}
|
||||
|
@ -525,7 +604,7 @@ jumpserver.initServerSideDataTable = function (options) {
|
|||
columns: options.columns || [],
|
||||
select: options.select || select,
|
||||
language: jumpserver.language,
|
||||
lengthMenu: [[10, 15, 25, 50], [10, 15, 25, 50]]
|
||||
lengthMenu: [[15, 25, 50, 9999], [15, 25, 50, 'All']]
|
||||
});
|
||||
table.selected = [];
|
||||
table.selected_rows = [];
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
{% load i18n %}
|
||||
<strong>Copyright</strong> {% trans ' Beijing Duizhan Tech, Inc. ' %} © 2014-2018
|
||||
<strong>Copyright</strong> {% trans ' Beijing Duizhan Tech, Inc. ' %} © 2014-2019
|
|
@ -5,6 +5,6 @@
|
|||
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
|
||||
</div>
|
||||
<div>
|
||||
<strong>Copyright</strong> {% trans ' Beijing Duizhan Tech, Inc. ' %}© 2014-2018
|
||||
<strong>Copyright</strong> {% trans ' Beijing Duizhan Tech, Inc. ' %}© 2014-2019
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<small>2014-2018</small>
|
||||
<small>2014-2019</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -33,16 +33,19 @@ class SessionViewSet(BulkModelViewSet):
|
|||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
terminal_id = self.kwargs.get("terminal", None)
|
||||
if terminal_id:
|
||||
terminal = get_object_or_404(Terminal, id=terminal_id)
|
||||
self.queryset = terminal.session_set.all()
|
||||
return self.queryset.all()
|
||||
queryset = queryset.filter(terminal=terminal)
|
||||
return queryset
|
||||
return queryset
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if hasattr(self.request.user, 'terminal'):
|
||||
serializer.validated_data["terminal"] = self.request.user.terminal
|
||||
sid = serializer.validated_data["system_user"]
|
||||
# guacamole提交的是id
|
||||
if is_uuid(sid):
|
||||
_system_user = SystemUser.get_system_user_by_id_or_cached(sid)
|
||||
if _system_user:
|
||||
|
|
|
@ -100,52 +100,18 @@ class StatusViewSet(viewsets.ModelViewSet):
|
|||
task_serializer_class = serializers.TaskSerializer
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
from_gua = self.request.query_params.get("from_guacamole", None)
|
||||
if not from_gua:
|
||||
self.handle_sessions()
|
||||
super().create(request, *args, **kwargs)
|
||||
self.handle_status(request)
|
||||
self.handle_sessions()
|
||||
tasks = self.request.user.terminal.task_set.filter(is_finished=False)
|
||||
serializer = self.task_serializer_class(tasks, many=True)
|
||||
return Response(serializer.data, status=201)
|
||||
|
||||
def handle_status(self, request):
|
||||
request.user.terminal.is_alive = True
|
||||
|
||||
def handle_sessions(self):
|
||||
sessions_active = []
|
||||
for session_data in self.request.data.get("sessions", []):
|
||||
self.create_or_update_session(session_data)
|
||||
if not session_data["is_finished"]:
|
||||
sessions_active.append(session_data["id"])
|
||||
|
||||
sessions_in_db_active = Session.objects.filter(
|
||||
is_finished=False,
|
||||
terminal=self.request.user.terminal.id
|
||||
)
|
||||
|
||||
for session in sessions_in_db_active:
|
||||
if str(session.id) not in sessions_active:
|
||||
session.is_finished = True
|
||||
session.date_end = timezone.now()
|
||||
session.save()
|
||||
|
||||
def create_or_update_session(self, session_data):
|
||||
session_data["terminal"] = self.request.user.terminal.id
|
||||
_id = session_data["id"]
|
||||
session = get_object_or_none(Session, id=_id)
|
||||
if session:
|
||||
serializer = serializers.SessionSerializer(
|
||||
data=session_data, instance=session
|
||||
)
|
||||
else:
|
||||
serializer = serializers.SessionSerializer(data=session_data)
|
||||
|
||||
if serializer.is_valid():
|
||||
session = serializer.save()
|
||||
return session
|
||||
else:
|
||||
msg = "session data is not valid {}: {}".format(
|
||||
serializer.errors, str(serializer.data)
|
||||
)
|
||||
logger.error(msg)
|
||||
return None
|
||||
sessions_id = self.request.data.get('sessions', [])
|
||||
Session.set_sessions_active(sessions_id)
|
||||
|
||||
def get_queryset(self):
|
||||
terminal_id = self.kwargs.get("terminal", None)
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import viewsets
|
||||
from rest_framework import viewsets, generics
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.permissions import IsSuperUser, WithBootstrapToken
|
||||
|
||||
|
||||
from ...models import Terminal
|
||||
from ...serializers import v2 as serializers
|
||||
|
||||
__all__ = ['TerminalViewSet', 'TerminalRegistrationViewSet']
|
||||
__all__ = ['TerminalViewSet', 'TerminalRegistrationApi']
|
||||
|
||||
|
||||
class TerminalViewSet(viewsets.ModelViewSet):
|
||||
|
@ -15,8 +19,19 @@ class TerminalViewSet(viewsets.ModelViewSet):
|
|||
permission_classes = [IsSuperUser]
|
||||
|
||||
|
||||
class TerminalRegistrationViewSet(viewsets.ModelViewSet):
|
||||
queryset = Terminal.objects.filter(is_deleted=False)
|
||||
class TerminalRegistrationApi(generics.CreateAPIView):
|
||||
serializer_class = serializers.TerminalRegistrationSerializer
|
||||
permission_classes = [WithBootstrapToken]
|
||||
http_method_names = ['post']
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
data = {k: v for k, v in request.data.items()}
|
||||
serializer = serializers.TerminalSerializer(
|
||||
data=data, context={'request': request}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
terminal = serializer.save()
|
||||
sa_serializer = serializer.sa_serializer_class(instance=terminal.user)
|
||||
data['service_account'] = sa_serializer.data
|
||||
return Response(data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
import datetime
|
||||
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from django.db.utils import OperationalError
|
||||
|
||||
from .base import CommandBase
|
||||
|
||||
|
@ -35,7 +37,25 @@ class CommandStore(CommandBase):
|
|||
input=c["input"], output=c["output"], session=c["session"],
|
||||
org_id=c["org_id"], timestamp=c["timestamp"]
|
||||
))
|
||||
return self.model.objects.bulk_create(_commands)
|
||||
error = False
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.model.objects.bulk_create(_commands)
|
||||
except OperationalError:
|
||||
error = True
|
||||
except:
|
||||
return False
|
||||
|
||||
if not error:
|
||||
return True
|
||||
for command in _commands:
|
||||
try:
|
||||
with transaction.atomic():
|
||||
command.save()
|
||||
except OperationalError:
|
||||
command.output = str(command.output.encode())
|
||||
command.save()
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def make_filter_kwargs(
|
||||
|
|
|
@ -39,5 +39,3 @@ class TerminalForm(forms.ModelForm):
|
|||
'name', 'remote_addr', 'comment',
|
||||
'command_storage', 'replay_storage',
|
||||
]
|
||||
help_texts = {
|
||||
}
|
||||
|
|
|
@ -8,10 +8,12 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.cache import cache
|
||||
|
||||
from users.models import User
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from common.utils import get_command_storage_setting, get_replay_storage_setting
|
||||
from .backends import get_multi_command_storage
|
||||
from .backends.command.models import AbstractSessionCommand
|
||||
|
||||
|
||||
|
@ -28,6 +30,17 @@ class Terminal(models.Model):
|
|||
is_deleted = models.BooleanField(default=False)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
STATUS_KEY_PREFIX = 'terminal_status_'
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
key = self.STATUS_KEY_PREFIX + str(self.id)
|
||||
return bool(cache.get(key))
|
||||
|
||||
@is_alive.setter
|
||||
def is_alive(self, value):
|
||||
key = self.STATUS_KEY_PREFIX + str(self.id)
|
||||
cache.set(key, value, 60)
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
|
@ -41,7 +54,7 @@ class Terminal(models.Model):
|
|||
self.user.is_active = active
|
||||
self.user.save()
|
||||
|
||||
def get_common_storage(self):
|
||||
def get_command_storage_setting(self):
|
||||
storage_all = get_command_storage_setting()
|
||||
if self.command_storage in storage_all:
|
||||
storage = storage_all.get(self.command_storage)
|
||||
|
@ -49,7 +62,7 @@ class Terminal(models.Model):
|
|||
storage = storage_all.get('default')
|
||||
return {"TERMINAL_COMMAND_STORAGE": storage}
|
||||
|
||||
def get_replay_storage(self):
|
||||
def get_replay_storage_setting(self):
|
||||
storage_all = get_replay_storage_setting()
|
||||
if self.replay_storage in storage_all:
|
||||
storage = storage_all.get(self.replay_storage)
|
||||
|
@ -61,10 +74,11 @@ class Terminal(models.Model):
|
|||
def config(self):
|
||||
configs = {}
|
||||
for k in dir(settings):
|
||||
if k.startswith('TERMINAL'):
|
||||
configs[k] = getattr(settings, k)
|
||||
configs.update(self.get_common_storage())
|
||||
configs.update(self.get_replay_storage())
|
||||
if not k.startswith('TERMINAL'):
|
||||
continue
|
||||
configs[k] = getattr(settings, k)
|
||||
configs.update(self.get_command_storage_setting())
|
||||
configs.update(self.get_replay_storage_setting())
|
||||
configs.update({
|
||||
'SECURITY_MAX_IDLE_TIME': settings.SECURITY_MAX_IDLE_TIME
|
||||
})
|
||||
|
@ -152,6 +166,7 @@ class Session(OrgModelMixin):
|
|||
date_end = models.DateTimeField(verbose_name=_("Date end"), null=True)
|
||||
|
||||
upload_to = 'replay'
|
||||
ACTIVE_CACHE_KEY_PREFIX = 'SESSION_ACTIVE_{}'
|
||||
|
||||
def get_rel_replay_path(self, version=2):
|
||||
"""
|
||||
|
@ -181,6 +196,26 @@ class Session(OrgModelMixin):
|
|||
except OSError as e:
|
||||
return None, e
|
||||
|
||||
@classmethod
|
||||
def set_sessions_active(cls, sessions_id):
|
||||
data = {cls.ACTIVE_CACHE_KEY_PREFIX.format(i): i for i in sessions_id}
|
||||
cache.set_many(data, timeout=5*60)
|
||||
|
||||
@classmethod
|
||||
def get_active_sessions(cls):
|
||||
return cls.objects.filter(is_finished=False)
|
||||
|
||||
def is_active(self):
|
||||
if self.protocol in ['ssh', 'telnet']:
|
||||
key = self.ACTIVE_CACHE_KEY_PREFIX.format(self.id)
|
||||
return bool(cache.get(key))
|
||||
return True
|
||||
|
||||
@property
|
||||
def command_amount(self):
|
||||
command_store = get_multi_command_storage()
|
||||
return command_store.count(session=str(self.id))
|
||||
|
||||
class Meta:
|
||||
db_table = "terminal_session"
|
||||
ordering = ["-date_start"]
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.core.cache import cache
|
||||
from rest_framework import serializers
|
||||
from rest_framework_bulk.serializers import BulkListSerializer
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from ..models import Terminal, Status, Session, Task
|
||||
from ..backends import get_multi_command_storage
|
||||
|
||||
|
||||
class TerminalSerializer(serializers.ModelSerializer):
|
||||
session_online = serializers.SerializerMethodField()
|
||||
is_alive = serializers.SerializerMethodField()
|
||||
is_alive = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Terminal
|
||||
|
@ -23,42 +21,23 @@ class TerminalSerializer(serializers.ModelSerializer):
|
|||
|
||||
@staticmethod
|
||||
def get_session_online(obj):
|
||||
return Session.objects.filter(terminal=obj.id, is_finished=False).count()
|
||||
|
||||
@staticmethod
|
||||
def get_is_alive(obj):
|
||||
key = StatusSerializer.CACHE_KEY_PREFIX + str(obj.id)
|
||||
return cache.get(key)
|
||||
|
||||
|
||||
return Session.objects.filter(terminal=obj, is_finished=False).count()
|
||||
|
||||
|
||||
class SessionSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
command_amount = serializers.SerializerMethodField()
|
||||
command_store = get_multi_command_storage()
|
||||
command_amount = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Session
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
|
||||
def get_command_amount(self, obj):
|
||||
return self.command_store.count(session=str(obj.id))
|
||||
|
||||
|
||||
class StatusSerializer(serializers.ModelSerializer):
|
||||
CACHE_KEY_PREFIX = 'terminal_status_'
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
fields = ['id', 'terminal']
|
||||
model = Status
|
||||
|
||||
def create(self, validated_data):
|
||||
terminal_id = str(validated_data['terminal'].id)
|
||||
key = self.CACHE_KEY_PREFIX + terminal_id
|
||||
cache.set(key, 1, 60)
|
||||
return validated_data
|
||||
|
||||
|
||||
class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
|
||||
|
@ -69,6 +48,6 @@ class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
|
||||
|
||||
class ReplaySerializer(serializers.Serializer):
|
||||
file = serializers.FileField()
|
||||
file = serializers.FileField(allow_empty_file=True)
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from common.utils import get_request_ip
|
||||
from users.serializers.v2 import ServiceAccountRegistrationSerializer
|
||||
from users.serializers.v2 import ServiceAccountSerializer
|
||||
from ..models import Terminal
|
||||
|
||||
|
||||
|
@ -11,36 +11,48 @@ __all__ = ['TerminalSerializer', 'TerminalRegistrationSerializer']
|
|||
|
||||
|
||||
class TerminalSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields = [
|
||||
'id', 'name', 'remote_addr', 'comment',
|
||||
]
|
||||
read_only_fields = ['id', 'remote_addr']
|
||||
|
||||
|
||||
class TerminalRegistrationSerializer(serializers.ModelSerializer):
|
||||
service_account = ServiceAccountRegistrationSerializer(read_only=True)
|
||||
service_account_serializer = None
|
||||
sa_serializer_class = ServiceAccountSerializer
|
||||
sa_serializer = None
|
||||
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields = [
|
||||
'id', 'name', 'remote_addr', 'comment', 'service_account'
|
||||
'id', 'name', 'remote_addr', 'command_storage',
|
||||
'replay_storage', 'user', 'is_accepted', 'is_deleted',
|
||||
'date_created', 'comment'
|
||||
]
|
||||
read_only_fields = ['id', 'remote_addr', 'service_account']
|
||||
read_only_fields = ['id', 'remote_addr', 'user', 'date_created']
|
||||
|
||||
def validate(self, attrs):
|
||||
self.service_account_serializer = ServiceAccountRegistrationSerializer(data=attrs)
|
||||
self.service_account_serializer.is_valid(raise_exception=True)
|
||||
return attrs
|
||||
def is_valid(self, raise_exception=False):
|
||||
valid = super().is_valid(raise_exception=raise_exception)
|
||||
if not valid:
|
||||
return valid
|
||||
data = {'name': self.validated_data.get('name')}
|
||||
kwargs = {'data': data}
|
||||
if self.instance and self.instance.user:
|
||||
kwargs['instance'] = self.instance.user
|
||||
self.sa_serializer = ServiceAccountSerializer(**kwargs)
|
||||
valid = self.sa_serializer.is_valid(raise_exception=True)
|
||||
return valid
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
sa = self.sa_serializer.save()
|
||||
instance.user = sa
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def create(self, validated_data):
|
||||
request = self.context.get('request')
|
||||
sa = self.service_account_serializer.save()
|
||||
instance = super().create(validated_data)
|
||||
instance.is_accepted = True
|
||||
instance.user = sa
|
||||
instance.remote_addr = get_request_ip(request)
|
||||
if request:
|
||||
instance.remote_addr = get_request_ip(request)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class TerminalRegistrationSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(max_length=128)
|
||||
comment = serializers.CharField(max_length=128)
|
||||
service_account = ServiceAccountSerializer(read_only=True)
|
||||
|
|
|
@ -1,28 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from celery import shared_task
|
||||
from django.core.cache import cache
|
||||
from django.db.utils import ProgrammingError, OperationalError
|
||||
|
||||
from common.utils import get_logger
|
||||
from .const import ASSETS_CACHE_KEY, USERS_CACHE_KEY, SYSTEM_USER_CACHE_KEY
|
||||
|
||||
RUNNING = False
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def set_session_info_cache():
|
||||
logger.debug("")
|
||||
from .utils import get_session_asset_list, get_session_user_list, \
|
||||
get_session_system_user_list
|
||||
|
||||
try:
|
||||
assets = get_session_asset_list()
|
||||
users = get_session_user_list()
|
||||
system_users = get_session_system_user_list()
|
||||
|
||||
cache.set(ASSETS_CACHE_KEY, assets)
|
||||
cache.set(USERS_CACHE_KEY, users)
|
||||
cache.set(SYSTEM_USER_CACHE_KEY, system_users)
|
||||
except (ProgrammingError, OperationalError):
|
||||
pass
|
||||
|
|
|
@ -10,8 +10,9 @@ from django.conf import settings
|
|||
from django.core.files.storage import default_storage
|
||||
|
||||
|
||||
from ops.celery.utils import register_as_period_task, after_app_ready_start, \
|
||||
after_app_shutdown_clean
|
||||
from ops.celery.decorator import (
|
||||
register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic
|
||||
)
|
||||
from .models import Status, Session, Command
|
||||
|
||||
|
||||
|
@ -23,28 +24,30 @@ logger = get_task_logger(__name__)
|
|||
@shared_task
|
||||
@register_as_period_task(interval=3600)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
@after_app_shutdown_clean_periodic
|
||||
def delete_terminal_status_period():
|
||||
yesterday = timezone.now() - datetime.timedelta(days=3)
|
||||
yesterday = timezone.now() - datetime.timedelta(days=1)
|
||||
Status.objects.filter(date_created__lt=yesterday).delete()
|
||||
|
||||
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600)
|
||||
@register_as_period_task(interval=600)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
@after_app_shutdown_clean_periodic
|
||||
def clean_orphan_session():
|
||||
active_sessions = Session.objects.filter(is_finished=False)
|
||||
for session in active_sessions:
|
||||
if not session.terminal or not session.terminal.is_active:
|
||||
session.is_finished = True
|
||||
session.save()
|
||||
if not session.is_active():
|
||||
continue
|
||||
session.is_finished = True
|
||||
session.date_end = timezone.now()
|
||||
session.save()
|
||||
|
||||
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600*24)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
@after_app_shutdown_clean_periodic
|
||||
def clean_expired_session_period():
|
||||
logger.info("Start clean expired session record, commands and replay")
|
||||
days = settings.TERMINAL_SESSION_KEEP_DURATION
|
||||
|
@ -64,3 +67,4 @@ def clean_expired_session_period():
|
|||
default_storage.delete(_local_path)
|
||||
# 删除session记录
|
||||
session.delete()
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
<td class="text-center">{{ session.remote_addr|default:"" }}</td>
|
||||
<td class="text-center">{{ session.protocol }}</td>
|
||||
<td class="text-center">{{ session.get_login_from_display }}</td>
|
||||
<td class="text-center">{{ session.id | get_session_command_amount }}</td>
|
||||
<td class="text-center">{{ session.command_amount }}</td>
|
||||
|
||||
<td class="text-center">{{ session.date_start }}</td>
|
||||
{# <td class="text-center">{{ session.date_last_active }}</td>#}
|
||||
|
|
|
@ -33,8 +33,6 @@
|
|||
<h3>{% trans 'Info' %}</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.remote_addr layout="horizontal" %}
|
||||
{# {% bootstrap_field form.ssh_port layout="horizontal" %}#}
|
||||
{# {% bootstrap_field form.http_port layout="horizontal" %}#}
|
||||
{% bootstrap_field form.command_storage layout="horizontal" %}
|
||||
{% bootstrap_field form.replay_storage layout="horizontal" %}
|
||||
|
||||
|
@ -60,14 +58,14 @@
|
|||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
|
||||
$('.input-group.date').datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
todayBtn: "linked",
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
calendarWeeks: true,
|
||||
autoclose: true
|
||||
}).on('submit', 'form', function (e) {
|
||||
e.preventDefault();
|
||||
var form = $('form');
|
||||
formSubmit({
|
||||
'url': '{% url 'api-terminal-v2:terminal-detail' pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', '{{ object.id }}'),
|
||||
'form': form,
|
||||
'method': 'PUT',
|
||||
'redirect_to': '{% url "terminal:terminal-list" %}'
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -11,10 +11,11 @@ app_name = 'terminal'
|
|||
|
||||
router = BulkRouter()
|
||||
router.register(r'terminal', api.TerminalViewSet, 'terminal')
|
||||
router.register(r'terminal-registrations', api.TerminalRegistrationViewSet, 'terminal-registration')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('terminal-registrations/', api.TerminalRegistrationApi.as_view(),
|
||||
name='terminal-registration')
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
|
|
@ -19,6 +19,7 @@ from orgs.utils import current_org
|
|||
from ..serializers import UserSerializer, UserPKUpdateSerializer, \
|
||||
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
|
||||
from ..models import User
|
||||
from ..signals import post_user_create
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
@ -37,6 +38,10 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet):
|
|||
permission_classes = (IsOrgAdmin,)
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def perform_create(self, serializer):
|
||||
user = serializer.save()
|
||||
post_user_create.send(self.__class__, user=user)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = current_org.get_org_users()
|
||||
return queryset
|
||||
|
|
|
@ -7,6 +7,6 @@ from ...serializers import v2 as serializers
|
|||
|
||||
|
||||
class ServiceAccountRegistrationViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = serializers.ServiceAccountRegistrationSerializer
|
||||
serializer_class = serializers.ServiceAccountSerializer
|
||||
permission_classes = (WithBootstrapToken,)
|
||||
http_method_names = ['post']
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.1.4 on 2019-01-07 11:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0017_auto_20181123_1113'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='source',
|
||||
field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius')], default='local', max_length=30, verbose_name='Source'),
|
||||
),
|
||||
]
|
|
@ -41,10 +41,12 @@ class User(AbstractUser):
|
|||
SOURCE_LOCAL = 'local'
|
||||
SOURCE_LDAP = 'ldap'
|
||||
SOURCE_OPENID = 'openid'
|
||||
SOURCE_RADIUS = 'radius'
|
||||
SOURCE_CHOICES = (
|
||||
(SOURCE_LOCAL, 'Local'),
|
||||
(SOURCE_LDAP, 'LDAP/AD'),
|
||||
(SOURCE_OPENID, 'OpenID'),
|
||||
(SOURCE_RADIUS, 'Radius'),
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
username = models.CharField(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
from ..models import User, AccessKey
|
||||
|
||||
|
@ -12,7 +13,7 @@ class AccessKeySerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ['id', 'secret']
|
||||
|
||||
|
||||
class ServiceAccountRegistrationSerializer(serializers.ModelSerializer):
|
||||
class ServiceAccountSerializer(serializers.ModelSerializer):
|
||||
access_key = AccessKeySerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -30,15 +31,22 @@ class ServiceAccountRegistrationSerializer(serializers.ModelSerializer):
|
|||
def validate_name(self, name):
|
||||
email = self.get_email()
|
||||
username = self.get_username()
|
||||
if User.objects.filter(email=email) or \
|
||||
User.objects.filter(username=username):
|
||||
raise serializers.ValidationError('name not unique', code='unique')
|
||||
if self.instance:
|
||||
users = User.objects.exclude(id=self.instance.id)
|
||||
else:
|
||||
users = User.objects.all()
|
||||
if users.filter(email=email) or \
|
||||
users.filter(username=username):
|
||||
raise serializers.ValidationError(_('name not unique'), code='unique')
|
||||
return name
|
||||
|
||||
def save(self, **kwargs):
|
||||
self.validated_data['email'] = self.get_email()
|
||||
self.validated_data['username'] = self.get_username()
|
||||
self.validated_data['role'] = User.ROLE_APP
|
||||
return super().save(**kwargs)
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['email'] = self.get_email()
|
||||
validated_data['username'] = self.get_username()
|
||||
validated_data['role'] = User.ROLE_APP
|
||||
instance = super().create(validated_data)
|
||||
instance.create_access_key()
|
||||
return instance
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
|
||||
from celery import shared_task
|
||||
|
||||
from ops.celery.utils import (
|
||||
create_or_update_celery_periodic_tasks,
|
||||
after_app_ready_start
|
||||
)
|
||||
from ops.celery.utils import create_or_update_celery_periodic_tasks
|
||||
from ops.celery.decorator import after_app_ready_start
|
||||
from .models import User
|
||||
from common.utils import get_logger
|
||||
from .utils import write_login_log, send_password_expiration_reminder_mail
|
||||
|
|
212
config_docker.py
212
config_docker.py
|
@ -1,212 +0,0 @@
|
|||
"""
|
||||
jumpserver.config
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Jumpserver project setting file
|
||||
|
||||
:copyright: (c) 2014-2017 by Jumpserver Team
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class Config:
|
||||
# Use it to encrypt or decrypt data
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x'
|
||||
|
||||
# How many line display every page if using django pager, default 25
|
||||
DISPLAY_PER_PAGE = 25
|
||||
|
||||
# It's used to identify your site, When we send a create mail to user, we only know login url is /login/
|
||||
# But we should know the absolute url like: http://jms.jumpserver.org/login/, so SITE_URL is
|
||||
# HTTP_PROTOCOL://HOST[:PORT]
|
||||
SITE_URL = 'http://localhost'
|
||||
|
||||
# Django security setting, if your disable debug model, you should setting that
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
# Development env open this, when error occur display the full process track, Production disable it
|
||||
DEBUG = True
|
||||
|
||||
# DEBUG, INFO, WARNING, ERROR, CRITICAL can set. See https://docs.djangoproject.com/en/1.10/topics/logging/
|
||||
LOG_LEVEL = 'DEBUG'
|
||||
LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
||||
|
||||
# Database setting, Support sqlite3, mysql, postgres ....
|
||||
# See https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||
|
||||
# SQLite setting:
|
||||
DB_ENGINE = 'sqlite3'
|
||||
DB_NAME = os.path.join(BASE_DIR, 'data', 'db.sqlite3')
|
||||
|
||||
# MySQL or postgres setting like:
|
||||
# DB_ENGINE = 'mysql'
|
||||
# DB_HOST = '127.0.0.1'
|
||||
# DB_PORT = 3306
|
||||
# DB_USER = 'root'
|
||||
# DB_PASSWORD = ''
|
||||
# DB_NAME = 'jumpserver'
|
||||
|
||||
# When Django start it will bind this host and port
|
||||
# ./manage.py runserver 127.0.0.1:8080
|
||||
HTTP_BIND_HOST = '0.0.0.0'
|
||||
HTTP_LISTEN_PORT = 8080
|
||||
|
||||
# Use Redis as broker for celery and web socket
|
||||
REDIS_HOST = '127.0.0.1'
|
||||
REDIS_PORT = 6379
|
||||
REDIS_PASSWORD = ''
|
||||
BROKER_URL = 'redis://%(password)s%(host)s:%(port)s/3' % {
|
||||
'password': REDIS_PASSWORD,
|
||||
'host': REDIS_HOST,
|
||||
'port': REDIS_PORT,
|
||||
}
|
||||
|
||||
# Api token expiration when create, Jumpserver refresh time when request arrive
|
||||
TOKEN_EXPIRATION = 3600
|
||||
|
||||
# Session and csrf domain settings
|
||||
SESSION_COOKIE_AGE = 3600*24
|
||||
|
||||
# Email SMTP setting, we only support smtp send mail
|
||||
EMAIL_HOST = 'smtp.163.com'
|
||||
EMAIL_PORT = 25
|
||||
EMAIL_HOST_USER = ''
|
||||
EMAIL_HOST_PASSWORD = '' # Caution: Some SMTP server using `Authorization Code` except password
|
||||
EMAIL_USE_SSL = True if EMAIL_PORT == 465 else False
|
||||
EMAIL_USE_TLS = True if EMAIL_PORT == 587 else False
|
||||
EMAIL_SUBJECT_PREFIX = '[Jumpserver] '
|
||||
|
||||
CAPTCHA_TEST_MODE = False
|
||||
|
||||
# You can set jumpserver usage url here, that when user submit wizard redirect to
|
||||
USER_GUIDE_URL = ''
|
||||
|
||||
# LDAP Auth settings
|
||||
AUTH_LDAP = False
|
||||
AUTH_LDAP_SERVER_URI = 'ldap://localhost:389'
|
||||
AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org'
|
||||
AUTH_LDAP_BIND_PASSWORD = ''
|
||||
AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org'
|
||||
AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)'
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"username": "cn",
|
||||
"name": "sn",
|
||||
"email": "mail"
|
||||
}
|
||||
AUTH_LDAP_START_TLS = False
|
||||
|
||||
#
|
||||
# OTP_VALID_WINDOW = 0
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __getattr__(self, item):
|
||||
return None
|
||||
|
||||
|
||||
class DockerConfig(Config):
|
||||
"""
|
||||
配置文件默认从环境变量里读取,如果没有会使用后面的默认值
|
||||
"""
|
||||
# 用来加密数据的key, 可以修改,但务必保存好这个字符串,丢失它后加密会无法解开
|
||||
# SECRET_KEY = "SOME_KEY_NO_ONE_GUESS"
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY") or "MD923lkSDi8213kl),3()&^%aM2q1mz;223lkM0o1"
|
||||
# 访问的域名, 格式 http[s]://域名[:端口号]
|
||||
# SITE_URL = "http://jumpserver.fit2cloud.com"
|
||||
SITE_URL = os.environ.get("SITE_URL") or 'http://localhost'
|
||||
# 是否开启DEBUG模式
|
||||
# DEBUG = True, or DEBUG = False,
|
||||
DEBUG = bool(os.environ.get("DEBUG")) if os.environ.get("DEBUG") else False
|
||||
# 日志级别, 默认 INFO
|
||||
# LOG_LEVEL = WARN
|
||||
LOG_LEVEL = os.environ.get("LOG_LEVEL") or "INFO"
|
||||
# 使用的数据库类型,支持 SQLite, MySQL, PostgreSQL, Oracle
|
||||
# 数据库设置, 如果使用外部的mysql请设置,否则不要改动
|
||||
|
||||
# DB_ENGINE = "oracle" | "postgre" | "mysql" | "sqlite3"
|
||||
DB_ENGINE = os.environ.get("DB_ENGINE") or 'mysql'
|
||||
# DB_HOST = "192.168.1.1"
|
||||
DB_HOST = os.environ.get("DB_HOST") or 'mysql'
|
||||
# 端口号
|
||||
# DB_PORT = 3306
|
||||
DB_PORT = os.environ.get("DB_PORT") or 3306
|
||||
# 数据库账号
|
||||
# DB_USER = "jumpserver"
|
||||
DB_USER = os.environ.get("DB_USER") or 'root'
|
||||
# 数据库密码
|
||||
# DB_PASSWORD = "db_jumpserver_password"
|
||||
DB_PASSWORD = os.environ.get("DB_PASSWORD") or ''
|
||||
# 数据库名称
|
||||
# DB_NAME = "jumpserver"
|
||||
DB_NAME = os.environ.get("DB_NAME") or 'jumpserver'
|
||||
|
||||
# Redis配置,如果不使用外部redis不要改动
|
||||
# Redis地址
|
||||
# REDIS_HOST = "192.168.1.1"
|
||||
REDIS_HOST = os.environ.get("REDIS_HOST") or 'redis'
|
||||
# Redis端口号
|
||||
# REDIS_PORT = 6380
|
||||
REDIS_PORT = os.environ.get("REDIS_PORT") or 6379
|
||||
# Redis密码
|
||||
# REDIS_PASSWORD = "redis_password"
|
||||
REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD") or ''
|
||||
|
||||
# 邮箱SMTP设置, 可以参考各运营商配置文档
|
||||
# SMTP服务器地址
|
||||
# EMAIL_HOST = 'smtp.qq.com'
|
||||
EMAIL_HOST = 'smtp.163.com'
|
||||
# SMTP端口号
|
||||
# EMAIL_PORT = 465
|
||||
EMAIL_PORT = 25
|
||||
# SMTP连接邮箱地址
|
||||
# EMAIL_HOST_USER = "noreply@jumpserver.org"
|
||||
EMAIL_HOST_USER = ''
|
||||
# SMTP邮箱的密码, 注意 一些运营商通常要求使用授权码来发SMTP邮件
|
||||
EMAIL_HOST_PASSWORD = ''
|
||||
# 是否启用SSL, 如果端口号是 465通常设置为True
|
||||
# EMAIL_USE_SSL = True
|
||||
EMAIL_USE_SSL = True if EMAIL_PORT == 465 else False
|
||||
# 是否启用TLS, 如果端口号是 587通常设置为True
|
||||
# EMAIL_USE_TLS = True
|
||||
EMAIL_USE_TLS = True if EMAIL_PORT == 587 else False
|
||||
# 邮件的主题前缀
|
||||
EMAIL_SUBJECT_PREFIX = '[Jumpserver] '
|
||||
|
||||
# 认证启用LDAP的设置
|
||||
# 是否启用LDAP,默认不启用
|
||||
# AUTH_LDAP = True
|
||||
AUTH_LDAP = False
|
||||
# LDAP的地址
|
||||
AUTH_LDAP_SERVER_URI = 'ldap://localhost:389'
|
||||
# LDAP绑定的查询账户
|
||||
AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org'
|
||||
# 密码
|
||||
AUTH_LDAP_BIND_PASSWORD = ''
|
||||
# 用户所在的ou
|
||||
AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org'
|
||||
# 查询时使用的过滤器, 仅可以修改前面的表示符,可能是cn或uid, 也就是登录用户名所在字段
|
||||
# AUTH_LDAP_SEARCH_FILTER = '(uid=%(user)s)'
|
||||
AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)'
|
||||
# LDAP用户信息映射到Jumpserver
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"username": "cn", # 将LDAP信息中的 `cn` 字段映射为 `username(用户名)`
|
||||
"name": "sn", # 将 LDAP信息中的 `sn` 映射为 `name(姓名)`
|
||||
"email": "mail" # 将 LDAP信息中的 `mail` 映射为 `email(邮箱地址)`
|
||||
}
|
||||
# 是否启用TLS加密
|
||||
AUTH_LDAP_START_TLS = False
|
||||
|
||||
|
||||
#
|
||||
OTP_VALID_WINDOW = int(os.environ.get("OTP_VALID_WINDOW")) if os.environ.get("OTP_VALID_WINDOW") else 0
|
||||
|
||||
|
||||
# Default using Config settings, you can write if/else for different env
|
||||
config = DockerConfig()
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jumpserver.config
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Jumpserver project setting file
|
||||
|
||||
:copyright: (c) 2014-2017 by Jumpserver Team
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class Config:
|
||||
"""
|
||||
Jumpserver Config File
|
||||
Jumpserver 配置文件
|
||||
|
||||
Jumpserver use this config for drive django framework running,
|
||||
You can set is value or set the same envirment value,
|
||||
Jumpserver look for config order: file => env => default
|
||||
|
||||
Jumpserver使用配置来驱动Django框架的运行,
|
||||
你可以在该文件中设置,或者设置同样名称的环境变量,
|
||||
Jumpserver使用配置的顺序: 文件 => 环境变量 => 默认值
|
||||
"""
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
# 加密秘钥 生产环境中请修改为随机字符串,请勿外泄
|
||||
SECRET_KEY = '2vym+ky!997d5kkcc64mnz06y1mmui3lut#(^wd=%s_qj$1%x'
|
||||
|
||||
# SECURITY WARNING: keep the bootstrap token used in production secret!
|
||||
# 预共享Token coco和guacamole用来注册服务账号,不在使用原来的注册接受机制
|
||||
BOOTSTRAP_TOKEN = 'PleaseChangeMe'
|
||||
|
||||
# Development env open this, when error occur display the full process track, Production disable it
|
||||
# DEBUG 模式 开启DEBUG后遇到错误时可以看到更多日志
|
||||
# DEBUG = True
|
||||
|
||||
# DEBUG, INFO, WARNING, ERROR, CRITICAL can set. See https://docs.djangoproject.com/en/1.10/topics/logging/
|
||||
# 日志级别
|
||||
# LOG_LEVEL = 'DEBUG'
|
||||
# LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
||||
|
||||
# Session expiration setting, Default 24 hour, Also set expired on on browser close
|
||||
# 浏览器Session过期时间,默认24小时, 也可以设置浏览器关闭则过期
|
||||
# SESSION_COOKIE_AGE = 3600 * 24
|
||||
# SESSION_EXPIRE_AT_BROWSER_CLOSE = False
|
||||
|
||||
# Database setting, Support sqlite3, mysql, postgres ....
|
||||
# 数据库设置
|
||||
# See https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||
|
||||
# SQLite setting:
|
||||
# 使用单文件sqlite数据库
|
||||
# DB_ENGINE = 'sqlite3'
|
||||
# DB_NAME = os.path.join(BASE_DIR, 'data', 'db.sqlite3')
|
||||
|
||||
# MySQL or postgres setting like:
|
||||
# 使用Mysql作为数据库
|
||||
DB_ENGINE = 'mysql'
|
||||
DB_HOST = '127.0.0.1'
|
||||
DB_PORT = 3306
|
||||
DB_USER = 'jumpserver'
|
||||
DB_PASSWORD = ''
|
||||
DB_NAME = 'jumpserver'
|
||||
|
||||
# When Django start it will bind this host and port
|
||||
# ./manage.py runserver 127.0.0.1:8080
|
||||
# 运行时绑定端口
|
||||
HTTP_BIND_HOST = '0.0.0.0'
|
||||
HTTP_LISTEN_PORT = 8080
|
||||
|
||||
# Use Redis as broker for celery and web socket
|
||||
# Redis配置
|
||||
REDIS_HOST = '127.0.0.1'
|
||||
REDIS_PORT = 6379
|
||||
# REDIS_PASSWORD = ''
|
||||
# REDIS_DB_CELERY = 3
|
||||
# REDIS_DB_CACHE = 4
|
||||
|
||||
# Use OpenID authorization
|
||||
# 使用OpenID 来进行认证设置
|
||||
# BASE_SITE_URL = 'http://localhost:8080'
|
||||
# AUTH_OPENID = False # True or False
|
||||
# AUTH_OPENID_SERVER_URL = 'https://openid-auth-server.com/'
|
||||
# AUTH_OPENID_REALM_NAME = 'realm-name'
|
||||
# AUTH_OPENID_CLIENT_ID = 'client-id'
|
||||
# AUTH_OPENID_CLIENT_SECRET = 'client-secret'
|
||||
|
||||
#
|
||||
# OTP_VALID_WINDOW = 0
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __getattr__(self, item):
|
||||
return None
|
||||
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
pass
|
||||
|
||||
|
||||
class TestConfig(Config):
|
||||
pass
|
||||
|
||||
|
||||
class ProductionConfig(Config):
|
||||
pass
|
||||
|
||||
|
||||
# Default using Config settings, you can write if/else for different env
|
||||
config = DevelopmentConfig()
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
# 加密秘钥 生产环境中请修改为随机字符串,请勿外泄, 可使用命令生成
|
||||
# $ cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 49;echo
|
||||
SECRET_KEY:
|
||||
|
||||
# SECURITY WARNING: keep the bootstrap token used in production secret!
|
||||
# 预共享Token coco和guacamole用来注册服务账号,不在使用原来的注册接受机制
|
||||
BOOTSTRAP_TOKEN:
|
||||
|
||||
# Development env open this, when error occur display the full process track, Production disable it
|
||||
# DEBUG 模式 开启DEBUG后遇到错误时可以看到更多日志
|
||||
# DEBUG: true
|
||||
|
||||
# DEBUG, INFO, WARNING, ERROR, CRITICAL can set. See https://docs.djangoproject.com/en/1.10/topics/logging/
|
||||
# 日志级别
|
||||
# LOG_LEVEL: DEBUG
|
||||
# LOG_DIR:
|
||||
|
||||
# Session expiration setting, Default 24 hour, Also set expired on on browser close
|
||||
# 浏览器Session过期时间,默认24小时, 也可以设置浏览器关闭则过期
|
||||
# SESSION_COOKIE_AGE: 3600 * 24
|
||||
# SESSION_EXPIRE_AT_BROWSER_CLOSE: False
|
||||
|
||||
# Database setting, Support sqlite3, mysql, postgres ....
|
||||
# 数据库设置
|
||||
# See https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||
|
||||
# SQLite setting:
|
||||
# 使用单文件sqlite数据库
|
||||
# DB_ENGINE: sqlite3
|
||||
# DB_NAME:
|
||||
|
||||
# MySQL or postgres setting like:
|
||||
# 使用Mysql作为数据库
|
||||
DB_ENGINE: mysql
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
DB_USER: jumpserver
|
||||
DB_PASSWORD:
|
||||
DB_NAME: jumpserver
|
||||
|
||||
# When Django start it will bind this host and port
|
||||
# ./manage.py runserver 127.0.0.1:8080
|
||||
# 运行时绑定端口
|
||||
HTTP_BIND_HOST: 0.0.0.0
|
||||
HTTP_LISTEN_PORT: 8080
|
||||
|
||||
# Use Redis as broker for celery and web socket
|
||||
# Redis配置
|
||||
REDIS_HOST: 127.0.0.1
|
||||
REDIS_PORT: 6379
|
||||
# REDIS_PASSWORD:
|
||||
# REDIS_DB_CELERY: 3
|
||||
# REDIS_DB_CACHE: 4
|
||||
|
||||
# Use OpenID authorization
|
||||
# 使用OpenID 来进行认证设置
|
||||
# BASE_SITE_URL: http://localhost:8080
|
||||
# AUTH_OPENID: false # True or False
|
||||
# AUTH_OPENID_SERVER_URL: https://openid-auth-server.com/
|
||||
# AUTH_OPENID_REALM_NAME: realm-name
|
||||
# AUTH_OPENID_CLIENT_ID: client-id
|
||||
# AUTH_OPENID_CLIENT_SECRET: client-secret
|
||||
|
||||
# OTP settings
|
||||
# OTP/MFA 配置
|
||||
# OTP_VALID_WINDOW: 0
|
||||
# OTP_ISSUER_NAME: Jumpserver
|
|
@ -7,5 +7,10 @@ function cleanup()
|
|||
fi
|
||||
}
|
||||
|
||||
service="all"
|
||||
if [ "$1" != "" ];then
|
||||
service=$1
|
||||
fi
|
||||
|
||||
trap cleanup EXIT
|
||||
python jms start all
|
||||
python jms start $service
|
||||
|
|
14
jms
14
jms
|
@ -15,9 +15,10 @@ sys.path.append(BASE_DIR)
|
|||
from apps import __version__
|
||||
|
||||
try:
|
||||
from config import config as CONFIG
|
||||
from apps.jumpserver.conf import load_user_config
|
||||
CONFIG = load_user_config()
|
||||
except ImportError:
|
||||
print("Could not find config file, `cp config_example.py config.py`")
|
||||
print("Could not find config file, `cp config_example.yml config.yml`")
|
||||
sys.exit(1)
|
||||
|
||||
os.environ["PYTHONIOENCODING"] = "UTF-8"
|
||||
|
@ -107,8 +108,7 @@ def is_running(s, unlink=True):
|
|||
pid_file = get_pid_file_path(s)
|
||||
|
||||
if os.path.isfile(pid_file):
|
||||
with open(pid_file, 'r') as f:
|
||||
pid = get_pid(s)
|
||||
pid = get_pid(s)
|
||||
if check_pid(pid):
|
||||
return True
|
||||
|
||||
|
@ -120,12 +120,15 @@ def is_running(s, unlink=True):
|
|||
def parse_service(s):
|
||||
if s == 'all':
|
||||
return all_services
|
||||
elif "," in s:
|
||||
return [i.strip() for i in s.split(',')]
|
||||
else:
|
||||
return [s]
|
||||
|
||||
|
||||
def start_gunicorn():
|
||||
print("\n- Start Gunicorn WSGI HTTP Server")
|
||||
prepare()
|
||||
service = 'gunicorn'
|
||||
bind = '{}:{}'.format(HTTP_HOST, HTTP_PORT)
|
||||
log_format = '%(h)s %(t)s "%(r)s" %(s)s %(b)s '
|
||||
|
@ -218,7 +221,6 @@ def start_service(s):
|
|||
print(time.ctime())
|
||||
print('Jumpserver version {}, more see https://www.jumpserver.org'.format(
|
||||
__version__))
|
||||
prepare()
|
||||
|
||||
services_handler = {
|
||||
"gunicorn": start_gunicorn,
|
||||
|
@ -316,7 +318,7 @@ if __name__ == '__main__':
|
|||
)
|
||||
parser.add_argument(
|
||||
"service", type=str, default="all", nargs="?",
|
||||
choices=("all", "gunicorn", "celery", "beat"),
|
||||
choices=("all", "gunicorn", "celery", "beat", "celery,beat"),
|
||||
help="The service to start",
|
||||
)
|
||||
parser.add_argument('-d', '--daemon', nargs="?", const=1)
|
||||
|
|
|
@ -78,3 +78,4 @@ python-keycloak-client==0.1.3
|
|||
rest_condition==1.0.3
|
||||
python-ldap==3.1.0
|
||||
tencentcloud-sdk-python==3.0.40
|
||||
django-radius==1.3.3
|
||||
|
|
|
@ -17,13 +17,13 @@ class UserCreation:
|
|||
self.domain = domain
|
||||
|
||||
def auth(self):
|
||||
url = "{}/api/users/v1/token/".format(self.domain)
|
||||
url = "{}/api/users/v1/auth/".format(self.domain)
|
||||
data = {"username": self.username, "password": self.password}
|
||||
resp = requests.post(url, data=data)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
self.headers.update({
|
||||
'Authorization': '{} {}'.format(data['Keyword'], data['Token'])
|
||||
'Authorization': '{} {}'.format('Bearer', data['token'])
|
||||
})
|
||||
else:
|
||||
print("用户名 或 密码 或 地址 不对")
|
||||
|
|
Loading…
Reference in New Issue