v8.0: Delete check_version.html and refactor version checking logic

Removed the `check_version.html` template and moved version checking to `script.js`. This refactoring simplifies the codebase by consolidating version check handling into JavaScript, removing deprecated HTML templates, and enhancing the backend logic and API routes.
pull/399/head
Aidaho 2024-08-12 23:41:31 +03:00
parent 7fb1ad4b69
commit a7a8b4b10d
28 changed files with 732 additions and 512 deletions

View File

@ -2,13 +2,12 @@ from flask_swagger import swagger
from flask import jsonify, render_template, abort from flask import jsonify, render_template, abort
from flask_pydantic import validate from flask_pydantic import validate
from app import app, jwt from app import app
from app.api.routes import bp from app.api.routes import bp
from app.views.install.views import InstallView from app.views.install.views import InstallView
from app.views.server.views import ( from app.views.server.views import ServerView, ServerGroupView, ServerGroupsView, ServersView, ServerIPView
ServerView, CredView, CredsView, ServerGroupView, ServerGroupsView, ServersView, ServerIPView from app.views.server.cred_views import CredView, CredsView
) from app.views.server.backup_vews import BackupView, S3BackupView, GitBackupView
from app.views.server.backup_vews import BackupView, S3BackupView
from app.views.service.views import ServiceView, ServiceActionView, ServiceBackendView, ServiceConfigView, ServiceConfigVersionsView from app.views.service.views import ServiceView, ServiceActionView, ServiceBackendView, ServiceConfigView, ServiceConfigVersionsView
from app.views.ha.views import HAView, HAVIPView, HAVIPsView from app.views.ha.views import HAView, HAVIPView, HAVIPsView
from app.views.user.views import UserView, UserGroupView, UserRoles from app.views.user.views import UserView, UserGroupView, UserRoles
@ -30,20 +29,10 @@ def before_request():
pass pass
@jwt.expired_token_loader
def my_expired_token_callback(jwt_header, jwt_payload):
return jsonify(error="Token is expired"), 401
@jwt.unauthorized_loader
def custom_unauthorized_response(_err):
return jsonify(error="Authorize first"), 401
def register_api(view, endpoint, url, pk='listener_id', pk_type='int'): def register_api(view, endpoint, url, pk='listener_id', pk_type='int'):
view_func = view.as_view(endpoint) view_func = view.as_view(endpoint)
bp.add_url_rule(url, view_func=view_func, methods=['POST']) bp.add_url_rule(url, view_func=view_func, methods=['POST'])
bp.add_url_rule(f'{url}/<{pk_type}:{pk}>', view_func=view_func, methods=['GET', 'PUT', 'DELETE']) bp.add_url_rule(f'{url}/<{pk_type}:{pk}>', view_func=view_func, methods=['GET', 'PUT', 'PATCH', 'DELETE'])
def register_api_id_ip(view, endpoint, url: str = '', methods: list = ['GET', 'POST']): def register_api_id_ip(view, endpoint, url: str = '', methods: list = ['GET', 'POST']):
@ -73,9 +62,10 @@ register_api_id_ip(ServiceActionView, 'service_action', '/<any(start, stop, relo
register_api(ServerView, 'server', '/server', 'server_id') register_api(ServerView, 'server', '/server', 'server_id')
register_api(BackupView, 'backup_fs', '/server/backup/fs', 'backup_id') register_api(BackupView, 'backup_fs', '/server/backup/fs', 'backup_id')
register_api(S3BackupView, 'backup_s3', '/server/backup/s3', 'backup_id') register_api(S3BackupView, 'backup_s3', '/server/backup/s3', 'backup_id')
register_api(GitBackupView, 'backup_git', '/server/backup/git', 'backup_id')
bp.add_url_rule('/server/<server_id>/ip', view_func=ServerIPView.as_view('server_ip_ip'), methods=['GET']) bp.add_url_rule('/server/<server_id>/ip', view_func=ServerIPView.as_view('server_ip_ip'), methods=['GET'])
bp.add_url_rule('/server/<int:server_id>/ip', view_func=ServerIPView.as_view('server_ip'), methods=['GET']) bp.add_url_rule('/server/<int:server_id>/ip', view_func=ServerIPView.as_view('server_ip'), methods=['GET'])
register_api(CredView, 'cred', '/server/cred', 'creds_id') register_api(CredView, 'cred', '/server/cred', 'cred_id')
bp.add_url_rule('/server/creds', view_func=CredsView.as_view('creds'), methods=['GET']) bp.add_url_rule('/server/creds', view_func=CredsView.as_view('creds'), methods=['GET'])
bp.add_url_rule('/servers', view_func=ServersView.as_view('servers'), methods=['GET']) bp.add_url_rule('/servers', view_func=ServersView.as_view('servers'), methods=['GET'])

View File

@ -147,7 +147,7 @@ def default_values():
print(str(e)) print(str(e))
try: try:
Groups.insert(name='Default', description='All servers are included in this group by default', group_id=1).on_conflict_ignore().execute() Groups.insert(name='Default', description='All servers are included in this group by default', id=1).on_conflict_ignore().execute()
except Exception as e: except Exception as e:
print(str(e)) print(str(e))

View File

@ -4,7 +4,6 @@ import datetime
from app import scheduler from app import scheduler
import app.modules.db.sql as sql import app.modules.db.sql as sql
import app.modules.db.user as user_sql
import app.modules.db.roxy as roxy_sql import app.modules.db.roxy as roxy_sql
import app.modules.db.history as history_sql import app.modules.db.history as history_sql
import app.modules.roxywi.roxy as roxy import app.modules.roxywi.roxy as roxy

View File

@ -4,7 +4,6 @@ from flask import g
import app.modules.db.server as server_sql import app.modules.db.server as server_sql
import app.modules.roxywi.common as roxywi_common import app.modules.roxywi.common as roxywi_common
from app.modules.roxywi.exception import RoxywiResourceNotFound
from app.modules.roxywi.class_models import ServerRequest, GroupQuery, CredRequest, ChannelRequest from app.modules.roxywi.class_models import ServerRequest, GroupQuery, CredRequest, ChannelRequest
from app.middleware import get_user_params from app.middleware import get_user_params

View File

@ -48,9 +48,9 @@ def delete_s3_backups(backup_id: int) -> bool:
return True return True
def insert_new_git(server_id, service_id, repo, branch, period, cred, description) -> None: def insert_new_git(server_id, service_id, repo, branch, period, cred, description) -> int:
try: try:
GitSetting.insert( return GitSetting.insert(
server_id=server_id, service_id=service_id, repo=repo, branch=branch, period=period, server_id=server_id, service_id=service_id, repo=repo, branch=branch, period=period,
cred_id=cred, description=description cred_id=cred, description=description
).execute() ).execute()
@ -58,15 +58,11 @@ def insert_new_git(server_id, service_id, repo, branch, period, cred, descriptio
out_error(e) out_error(e)
def delete_git(git_id): def delete_git(git_id: int) -> None:
query = GitSetting.delete().where(GitSetting.id == git_id)
try: try:
query.execute() GitSetting.delete().where(GitSetting.id == git_id).execute()
except Exception as e: except Exception as e:
out_error(e) out_error(e)
return False
else:
return True
def select_gits(**kwargs): def select_gits(**kwargs):

View File

@ -4,7 +4,9 @@ from app.modules.roxywi.exception import RoxywiResourceNotFound
def select_ssh(**kwargs): def select_ssh(**kwargs):
if kwargs.get("name") is not None: if kwargs.get("group") and kwargs.get("cred_id"):
query = Cred.select().where((Cred.id == kwargs.get('cred_id')) & (Cred.group_id == kwargs.get('group')))
elif kwargs.get("name") is not None:
query = Cred.select().where(Cred.name == kwargs.get('name')) query = Cred.select().where(Cred.name == kwargs.get('name'))
elif kwargs.get("id") is not None: elif kwargs.get("id") is not None:
query = Cred.select().where(Cred.id == kwargs.get('id')) query = Cred.select().where(Cred.id == kwargs.get('id'))
@ -16,6 +18,8 @@ def select_ssh(**kwargs):
query = Cred.select() query = Cred.select()
try: try:
query_res = query.execute() query_res = query.execute()
except Cred.DoesNotExist:
raise RoxywiResourceNotFound
except Exception as e: except Exception as e:
out_error(e) out_error(e)
else: else:

View File

@ -25,10 +25,10 @@ def delete_server(server_id):
return True return True
def update_server(hostname, group, type_ip, enable, master, server_id, cred, port, desc, firewall, protected): def update_server(hostname, ip, group, type_ip, enable, master, server_id, cred, port, desc, firewall, protected):
try: try:
server_update = Server.update( server_update = Server.update(
hostname=hostname, group_id=group, type_ip=type_ip, enabled=enable, master=master, cred_id=cred, hostname=hostname, ip=ip, group_id=group, type_ip=type_ip, enabled=enable, master=master, cred_id=cred,
port=port, description=desc, firewall_enable=firewall, protected=protected port=port, description=desc, firewall_enable=firewall, protected=protected
).where(Server.server_id == server_id) ).where(Server.server_id == server_id)
server_update.execute() server_update.execute()

View File

@ -4,7 +4,7 @@ from typing import Optional, Annotated, Union, Literal, Any, Dict, List
from shlex import quote from shlex import quote
from pydantic_core import CoreSchema, core_schema from pydantic_core import CoreSchema, core_schema
from pydantic import BaseModel, field_validator, StringConstraints, IPvAnyAddress, AnyUrl, root_validator, GetCoreSchemaHandler from pydantic import BaseModel, Base64Str, StringConstraints, IPvAnyAddress, AnyUrl, root_validator, GetCoreSchemaHandler
DomainName = Annotated[str, StringConstraints(pattern=r"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$")] DomainName = Annotated[str, StringConstraints(pattern=r"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$")]
@ -110,7 +110,7 @@ class ServerRequest(BaseModel):
description: Optional[EscapedString] = None description: Optional[EscapedString] = None
group_id: Optional[int] = None group_id: Optional[int] = None
protected: Optional[bool] = 0 protected: Optional[bool] = 0
master: Optional[int] = None master: Optional[int] = 0
port: Annotated[int, Gt(1), Le(65535)] = 22 port: Annotated[int, Gt(1), Le(65535)] = 22
haproxy: Optional[bool] = 0 haproxy: Optional[bool] = 0
nginx: Optional[bool] = 0 nginx: Optional[bool] = 0
@ -137,7 +137,7 @@ class CredRequest(BaseModel):
class CredUploadRequest(BaseModel): class CredUploadRequest(BaseModel):
private_key: str private_key: Union[Base64Str, str]
passphrase: Optional[EscapedString] = None passphrase: Optional[EscapedString] = None
@ -252,3 +252,14 @@ class S3BackupRequest(BaseModel):
access_key: Optional[EscapedString] = None access_key: Optional[EscapedString] = None
time: Optional[EscapedString] = None time: Optional[EscapedString] = None
description: Optional[EscapedString] = None description: Optional[EscapedString] = None
class GitBackupRequest(BaseModel):
server_id: int
service_id: int
init: Optional[bool] = 0
repo: Optional[EscapedString] = None
branch: Optional[EscapedString] = 'main'
time: Optional[EscapedString] = 'weekly'
cred_id: Optional[int] = None
description: Optional[EscapedString] = None

View File

@ -1,5 +1,6 @@
import os import os
import re import re
from packaging import version
import distro import distro
import requests import requests
@ -26,33 +27,31 @@ def is_docker() -> bool:
return True return True
return False return False
def check_ver(): def check_ver():
return roxy_sql.get_ver() return roxy_sql.get_ver()
def versions(): def versions():
json_data = {
'need_update': 0
}
try: try:
current_ver = check_ver() current_ver = roxy_sql.get_ver()
current_ver_without_dots = current_ver.split('.') json_data['current_ver'] = roxy_sql.get_ver()
current_ver_without_dots = ''.join(current_ver_without_dots) except Exception as e:
current_ver_without_dots = current_ver_without_dots.replace('\n', '') raise Exception(f'Cannot get current version: {e}')
current_ver_without_dots = int(current_ver_without_dots)
except Exception:
current_ver = "Cannot get current version"
current_ver_without_dots = 0
try: try:
new_ver = check_new_version('roxy-wi') new_ver = check_new_version('roxy-wi')
new_ver_without_dots = new_ver.split('.') json_data['new_ver'] = new_ver
new_ver_without_dots = ''.join(new_ver_without_dots)
new_ver_without_dots = new_ver_without_dots.replace('\n', '')
new_ver_without_dots = int(new_ver_without_dots)
except Exception as e: except Exception as e:
new_ver = "Cannot get a new version" raise Exception(f'Cannot get new version: {e}')
new_ver_without_dots = 0
roxywi_common.logging('Roxy-WI server', f' {e}', roxywi=1)
return current_ver, new_ver, current_ver_without_dots, new_ver_without_dots if version.parse(current_ver) < version.parse(new_ver):
json_data['need_update'] = 1
return json_data
def check_new_version(service): def check_new_version(service):

View File

@ -1,8 +1,10 @@
import os import os
import base64
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
import paramiko import paramiko
from flask import render_template from flask import render_template
from playhouse.shortcuts import model_to_dict
import app.modules.db.cred as cred_sql import app.modules.db.cred as cred_sql
import app.modules.db.group as group_sql import app.modules.db.group as group_sql
@ -153,7 +155,7 @@ def upload_ssh_key(ssh_id: int, key: str, passphrase: str) -> None:
roxywi_common.logging('RMON server', e.args[0], roxywi=1) roxywi_common.logging('RMON server', e.args[0], roxywi=1)
raise Exception(e) raise Exception(e)
if passphrase != "''": if passphrase:
try: try:
passphrase = crypt_password(passphrase) passphrase = crypt_password(passphrase)
except Exception as e: except Exception as e:
@ -244,3 +246,31 @@ def decrypt_password(password: str) -> str:
except Exception as e: except Exception as e:
raise Exception(f'error: Cannot decrypt password: {e}') raise Exception(f'error: Cannot decrypt password: {e}')
return decryp_pass return decryp_pass
def get_creds(group_id: int = None, cred_id: int = None) -> list:
json_data = []
lib_path = get_config.get_config_var('main', 'lib_path')
if group_id and cred_id:
creds = cred_sql.select_ssh(group=group_id, cred_id=cred_id)
elif group_id:
creds = cred_sql.select_ssh(group=group_id)
else:
creds = cred_sql.select_ssh()
for cred in creds:
cred_dict = model_to_dict(cred)
cred_dict['name'] = cred_dict['name'].replace("'", "")
ssh_key_file = f'{lib_path}/keys/{cred_dict["name"]}.pem'
if os.path.isfile(ssh_key_file):
with open(ssh_key_file, 'rb') as key:
cred_dict['private_key'] = base64.b64encode(key.read()).decode('utf-8')
else:
cred_dict['private_key'] = ''
if cred_dict['password']:
cred_dict['password'] = decrypt_password(cred_dict['password'])
if cred_dict['passphrase']:
cred_dict['passphrase'] = decrypt_password(cred_dict['passphrase'])
json_data.append(cred_dict)
return json_data

View File

@ -1,3 +1,4 @@
from docutils.parsers.rst.directives import body
from flask import render_template from flask import render_template
import app.modules.db.sql as sql import app.modules.db.sql as sql
@ -8,7 +9,7 @@ import app.modules.db.service as service_sql
import app.modules.server.ssh as ssh_mod import app.modules.server.ssh as ssh_mod
import app.modules.roxywi.common as roxywi_common import app.modules.roxywi.common as roxywi_common
import app.modules.service.installation as installation_mod import app.modules.service.installation as installation_mod
from app.modules.roxywi.class_models import BackupRequest, IdResponse, IdDataResponse, BaseResponse, S3BackupRequest from app.modules.roxywi.class_models import BackupRequest, IdResponse, IdDataResponse, BaseResponse, S3BackupRequest, GitBackupRequest
def create_backup_inv(json_data: BackupRequest, del_id: int = 0) -> None: def create_backup_inv(json_data: BackupRequest, del_id: int = 0) -> None:
@ -51,6 +52,29 @@ def create_s3_backup_inv(data: S3BackupRequest, tag: str) -> None:
raise Exception(f'error: {e}') raise Exception(f'error: {e}')
def create_git_backup_inv(data: GitBackupRequest, server_ip: str, service: str, del_job: int = 0) -> None:
service_config_dir = sql.get_setting(service + '_dir')
ssh_settings = ssh_mod.return_ssh_keys_path(server_ip, id=data.cred_id)
print('del_job',del_job)
inv = {"server": {"hosts": {}}}
inv["server"]["hosts"][server_ip] = {
"REPO": data.repo,
"CONFIG_DIR": service_config_dir,
"PERIOD": data.time,
"INIT": data.init,
"BRANCH": data.branch,
"SERVICE": service,
"DELJOB": del_job,
"KEY": ssh_settings['key']
}
try:
installation_mod.run_ansible(inv, [server_ip], 'git_backup')
except Exception as e:
raise Exception(f'error: {e}')
def create_backup(json_data: BackupRequest, is_api: bool) -> tuple: def create_backup(json_data: BackupRequest, is_api: bool) -> tuple:
if backup_sql.check_exists_backup(json_data.server): if backup_sql.check_exists_backup(json_data.server):
raise Exception(f'warning: Backup job for {json_data.server} already exists') raise Exception(f'warning: Backup job for {json_data.server} already exists')
@ -71,7 +95,6 @@ def create_backup(json_data: BackupRequest, is_api: bool) -> tuple:
return IdDataResponse(data=data, id=last_id).model_dump(mode='json'), 201 return IdDataResponse(data=data, id=last_id).model_dump(mode='json'), 201
def delete_backup(json_data: BackupRequest, backup_id: int) -> tuple: def delete_backup(json_data: BackupRequest, backup_id: int) -> tuple:
create_backup_inv(json_data, backup_id) create_backup_inv(json_data, backup_id)
backup_sql.delete_backups(backup_id) backup_sql.delete_backups(backup_id)
@ -88,13 +111,6 @@ def update_backup(json_data: BackupRequest, backup_id: int) -> tuple:
def create_s3_backup(data: S3BackupRequest, is_api: bool) -> tuple: def create_s3_backup(data: S3BackupRequest, is_api: bool) -> tuple:
# if deljob:
# time = ''
# secret_key = ''
# access_key = ''
# tag = 'delete'
# else:
# tag = 'add'
if backup_sql.check_exists_s3_backup(data.server): if backup_sql.check_exists_s3_backup(data.server):
raise Exception(f'Backup job for {data.server} already exists') raise Exception(f'Backup job for {data.server} already exists')
@ -103,7 +119,6 @@ def create_s3_backup(data: S3BackupRequest, is_api: bool) -> tuple:
except Exception as e: except Exception as e:
raise e raise e
# if not deljob:
try: try:
last_id = backup_sql.insert_s3_backup_job(**data.model_dump(mode='json')) last_id = backup_sql.insert_s3_backup_job(**data.model_dump(mode='json'))
roxywi_common.logging('backup ', f'a new S3 backup job for server {data.server} has been created', roxywi=1, login=1) roxywi_common.logging('backup ', f'a new S3 backup job for server {data.server} has been created', roxywi=1, login=1)
@ -115,11 +130,6 @@ def create_s3_backup(data: S3BackupRequest, is_api: bool) -> tuple:
else: else:
temp = render_template('ajax/new_s3_backup.html', backups=backup_sql.select_s3_backups(**data.model_dump(mode='json'))) temp = render_template('ajax/new_s3_backup.html', backups=backup_sql.select_s3_backups(**data.model_dump(mode='json')))
return IdDataResponse(id=last_id, data=temp).model_dump(mode='json'), 201 return IdDataResponse(id=last_id, data=temp).model_dump(mode='json'), 201
# elif deljob:
# backup_sql.delete_s3_backups(deljob)
# roxywi_common.logging('backup ', f' a S3 backup job for server {server} has been deleted', roxywi=1, login=1)
# return 'ok'
def delete_s3_backup(data: S3BackupRequest, backup_id: int) -> None: def delete_s3_backup(data: S3BackupRequest, backup_id: int) -> None:
@ -131,46 +141,49 @@ def delete_s3_backup(data: S3BackupRequest, backup_id: int) -> None:
raise e raise e
def git_backup(server_id, service_id, git_init, repo, branch, period, cred, del_job, description, backup_id) -> str: def create_git_backup(data: GitBackupRequest, is_api: bool) -> tuple:
server_ip = server_sql.select_server_ip_by_id(server_id) server_ip = server_sql.select_server_ip_by_id(data.server_id)
service_name = service_sql.select_service_name_by_id(service_id).lower() service_name = service_sql.select_service_name_by_id(data.service_id).lower()
service_config_dir = sql.get_setting(service_name + '_dir') try:
ssh_settings = ssh_mod.return_ssh_keys_path(server_ip, id=cred) create_git_backup_inv(data, server_ip, service_name)
except Exception as e:
if repo is None or git_init == '0': raise Exception(e)
repo = ''
if branch is None or branch == '0':
branch = 'main'
inv = {"server": {"hosts": {}}}
inv["server"]["hosts"][server_ip] = {
"REPO": repo,
"CONFIG_DIR": service_config_dir,
"PERIOD": period,
"INIT": git_init,
"BRANCH": branch,
"SERVICE": service_name,
"DELJOB": del_job,
"KEY": ssh_settings['key']
}
try: try:
installation_mod.run_ansible(inv, [server_ip], 'git_backup') last_id = backup_sql.insert_new_git(server_id=data.server_id, service_id=data.service_id, repo=data.repo, branch=data.branch, period=data.time,
cred=data.cred_id, description=data.description)
roxywi_common.logging(server_ip, 'A new git job has been created', roxywi=1, login=1, keep_history=1,
service=service_name)
except Exception as e: except Exception as e:
raise Exception(f'error: {e}') raise Exception(e)
if not del_job: if is_api:
backup_sql.insert_new_git(server_id=server_id, service_id=service_id, repo=repo, branch=branch, period=period, cred=cred, description=description) return IdResponse(id=last_id).model_dump(mode='json'), 201
else:
kwargs = { kwargs = {
"gits": backup_sql.select_gits(server_id=server_id, service_id=service_id), "gits": backup_sql.select_gits(server_id=data.server_id, service_id=data.service_id),
"sshs": cred_sql.select_ssh(), "sshs": cred_sql.select_ssh(),
"servers": roxywi_common.get_dick_permit(), "servers": roxywi_common.get_dick_permit(),
"services": service_sql.select_services(), "services": service_sql.select_services(),
"new_add": 1, "new_add": 1,
"lang": roxywi_common.get_user_lang_for_flask() "lang": roxywi_common.get_user_lang_for_flask()
} }
roxywi_common.logging(server_ip, 'A new git job has been created', roxywi=1, login=1, keep_history=1, service=service_name)
return render_template('ajax/new_git.html', **kwargs) temp = render_template('ajax/new_git.html', **kwargs)
else: return IdDataResponse(id=last_id, data=temp).model_dump(mode='json'), 201
if backup_sql.delete_git(backup_id):
return 'ok'
def delete_git_backup(data: GitBackupRequest, backup_id: int) -> tuple:
server_ip = server_sql.select_server_ip_by_id(data.server_id)
service_name = service_sql.select_service_name_by_id(data.service_id).lower()
try:
create_git_backup_inv(data, server_ip, service_name, 1)
except Exception as e:
raise Exception(e)
try:
backup_sql.delete_git(backup_id)
except Exception as e:
raise Exception(e)
return BaseResponse().model_dump(mode='json'), 204

View File

@ -1,9 +1,9 @@
import os import os
import json import json
from typing import Union from typing import Union
from packaging import version from packaging import version
import distro
import ansible import ansible
import ansible_runner import ansible_runner
@ -212,6 +212,8 @@ def run_ansible(inv: dict, server_ips: list, ansible_role: str) -> dict:
proxy = sql.get_setting('proxy') proxy = sql.get_setting('proxy')
proxy_serv = '' proxy_serv = ''
tags = '' tags = ''
python_int = '/usr/bin/python3' if distro.id() == 'ubuntu' else '/usr/bin/python3.11'
try: try:
agent_pid = server_mod.start_ssh_agent() agent_pid = server_mod.start_ssh_agent()
except Exception as e: except Exception as e:
@ -261,7 +263,7 @@ def run_ansible(inv: dict, server_ips: list, ansible_role: str) -> dict:
'AWX_DISPLAY': False, 'AWX_DISPLAY': False,
'SSH_AUTH_PID': agent_pid['pid'], 'SSH_AUTH_PID': agent_pid['pid'],
'SSH_AUTH_SOCK': agent_pid['socket'], 'SSH_AUTH_SOCK': agent_pid['socket'],
'ANSIBLE_PYTHON_INTERPRETER': '/usr/bin/python3' 'ANSIBLE_PYTHON_INTERPRETER': python_int
} }
kwargs = { kwargs = {
'private_data_dir': '/var/www/haproxy-wi/app/scripts/ansible/', 'private_data_dir': '/var/www/haproxy-wi/app/scripts/ansible/',

View File

@ -171,6 +171,7 @@ def send_email(email_to: str, subject: str, message: str) -> None:
def telegram_send_mess(mess, level, **kwargs): def telegram_send_mess(mess, level, **kwargs):
token_bot = '' token_bot = ''
channel_name = '' channel_name = ''
proxy = sql.get_setting('proxy')
if kwargs.get('channel_id') == 0: if kwargs.get('channel_id') == 0:
return return
@ -180,8 +181,6 @@ def telegram_send_mess(mess, level, **kwargs):
else: else:
telegrams = channel_sql.get_receiver_by_ip('telegram', kwargs.get('ip')) telegrams = channel_sql.get_receiver_by_ip('telegram', kwargs.get('ip'))
proxy = sql.get_setting('proxy')
for telegram in telegrams: for telegram in telegrams:
token_bot = telegram.token token_bot = telegram.token
channel_name = telegram.chanel_name channel_name = telegram.chanel_name

View File

@ -16,6 +16,7 @@ import app.modules.roxywi.auth as roxywi_auth
import app.modules.roxywi.common as roxywi_common import app.modules.roxywi.common as roxywi_common
import app.modules.tools.smon as smon_mod import app.modules.tools.smon as smon_mod
import app.modules.tools.common as tools_common import app.modules.tools.common as tools_common
import app.modules.server.ssh as ssh_mod
from app.views.admin.views import SettingsView from app.views.admin.views import SettingsView
bp.add_url_rule( bp.add_url_rule(
@ -41,12 +42,12 @@ def admin():
users = user_sql.select_users() users = user_sql.select_users()
servers = server_sql.select_servers(full=1) servers = server_sql.select_servers(full=1)
masters = server_sql.select_servers(get_master_servers=1) masters = server_sql.select_servers(get_master_servers=1)
sshs = cred_sql.select_ssh() sshs = ssh_mod.get_creds()
else: else:
users = user_sql.select_users(group=user_group) users = user_sql.select_users(group=user_group)
servers = roxywi_common.get_dick_permit(virt=1, disable=0, only_group=1) servers = roxywi_common.get_dick_permit(virt=1, disable=0, only_group=1)
masters = server_sql.select_servers(get_master_servers=1, uuid=g.user_params['user_id']) masters = server_sql.select_servers(get_master_servers=1, uuid=g.user_params['user_id'])
sshs = cred_sql.select_ssh(group=user_group) sshs = ssh_mod.get_creds(group_id=user_group)
kwargs = { kwargs = {
'lang': g.user_params['lang'], 'lang': g.user_params['lang'],

View File

@ -4,7 +4,7 @@ from flask import render_template, request, g, abort, jsonify, redirect, url_for
from flask_jwt_extended import jwt_required from flask_jwt_extended import jwt_required
from flask_pydantic.exceptions import ValidationError from flask_pydantic.exceptions import ValidationError
from app import app, cache from app import app, cache, jwt
from app.routes.main import bp from app.routes.main import bp
import app.modules.db.user as user_sql import app.modules.db.user as user_sql
import app.modules.db.server as server_sql import app.modules.db.server as server_sql
@ -26,6 +26,16 @@ def _jinja2_filter_datetime(date, fmt=None):
return common.get_time_zoned_date(date, fmt) return common.get_time_zoned_date(date, fmt)
@jwt.expired_token_loader
def my_expired_token_callback(jwt_header, jwt_payload):
return jsonify(error="Token is expired"), 401
@jwt.unauthorized_loader
def custom_unauthorized_response(_err):
return jsonify(error="Authorize first"), 401
@app.errorhandler(ValidationError) @app.errorhandler(ValidationError)
def handle_pydantic_validation_errors1(e): def handle_pydantic_validation_errors1(e):
errors = [] errors = []
@ -70,6 +80,7 @@ def page_is_forbidden(e):
def page_not_found(e): def page_not_found(e):
if 'api' in request.url: if 'api' in request.url:
return jsonify({'error': str(e)}), 404 return jsonify({'error': str(e)}), 404
get_user_params()
kwargs = { kwargs = {
'user_params': g.user_params, 'user_params': g.user_params,
'title': e, 'title': e,
@ -79,23 +90,15 @@ def page_not_found(e):
@app.errorhandler(405) @app.errorhandler(405)
@get_user_params()
def method_not_allowed(e): def method_not_allowed(e):
if 'api' in request.url: return jsonify({'error': str(e)}), 405
return jsonify({'error': str(e)}), 405
kwargs = {
'user_params': g.user_params,
'title': e,
'e': e
}
return render_template('error.html', **kwargs), 405
@app.errorhandler(500) @app.errorhandler(500)
@get_user_params()
def internal_error(e): def internal_error(e):
if 'api' in request.url: if 'api' in request.url:
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
get_user_params()
kwargs = { kwargs = {
'user_params': g.user_params, 'user_params': g.user_params,
'title': e, 'title': e,
@ -224,4 +227,4 @@ def service_history(service, server_ip):
@bp.route('/internal/show_version') @bp.route('/internal/show_version')
@cache.cached() @cache.cached()
def show_roxywi_version(): def show_roxywi_version():
return render_template('ajax/check_version.html', versions=roxy.versions()) return jsonify(roxy.versions())

View File

@ -7,14 +7,15 @@ from app.routes.server import bp
import app.modules.db.cred as cred_sql import app.modules.db.cred as cred_sql
import app.modules.db.server as server_sql import app.modules.db.server as server_sql
import app.modules.db.backup as backup_sql import app.modules.db.backup as backup_sql
import app.modules.db.service as service_sql
import app.modules.common.common as common import app.modules.common.common as common
import app.modules.roxywi.auth as roxywi_auth import app.modules.roxywi.auth as roxywi_auth
import app.modules.roxywi.common as roxywi_common import app.modules.roxywi.common as roxywi_common
import app.modules.server.server as server_mod import app.modules.server.server as server_mod
import app.modules.service.backup as backup_mod
from app.middleware import get_user_params from app.middleware import get_user_params
from app.views.server.views import ServerView, CredView, CredsView, ServerGroupView, ServerGroupsView, ServerIPView from app.views.server.views import ServerView, ServerGroupView, ServerGroupsView, ServerIPView
from app.views.server.backup_vews import BackupView, S3BackupView from app.views.server.cred_views import CredView, CredsView
from app.views.server.backup_vews import BackupView, S3BackupView, GitBackupView
def register_api(view, endpoint, url, pk='listener_id', pk_type='int'): def register_api(view, endpoint, url, pk='listener_id', pk_type='int'):
@ -33,6 +34,7 @@ bp.add_url_rule('/<server_id>/ip', view_func=ServerIPView.as_view('server_ip_ip'
bp.add_url_rule('/<int:server_id>/ip', view_func=ServerIPView.as_view('server_ip'), methods=['GET']) bp.add_url_rule('/<int:server_id>/ip', view_func=ServerIPView.as_view('server_ip'), methods=['GET'])
bp.add_url_rule('/backup', view_func=BackupView.as_view('backup', False), methods=['POST']) bp.add_url_rule('/backup', view_func=BackupView.as_view('backup', False), methods=['POST'])
bp.add_url_rule('/backup/s3', view_func=S3BackupView.as_view('backup_s3', False), methods=['POST']) bp.add_url_rule('/backup/s3', view_func=S3BackupView.as_view('backup_s3', False), methods=['POST'])
bp.add_url_rule('/backup/git', view_func=GitBackupView.as_view('backup_git', False), methods=['POST'])
error_mess = roxywi_common.return_error_message() error_mess = roxywi_common.return_error_message()
@ -137,6 +139,7 @@ def load_backup():
kwargs = { kwargs = {
'sshs': cred_sql.select_ssh(group=user_group), 'sshs': cred_sql.select_ssh(group=user_group),
'servers': roxywi_common.get_dick_permit(virt=1, disable=0, only_group=1), 'servers': roxywi_common.get_dick_permit(virt=1, disable=0, only_group=1),
'services': service_sql.select_services(),
'backups': backup_sql.select_backups(), 'backups': backup_sql.select_backups(),
's3_backups': backup_sql.select_s3_backups(), 's3_backups': backup_sql.select_s3_backups(),
'gits': backup_sql.select_gits(), 'gits': backup_sql.select_gits(),
@ -145,26 +148,3 @@ def load_backup():
'user_subscription': roxywi_common.return_user_subscription(), 'user_subscription': roxywi_common.return_user_subscription(),
} }
return render_template('include/admin_backup.html', **kwargs) return render_template('include/admin_backup.html', **kwargs)
@bp.route('/git', methods=['DELETE', 'POST'])
def create_git_backup():
json_data = request.get_json()
server_id = int(json_data['server'])
service_id = int(json_data['service'])
git_init = int(json_data['init'])
repo = common.checkAjaxInput(json_data['repo'])
branch = common.checkAjaxInput(json_data['branch'])
period = common.checkAjaxInput(json_data['time'])
cred = int(json_data['cred'])
del_job = int(json_data['del_job'])
description = common.checkAjaxInput(json_data['desc'])
backup_id = ''
if request.method == 'DELETE':
backup_id = json_data['backup_id']
try:
data = backup_mod.git_backup(server_id, service_id, git_init, repo, branch, period, cred, del_job, description, backup_id)
return jsonify({'status': 'ok', 'data': data})
except Exception as e:
return roxywi_common.handle_json_exceptions(e, f'Cannot {request.method} git backup')

View File

@ -1,4 +1,7 @@
--- ---
- name: restart rsyslog
service: name=rsyslog state=restarted
- name: restart docker - name: restart docker
service: "name=docker state={{ docker_restart_handler_state }}" service: "name=docker state={{ docker_restart_handler_state }}"
ignore_errors: "{{ ansible_check_mode }}" ignore_errors: "{{ ansible_check_mode }}"

View File

@ -11,7 +11,7 @@
- name: Fail if has been installed - name: Fail if has been installed
fail: fail:
msg="Git configuration not found. Initialize git at the beginning" msg="Git configuration not found. Initialize git at the beginning"
when: not register_name.stat.exists and not INIT and DELJOB when: not register_name.stat.exists and not INIT and not DELJOB
- name: Install git - name: Install git
package: package:
@ -34,15 +34,16 @@
- name: Copy ssh file - name: Copy ssh file
copy: copy:
src: '{{ KEY }}' src: '{{ KEY }}'
dest: '/home/{{ ansible_user }}/.ssh/id_rsa' dest: '/home/{{ ansible_user }}/.ssh/git_{{ SERVICE }}_key'
mode: 0600 mode: 0600
group: '{{ ansible_user }}' group: '{{ ansible_user }}'
owner: '{{ ansible_user }}' owner: '{{ ansible_user }}'
force: no force: yes
when: INIT when: INIT
- name: Add write permissions - name: Add write permissions
shell: "chmod o+wr -R {{ CONFIG_DIR }}" shell: "chmod o+wr -R {{ CONFIG_DIR }}"
when: not DELJOB
- name: Git init - name: Git init
shell: 'cd {{ CONFIG_DIR }} && git init' shell: 'cd {{ CONFIG_DIR }} && git init'
@ -58,13 +59,18 @@
email = roxy-wi@.com email = roxy-wi@.com
when: INIT when: INIT
- name: Add dir exception
shell: 'git config --global --add safe.directory {{ CONFIG_DIR }}'
when: INIT
become: no
- name: Git add remote - name: Git add remote
shell: 'cd {{ CONFIG_DIR }} && git add --all . && git commit -m "Roxy-WI init repo" && git branch -M {{ BRANCH }} && git remote add origin {{ REPO }}' shell: 'cd {{ CONFIG_DIR }} && git add --all . && git commit -m "Roxy-WI init repo" && git branch -M {{ BRANCH }} && git remote add origin {{ REPO }}'
when: INIT when: INIT
become: no become: no
- name: Git add push - name: Git add push
shell: 'cd {{ CONFIG_DIR }} && GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git push -u origin {{ BRANCH }}' shell: 'cd {{ CONFIG_DIR }} && GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -i /home/{{ ansible_user }}/.ssh/git_{{ SERVICE }}_key" git push -u origin {{ BRANCH }}'
when: INIT when: INIT
become: no become: no

View File

@ -219,18 +219,17 @@ function addGit(dialog_id) {
valid = valid && checkLength(branch_div, "Branch name", 1); valid = valid && checkLength(branch_div, "Branch name", 1);
if (valid) { if (valid) {
let jsonData = { let jsonData = {
"server": server_div.val(), "server_id": server_div.val(),
"service": service_div.val(), "service_id": service_div.val(),
"init": git_init, "init": git_init,
"repo": $('#git-repo').val(), "repo": $('#git-repo').val(),
"branch": branch_div.val(), "branch": branch_div.val(),
"time": time_div.val(), "time": time_div.val(),
"cred": cred_div.val(), "cred_id": cred_div.val(),
"del_job": 0,
"desc": $('#git-description').text(), "desc": $('#git-description').text(),
} }
$.ajax({ $.ajax({
url: "/server/git", url: "/server/backup/git",
data: JSON.stringify(jsonData), data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
type: "POST", type: "POST",
@ -388,34 +387,35 @@ function removeS3Backup(id) {
function removeGit(id) { function removeGit(id) {
$("#git-table-" + id).css("background-color", "#f2dede"); $("#git-table-" + id).css("background-color", "#f2dede");
let jsonData = { let jsonData = {
"backup_id": id, "cred_id": $('#git-credentials-id-' + id).text(),
"del_job": 1, "server_id": $('#git-server-id-' + id).text(),
"init": 0, "service_id": $('#git-service-id-' + id).text(),
"repo": 0,
"branch": 0,
"time": 0,
"cred": $('#git-credentials-id-' + id).text(),
"server": $('#git-server-id-' + id).text(),
"service": $('#git-service-id-' + id).text(),
"desc": '',
} }
$.ajax({ $.ajax({
url: "/server/git", url: api_prefix + "/server/backup/git/" + id,
data: JSON.stringify(jsonData), data: JSON.stringify(jsonData),
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
type: "DELETE", type: "DELETE",
success: function (data) { statusCode: {
if (data.status === 'failed') { 204: function (xhr) {
toastr.error(data.error);
} else {
$("#git-table-" + id).remove(); $("#git-table-" + id).remove();
},
404: function (xhr) {
$("#git-table-" + id).remove();
}
},
success: function (data) {
if (data) {
if (data.status === "failed") {
toastr.error(data);
}
} }
} }
}); });
} }
function updateBackup(id) { function updateBackup(id) {
toastr.clear(); toastr.clear();
if ($("#backup-type-" + id + " option:selected").val() == "-------" || $('#backup-rserver-' + id).val() == '' || $('#backup-rpath-' + id).val() == '') { if ($("#backup-type-" + id + " option:selected").val() === "-------" || $('#backup-rserver-' + id).val() === '' || $('#backup-rpath-' + id).val() === '') {
toastr.error('All fields must be completed'); toastr.error('All fields must be completed');
} else { } else {
let jsonData = { let jsonData = {

View File

@ -1163,21 +1163,26 @@ function show_version() {
NProgress.configure({showSpinner: false}); NProgress.configure({showSpinner: false});
$.ajax( { $.ajax( {
url: "/internal/show_version", url: "/internal/show_version",
contentType: "application/json; charset=utf-8",
success: function( data ) { success: function( data ) {
$('#version').html(data); if (data.need_update) {
var showUpdates = $( "#show-updates" ).dialog({ $('#version').html('<span id="show-updates-button" class="new-version-exists" style="cursor: pointer;">v' + data.current_ver + '</span>');
} else {
$('#version').html('v' + data.current_ver);
}
let showUpdates = $("#show-updates").dialog({
autoOpen: false, autoOpen: false,
width: 600, width: 600,
modal: true, modal: true,
title: 'There is a new Roxy-WI version', title: 'There is a new Roxy-WI version',
buttons: { buttons: {
Close: function() { Close: function () {
$( this ).dialog( "close" ); $(this).dialog("close");
clearTips(); clearTips();
} }
} }
}); });
$('#show-updates-button').click(function() { $('#show-updates-button').click(function () {
showUpdates.dialog('open'); showUpdates.dialog('open');
}); });
} }

View File

@ -1,18 +0,0 @@
{%- if versions is defined -%}
{%- set current_ver = versions.0 -%}
{%- set new_ver = versions.1 %}
{%- set current_ver_without_dots = versions.2 %}
{%- set new_ver_without_dots = versions.3 %}
{%- endif -%}
{%- if new_ver_without_dots is defined and current_ver_without_dots is defined and new_ver is defined and new_ver_without_dots is defined -%}
<a style="color: #000; cursor: pointer;">
{%- if new_ver_without_dots > current_ver_without_dots and new_ver != "Sorry cannot get current version" %}
<span id="show-updates-button" class="new-version-exists">v{{current_ver}}</span>
<script defer src="/static/js/fontawesome.min.js"></script>
{%- else %}
<a href="/admin#updatehapwi" title="Update center" style="color: black;">v{{current_ver}}</a>
{%- endif %}
</a>
{%- else %}
<a href="/admin#updatehapwi" title="Update center" style="color: black;">v{{current_ver}}</a>
{% endif -%}

View File

@ -1,8 +1,4 @@
{% import 'languages/'+lang|default('en')+'.html' as lang %} {% import 'languages/'+lang|default('en')+'.html' as lang %}
{% set current_ver = versions.0 %}
{% set new_ver = versions.1 %}
{% set current_ver_without_dots = versions.2 %}
{% set new_ver_without_dots = versions.3 %}
{% set services_name = { {% set services_name = {
'roxy-wi-checker': { 'link': 'checker', 'name': 'Checker', 'desc': lang.admin_page.desc.checker_desc }, 'roxy-wi-checker': { 'link': 'checker', 'name': 'Checker', 'desc': lang.admin_page.desc.checker_desc },
'roxy-wi-keep_alive': { 'link': 'auto_start', 'name': 'Auto start', 'desc': lang.admin_page.desc.auto_start_desc }, 'roxy-wi-keep_alive': { 'link': 'auto_start', 'name': 'Auto start', 'desc': lang.admin_page.desc.auto_start_desc },
@ -19,7 +15,7 @@
</td> </td>
<td> <td>
<b <b
{% if new_ver_without_dots > current_ver_without_dots and new_ver != "Sorry cannot get current version" %} {% if versions.need_update %}
title=lang.admin_page.desc.a_new_version+" Roxy-WI" title=lang.admin_page.desc.a_new_version+" Roxy-WI"
style="color: var(--red-color)" style="color: var(--red-color)"
{% else %} {% else %}
@ -27,14 +23,14 @@
style="color: var(--green-color)" style="color: var(--green-color)"
{% endif %} {% endif %}
> >
{{current_ver}} {{versions.current_ver}}
</b> </b>
</td> </td>
<td class="padding10"> <td class="padding10">
<b>{{new_ver}}</b> <b>{{versions.new_ver}}</b>
</td> </td>
<td> <td>
{% if new_ver_without_dots > current_ver_without_dots and new_ver != "Sorry cannot get current version" %} {% if versions.need_update %}
<a class="ui-button ui-widget ui-corner-all" onclick="updateService('roxy-wi')" title="{{lang.words.w_update|title()}} Roxy-WI">{{lang.words.w_update|title()}}</a> <a class="ui-button ui-widget ui-corner-all" onclick="updateService('roxy-wi')" title="{{lang.words.w_update|title()}} Roxy-WI">{{lang.words.w_update|title()}}</a>
{% endif %} {% endif %}
</td> </td>

View File

@ -38,7 +38,7 @@
</td> </td>
<td style="width: 10%"> <td style="width: 10%">
{% for ssh in sshs %} {% for ssh in sshs %}
{% if ssh.enable == 1 %} {% if ssh.key_enabled == 1 %}
{% if ssh.id == b.cred_id %} {% if ssh.id == b.cred_id %}
<span id="git-credentials-id-{{b.id}}" style="display: none">{{ ssh.id }}</span> <span id="git-credentials-id-{{b.id}}" style="display: none">{{ ssh.id }}</span>
<span id="git-credentials-{{b.id}}">{{ ssh.name }}</span> <span id="git-credentials-{{b.id}}">{{ ssh.name }}</span>
@ -47,7 +47,7 @@
{% endfor %} {% endfor %}
</td> </td>
<td style="width: 100%"> <td style="width: 100%">
<span type="text" id="git-description-{{b.id}}"> <span id="git-description-{{b.id}}">
{% if b.description %} {% if b.description %}
{{b.description}} {{b.description}}
{% endif %} {% endif %}

View File

@ -20,7 +20,7 @@
<tr style="width: 50%;" id="ssh-table-{{ssh.id}}" class="{{ loop.cycle('odd', 'even') }}"> <tr style="width: 50%;" id="ssh-table-{{ssh.id}}" class="{{ loop.cycle('odd', 'even') }}">
<td class="first-collumn padding10"> <td class="first-collumn padding10">
{% set id = 'ssh_name-' + ssh.id|string() %} {% set id = 'ssh_name-' + ssh.id|string() %}
{{ input(id, value=ssh.name, size='15') }} {{ input(id, value=ssh.name.replace("'", ""), size='15') }}
</td> </td>
<td class="first-collumn" valign="top" style="padding-top: 15px;"> <td class="first-collumn" valign="top" style="padding-top: 15px;">
{% if ssh.key_enabled == 1 %} {% if ssh.key_enabled == 1 %}
@ -48,9 +48,9 @@
{{ input(id, value=ssh.username, title='SSH user name') }} {{ input(id, value=ssh.username, title='SSH user name') }}
</p> </p>
{% if ssh.key_enabled == 1 %} {% if ssh.key_enabled == 1 %}
<input type="password" id="ssh_pass-{{ssh.id}}" class="form-control" title="User password, if SSH key is disabled" placeholder="*****" style="display: none;" autocomplete="new-password"> <input type="password" id="ssh_pass-{{ssh.id}}" class="form-control" title="User password, if SSH key is disabled" value="{{ ssh.password }}" style="display: none;" autocomplete="new-password">
{% else %} {% else %}
<input type="password" id="ssh_pass-{{ssh.id}}" class="form-control" title="User password, if SSH key is disabled" placeholder="*****" autocomplete="new-password"> <input type="password" id="ssh_pass-{{ssh.id}}" class="form-control" title="User password, if SSH key is disabled" value="{{ ssh.password }}" autocomplete="new-password">
{% endif %} {% endif %}
<br> <br>
</td> </td>

View File

@ -8,7 +8,7 @@ import app.modules.db.backup as backup_sql
import app.modules.service.backup as backup_mod import app.modules.service.backup as backup_mod
import app.modules.roxywi.common as roxywi_common import app.modules.roxywi.common as roxywi_common
from app.middleware import get_user_params, page_for_admin, check_group from app.middleware import get_user_params, page_for_admin, check_group
from app.modules.roxywi.class_models import BackupRequest, S3BackupRequest, BaseResponse from app.modules.roxywi.class_models import BackupRequest, S3BackupRequest, GitBackupRequest, BaseResponse
class BackupView(MethodView): class BackupView(MethodView):
@ -209,7 +209,7 @@ class BackupView(MethodView):
class S3BackupView(MethodView): class S3BackupView(MethodView):
methods = ['GET', 'POST', 'PUT', 'DELETE'] methods = ['GET', 'POST', 'DELETE']
decorators = [jwt_required(), get_user_params(), page_for_admin(), check_group()] decorators = [jwt_required(), get_user_params(), page_for_admin(), check_group()]
def __init__(self, is_api=True): def __init__(self, is_api=True):
@ -352,3 +352,151 @@ class S3BackupView(MethodView):
return BaseResponse().model_dump(mode='json'), 204 return BaseResponse().model_dump(mode='json'), 204
except Exception as e: except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete S3 backup') return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete S3 backup')
class GitBackupView(MethodView):
methods = ['GET', 'POST', 'DELETE']
decorators = [jwt_required(), get_user_params(), page_for_admin(), check_group()]
def __init__(self, is_api=True):
self.is_api = is_api
@staticmethod
def get(backup_id: int):
"""
Retrieves the details of a specific Git backup configuration.
---
tags:
- Git Backup
parameters:
- in: path
name: backup_id
type: 'integer'
required: true
description: The ID of the specific Git backup
responses:
200:
description: Successful operation
schema:
type: 'object'
properties:
branch:
type: 'string'
description: 'The branch the backup is on'
cred_id:
type: 'integer'
description: 'The ID of the credentials used for the backup'
description:
type: 'string'
description: 'Description for the Git backup configuration'
id:
type: 'integer'
description: 'The ID of the backup'
period:
type: 'string'
description: 'The timing for the Git backup task'
repo:
type: 'string'
description: 'The repository URL for the backup'
server_id:
type: 'integer'
description: 'The ID of the server that was backed up'
service_id:
type: 'integer'
description: 'The service ID of the backup'
default:
description: Unexpected error
"""
try:
backup = backup_sql.get_backup(backup_id, 'git')
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, '')
return jsonify(model_to_dict(backup, recurse=False))
@validate(body=GitBackupRequest)
def post(self, body: GitBackupRequest):
"""
Create a new Git backup.
---
tags:
- Git Backup
parameters:
- name: config
in: body
required: true
description: The configuration for Git backup service
schema:
type: 'object'
properties:
server_id:
type: 'integer'
description: 'The ID of the server to backed up'
service_id:
type: 'integer'
description: 'Service ID'
init:
type: 'integer'
description: 'Indicates whether to initialize the repository'
repo:
type: 'string'
description: 'The repository from where to fetch the data for backup'
branch:
type: 'string'
description: 'The branch to pull for backup'
time:
type: 'string'
description: 'The timing for the Git backup task'
cred_id:
type: 'integer'
description: 'The ID of the credentials to be used for backup'
description:
type: 'string'
description: 'Description for the Git backup configuration'
responses:
201:
description: Successful operation
default:
description: Unexpected error
"""
try:
return backup_mod.create_git_backup(body, self.is_api)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create GIT backup')
@validate(body=GitBackupRequest)
def delete(self, backup_id: int, body: GitBackupRequest):
"""
Deletes a specific Git based backup configuration.
---
tags:
- Git Backup
parameters:
- in: path
name: backup_id
type: 'integer'
required: true
description: The ID of the specific Git backup
- name: config
in: body
required: true
description: The configuration for Git backup service delete operation
schema:
type: 'object'
properties:
server_id:
type: 'integer'
description: 'ID of the server from where the backup is to be deleted'
service_id:
type: 'integer'
description: 'Service ID of the backup to be deleted'
responses:
204:
description: Successful operation
default:
description: Unexpected error
"""
try:
return backup_mod.delete_git_backup(body, backup_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete GIT backup')

View File

@ -0,0 +1,319 @@
import base64
from flask.views import MethodView
from flask_pydantic import validate
from flask import jsonify, g
from flask_jwt_extended import jwt_required
import app.modules.db.cred as cred_sql
import app.modules.roxywi.common as roxywi_common
import app.modules.server.ssh as ssh_mod
from app.middleware import get_user_params, page_for_admin, check_group
from app.modules.roxywi.exception import RoxywiGroupMismatch, RoxywiResourceNotFound
from app.modules.roxywi.class_models import BaseResponse, GroupQuery, CredRequest, CredUploadRequest
from app.modules.common.common_classes import SupportClass
class CredView(MethodView):
methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
decorators = [jwt_required(), get_user_params(), page_for_admin(level=2), check_group()]
def __init__(self, is_api=False):
self.is_api = is_api
@staticmethod
@validate(query=GroupQuery)
def get(cred_id: int, query: GroupQuery):
"""
Retrieve credential information for a specific ID
---
tags:
- 'SSH credentials'
parameters:
- in: 'path'
name: 'cred_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
responses:
200:
description: 'Individual Credential Information'
schema:
type: 'object'
properties:
group_id:
type: 'integer'
description: 'Group ID the credential belongs to'
id:
type: 'integer'
description: 'Credential ID'
key_enabled:
type: 'integer'
description: 'Key status of the credential'
name:
type: 'string'
description: 'Name of the credential'
username:
type: 'string'
description: 'Username associated with the credential'
password:
type: 'string'
description: 'Password associated with the credential'
passphrase:
type: 'string'
description: 'Password for the SSH private key'
private_key:
type: 'string'
description: 'SSH private key in base64 encoded format'
404:
description: 'Credential not found'
"""
group_id = SupportClass.return_group_id(query)
try:
creds = ssh_mod.get_creds(group_id=group_id, cred_id=cred_id)
return jsonify(creds), 200
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get credentials')
@validate(body=CredRequest)
def post(self, body: CredRequest):
"""
Create a new credential entry
---
tags:
- SSH credentials
parameters:
- in: 'path'
name: 'creds_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
- in: body
name: body
schema:
id: AddCredentials
required:
- group_шв
- name
- username
- key_enabled
- password
properties:
group_id:
type: integer
description: The ID of the group to create the credential for. Only for superAdmin role
name:
type: string
description: The credential name
username:
type: string
description: The username
key_enabled:
type: integer
description: If key is enabled or not
password:
type: string
description: The password
responses:
201:
description: Credential addition successful
"""
group_id = SupportClass.return_group_id(body)
try:
return ssh_mod.create_ssh_cred(body.name, body.password, group_id, body.username, body.key_enabled, self.is_api)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create new cred')
@validate(body=CredRequest)
def put(self, creds_id: int, body: CredRequest):
"""
Update a credential entry
---
tags:
- SSH credentials
parameters:
- in: 'path'
name: 'creds_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
- in: body
name: body
schema:
id: UpdateCredentials
required:
- name
- username
- key_enabled
- password
properties:
group_id:
type: integer
description: The ID of the group to create the credential for. Only for superAdmin role
name:
type: string
description: The credential name
username:
type: string
description: The username
key_enabled:
type: integer
description: If key is enabled or not
password:
type: string
description: The password
responses:
201:
description: Credential update successful
"""
group_id = SupportClass.return_group_id(body)
try:
self._check_is_correct_group(creds_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, ''), 404
try:
ssh_mod.update_ssh_key(creds_id, body.name, body.password, body.key_enabled, body.username, group_id)
return BaseResponse().model_dump(mode='json'), 201
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot update SSH key')
def delete(self, creds_id: int):
"""
Delete a credential entry
---
tags:
- SSH credentials
parameters:
- in: 'path'
name: 'creds_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
responses:
204:
description: Credential deletion successful
"""
try:
self._check_is_correct_group(creds_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, ''), 404
try:
ssh_mod.delete_ssh_key(creds_id)
return BaseResponse().model_dump(mode='json'), 204
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete SSH key')
@validate(body=CredUploadRequest)
def patch(self, creds_id: int, body: CredUploadRequest):
"""
Upload an SSH private key
---
tags:
- SSH credentials
parameters:
- in: 'path'
name: 'creds_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
- in: body
name: body
schema:
id: UploadSSHKey
required:
- private_key
- passphrase
properties:
private_key:
type: string
description: The private key string or base64 encoded string
passphrase:
type: string
description: The passphrase
responses:
201:
description: SSH key upload successful
"""
try:
self._check_is_correct_group(creds_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, ''), 404
try:
body.private_key = base64.b64decode(body.private_key).decode("ascii")
except Exception:
pass
try:
ssh_mod.upload_ssh_key(creds_id, body.private_key, body.passphrase)
return BaseResponse().model_dump(mode='json'), 201
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot upload SSH key')
@staticmethod
def _check_is_correct_group(creds_id: int):
if g.user_params['role'] == 1:
return True
try:
ssh = cred_sql.get_ssh(creds_id)
except RoxywiResourceNotFound:
raise RoxywiResourceNotFound
if ssh.group_id != g.user_params['group_id']:
raise RoxywiGroupMismatch
class CredsView(MethodView):
methods = ['GET']
decorators = [jwt_required(), get_user_params(), page_for_admin(level=2), check_group()]
@validate(query=GroupQuery)
def get(self, query: GroupQuery):
"""
Retrieve credential information based on group_id
---
tags:
- 'SSH credentials'
parameters:
- in: 'query'
name: 'group_id'
description: 'GroupQuery to filter servers. Only for superAdmin role'
required: false
type: 'integer'
responses:
200:
description: 'Credentials Information'
schema:
type: 'array'
items:
type: 'object'
properties:
group_id:
type: 'integer'
description: 'Group ID the credential belongs to'
id:
type: 'integer'
description: 'Credential ID'
key_enabled:
type: 'integer'
description: 'Key status of the credential'
name:
type: 'string'
description: 'Name of the credential'
username:
type: 'string'
description: 'Username of the credential'
password:
type: 'string'
description: 'Password associated with the credential'
passphrase:
type: 'string'
description: 'Password for the SSH private key'
private_key:
type: 'string'
description: 'SSH private key in base64 encoded format'
"""
group_id = SupportClass.return_group_id(query)
try:
creds = ssh_mod.get_creds(group_id=group_id)
return jsonify(creds), 200
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get credentials')

View File

@ -1,19 +1,17 @@
from flask.views import MethodView from flask.views import MethodView
from flask_pydantic import validate from flask_pydantic import validate
from flask import render_template, jsonify, request, g from flask import render_template, jsonify, request
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
from flask_jwt_extended import jwt_required from flask_jwt_extended import jwt_required
from app.modules.db.db_model import Cred
import app.modules.db.cred as cred_sql import app.modules.db.cred as cred_sql
import app.modules.db.group as group_sql import app.modules.db.group as group_sql
import app.modules.db.server as server_sql import app.modules.db.server as server_sql
import app.modules.roxywi.group as group_mod import app.modules.roxywi.group as group_mod
import app.modules.roxywi.common as roxywi_common import app.modules.roxywi.common as roxywi_common
import app.modules.server.ssh as ssh_mod
import app.modules.server.server as server_mod import app.modules.server.server as server_mod
from app.middleware import get_user_params, page_for_admin, check_group from app.middleware import get_user_params, page_for_admin, check_group
from app.modules.roxywi.exception import RoxywiGroupMismatch, RoxywiResourceNotFound from app.modules.roxywi.exception import RoxywiResourceNotFound
from app.modules.roxywi.class_models import ( from app.modules.roxywi.class_models import (
BaseResponse, IdResponse, IdDataResponse, ServerRequest, GroupQuery, GroupRequest, CredRequest, CredUploadRequest BaseResponse, IdResponse, IdDataResponse, ServerRequest, GroupQuery, GroupRequest, CredRequest, CredUploadRequest
) )
@ -132,21 +130,20 @@ class ServerView(MethodView):
required: required:
- hostname - hostname
- ip - ip
- enabled - cred_id
- creds_id
- port - port
- description - group_id
properties: properties:
hostname: hostname:
type: string type: string
description: The server name description: The server name
ip: ip:
type: string type: string
description: The server IP address description: The server IP address or domain name
enabled: enabled:
type: integer type: integer
description: If server is enabled or not description: If server is enabled or not
creds_id: cred_id:
type: integer type: integer
description: The ID of the credentials description: The ID of the credentials
port: port:
@ -158,6 +155,18 @@ class ServerView(MethodView):
group_id: group_id:
type: integer type: integer
description: The ID of the group to create the server for. Only for superAdmin role description: The ID of the group to create the server for. Only for superAdmin role
type_ip:
type: integer
description: Is server virtual (VIP address) or not
master:
type: integer
description: Server id of the master server
firewall_enable:
type: integer
description: Is firewalld enabled or not
protected:
type: integer
description: Is the server protected from changes by a non-admin role
responses: responses:
201: 201:
description: Server creation successful description: Server creation successful
@ -216,19 +225,22 @@ class ServerView(MethodView):
schema: schema:
id: UpdateServer id: UpdateServer
required: required:
- name - hostname
- enabled - ip
- creds_id - cred_id
- port - port
- description - group_id
properties: properties:
name: hostname:
type: string type: string
description: The server name description: The server name
ip:
type: string
description: The server IP or domain name
enabled: enabled:
type: integer type: integer
description: If server is enabled or not description: If server is enabled or not
creds_id: cred_id:
type: integer type: integer
description: The ID of the credentials description: The ID of the credentials
port: port:
@ -240,6 +252,18 @@ class ServerView(MethodView):
group_id: group_id:
type: integer type: integer
description: The ID of the group to update the server for. Only for superAdmin role description: The ID of the group to update the server for. Only for superAdmin role
type_ip:
type: integer
description: Is server virtual (VIP address) or not
master:
type: integer
description: Server id of the master server
firewall_enable:
type: integer
description: Is firewalld enabled or not
protected:
type: integer
description: Is the server protected from changes by a non-admin role
responses: responses:
201: 201:
description: Server update successful description: Server update successful
@ -248,7 +272,7 @@ class ServerView(MethodView):
try: try:
server_sql.update_server( server_sql.update_server(
body.hostname, group_id, body.type_ip, body.enabled, body.master, server_id, body.cred_id, body.port, body.description, body.hostname, body.ip, group_id, body.type_ip, body.enabled, body.master, server_id, body.cred_id, body.port, body.description,
body.firewall_enable, body.protected body.firewall_enable, body.protected
) )
server_ip = server_sql.select_server_ip_by_id(server_id) server_ip = server_sql.select_server_ip_by_id(server_id)
@ -570,294 +594,6 @@ class ServerGroupsView(MethodView):
return jsonify(groups_list) return jsonify(groups_list)
class CredView(MethodView):
methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
decorators = [jwt_required(), get_user_params(), page_for_admin(level=2), check_group()]
def __init__(self, is_api=False):
self.is_api = is_api
@staticmethod
def get(creds_id: int):
"""
Retrieve credential information for a specific ID
---
tags:
- 'SSH credentials'
parameters:
- in: 'path'
name: 'creds_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
responses:
200:
description: 'Individual Credential Information'
schema:
type: 'object'
properties:
group_id:
type: 'integer'
description: 'Group ID the credential belongs to'
id:
type: 'integer'
description: 'Credential ID'
key_enabled:
type: 'integer'
description: 'Key status of the credential'
name:
type: 'string'
description: 'Name of the credential'
username:
type: 'string'
description: 'Username associated with the credential'
404:
description: 'Credential not found'
"""
group_id = int(g.user_params['group_id'])
try:
creds = cred_sql.get_ssh_by_id_and_group(creds_id, group_id)
for cred in creds:
return jsonify(model_to_dict(cred, exclude=[Cred.password, Cred.passphrase])), 200
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get credentials')
@validate(body=CredRequest)
def post(self, body: CredRequest):
"""
Create a new credential entry
---
tags:
- SSH credentials
parameters:
- in: 'path'
name: 'creds_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
- in: body
name: body
schema:
id: AddCredentials
required:
- group_шв
- name
- username
- key_enabled
- password
properties:
group_id:
type: integer
description: The ID of the group to create the credential for. Only for superAdmin role
name:
type: string
description: The credential name
username:
type: string
description: The username
key_enabled:
type: integer
description: If key is enabled or not
password:
type: string
description: The password
responses:
201:
description: Credential addition successful
"""
group_id = SupportClass.return_group_id(body)
try:
return ssh_mod.create_ssh_cred(body.name, body.password, group_id, body.username, body.key_enabled, self.is_api)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create new cred')
@validate(body=CredRequest)
def put(self, creds_id: int, body: CredRequest):
"""
Update a credential entry
---
tags:
- SSH credentials
parameters:
- in: 'path'
name: 'creds_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
- in: body
name: body
schema:
id: UpdateCredentials
required:
- name
- username
- key_enabled
- password
properties:
group_id:
type: integer
description: The ID of the group to create the credential for. Only for superAdmin role
name:
type: string
description: The credential name
username:
type: string
description: The username
key_enabled:
type: integer
description: If key is enabled or not
password:
type: string
description: The password
responses:
201:
description: Credential update successful
"""
group_id = SupportClass.return_group_id(body)
try:
self._check_is_correct_group(creds_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, ''), 404
try:
ssh_mod.update_ssh_key(creds_id, body.name, body.password, body.key_enabled, body.username, group_id)
return BaseResponse().model_dump(mode='json'), 201
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot update SSH key')
def delete(self, creds_id: int):
"""
Delete a credential entry
---
tags:
- SSH credentials
parameters:
- in: 'path'
name: 'creds_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
responses:
204:
description: Credential deletion successful
"""
try:
self._check_is_correct_group(creds_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, ''), 404
try:
ssh_mod.delete_ssh_key(creds_id)
return BaseResponse().model_dump(mode='json'), 204
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot delete SSH key')
@validate(body=CredUploadRequest)
def patch(self, creds_id: int, body: CredUploadRequest):
"""
Upload an SSH private key
---
tags:
- SSH credentials
parameters:
- in: 'path'
name: 'creds_id'
description: 'ID of the credential to retrieve'
required: true
type: 'integer'
- in: body
name: body
schema:
id: UploadSSHKey
required:
- private_key
- passphrase
properties:
private_key:
type: string
description: The private key string
passphrase:
type: string
description: The passphrase
responses:
201:
description: SSH key upload successful
"""
try:
self._check_is_correct_group(creds_id)
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, ''), 404
try:
ssh_mod.upload_ssh_key(creds_id, body.private_key, body.passphrase)
return BaseResponse().model_dump(mode='json'), 201
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot upload SSH key')
@staticmethod
def _check_is_correct_group(creds_id: int):
if g.user_params['role'] == 1:
return True
try:
ssh = cred_sql.get_ssh(creds_id)
except RoxywiResourceNotFound:
raise RoxywiResourceNotFound
if ssh.group_id != g.user_params['group_id']:
raise RoxywiGroupMismatch
class CredsView(MethodView):
methods = ['GET']
decorators = [jwt_required(), get_user_params(), page_for_admin(level=2), check_group()]
@validate(query=GroupQuery)
def get(self, query: GroupQuery):
"""
Retrieve credential information based on group_id
---
tags:
- 'SSH credentials'
parameters:
- in: 'query'
name: 'group_id'
description: 'GroupQuery to filter servers. Only for superAdmin role'
required: false
type: 'integer'
responses:
200:
description: 'Credentials Information'
schema:
type: 'array'
items:
type: 'object'
properties:
group_id:
type: 'integer'
description: 'Group ID the credential belongs to'
id:
type: 'integer'
description: 'Credential ID'
key_enabled:
type: 'integer'
description: 'Key status of the credential'
name:
type: 'string'
description: 'Name of the credential'
username:
type: 'string'
description: 'Username of the credential'
"""
group_id = SupportClass.return_group_id(query)
try:
creds = cred_sql.select_ssh(group=group_id)
json_data = []
for cred in creds:
json_data.append(model_to_dict(cred, exclude=[Cred.password, Cred.passphrase]))
return jsonify(json_data), 200
except Exception as e:
return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get credentials')
class ServerIPView(MethodView): class ServerIPView(MethodView):
class ServersView(MethodView): class ServersView(MethodView):
methods = ["GET"] methods = ["GET"]

View File

@ -12,7 +12,6 @@ slack-sdk>=3.4.0
peewee>=3.14.10 peewee>=3.14.10
PyMySQL>=1.0.2 PyMySQL>=1.0.2
distro>=1.2.0 distro>=1.2.0
bottle>=0.12.20
psutil>=5.9.1 psutil>=5.9.1
pdpyras>=4.5.2 pdpyras>=4.5.2
pika>=1.3.1 pika>=1.3.1