diff --git a/apps/common/api.py b/apps/common/api.py index e09cf9726..193132b7a 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- # + +import os import json +import jms_storage from rest_framework.views import Response, APIView from ldap3 import Server, Connection @@ -10,6 +13,7 @@ from django.conf import settings from .permissions import IsOrgAdmin from .serializers import MailTestSerializer, LDAPTestSerializer +from .models import Setting class MailTestingAPI(APIView): @@ -85,6 +89,78 @@ class LDAPTestingAPI(APIView): return Response({"error": str(serializer.errors)}, status=401) +class ReplayStorageCreateAPI(APIView): + permission_classes = (IsOrgAdmin,) + + def post(self, request): + storage_data = request.data + + if storage_data.get('TYPE') == 'ceph': + port = storage_data.get('PORT') + if port.isdigit(): + storage_data['PORT'] = int(storage_data.get('PORT')) + + storage_name = storage_data.pop('NAME') + data = {storage_name: storage_data} + + if not self.is_valid(storage_data): + return Response({"error": _("Error: Account invalid")}, status=401) + + Setting.save_storage('TERMINAL_REPLAY_STORAGE', data) + return Response({"msg": _('Create succeed')}, status=200) + + @staticmethod + def is_valid(storage_data): + if storage_data.get('TYPE') == 'server': + return True + storage = jms_storage.get_object_storage(storage_data) + target = 'tests.py' + src = os.path.join(settings.BASE_DIR, 'common', target) + ok, msg = storage.upload(src=src, target=target) + if not ok: + return False + storage.delete(path=target) + return True + + +class ReplayStorageDeleteAPI(APIView): + + def post(self, request): + storage_name = str(request.data.get('name')) + Setting.delete_storage('TERMINAL_REPLAY_STORAGE', storage_name) + return Response({"msg": _('Delete succeed')}, status=200) + + +class CommandStorageCreateAPI(APIView): + permission_classes = (IsOrgAdmin,) + + def post(self, request): + storage_data = request.data + storage_name = storage_data.pop('NAME') + data = {storage_name: storage_data} + if not self.is_valid(storage_data): + return Response({"error": _("Error: Account invalid")}, status=401) + + Setting.save_storage('TERMINAL_COMMAND_STORAGE', data) + return Response({"msg": _('Create succeed')}, status=200) + + @staticmethod + def is_valid(storage_data): + if storage_data.get('TYPE') == 'server': + return True + storage = jms_storage.get_log_storage(storage_data) + return storage.ping() + + +class CommandStorageDeleteAPI(APIView): + permission_classes = (IsOrgAdmin,) + + def post(self, request): + storage_name = str(request.data.get('name')) + Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name) + return Response({"msg": _('Delete succeed')}, status=200) + + class DjangoSettingsAPI(APIView): def get(self, request): if not settings.DEBUG: diff --git a/apps/common/forms.py b/apps/common/forms.py index 610e1a83e..491ae779a 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -135,30 +135,34 @@ class TerminalSettingForm(BaseForm): ('hostname', _('Hostname')), ('ip', _('IP')), ) - TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( - choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by") - ) - TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField( - initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds") - ) TERMINAL_PASSWORD_AUTH = forms.BooleanField( initial=True, required=False, label=_("Password auth") ) TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField( initial=True, required=False, label=_("Public key auth") ) - TERMINAL_COMMAND_STORAGE = FormEncryptDictField( - label=_("Command storage"), help_text=_( - "Set terminal storage setting, `default` is the using as default," - "You can set other storage and some terminal using" - ) + TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField( + initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds") ) - TERMINAL_REPLAY_STORAGE = FormEncryptDictField( - label=_("Replay storage"), help_text=_( - "Set replay storage setting, `default` is the using as default," - "You can set other storage and some terminal using" - ) + TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( + choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by") ) + # TERMINAL_COMMAND_STORAGE = FormEncryptDictField( + # label=_("Command storage"), help_text=_( + # "Set terminal storage setting, `default` is the using as default," + # "You can set other storage and some terminal using" + # ) + # ) + # TERMINAL_REPLAY_STORAGE = FormEncryptDictField( + # label=_("Replay storage"), help_text=_( + # "Set replay storage setting, `default` is the using as default," + # "You can set other storage and some terminal using" + # ) + # ) + + +class TerminalCommandStorage(BaseForm): + pass class SecuritySettingForm(BaseForm): diff --git a/apps/common/models.py b/apps/common/models.py index 61f5512c9..812d491a9 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -67,6 +67,30 @@ class Setting(models.Model): except json.JSONDecodeError as e: raise ValueError("Json dump error: {}".format(str(e))) + @classmethod + def save_storage(cls, name, data): + obj = cls.objects.filter(name=name).first() + if not obj: + obj = cls() + obj.name = name + obj.encrypted = True + obj.cleaned_value = data + else: + value = obj.cleaned_value + value.update(data) + obj.cleaned_value = value + obj.save() + return obj + + @classmethod + def delete_storage(cls, name, storage_name): + obj = cls.objects.get(name=name) + value = obj.cleaned_value + value.pop(storage_name, '') + obj.cleaned_value = value + obj.save() + return True + @classmethod def refresh_all_settings(cls): try: diff --git a/apps/common/templates/common/command_storage_create.html b/apps/common/templates/common/command_storage_create.html new file mode 100644 index 000000000..0f631161f --- /dev/null +++ b/apps/common/templates/common/command_storage_create.html @@ -0,0 +1,177 @@ +{#{% extends 'base.html' %}#} +{% extends '_base_create_update.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block content %} + <div class="wrapper wrapper-content animated fadeInRight"> + <div class="row"> + <div class="col-sm-12"> + <div class="ibox float-e-margins"> + <div class="ibox-title"> + <h5>{{ action }}</h5> + <div class="ibox-tools"> + <a class="collapse-link"> + <i class="fa fa-chevron-up"></i> + </a> + <a class="dropdown-toggle" data-toggle="dropdown" href="#"> + <i class="fa fa-wrench"></i> + </a> + <a class="close-link"> + <i class="fa fa-times"></i> + </a> + </div> + </div> + + <div class="ibox-content"> + <form action="" method="POST" class="form-horizontal"> + <div class="form-group"> + <label class="col-md-2 control-label" for="id_type">{% trans "Type" %}</label> + <div class="col-md-9"> + <select id="id_type" class="selector form-control"> + <option value ="server" selected="selected">server</option> + <option value ="es">es (elasticsearch)</option> + </select> + </div> + </div> + + <div class="form-group"> + <label class="col-md-2 control-label" for="id_name">{% trans "Name" %}</label> + <div class="col-md-9"> + <input id="id_name" class="form-control" type="text" name="NAME" value=""> + <div class="help-block">* required</div> + <div id="id_error" style="color: red;"></div> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_hosts">{% trans "Hosts" %}</label> + <div class="col-md-9"> + <input id="id_hosts" class="form-control" type="text" name="HOSTS" value=""> + <div class="help-block">如果有多台主机,请使用逗号 ( , ) 进行分割</div> + </div> + </div> + +{# <div class="form-group" style="display: none;" >#} +{# <label class="col-md-2 control-label" for="id_other">{% trans "Other" %}</label>#} +{# <div class="col-md-9">#} +{# <input id="id_other" class="form-control" type="text" name="OTHER" value="">#} +{# </div>#} +{# </div>#} + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_bucket">{% trans "Index" %}</label> + <div class="col-md-9"> + <input id="id_index" class="form-control" type="text" name="INDEX" value="jumpserver"> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_doc_type">{% trans "Doc type" %}</label> + <div class="col-md-9"> + <input id="id_doc_type" class="form-control" type="text" name="DOC_TYPE" value="command_store"> + </div> + </div> + + <div class="hr-line-dashed"></div> + <div class="form-group"> + <div class="col-sm-4 col-sm-offset-2"> + <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button> + <a class="btn btn-primary" type="" id="id_submit_button" >{% trans 'Submit' %}</a> + </div> + </div> + </form> + </div> + </div> + </div> + </div> + </div> +{% endblock %} + +{% block custom_foot_js %} +<script> + +var field_of_all, need_get_field_of_server, need_get_field_of_es; + +function showField(field){ + $.each(field, function(index, value){ + $(value).parent('div').parent('div').css('display', ''); + }); +} + +function hiddenField(field){ + $.each(field, function(index, value){ + $(value).parent('div').parent('div').css('display', 'none'); + }) +} + +function getFieldByType(type){ + + if(type === 'server'){ + return need_get_field_of_server + } + else if(type === 'es'){ + return need_get_field_of_es + } +} + +function ajaxAPI(url, data, success, error){ + $.ajax({ + url: url, + data: data, + method: 'POST', + contentType: 'application/json; charset=utf-8', + success: success, + error: error + }) +} + +$(document).ready(function() { + var name_id = '#id_name'; + var hosts_id = '#id_hosts'; + {#var other_id = '#id_other';#} + var index_id = '#id_index'; + var doc_type_id = '#id_doc_type'; + + field_of_all = [name_id, hosts_id, index_id, doc_type_id]; + need_get_field_of_server = [name_id]; + need_get_field_of_es = [name_id, hosts_id, index_id, doc_type_id]; +}) +.on('change', '.selector', function(){ + var type = $('.selector').val(); + console.log(type); + hiddenField(field_of_all); + var field = getFieldByType(type); + showField(field) +}) + +.on('click', '#id_submit_button', function(){ + var type = $('.selector').val(); + var field = getFieldByType(type); + var data = {'TYPE': type}; + $.each(field, function(index, id_field){ + var name = $(id_field).attr('name'); + var value = $(id_field).val(); + if(name === 'HOSTS'){ + data[name] = value.split(','); + } + else{ + data[name] = value + } + }); + var url = "{% url 'api-common:command-storage-create' %}"; + var success = function(data, textStatus) { + console.log(data, textStatus); + location = "{% url 'common:terminal-setting' %}"; + }; + var error = function(data, textStatus) { + var error_msg = data.responseJSON.error; + $('#id_error').html(error_msg) + }; + ajaxAPI(url, JSON.stringify(data), success, error) + +}) +</script> +{% endblock %} diff --git a/apps/common/templates/common/replay_storage_create.html b/apps/common/templates/common/replay_storage_create.html new file mode 100644 index 000000000..d717a508d --- /dev/null +++ b/apps/common/templates/common/replay_storage_create.html @@ -0,0 +1,242 @@ +{#{% extends 'base.html' %}#} +{% extends '_base_create_update.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block content %} + <div class="wrapper wrapper-content animated fadeInRight"> + <div class="row"> + <div class="col-sm-12"> + <div class="ibox float-e-margins"> + <div class="ibox-title"> + <h5>{{ action }}</h5> + <div class="ibox-tools"> + <a class="collapse-link"> + <i class="fa fa-chevron-up"></i> + </a> + <a class="dropdown-toggle" data-toggle="dropdown" href="#"> + <i class="fa fa-wrench"></i> + </a> + <a class="close-link"> + <i class="fa fa-times"></i> + </a> + </div> + </div> + + <div class="ibox-content"> + <form action="" method="POST" class="form-horizontal"> + <div class="form-group"> + <label class="col-md-2 control-label" for="id_type">{% trans "Type" %}</label> + <div class="col-md-9"> + <select id="id_type" class="selector form-control"> + <option value ="server" selected="selected">server</option> + <option value ="s3">s3</option> + <option value="oss">oss</option> + <option value ="azure">azure</option> + <option value="ceph">ceph</option> + </select> + </div> + </div> + + <div class="form-group"> + <label class="col-md-2 control-label" for="id_name">{% trans "Name" %}</label> + <div class="col-md-9"> + <input id="id_name" class="form-control" type="text" name="NAME" value=""> + <div class="help-block">* required</div> +{# <div id="id_error" style="display: none; color: red;">{% trans 'Error: Account invalid' %}</div>#} + <div id="id_error" style="color: red;"></div> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_host">{% trans "Host" %}</label> + <div class="col-md-9"> + <input id="id_host" class="form-control" type="text" name="HOSTNAME" value=""> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_port">{% trans "Port" %}</label> + <div class="col-md-9"> + <input id="id_port" class="form-control" type="text" name="PORT" value=""> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_bucket">{% trans "Bucket" %}</label> + <div class="col-md-9"> + <input id="id_bucket" class="form-control" type="text" name="BUCKET" value=""> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_access_key">{% trans "Access key" %}</label> + <div class="col-md-9"> + <input id="id_access_key" class="form-control" type="text" name="ACCESS_KEY" value=""> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_secret_key">{% trans "Secret key" %}</label> + <div class="col-md-9"> + <input id="id_secret_key" class="form-control" type="text" name="SECRET_KEY" value=""> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_container_name">{% trans "Container name" %}</label> + <div class="col-md-9"> + <input id="id_container_name" class="form-control" type="text" name="CONTAINER_NAME" value=""> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_account_name">{% trans "Account name" %}</label> + <div class="col-md-9"> + <input id="id_account_name" class="form-control" type="text" name="ACCOUNT_NAME" value=""> + </div> + </div> + + <div class="form-group" style="display: none;" > + <label class="col-md-2 control-label" for="id_account_key">{% trans "Account key" %}</label> + <div class="col-md-9"> + <input id="id_account_key" class="form-control" type="text" name="ACCOUNT_KEY" value=""> + </div> + </div> + + <div class="form-group" style="display: none;" > + <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=""> + </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> + </div> + + <div class="form-group" style="display: none;" > + <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=""> + </div> + </div> + + <div class="hr-line-dashed"></div> + <div class="form-group"> + <div class="col-sm-4 col-sm-offset-2"> + <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button> + <a class="btn btn-primary" type="" id="id_submit_button" >{% trans 'Submit' %}</a> + </div> + </div> + </form> + </div> + </div> + </div> + </div> + </div> +{% endblock %} + +{% block custom_foot_js %} +<script> + +var field_of_all, need_get_field_of_server, need_get_field_of_s3, + need_get_field_of_oss, need_get_field_of_azure, need_get_field_of_ceph; + +function showField(field){ + $.each(field, function(index, value){ + $(value).parent('div').parent('div').css('display', ''); + }); +} + +function hiddenField(field){ + $.each(field, function(index, value){ + $(value).parent('div').parent('div').css('display', 'none'); + }) +} + +function getFieldByType(type){ + + if(type === 'server'){ + return need_get_field_of_server + } + else if(type === 's3'){ + return need_get_field_of_s3 + } + else if(type === 'oss'){ + return need_get_field_of_oss + } + else if(type === 'azure'){ + return need_get_field_of_azure + } + else if(type === 'ceph'){ + return need_get_field_of_ceph + } +} + +function ajaxAPI(url, data, success, error){ + $.ajax({ + url: url, + data: data, + method: 'POST', + contentType: 'application/json; charset=utf-8', + success: success, + error: error + }) +} + +$(document).ready(function() { + var name_id = '#id_name'; + var host_id = '#id_host'; + var port_id = '#id_port'; + var bucket_id = '#id_bucket'; + var access_key_id = '#id_access_key'; + var secret_key_id = '#id_secret_key'; + var container_name_id = '#id_container_name'; + var account_name_id = '#id_account_name'; + var account_key_id = '#id_account_key'; + var endpoint_id = '#id_endpoint'; + var endpoint_suffix_id = '#id_endpoint_suffix'; + var region_id = '#id_region'; + + 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_oss = [name_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); + hiddenField(field_of_all); + var field = getFieldByType(type); + showField(field) +}) + +.on('click', '#id_submit_button', function(){ + var type = $('.selector').val(); + var field = getFieldByType(type); + var data = {'TYPE': type}; + $.each(field, function(index, id_field){ + var name = $(id_field).attr('name'); + data[name] = $(id_field).val(); + }); + var url = "{% url 'api-common:replay-storage-create' %}"; + var success = function(data, textStatus) { + location = "{% url 'common:terminal-setting' %}"; + }; + var error = function(data, textStatus) { + var error_msg = data.responseJSON.error; + $('#id_error').html(error_msg) + }; + ajaxAPI(url, JSON.stringify(data), success, error) + +}) +</script> +{% endblock %} diff --git a/apps/common/templates/common/terminal_setting.html b/apps/common/templates/common/terminal_setting.html index 320f628b0..f75dea79c 100644 --- a/apps/common/templates/common/terminal_setting.html +++ b/apps/common/templates/common/terminal_setting.html @@ -63,6 +63,14 @@ {% endif %} {% endfor %} + <div class="form-group"> + <div class="col-sm-4 col-sm-offset-2"> + <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button> + <button id="submit_button" class="btn btn-primary" + type="submit">{% trans 'Submit' %}</button> + </div> + </div> + <div class="hr-line-dashed"></div> <h3>{% trans "Command storage" %}</h3> @@ -71,6 +79,7 @@ <tr> <th>{% trans 'Name' %}</th> <th>{% trans 'Type' %}</th> + <th>{% trans 'Action' %}</th> </tr> </thead> <tbody> @@ -78,10 +87,13 @@ <tr> <td>{{ name }}</td> <td>{{ setting.TYPE }}</td> + <td><a class="btn btn-xs btn-danger m-l-xs btn-del-command" data-name="{{ name }}">{% trans 'Delete' %}</a></td> </tr> {% endfor %} </tbody> </table> + <a href="{% url 'common:command-storage-create' %}" class="btn btn-primary">{% trans 'Add' %}</a> + <div class="hr-line-dashed"></div> <h3>{% trans "Replay storage" %}</h3> <table class="table table-hover " id="task-history-list-table"> @@ -89,6 +101,7 @@ <tr> <th>{% trans 'Name' %}</th> <th>{% trans 'Type' %}</th> + <th>{% trans 'Action' %}</th> </tr> </thead> <tbody> @@ -96,18 +109,14 @@ <tr> <td>{{ name }}</td> <td>{{ setting.TYPE }}</td> + <td><a class="btn btn-xs btn-danger m-l-xs btn-del-replay" data-name="{{ name }}">{% trans 'Delete' %}</a></td> </tr> {% endfor %} </tbody> </table> + <a href="{% url 'common:replay-storage-create' %}" class="btn btn-primary">{% trans 'Add' %}</a> + <div class="hr-line-dashed"></div> - <div class="form-group"> - <div class="col-sm-4 col-sm-offset-2"> - <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button> - <button id="submit_button" class="btn btn-primary" - type="submit">{% trans 'Submit' %}</button> - </div> - </div> </form> </div> </div> @@ -116,40 +125,63 @@ </div> </div> </div> - </div> {% endblock %} {% block custom_foot_js %} - <script> - $(document).ready(function () { - }) - .on("click", ".btn-test", function () { - var data = {}; - var form = $("form").serializeArray(); - $.each(form, function (i, field) { - data[field.name] = field.value; - }); +<script> - var the_url = "{% url 'api-common:ldap-testing' %}"; +function ajaxAPI(url, data, success, error, method){ + $.ajax({ + url: url, + data: data, + method: method, + contentType: 'application/json; charset=utf-8', + success: success, + error: error + }) +} - function error(message) { - toastr.error(message) - } +function deleteStorage($this, the_url){ + var name = $this.data('name'); + function doDelete(){ + console.log('delete storage'); + var data = {"name": name}; + var method = 'POST'; + var success = function(){ + $this.parent().parent().remove(); + toastr.success("{% trans 'Delete success' %}"); + }; + var error = function(){ + toastr.error("{% trans 'Delete failed' %}}"); + }; + ajaxAPI(the_url, JSON.stringify(data), success, error, method); + } + swal({ + title: "{% trans 'Are you sure about deleting it?' %}", + text: " [" + name + "] ", + type: "warning", + showCancelButton: true, + cancelButtonText: "{% trans 'Cancel' %}", + confirmButtonColor: "#ed5565", + confirmButtonText: "{% trans 'Confirm' %}", + closeOnConfirm: true + }, function () { + doDelete() + }); +} - function success(message) { - toastr.success(message.msg) - } +$(document).ready(function () { - APIUpdateAttr({ - url: the_url, - body: JSON.stringify(data), - method: "POST", - flash_message: false, - success: success, - error: error - }); - }) - .on('click', '', function () { +}) +.on('click', '.btn-del-replay', function(){ + var $this = $(this); + var the_url = "{% url 'api-common:replay-storage-delete' %}"; + deleteStorage($this, the_url); +}) +.on('click', '.btn-del-command', function() { + var $this = $(this); + var the_url = "{% url 'api-common:command-storage-delete' %}"; + deleteStorage($this, the_url) +}); - }) - </script> +</script> {% endblock %} diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py index 5b44684ec..3c86a3a2a 100644 --- a/apps/common/urls/api_urls.py +++ b/apps/common/urls/api_urls.py @@ -9,5 +9,9 @@ app_name = 'common' urlpatterns = [ path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'), path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'), + path('terminal/replay-storage/create/', api.ReplayStorageCreateAPI.as_view(), name='replay-storage-create'), + path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'), + path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'), + path('terminal/command-storage/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'), # path('django-settings/', api.DjangoSettingsAPI.as_view(), name='django-settings'), ] diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py index e7ccddd06..8c2b91297 100644 --- a/apps/common/urls/view_urls.py +++ b/apps/common/urls/view_urls.py @@ -11,5 +11,7 @@ urlpatterns = [ url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'), + url(r'^terminal/replay-storage/create$', views.ReplayStorageCreateView.as_view(), name='replay-storage-create'), + url(r'^terminal/command-storage/create$', views.CommandStorageCreateView.as_view(), name='command-storage-create'), url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'), ] diff --git a/apps/common/utils.py b/apps/common/utils.py index ec55f43a1..e8476194f 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -387,6 +387,49 @@ def get_request_ip(request): return login_ip +def get_command_storage_or_create_default_storage(): + from common.models import common_settings, Setting + name = 'TERMINAL_COMMAND_STORAGE' + default = {'default': {'TYPE': 'server'}} + command_storage = common_settings.TERMINAL_COMMAND_STORAGE + if command_storage is None: + obj = Setting() + obj.name = name + obj.encrypted = True + obj.cleaned_value = default + obj.save() + if isinstance(command_storage, dict) and not command_storage: + obj = Setting.objects.get(name=name) + value = obj.cleaned_value + value.update(default) + obj.cleaned_value = value + obj.save() + command_storage = common_settings.TERMINAL_COMMAND_STORAGE + return command_storage + + +def get_replay_storage_or_create_default_storage(): + from common.models import common_settings, Setting + name = 'TERMINAL_REPLAY_STORAGE' + default = {'default': {'TYPE': 'server'}} + replay_storage = common_settings.TERMINAL_REPLAY_STORAGE + if replay_storage is None: + obj = Setting() + obj.name = name + obj.encrypted = True + obj.cleaned_value = default + obj.save() + replay_storage = common_settings.TERMINAL_REPLAY_STORAGE + if isinstance(replay_storage, dict) and not replay_storage: + obj = Setting.objects.get(name=name) + value = obj.cleaned_value + value.update(default) + obj.cleaned_value = value + obj.save() + replay_storage = common_settings.TERMINAL_REPLAY_STORAGE + return replay_storage + + class TeeObj: origin_stdout = sys.stdout diff --git a/apps/common/views.py b/apps/common/views.py index 08c4828f9..04d844c04 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -4,10 +4,12 @@ from django.contrib import messages from django.utils.translation import ugettext as _ from django.conf import settings +from common.models import common_settings from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ TerminalSettingForm, SecuritySettingForm from common.permissions import SuperUserRequiredMixin from .signals import ldap_auth_enable +from . import utils class BasicSettingView(SuperUserRequiredMixin, TemplateView): @@ -95,14 +97,15 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView): template_name = "common/terminal_setting.html" def get_context_data(self, **kwargs): - command_storage = settings.TERMINAL_COMMAND_STORAGE - replay_storage = settings.TERMINAL_REPLAY_STORAGE + command_storage = utils.get_command_storage_or_create_default_storage() + replay_storage = utils.get_replay_storage_or_create_default_storage() + context = { 'app': _('Settings'), 'action': _('Terminal setting'), 'form': self.form_class(), 'replay_storage': replay_storage, - 'command_storage': command_storage, + 'command_storage': command_storage } kwargs.update(context) return super().get_context_data(**kwargs) @@ -120,6 +123,30 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView): return render(request, self.template_name, context) +class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView): + template_name = 'common/replay_storage_create.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Settings'), + 'action': _('Create replay storage') + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView): + template_name = 'common/command_storage_create.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Settings'), + 'action': _('Create command storage') + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + class SecuritySettingView(SuperUserRequiredMixin, TemplateView): form_class = SecuritySettingForm template_name = "common/security_setting.html" diff --git a/apps/templates/_base_create_update.html b/apps/templates/_base_create_update.html index ec14da79b..be3813804 100644 --- a/apps/templates/_base_create_update.html +++ b/apps/templates/_base_create_update.html @@ -31,8 +31,8 @@ <div class="ibox-content"> {% if form.errors.all %} <div class="alert alert-danger" style="margin: 20px auto 0px"> - {{ form.errors.all }} - </div> + {{ form.errors.all }} + </div> {% endif %} {% block form %} {% endblock %} diff --git a/apps/terminal/backends/__init__.py b/apps/terminal/backends/__init__.py index 9a1c338f5..1c454a32d 100644 --- a/apps/terminal/backends/__init__.py +++ b/apps/terminal/backends/__init__.py @@ -2,6 +2,9 @@ from importlib import import_module from django.conf import settings from .command.serializers import SessionCommandSerializer +from common import utils +from common.models import common_settings, Setting + TYPE_ENGINE_MAPPING = { 'elasticsearch': 'terminal.backends.command.es', } @@ -16,7 +19,9 @@ def get_command_storage(): def get_terminal_command_storages(): storage_list = {} - for name, params in settings.TERMINAL_COMMAND_STORAGE.items(): + command_storage = utils.get_command_storage_or_create_default_storage() + + for name, params in command_storage.items(): tp = params['TYPE'] if tp == 'server': storage = get_command_storage() diff --git a/apps/terminal/forms.py b/apps/terminal/forms.py index dbba3cb01..55997a31b 100644 --- a/apps/terminal/forms.py +++ b/apps/terminal/forms.py @@ -2,36 +2,39 @@ # from django import forms -from django.conf import settings from django.utils.translation import ugettext_lazy as _ from .models import Terminal def get_all_command_storage(): - # storage_choices = [] - from common.models import Setting - Setting.refresh_all_settings() - for k, v in settings.TERMINAL_COMMAND_STORAGE.items(): - yield (k, k) + from common import utils + command_storage = utils.get_command_storage_or_create_default_storage() + command_storage_choice = [] + for k, v in command_storage.items(): + command_storage_choice.append((k, k)) + + return command_storage_choice def get_all_replay_storage(): - # storage_choices = [] - from common.models import Setting - Setting.refresh_all_settings() - for k, v in settings.TERMINAL_REPLAY_STORAGE.items(): - yield (k, k) + from common import utils + replay_storage = utils.get_replay_storage_or_create_default_storage() + replay_storage_choice = [] + for k, v in replay_storage.items(): + replay_storage_choice.append((k, k)) + + return replay_storage_choice class TerminalForm(forms.ModelForm): command_storage = forms.ChoiceField( - choices=get_all_command_storage(), + choices=get_all_command_storage, label=_("Command storage"), help_text=_("Command can store in server db or ES, default to server, more see docs"), ) replay_storage = forms.ChoiceField( - choices=get_all_replay_storage(), + choices=get_all_replay_storage, label=_("Replay storage"), help_text=_("Replay file can store in server disk, AWS S3, Aliyun OSS, default to server, more see docs"), )