mirror of https://github.com/Aidaho12/haproxy-wi
v8.0: Add DELETE endpoint for version deletion in ServiceConfigView
Added support for deleting configuration file versions via the DELETE method in ServiceConfigVersionsView. Updated corresponding routes, templates, and scripts to handle this functionality.pull/399/head
parent
89022e59be
commit
41a8c90556
|
@ -55,7 +55,7 @@ bp.add_url_rule('/ha/<service>/<int:cluster_id>/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', '/<any(start, stop, reload, restart):action>', methods=['GET'])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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('/<service>/<server_id>', view_func=ServiceConfigView.as_view('config_view_ip'), methods=['POST'])
|
||||
bp.add_url_rule('/<service>/<server_id>/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/<service>', defaults={'server_ip': None}, methods=['GET', 'POST'])
|
||||
@bp.route('/versions/<service>/<server_ip>', methods=['GET', 'POST'])
|
||||
@bp.route('/versions/<service>', defaults={'server_ip': None}, methods=['GET'])
|
||||
@bp.route('/versions/<service>/<server_ip>', 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/<service>/<server_ip>/<configver>', defaults={'save': None}, methods=['GET', 'POST'])
|
||||
@bp.route('/versions/<service>/<server_ip>/<configver>/save', defaults={'save': 1}, methods=['GET', 'POST'])
|
||||
@bp.route('/versions/<service>/<server_ip>/<configver>', 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/<service>/<server_ip>/<configver>/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/<server_ip>')
|
||||
@get_user_params()
|
||||
def haproxy_section(server_ip):
|
||||
|
|
|
@ -959,7 +959,7 @@ $( function() {
|
|||
});
|
||||
let add_server_var = '<br /><input name="servers" title="Backend IP" size=14 placeholder="xxx.xxx.xxx.xxx" class="form-control second-server" style="margin: 2px 0 4px 0;">: ' +
|
||||
'<input name="server_port" required title="Backend port" size=3 placeholder="yyy" class="form-control second-server add_server_number" type="number"> ' +
|
||||
'port check: <input name="port_check" required title="Maxconn. Default 200" size=5 value="200" class="form-control add_server_number" type="number">' +
|
||||
'Port check: <input name="port_check" required title="Maxconn. Default 200" size=5 value="200" class="form-control add_server_number" type="number">' +
|
||||
' maxconn: <input name="server_maxconn" required title="Maxconn. Default 200" size=5 value="200" class="form-control add_server_number" type="number">'
|
||||
$('[name=add-server-input]').click(function () {
|
||||
$("[name=add_servers]").append(add_server_var);
|
||||
|
|
|
@ -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, "<br>");
|
||||
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();
|
||||
});
|
||||
})
|
||||
|
|
|
@ -368,7 +368,7 @@
|
|||
<br>
|
||||
{% if role <= 3 %}
|
||||
{% if not is_serv_protected or role <= 2 %}
|
||||
<form action="/config/versions/{{service}}/{{serv}}/{{configver}}/save" method="post" class="left-space">
|
||||
<form action="/config/versions/{{service}}/{{serv}}/{{configver}}/save" id="save_version" method="post" class="left-space">
|
||||
<input type="hidden" value="{{serv}}" name="serv">
|
||||
<input type="hidden" value="{{service}}" name="service">
|
||||
<input type="hidden" value="{{configver}}" name="configver">
|
||||
|
@ -405,5 +405,4 @@
|
|||
function expand_button(event) {
|
||||
$("#expand_link").click();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% import 'languages/'+lang|default('en')+'.html' as lang1 %}
|
||||
{% from 'include/input_macros.html' import copy_to_clipboard %}
|
||||
<script src="/static/js/configshow.js"></script>
|
||||
{% if for_delver == '1' %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
@ -47,7 +48,7 @@
|
|||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<form action="{{action}}" method="post">
|
||||
<form action="/config/{{ service }}/{{ server_ip }}/versions" method="delete" id="delete_versions_form">
|
||||
<table class="overview hover order-column display compact" id="table_version">
|
||||
<thead>
|
||||
<tr class="overviewHead">
|
||||
|
@ -76,7 +77,7 @@
|
|||
<tr>
|
||||
<td style="padding-left: 15px">
|
||||
<label for="{{c.id}}" id="select_{{c.id}}"></label>
|
||||
<input type="checkbox" value="{{c.local_path}}" name="do_delete" id="{{c.id}}">
|
||||
<input type="checkbox" value="{{c.local_path}}" name="versions" id="{{c.id}}">
|
||||
</td>
|
||||
<td>
|
||||
{% for u in users %}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
{% block title %}{{ lang.menu_links.versions.h2 }} {{ lang.words[service] }}{% endblock %}
|
||||
{% block h2 %}{{ lang.menu_links.versions.h2 }} {{ lang.words[service] }}{% endblock %}
|
||||
{% block content %}
|
||||
<script src="/static/js/configshow.js"></script>
|
||||
<p>
|
||||
<form action="/config/versions/{{service}}/{{serv}}" method="post" class="left-space">
|
||||
<input type="hidden" id="service" value="{{service}}">
|
||||
|
@ -9,19 +10,6 @@
|
|||
<button type="submit" value="open" name="open" class="btn btn-default">{{lang.words.open|title()}}</button>
|
||||
</form>
|
||||
</p>
|
||||
{% if not aftersave %}
|
||||
{% if stderr %}
|
||||
{% include 'include/errors.html' %}
|
||||
{% endif %}
|
||||
<div id="config_version_div"></div>
|
||||
<script>showListOfVersion(0)</script>
|
||||
{% endif %}
|
||||
{% if aftersave %}
|
||||
<div class="alert alert-info alert-two-row">{{lang.phrases.version_has_been_uploaded}}: {{ configver }} </div>
|
||||
{% if 'is valid' not in stderr %}
|
||||
{% include 'include/errors.html' %}
|
||||
{% else %}
|
||||
<div class="alert alert-success">{{lang.words.config|title()}} {{lang.words.is}} {{lang.words.valid}}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -18,25 +18,11 @@
|
|||
{% endif %}
|
||||
</form>
|
||||
</p>
|
||||
{% if aftersave %}
|
||||
<div class="alert alert-info"><b>{{lang.phrases.files_been_deleted}}:</b><br /> </div>
|
||||
{% if stderr %}
|
||||
{% include 'include/errors.html' %}
|
||||
{% else %}
|
||||
<div class="alert alert-success" style="margin-left: var(--indent);">
|
||||
{% for f in file %}
|
||||
{{f}}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div id="config_version_div"></div>
|
||||
{% if not aftersave %}
|
||||
<div class="add-note addName alert-info" style="width: inherit; margin-right: 15px; margin-top: 40%">
|
||||
{{lang.phrases.work_with_prev}} {{ service[0]|upper}}{{service[1:] }}. {{lang.phrases.roll_back}}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if serv and not aftersave %}
|
||||
{% if serv %}
|
||||
{% for select in g.user_params['servers'] %}
|
||||
{% if select.2 == serv %}
|
||||
<script>showListOfVersion(1)</script>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue