crafty-4/app/classes/models/management.py

587 lines
21 KiB
Python
Raw Normal View History

2021-09-08 22:01:10 +00:00
import logging
import datetime
from peewee import (
ForeignKeyField,
CharField,
IntegerField,
DateTimeField,
FloatField,
TextField,
AutoField,
BooleanField,
)
from playhouse.shortcuts import model_to_dict
2021-09-08 22:01:10 +00:00
from app.classes.models.base_model import BaseModel
2023-01-19 17:06:37 +00:00
from app.classes.models.users import HelperUsers
from app.classes.models.servers import Servers
from app.classes.models.server_permissions import PermissionsServers
from app.classes.shared.main_models import DatabaseShortcuts
from app.classes.shared.websocket_manager import WebSocketManager
2021-09-08 22:01:10 +00:00
2022-03-08 04:40:44 +00:00
logger = logging.getLogger(__name__)
2021-09-08 22:01:10 +00:00
2023-02-16 23:29:06 +00:00
# **********************************************************************************
2021-09-08 22:01:10 +00:00
# Audit_Log Class
# **********************************************************************************
class AuditLog(BaseModel):
2021-09-08 22:01:10 +00:00
audit_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
user_name = CharField(default="")
user_id = IntegerField(default=0, index=True)
source_ip = CharField(default="127.0.0.1")
2023-11-01 17:44:56 +00:00
server_id = ForeignKeyField(
Servers, backref="audit_server", null=True
) # When auditing global events, use server ID null
log_msg = TextField(default="")
2021-09-08 22:01:10 +00:00
2022-04-14 23:34:30 +00:00
class Meta:
table_name = "audit_log"
2021-09-08 22:01:10 +00:00
# **********************************************************************************
# Crafty Settings Class
# **********************************************************************************
class CraftySettings(BaseModel):
2023-06-18 15:09:00 +00:00
id = AutoField()
secret_api_key = CharField(default="")
cookie_secret = CharField(default="")
2022-11-09 19:13:35 +00:00
login_photo = CharField(default="login_1.jpg")
login_opacity = IntegerField(default=100)
2023-01-27 00:21:39 +00:00
master_server_dir = CharField(default="")
class Meta:
table_name = "crafty_settings"
# **********************************************************************************
2021-09-08 22:01:10 +00:00
# Host_Stats Class
# **********************************************************************************
class HostStats(BaseModel):
2021-09-08 22:01:10 +00:00
time = DateTimeField(default=datetime.datetime.now, index=True)
boot_time = CharField(default="")
cpu_usage = FloatField(default=0)
cpu_cores = IntegerField(default=0)
cpu_cur_freq = FloatField(default=0)
cpu_max_freq = FloatField(default=0)
mem_percent = FloatField(default=0)
mem_usage = CharField(default="")
mem_total = CharField(default="")
disk_json = TextField(default="")
class Meta:
table_name = "host_stats"
# **********************************************************************************
2021-09-08 22:01:10 +00:00
# Webhooks Class
# **********************************************************************************
class Webhooks(BaseModel):
2021-09-08 22:01:10 +00:00
id = AutoField()
2023-11-01 17:44:56 +00:00
server_id = ForeignKeyField(Servers, backref="webhook_server", null=True)
2023-06-03 17:35:29 +00:00
name = CharField(default="Custom Webhook", max_length=64)
2023-06-03 19:05:08 +00:00
url = CharField(default="")
2023-06-03 17:35:29 +00:00
webhook_type = CharField(default="Custom")
bot_name = CharField(default="Crafty Controller")
2023-06-03 19:05:08 +00:00
trigger = CharField(default="server_start,server_stop")
2023-06-03 17:35:29 +00:00
body = CharField(default="")
2023-09-28 23:07:03 +00:00
color = CharField(default="#005cd1")
2023-06-03 17:35:29 +00:00
enabled = BooleanField(default=True)
2021-09-08 22:01:10 +00:00
class Meta:
table_name = "webhooks"
# **********************************************************************************
2021-09-08 22:01:10 +00:00
# Schedules Class
# **********************************************************************************
class Schedules(BaseModel):
2021-09-08 22:01:10 +00:00
schedule_id = IntegerField(unique=True, primary_key=True)
server_id = ForeignKeyField(Servers, backref="schedule_server")
2021-09-08 22:01:10 +00:00
enabled = BooleanField()
action = CharField()
interval = IntegerField()
interval_type = CharField()
start_time = CharField(null=True)
command = CharField(null=True)
2022-08-05 17:52:50 +00:00
name = CharField()
one_time = BooleanField(default=False)
cron_string = CharField(default="")
2022-02-24 03:58:48 +00:00
parent = IntegerField(null=True)
delay = IntegerField(default=0)
2022-08-04 22:15:59 +00:00
next_run = CharField(default="")
2021-09-08 22:01:10 +00:00
class Meta:
table_name = "schedules"
2021-09-08 22:01:10 +00:00
# **********************************************************************************
2021-09-08 22:01:10 +00:00
# Backups Class
# **********************************************************************************
class Backups(BaseModel):
excluded_dirs = CharField(null=True)
2021-09-08 22:01:10 +00:00
max_backups = IntegerField()
server_id = ForeignKeyField(Servers, backref="backups_server")
2022-03-02 16:34:26 +00:00
compress = BooleanField(default=False)
shutdown = BooleanField(default=False)
2023-01-21 18:13:02 +00:00
before = CharField(default="")
after = CharField(default="")
2021-09-08 22:01:10 +00:00
class Meta:
table_name = "backups"
2021-09-08 22:01:10 +00:00
class HelpersManagement:
def __init__(self, database, helper):
self.database = database
self.helper = helper
# **********************************************************************************
2021-09-08 22:01:10 +00:00
# Host_Stats Methods
# **********************************************************************************
2021-09-08 22:01:10 +00:00
@staticmethod
def get_latest_hosts_stats():
# pylint: disable=no-member
query = HostStats.select().order_by(HostStats.id.desc()).get()
2021-09-08 22:01:10 +00:00
return model_to_dict(query)
# **********************************************************************************
2021-09-08 22:01:10 +00:00
# Audit_Log Methods
# **********************************************************************************
2021-09-08 22:01:10 +00:00
@staticmethod
2023-08-20 21:01:32 +00:00
def get_activity_log():
query = AuditLog.select()
return DatabaseShortcuts.return_db_rows(query)
def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ")
user_data = HelperUsers.get_user(user_id)
2021-09-08 22:01:10 +00:00
audit_msg = f"{str(user_data['username']).capitalize()} {log_msg}"
2021-09-08 22:01:10 +00:00
server_users = PermissionsServers.get_server_user_list(server_id)
for user in server_users:
try:
WebSocketManager().broadcast_user(user, "notification", audit_msg)
except Exception as e:
logger.error(f"Error broadcasting to user {user} - {e}")
AuditLog.insert(
{
AuditLog.user_name: user_data["username"],
AuditLog.user_id: user_id,
AuditLog.server_id: server_id,
AuditLog.log_msg: audit_msg,
AuditLog.source_ip: source_ip,
}
).execute()
# deletes records when there's more than 300
ordered = AuditLog.select().order_by(+AuditLog.created)
for item in ordered:
if not self.helper.get_setting("max_audit_entries"):
2022-01-31 23:09:23 +00:00
max_entries = 300
else:
max_entries = self.helper.get_setting("max_audit_entries")
if AuditLog.select().count() > max_entries:
AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute()
else:
return
2021-09-08 22:01:10 +00:00
def add_to_audit_log_raw(self, user_name, user_id, server_id, log_msg, source_ip):
AuditLog.insert(
{
AuditLog.user_name: user_name,
AuditLog.user_id: user_id,
AuditLog.server_id: server_id,
AuditLog.log_msg: log_msg,
AuditLog.source_ip: source_ip,
}
).execute()
# deletes records when there's more than 300
ordered = AuditLog.select().order_by(+AuditLog.created)
for item in ordered:
# configurable through app/config/config.json
if not self.helper.get_setting("max_audit_entries"):
2022-01-31 23:09:23 +00:00
max_entries = 300
else:
max_entries = self.helper.get_setting("max_audit_entries")
if AuditLog.select().count() > max_entries:
AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute()
else:
return
@staticmethod
def create_crafty_row():
CraftySettings.insert(
{
CraftySettings.secret_api_key: "",
CraftySettings.cookie_secret: "",
CraftySettings.login_photo: "login_1.jpg",
CraftySettings.login_opacity: 100,
}
).execute()
@staticmethod
def set_secret_api_key(key):
CraftySettings.update({CraftySettings.secret_api_key: key}).where(
CraftySettings.id == 1
).execute()
@staticmethod
def get_secret_api_key():
settings = CraftySettings.select(CraftySettings.secret_api_key).where(
CraftySettings.id == 1
)
return settings[0].secret_api_key
@staticmethod
def get_cookie_secret():
settings = CraftySettings.select(CraftySettings.cookie_secret).where(
CraftySettings.id == 1
)
return settings[0].cookie_secret
@staticmethod
def set_cookie_secret(key):
CraftySettings.update({CraftySettings.cookie_secret: key}).where(
CraftySettings.id == 1
).execute()
# **********************************************************************************
# Config Methods
# **********************************************************************************
2022-11-09 19:13:35 +00:00
@staticmethod
def get_login_image():
settings = CraftySettings.select(CraftySettings.login_photo).where(
CraftySettings.id == 1
)
return settings[0].login_photo
@staticmethod
def set_login_image(photo):
CraftySettings.update({CraftySettings.login_photo: photo}).where(
CraftySettings.id == 1
).execute()
@staticmethod
def get_login_opacity():
settings = CraftySettings.select(CraftySettings.login_opacity).where(
CraftySettings.id == 1
)
return settings[0].login_opacity
@staticmethod
def set_login_opacity(opacity):
CraftySettings.update({CraftySettings.login_opacity: opacity}).where(
CraftySettings.id == 1
).execute()
2023-01-27 00:21:39 +00:00
@staticmethod
def get_master_server_dir():
settings = CraftySettings.select(CraftySettings.master_server_dir).where(
CraftySettings.id == 1
)
return settings[0].master_server_dir
@staticmethod
def set_master_server_dir(server_dir):
CraftySettings.update({CraftySettings.master_server_dir: server_dir}).where(
CraftySettings.id == 1
).execute()
# **********************************************************************************
2021-09-08 22:01:10 +00:00
# Schedules Methods
# **********************************************************************************
2021-09-08 22:01:10 +00:00
@staticmethod
def create_scheduled_task(
server_id,
action,
interval,
interval_type,
start_time,
command,
2022-08-05 17:52:50 +00:00
name,
enabled=True,
one_time=False,
cron_string="* * * * *",
2022-02-24 05:19:54 +00:00
parent=None,
delay=0,
):
sch_id = Schedules.insert(
{
Schedules.server_id: server_id,
Schedules.action: action,
Schedules.enabled: enabled,
Schedules.interval: interval,
Schedules.interval_type: interval_type,
Schedules.start_time: start_time,
Schedules.command: command,
2022-08-05 17:52:50 +00:00
Schedules.name: name,
Schedules.one_time: one_time,
Schedules.cron_string: cron_string,
Schedules.parent: parent,
Schedules.delay: delay,
2022-08-04 22:15:59 +00:00
Schedules.next_run: "",
}
).execute()
2021-09-08 22:01:10 +00:00
return sch_id
@staticmethod
def delete_scheduled_task(schedule_id):
return Schedules.delete().where(Schedules.schedule_id == schedule_id).execute()
2021-09-08 22:01:10 +00:00
@staticmethod
def update_scheduled_task(schedule_id, updates):
Schedules.update(updates).where(Schedules.schedule_id == schedule_id).execute()
2021-11-29 21:22:46 +00:00
@staticmethod
def delete_scheduled_task_by_server(server_id):
2023-11-01 17:44:56 +00:00
Schedules.delete().where(Schedules.server_id == server_id).execute()
2021-11-29 21:22:46 +00:00
2021-09-08 22:01:10 +00:00
@staticmethod
def get_scheduled_task(schedule_id):
return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id))
2021-09-08 22:01:10 +00:00
@staticmethod
def get_scheduled_task_model(schedule_id):
return Schedules.select().where(Schedules.schedule_id == schedule_id).get()
2021-09-08 22:01:10 +00:00
@staticmethod
def get_schedules_by_server(server_id):
return Schedules.select().where(Schedules.server_id == server_id).execute()
2022-02-24 03:58:48 +00:00
@staticmethod
def get_child_schedules_by_server(schedule_id, server_id):
return (
Schedules.select()
.where(Schedules.server_id == server_id, Schedules.parent == schedule_id)
.execute()
)
2022-02-24 03:58:48 +00:00
2022-02-24 05:19:54 +00:00
@staticmethod
def get_child_schedules(schedule_id):
return Schedules.select().where(Schedules.parent == schedule_id)
2021-09-08 22:01:10 +00:00
@staticmethod
def get_schedules_all():
return Schedules.select().execute()
@staticmethod
def get_schedules_enabled():
return (
Schedules.select()
.where(Schedules.enabled == True) # pylint: disable=singleton-comparison
.execute()
)
2021-09-08 22:01:10 +00:00
# **********************************************************************************
2021-09-08 22:01:10 +00:00
# Backups Methods
# **********************************************************************************
2021-09-08 22:01:10 +00:00
@staticmethod
def get_backup_config(server_id):
try:
row = (
Backups.select().where(Backups.server_id == server_id).join(Servers)[0]
)
2021-09-08 22:01:10 +00:00
conf = {
"backup_path": row.server_id.backup_path,
"excluded_dirs": row.excluded_dirs,
2021-09-08 22:01:10 +00:00
"max_backups": row.max_backups,
2022-05-15 22:02:17 +00:00
"server_id": row.server_id_id,
"compress": row.compress,
"shutdown": row.shutdown,
2023-01-21 18:13:02 +00:00
"before": row.before,
"after": row.after,
2021-09-08 22:01:10 +00:00
}
except IndexError:
conf = {
"backup_path": None,
"excluded_dirs": None,
2021-09-08 22:01:10 +00:00
"max_backups": 0,
2022-03-02 16:34:26 +00:00
"server_id": server_id,
"compress": False,
"shutdown": False,
2023-01-21 18:13:02 +00:00
"before": "",
"after": "",
2021-09-08 22:01:10 +00:00
}
return conf
@staticmethod
def remove_backup_config(server_id):
Backups.delete().where(Backups.server_id == server_id).execute()
def set_backup_config(
self,
server_id: int,
backup_path: str = None,
max_backups: int = None,
excluded_dirs: list = None,
compress: bool = False,
shutdown: bool = False,
2023-01-21 18:13:02 +00:00
before: str = "",
after: str = "",
):
logger.debug(f"Updating server {server_id} backup config with {locals()}")
if Backups.select().where(Backups.server_id == server_id).exists():
2021-09-08 22:01:10 +00:00
new_row = False
conf = {}
2022-01-29 21:37:00 +00:00
else:
2021-09-08 22:01:10 +00:00
conf = {
"excluded_dirs": None,
2021-09-08 22:01:10 +00:00
"max_backups": 0,
"server_id": server_id,
"compress": False,
"shutdown": False,
2023-01-21 18:13:02 +00:00
"before": "",
"after": "",
2021-09-08 22:01:10 +00:00
}
new_row = True
if max_backups is not None:
conf["max_backups"] = max_backups
if excluded_dirs is not None:
dirs_to_exclude = ",".join(excluded_dirs)
conf["excluded_dirs"] = dirs_to_exclude
conf["compress"] = compress
conf["shutdown"] = shutdown
2023-01-21 18:13:02 +00:00
conf["before"] = before
conf["after"] = after
2021-09-08 22:01:10 +00:00
if not new_row:
with self.database.atomic():
if backup_path is not None:
server_rows = (
Servers.update(backup_path=backup_path)
.where(Servers.server_id == server_id)
.execute()
)
else:
server_rows = 0
backup_rows = (
Backups.update(conf).where(Backups.server_id == server_id).execute()
)
logger.debug(
f"Updating existing backup record. "
f"{server_rows}+{backup_rows} rows affected"
)
2021-09-08 22:01:10 +00:00
else:
with self.database.atomic():
conf["server_id"] = server_id
if backup_path is not None:
Servers.update(backup_path=backup_path).where(
Servers.server_id == server_id
)
Backups.create(**conf)
logger.debug("Creating new backup record.")
@staticmethod
def get_excluded_backup_dirs(server_id: int):
excluded_dirs = HelpersManagement.get_backup_config(server_id)["excluded_dirs"]
if excluded_dirs is not None and excluded_dirs != "":
dir_list = excluded_dirs.split(",")
else:
dir_list = []
return dir_list
def add_excluded_backup_dir(self, server_id: int, dir_to_add: str):
dir_list = self.get_excluded_backup_dirs(server_id)
if dir_to_add not in dir_list:
dir_list.append(dir_to_add)
excluded_dirs = ",".join(dir_list)
self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
else:
logger.debug(
f"Not adding {dir_to_add} to excluded directories - "
f"already in the excluded directory list for server ID {server_id}"
)
def del_excluded_backup_dir(self, server_id: int, dir_to_del: str):
dir_list = self.get_excluded_backup_dirs(server_id)
if dir_to_del in dir_list:
dir_list.remove(dir_to_del)
excluded_dirs = ",".join(dir_list)
self.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs)
else:
logger.debug(
f"Not removing {dir_to_del} from excluded directories - "
f"not in the excluded directory list for server ID {server_id}"
)
2023-06-03 17:35:29 +00:00
# **********************************************************************************
# Webhooks Class
# **********************************************************************************
class HelpersWebhooks:
def __init__(self, database):
self.database = database
@staticmethod
2023-06-03 19:05:08 +00:00
def create_webhook(create_data) -> int:
2023-06-03 17:35:29 +00:00
"""Create a webhook in the database
Args:
server_id: ID of a server this webhook will be married to
name: The name of the webhook
url: URL to the webhook
webhook_type: The provider this webhook will be sent to
bot name: The name that will appear when the webhook is sent
triggers: Server actions that will trigger this webhook
body: The message body of the webhook
enabled: Should Crafty trigger the webhook
Returns:
int: The new webhooks's id
Raises:
PeeweeException: If the webhook already exists
"""
return Webhooks.insert(
{
2023-06-03 19:05:08 +00:00
Webhooks.server_id: create_data["server_id"],
Webhooks.name: create_data["name"],
Webhooks.webhook_type: create_data["webhook_type"],
Webhooks.url: create_data["url"],
Webhooks.bot_name: create_data["bot_name"],
Webhooks.body: create_data["body"],
2023-09-28 23:07:03 +00:00
Webhooks.color: create_data["color"],
2023-06-03 19:05:08 +00:00
Webhooks.trigger: create_data["trigger"],
Webhooks.enabled: create_data["enabled"],
2023-06-03 17:35:29 +00:00
}
).execute()
2023-06-03 19:05:08 +00:00
@staticmethod
def modify_webhook(webhook_id, updata):
Webhooks.update(updata).where(Webhooks.id == webhook_id).execute()
@staticmethod
def get_webhook_by_id(webhook_id):
return model_to_dict(Webhooks.get(Webhooks.id == webhook_id))
@staticmethod
2023-06-03 22:11:20 +00:00
def get_webhooks_by_server(server_id, model):
if not model:
data = {}
for webhook in (
Webhooks.select().where(Webhooks.server_id == server_id).execute()
):
data[str(webhook.id)] = {
"webhook_type": webhook.webhook_type,
"name": webhook.name,
"url": webhook.url,
"bot_name": webhook.bot_name,
"trigger": webhook.trigger,
"body": webhook.body,
2023-09-28 23:07:03 +00:00
"color": webhook.color,
2023-06-03 22:11:20 +00:00
"enabled": webhook.enabled,
}
else:
data = Webhooks.select().where(Webhooks.server_id == server_id).execute()
2023-06-03 19:05:08 +00:00
return data
@staticmethod
def delete_webhook(webhook_id):
Webhooks.delete().where(Webhooks.id == webhook_id).execute()
@staticmethod
def delete_webhooks_by_server(server_id):
Webhooks.delete().where(Webhooks.server_id == server_id).execute()