diff --git a/app/api/routes/routes.py b/app/api/routes/routes.py index 268c9315..047a6cf2 100644 --- a/app/api/routes/routes.py +++ b/app/api/routes/routes.py @@ -55,7 +55,7 @@ bp.add_url_rule('/ha///vips', view_func=HAVIPsView.as_v register_api_id_ip(ServiceView, 'service', '/status', ['GET']) register_api_id_ip(ServiceBackendView, 'service_backend', '/backend', ['GET']) register_api_id_ip(ServiceConfigView, 'config_view') -register_api_id_ip(ServiceConfigVersionsView, 'config_version', '/versions', methods=['GET']) +register_api_id_ip(ServiceConfigVersionsView, 'config_version', '/versions', methods=['GET', 'DELETE']) register_api_id_ip(CheckerView, 'checker', '/tools') register_api_id_ip(InstallView, 'install', '/install', methods=['POST']) register_api_id_ip(ServiceActionView, 'service_action', '/', methods=['GET']) diff --git a/app/modules/db/metric.py b/app/modules/db/metric.py index 9337741e..fbef0f24 100644 --- a/app/modules/db/metric.py +++ b/app/modules/db/metric.py @@ -288,7 +288,7 @@ def select_table_metrics(group_id): avg_cur_1h, avg_cur_24h, avg_cur_3d, max_con_1h, max_con_24h, max_con_3d from (select servers.ip from servers where haproxy_metrics = 1 ) as ip, - (select servers.ip, servers.hostname as hostname from servers left join metrics as metr on servers.ip = metr.serv where servers.metrics = 1 %s) as hostname, + (select servers.ip, servers.hostname as hostname from servers left join metrics as metr on servers.ip = metr.serv where servers.haproxy_metrics = 1 %s) as hostname, (select servers.ip,round(avg(metr.sess_rate), 1) as avg_sess_1h from servers left join metrics as metr on metr.serv = servers.ip @@ -383,7 +383,7 @@ def select_table_metrics(group_id): avg_cur_24h, avg_cur_3d, max_con_1h, max_con_24h, max_con_3d from (select servers.ip from servers where haproxy_metrics = 1 ) as ip, - (select servers.ip, servers.hostname as hostname from servers left join metrics as metr on servers.ip = metr.serv where servers.metrics = 1 %s) as hostname, + (select servers.ip, servers.hostname as hostname from servers left join metrics as metr on servers.ip = metr.serv where servers.haproxy_metrics = 1 %s) as hostname, (select servers.ip,round(avg(metr.sess_rate), 1) as avg_sess_1h from servers left join metrics as metr on metr.serv = servers.ip diff --git a/app/modules/roxywi/class_models.py b/app/modules/roxywi/class_models.py index 4c2a976d..69e4c1cc 100644 --- a/app/modules/roxywi/class_models.py +++ b/app/modules/roxywi/class_models.py @@ -181,6 +181,10 @@ class ConfigFileNameQuery(BaseModel): version: Optional[str] = None +class VersionsForDelete(BaseModel): + versions: List[str] + + class ConfigRequest(BaseModel): action: Literal['save', 'test', 'reload', 'restart'] file_name: Optional[str] = None diff --git a/app/routes/config/routes.py b/app/routes/config/routes.py index c9a18730..3a72e01c 100644 --- a/app/routes/config/routes.py +++ b/app/routes/config/routes.py @@ -17,10 +17,11 @@ import app.modules.config.common as config_common import app.modules.config.section as section_mod import app.modules.service.haproxy as service_haproxy import app.modules.server.server as server_mod -from app.views.service.views import ServiceConfigView +from app.views.service.views import ServiceConfigView, ServiceConfigVersionsView from app.modules.roxywi.class_models import DataStrResponse bp.add_url_rule('//', view_func=ServiceConfigView.as_view('config_view_ip'), methods=['POST']) +bp.add_url_rule('///versions', view_func=ServiceConfigVersionsView.as_view('config_version'), methods=['DELETE']) @bp.before_request @@ -136,48 +137,16 @@ def config(service, serv, edit, config_file_name, new): return render_template('config.html', **kwargs) -@bp.route('/versions/', defaults={'server_ip': None}, methods=['GET', 'POST']) -@bp.route('/versions//', methods=['GET', 'POST']) +@bp.route('/versions/', defaults={'server_ip': None}, methods=['GET']) +@bp.route('/versions//', methods=['GET']) @check_services @get_user_params(disable=1) def versions(service, server_ip): roxywi_auth.page_for_admin(level=3) - after_save = '' - file = set() - stderr = '' - file_format = config_common.get_file_format(service) - - if request.form.get('del'): - after_save = 1 - for get in request.form.getlist('do_delete'): - if file_format in get and server_ip in get: - try: - if config_sql.delete_config_version(service, get): - try: - os.remove(get) - except OSError as e: - if 'No such file or directory' in str(e): - pass - else: - config_dir = config_common.get_config_dir('haproxy') - os.remove(os.path.join(config_dir, get)) - try: - file.add(get + "\n") - roxywi_common.logging( - server_ip, f"Version of config has been deleted: {get}", login=1, keep_history=1, - service=service - ) - except Exception: - pass - except OSError as e: - stderr = "Error: %s - %s." % (e.filename, e.strerror) kwargs = { 'serv': server_ip, - 'aftersave': after_save, - 'file': file, 'service': service, - 'stderr': stderr, 'lang': g.user_params['lang'] } return render_template('delver.html', **kwargs) @@ -193,48 +162,48 @@ def list_of_version(service): return config_mod.list_of_versions(server_ip, service, config_ver, for_delver) -@bp.route('/versions///', defaults={'save': None}, methods=['GET', 'POST']) -@bp.route('/versions////save', defaults={'save': 1}, methods=['GET', 'POST']) +@bp.route('/versions///', methods=['GET']) @check_services @get_user_params(disable=1) -def show_version(service, server_ip, configver, save): +def show_version(service, server_ip, configver): roxywi_auth.page_for_admin(level=3) - service_desc = service_sql.select_service(service) - config_dir = config_common.get_config_dir('haproxy') - configver = config_dir + configver - aftersave = 0 - stderr = '' - - if save: - aftersave = 1 - save_action = request.form.get('save') - try: - roxywi_common.logging( - server_ip, f"Version of config has been uploaded {configver}", login=1, keep_history=1, service=service - ) - except Exception: - pass - - if service == 'keepalived': - stderr = config_mod.upload_and_restart(server_ip, configver, save_action, service) - elif service in ('nginx', 'apache'): - config_file_name = config_sql.select_remote_path_from_version(server_ip=server_ip, service=service, local_path=configver) - stderr = config_mod.master_slave_upload_and_restart(server_ip, configver, save_action, service_desc.slug, config_file_name=config_file_name) - else: - stderr = config_mod.master_slave_upload_and_restart(server_ip, configver, save_action, service) kwargs = { 'serv': server_ip, - 'aftersave': aftersave, - 'configver': configver, 'service': service, - 'stderr': stderr, 'lang': g.user_params['lang'] } return render_template('configver.html', **kwargs) +@bp.route('/versions////save', methods=['POST']) +@check_services +@get_user_params() +def save_version(service, server_ip, configver): + roxywi_auth.page_for_admin(level=3) + config_dir = config_common.get_config_dir('haproxy') + configver = config_dir + configver + service_desc = service_sql.select_service(service) + save_action = request.json.get('action') + try: + roxywi_common.logging( + server_ip, f"Version of config has been uploaded {configver}", login=1, keep_history=1, service=service + ) + except Exception: + pass + + if service == 'keepalived': + stderr = config_mod.upload_and_restart(server_ip, configver, save_action, service) + elif service in ('nginx', 'apache'): + config_file_name = config_sql.select_remote_path_from_version(server_ip=server_ip, service=service, local_path=configver) + stderr = config_mod.master_slave_upload_and_restart(server_ip, configver, save_action, service_desc.slug, config_file_name=config_file_name) + else: + stderr = config_mod.master_slave_upload_and_restart(server_ip, configver, save_action, service) + + return DataStrResponse(data=stderr).model_dump(mode='json'), 201 + + @bp.route('/section/haproxy/') @get_user_params() def haproxy_section(server_ip): diff --git a/app/static/js/add.js b/app/static/js/add.js index b59f0799..3f8752ba 100644 --- a/app/static/js/add.js +++ b/app/static/js/add.js @@ -959,7 +959,7 @@ $( function() { }); let add_server_var = '
: ' + ' ' + - 'port check: ' + + 'Port check: ' + ' maxconn: ' $('[name=add-server-input]').click(function () { $("[name=add_servers]").append(add_server_var); diff --git a/app/static/js/configshow.js b/app/static/js/configshow.js index 8645a1dd..a4cc8e18 100644 --- a/app/static/js/configshow.js +++ b/app/static/js/configshow.js @@ -82,4 +82,73 @@ $( function() { }); e.preventDefault(); }); + + $("#save_version").on("click", ":submit", function (e) { + let frm = $('#save_version'); + let unindexed_array = frm.serializeArray(); + let indexed_array = {}; + $.map(unindexed_array, function (n, i) { + if (n['value'] != 'undefined') { + indexed_array[n['name']] = n['value']; + } + }); + indexed_array['action'] = $(this).val(); + $.ajax({ + url: frm.attr('action'), + dataType: 'json', + data: JSON.stringify(indexed_array), + type: frm.attr('method'), + contentType: "application/json; charset=UTF-8", + success: function (data) { + toastr.clear(); + data.data = data.data.replace(/\n/g, "
"); + returnNiceCheckingConfig(data.data); + if (data.status === 'failed') { + toastr.warning(data.error) + } + } + }); + e.preventDefault(); + }); + $("#delete_versions_form").on("click", ":submit", function (e) { + let frm = $('#delete_versions_form'); + let unindexed_array = frm.serializeArray(); + let indexed_array = {}; + indexed_array['versions'] = []; + $.map(unindexed_array, function (n, i) { + if (n['value'] != 'undefined') { + if (n['name'] === 'versions') { + indexed_array['versions'].push(n['value']); + } else { + indexed_array[n['name']] = n['value']; + } + } + }); + indexed_array['action'] = $(this).val(); + $.ajax({ + url: frm.attr('action'), + dataType: 'json', + data: JSON.stringify(indexed_array), + type: frm.attr('method'), + contentType: "application/json; charset=UTF-8", + statusCode: { + 204: function (xhr) { + toastr.success('The versions of configs have been deleted'); + showListOfVersion(1); + }, + 404: function (xhr) { + toastr.success('The versions of configs have been deleted'); + showListOfVersion(1); + } + }, + success: function (data) { + if (data) { + if (data.status === "failed") { + toastr.error(data); + } + } + } + }); + e.preventDefault(); + }); }) diff --git a/app/templates/ajax/config_show.html b/app/templates/ajax/config_show.html index b152585f..97b8a6bb 100644 --- a/app/templates/ajax/config_show.html +++ b/app/templates/ajax/config_show.html @@ -368,7 +368,7 @@
{% if role <= 3 %} {% if not is_serv_protected or role <= 2 %} -
+ @@ -405,5 +405,4 @@ function expand_button(event) { $("#expand_link").click(); } - diff --git a/app/templates/ajax/show_list_version.html b/app/templates/ajax/show_list_version.html index 0c6a0c2e..abc8aa85 100644 --- a/app/templates/ajax/show_list_version.html +++ b/app/templates/ajax/show_list_version.html @@ -1,5 +1,6 @@ {% import 'languages/'+lang|default('en')+'.html' as lang1 %} {% from 'include/input_macros.html' import copy_to_clipboard %} + {% if for_delver == '1' %}

@@ -9,19 +10,6 @@

- {% if not aftersave %} - {% if stderr %} - {% include 'include/errors.html' %} - {% endif %}
- {% endif %} - {% if aftersave %} -
{{lang.phrases.version_has_been_uploaded}}: {{ configver }}
- {% if 'is valid' not in stderr %} - {% include 'include/errors.html' %} - {% else %} -
{{lang.words.config|title()}} {{lang.words.is}} {{lang.words.valid}}
- {% endif %} - {% endif %} {% endblock %} diff --git a/app/templates/delver.html b/app/templates/delver.html index 894b8b21..3702a127 100644 --- a/app/templates/delver.html +++ b/app/templates/delver.html @@ -18,25 +18,11 @@ {% endif %}

- {% if aftersave %} -
{{lang.phrases.files_been_deleted}}:
- {% if stderr %} - {% include 'include/errors.html' %} - {% else %} -
- {% for f in file %} - {{f}} - {% endfor %} -
- {% endif %} - {% endif %}
- {% if not aftersave %}
{{lang.phrases.work_with_prev}} {{ service[0]|upper}}{{service[1:] }}. {{lang.phrases.roll_back}}
- {% endif %} - {% if serv and not aftersave %} + {% if serv %} {% for select in g.user_params['servers'] %} {% if select.2 == serv %} diff --git a/app/views/service/views.py b/app/views/service/views.py index e6b03d9a..f557a747 100644 --- a/app/views/service/views.py +++ b/app/views/service/views.py @@ -1,3 +1,4 @@ +import os from typing import Union, Literal from flask.views import MethodView @@ -6,6 +7,7 @@ from flask import jsonify, g from flask_jwt_extended import jwt_required import app.modules.db.sql as sql +import app.modules.db.config as config_sql import app.modules.db.server as server_sql import app.modules.db.service as service_sql import app.modules.roxywi.common as roxywi_common @@ -16,7 +18,8 @@ import app.modules.service.action as service_action import app.modules.service.common as service_common from app.middleware import get_user_params, page_for_admin, check_group, check_services from app.modules.roxywi.exception import RoxywiResourceNotFound -from app.modules.roxywi.class_models import BaseResponse, ErrorResponse, DataResponse, DataStrResponse, ConfigFileNameQuery, ConfigRequest +from app.modules.roxywi.class_models import BaseResponse, ErrorResponse, DataResponse, DataStrResponse, \ + ConfigFileNameQuery, ConfigRequest, VersionsForDelete from app.modules.common.common_classes import SupportClass @@ -404,10 +407,10 @@ class ServiceConfigView(MethodView): class ServiceConfigVersionsView(MethodView): - methods = ['GET', 'POST'] + methods = ['GET', 'POST', 'DELETE'] decorators = [jwt_required(), get_user_params(), check_services, page_for_admin(level=4), check_group()] - def get(self, service, server_id: Union[int, str]): + def get(self, service: str, server_id: Union[int, str]): """ This endpoint returns a list of configuration file versions for a specified service on a specific server. --- @@ -439,3 +442,74 @@ class ServiceConfigVersionsView(MethodView): file_format = config_common.get_file_format(service) files = roxywi_common.get_files(config_dir, file_format, server_ip) return DataResponse(data=files).model_dump(mode='json') + + @validate(body=VersionsForDelete) + def delete(self, service: str, server_id: Union[int, str], body: VersionsForDelete): + """ + This endpoint deletes specified configuration file versions for a particular service on a specific server. + --- + tags: + - Service config + parameters: + - in: path + name: service + type: string + enum: [haproxy, nginx, apache, keepalived] + required: true + description: The type of service (haproxy, nginx, apache, keepalived) + - in: path + name: server_id + type: string + required: true + description: The ID or IP of the server + - in: body + name: body + description: JSON array of paths to version files to be deleted + schema: + type: array + items: + type: string + description: Path to the version file + required: true + responses: + 204: + description: 'Successful operation, specified configuration file versions are deleted' + 400: + description: 'Invalid service type or server ID' + 404: + description: 'Service or server not found' + 500: + description: 'Internal server error' + """ + file = set() + file_format = config_common.get_file_format(service) + + try: + server_ip = SupportClass(False).return_server_ip_or_id(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e ,'') + + for get in body.versions: + if file_format in get and server_ip in get: + try: + if config_sql.delete_config_version(service, get): + try: + os.remove(get) + except OSError as e: + if 'No such file or directory' in str(e): + pass + else: + config_dir = config_common.get_config_dir('haproxy') + os.remove(os.path.join(config_dir, get)) + try: + file.add(get + "\n") + roxywi_common.logging( + server_ip, f"Version of config has been deleted: {get}", login=1, keep_history=1, + service=service + ) + except Exception: + pass + except OSError as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete config version') + + return BaseResponse().model_dump(mode='json'), 204