Backup statuses

Default backups
This commit is contained in:
amcmanu3 2024-05-26 13:45:13 -04:00
parent bf196b68c0
commit 1b073a2401
8 changed files with 170 additions and 51 deletions

View File

@ -10,7 +10,6 @@ from peewee import (
TextField,
AutoField,
BooleanField,
UUIDField,
)
from playhouse.shortcuts import model_to_dict
@ -18,6 +17,7 @@ from app.classes.models.base_model import BaseModel
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.helpers import Helpers
from app.classes.shared.websocket_manager import WebSocketManager
logger = logging.getLogger(__name__)
@ -105,7 +105,7 @@ class Schedules(BaseModel):
# Backups Class
# **********************************************************************************
class Backups(BaseModel):
backup_id = UUIDField(primary_key=True, default=uuid.uuid4)
backup_id = CharField(primary_key=True, default=Helpers.create_uuid())
backup_name = CharField(default="New Backup")
backup_location = CharField(default="")
excluded_dirs = CharField(null=True)
@ -115,6 +115,8 @@ class Backups(BaseModel):
shutdown = BooleanField(default=False)
before = CharField(default="")
after = CharField(default="")
default = BooleanField(default=False)
status = CharField(default='{"status": "Standby", "message": ""}')
enabled = BooleanField(default=True)
class Meta:
@ -348,7 +350,7 @@ class HelpersManagement:
return model_to_dict(Backups.get(Backups.backup_id == backup_id))
@staticmethod
def get_backups_by_server(server_id, model):
def get_backups_by_server(server_id, model=False):
if not model:
data = {}
for backup in (
@ -365,11 +367,26 @@ class HelpersManagement:
"shutdown": backup.shutdown,
"before": backup.before,
"after": backup.after,
"default": backup.default,
"enabled": backup.enabled,
}
else:
data = Backups.select().where(Backups.server_id == server_id).execute()
return data
@staticmethod
def get_default_server_backup(server_id: str) -> dict:
bu_query = Backups.select().where(
Backups.server_id == server_id & Backups.default == True
)
backup_model = bu_query.first()
if backup_model:
return model_to_dict(backup_model)
else:
raise IndexError
@staticmethod
def remove_backup_config(backup_id):
Backups.delete().where(Backups.backup_id == backup_id).execute()

View File

@ -1107,12 +1107,12 @@ class ServerInstance:
f.write("eula=true")
self.run_threaded_server(user_id)
def a_backup_server(self, backup_id):
def server_backup_threader(self, backup_id, update=False):
backup_thread = threading.Thread(
target=self.backup_server,
daemon=True,
name=f"backup_{self.name}",
args=[backup_id],
args=[backup_id, update],
)
logger.info(
f"Starting Backup Thread for server {self.settings['server_name']}."
@ -1140,7 +1140,7 @@ class ServerInstance:
logger.info(f"Backup Thread started for server {self.settings['server_name']}.")
@callback
def backup_server(self, backup_id):
def backup_server(self, backup_id, update):
was_server_running = None
logger.info(f"Starting server {self.name} (ID {self.server_id}) backup")
server_users = PermissionsServers.get_server_user_list(self.server_id)
@ -1174,9 +1174,12 @@ class ServerInstance:
"Found shutdown preference. Delaying"
+ "backup start. Shutting down server."
)
if self.check_running():
self.stop_server()
was_server_running = True
if not update:
if self.check_running():
self.stop_server()
was_server_running = True
else:
was_server_running = False
self.helper.ensure_dir_exists(backup_location)
try:
@ -1318,7 +1321,7 @@ class ServerInstance:
def jar_update(self):
self.stats_helper.set_update(True)
update_thread = threading.Thread(
target=self.a_jar_update, daemon=True, name=f"exe_update_{self.name}"
target=self.threaded_jar_update, daemon=True, name=f"exe_update_{self.name}"
)
update_thread.start()
@ -1359,10 +1362,13 @@ class ServerInstance:
def check_update(self):
return self.stats_helper.get_server_stats()["updating"]
def a_jar_update(self):
def threaded_jar_update(self):
server_users = PermissionsServers.get_server_user_list(self.server_id)
was_started = "-1"
self.a_backup_server()
# Get default backup configuration
backup_config = HelpersManagement.get_default_server_backup(self.server_id)
# start threaded backup
self.server_backup_threader(backup_config["backup_id"], True)
# checks if server is running. Calls shutdown if it is running.
if self.check_running():
was_started = True
@ -1516,12 +1522,6 @@ class ServerInstance:
WebSocketManager().broadcast_user_page(
user, "/panel/dashboard", "send_start_reload", {}
)
WebSocketManager().broadcast_user(
user,
"notification",
"Executable update finished for " + self.name,
)
self.management_helper.add_to_audit_log_raw(
"Alert",
"-1",

View File

@ -140,7 +140,7 @@ class TasksManager:
)
elif command == "backup_server":
svr.a_backup_server(cmd["action_id"])
svr.server_backup_threader(cmd["action_id"])
elif command == "update_executable":
svr.jar_update()

View File

@ -124,6 +124,15 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
server_id,
self.get_remote_ip(),
)
if backup_conf["default"]:
return self.finish_json(
405,
{
"status": "error",
"error": "NOT_ALLOWED",
"error_data": "Cannot delete default backup",
},
)
self.controller.management.delete_backup_config(backup_id)
return self.finish_json(200, {"status": "ok"})

View File

@ -70,7 +70,9 @@
<tr class="rounded">
<th scope="col" style="width: 15%; min-width: 10px;">{{ translate('serverBackups', 'name',
data['lang']) }} </th>
<th scope="col" style="width: 60%; min-width: 50px;">{{ translate('serverBackups',
<th scope="col" style="width: 5%; min-width: 10px;">{{ translate('serverBackups', 'status',
data['lang']) }} </th>
<th scope="col" style="width: 55%; min-width: 50px;">{{ translate('serverBackups',
'storageLocation', data['lang']) }}</th>
<th scope="col" style="width: 10%; min-width: 50px;">{{ translate('serverBackups',
'maxBackups', data['lang']) }}</th>
@ -83,6 +85,19 @@
<tr>
<td id="{{backup.backup_name}}" class="id">
<p>{{backup.backup_name}}</p>
<br>
{% if backup.default %}
<span class="badge-pill badge-outline-warning">{{ translate('serverBackups', 'default',
data['lang']) }}</span><small><button class="badge-pill badge-outline-info backup-explain"
data-explain="{{ translate('serverBackups', 'defaultExplain', data['lang'])}}"><i
class="fa-solid fa-question"></i></button></small>
{% end %}
</td>
<td>
<div id="{{backup.backup_id}}_status">
<span class="badge-pill badge-outline-success backup-status"
data-status="{{ backup.status }}"></span>
</div>
</td>
<td id="{{backup.backup_location}}" class="type">
<p>{{backup.backup_location}}</p>
@ -96,9 +111,11 @@
class="btn btn-info">
<i class="fas fa-pencil-alt"></i>
</button>
{% if not backup.default %}
<button data-backup={{ backup.backup_id }} class="btn btn-danger del_button">
<i class="fas fa-trash" aria-hidden="true"></i>
</button>
{% end %}
<button data-backup={{ backup.backup_id }} data-toggle="tooltip"
title="{{ translate('serverBackups', 'run', data['lang']) }}"
class="btn btn-outline-warning run-backup backup_now_button">
@ -126,6 +143,13 @@
<tr>
<td id="{{backup.backup_name}}" class="id">
<p>{{backup.backup_name}}</p>
<br>
{% if backup.default %}
<span class="badge-pill badge-outline-warning">{{ translate('serverBackups', 'default',
data['lang']) }}</span><small><button class="badge-pill badge-outline-info backup-explain"
data-explain="{{ translate('serverBackups', 'defaultExplain', data['lang'])}}"><i
class="fa-solid fa-question"></i></button></small>
{% end %}
</td>
<td id="backup_edit" class="action">
<button
@ -133,9 +157,11 @@
class="btn btn-info">
<i class="fas fa-pencil-alt"></i>
</button>
{% if not backup.default %}
<button data-backup={{ backup.backup_id }} class="btn btn-danger del_button">
<i class="fas fa-trash" aria-hidden="true"></i>
</button>
{% end %}
<button data-backup={{ backup.backup_id }} data-toggle="tooltip"
title="{{ translate('serverBackups', 'run', data['lang']) }}"
class="btn btn-outline-warning test-socket">
@ -230,14 +256,7 @@
let responseData = await res.json();
if (responseData.status === "ok") {
console.log(responseData);
$("#backup_button").html(`<div class="progress" style="height: 15px;">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar"
role="progressbar" style="width:{{data['backup_stats']['percent']}}%;"
aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{
data['backup_stats']['percent'] }}%</div>
</div>
<p>Backing up <i class="fas fa-spin fa-spinner"></i> <span
id="total_files">{{data['server_stats']['world_size']}}</span></p>`);
$("#backup_button").prop('disabled', true)
} else {
bootbox.alert({
@ -269,6 +288,54 @@
$(document).ready(function () {
console.log("ready!");
$(".backup-explain").on("click", function () {
bootbox.alert($(this).data("explain"));
});
$('.backup-status').each(function () {
// Get the JSON string from the element's text
var data = $(this).data('status');
try {
console.log("DATA " + data)
// Update the element's text with the status value
$(this).text(data.status);
// Optionally, add classes based on status to style the element
if (data.status === 'Active') {
$(this).addClass('badge-pill badge-outline-success');
} else if (data.status === 'Failed') {
$(this).addClass('badge-pill badge-outline-danger');
} else if (data.status === 'Standby') {
$(this).addClass('badge-pill badge-outline-secondary');
}
} catch (e) {
console.error('Invalid JSON string:', jsonString);
}
});
if (webSocket) {
webSocket.on('backup_status', function (backup) {
console.log("PEEPEEPOOPOO")
text = ``;
console.log(backup)
if (backup.percent >= 100) {
$(`#${backup.backup_id}_status`).html(`<span class="badge-pill badge-outline-success backup-status"
>Completed</span>`);
setTimeout(function () {
window.location.reload(1);
}, 5000);
} else {
text = `<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width:${backup.percent}%;"
aria-valuenow="${backup.percent}" aria-valuemin="0" aria-valuemax="100">${backup.percent}%</div>`
$(`#${backup.backup_id}_status`).html(text);
}
});
}
$('#backup_table').DataTable({
"order": [[1, "desc"]],
@ -378,21 +445,6 @@
bootbox.alert("You must input a path before selecting this button");
}
});
if (webSocket) {
webSocket.on('backup_status', function (backup) {
if (backup.percent >= 100) {
document.getElementById('backup_progress_bar').innerHTML = '100%';
document.getElementById('backup_progress_bar').style.width = '100%';
setTimeout(function () {
window.location.reload(1);
}, 5000);
} else {
document.getElementById('backup_progress_bar').innerHTML = backup.percent + '%';
document.getElementById('backup_progress_bar').style.width = backup.percent + '%';
document.getElementById('total_files').innerHTML = backup.total_files;
}
});
}
function getDirView(event) {
let path = event.target.parentElement.getAttribute("data-path");

View File

@ -63,7 +63,14 @@
{% end %}
<form id="backup-form" class="forms-sample">
<div class="form-group">
<label for="backup_name">{{ translate('serverBackups', 'name', data['lang']) }} </label>
<label for="backup_name">{{ translate('serverBackups', 'name', data['lang']) }}
{% if data["backup_config"]["default"] %}
&nbsp;&nbsp; <span class="badge-pill badge-outline-warning">{{ translate('serverBackups', 'default',
data['lang']) }}</span><small><button class="badge-pill badge-outline-info backup-explain"
data-explain="{{ translate('serverBackups', 'defaultExplain', data['lang'])}}"><i
class="fa-solid fa-question"></i></button></small>
{% end %}
</label>
{% if data["backup_config"].get("backup_id", None) %}
<input type="text" class="form-control" name="backup_name" id="backup_name"
value="{{ data['backup_config']['backup_name'] }}">
@ -188,7 +195,8 @@
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang'])
}}</button>
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang'])
<button type="reset" class="btn btn-light cancel-button">{{ translate('serverBackups', 'cancel',
data['lang'])
}}</button>
</form>
</div>
@ -431,6 +439,13 @@
}
$(document).ready(function () {
$(".backup-explain").on("click", function (e) {
e.preventDefault();
bootbox.alert($(this).data("explain"));
});
$(".cancel-button").on("click", function () {
location.href = `/panel/server_detail?id=${server_id}&subpage=backup`
});
$("#backup-form").on("submit", async function (e) {
e.preventDefault();
const token = getCookie("_xsrf")

View File

@ -3,9 +3,11 @@ import uuid
import peewee
import logging
from app.classes.models.management import Backups, Schedules
from app.classes.shared.helpers import Helpers
from app.classes.shared.console import Console
from app.classes.shared.migration import Migrator, MigrateHistory
from app.classes.models.management import Backups, Schedules
logger = logging.getLogger(__name__)
@ -15,11 +17,20 @@ def migrate(migrator: Migrator, database, **kwargs):
Write your migrations here.
"""
db = database
Console.info("Starting Backups migrations")
Console.info(
"Migrations: Adding columns [backup_id, "
"backup_name, backup_location, enabled, default, action_id, backup_status]"
)
migrator.add_columns("backups", backup_id=peewee.UUIDField(default=uuid.uuid4))
migrator.add_columns("backups", backup_name=peewee.CharField(default="Default"))
migrator.add_columns("backups", backup_location=peewee.CharField(default=""))
migrator.add_columns("backups", enabled=peewee.BooleanField(default=True))
migrator.add_columns("backups", default=peewee.BooleanField(default=False))
migrator.add_columns(
"backups",
status=peewee.CharField(default='{"status": "Standby", "message": ""}'),
)
migrator.add_columns(
"schedules", action_id=peewee.CharField(null=True, default=None)
)
@ -52,7 +63,7 @@ def migrate(migrator: Migrator, database, **kwargs):
database = db
class NewBackups(peewee.Model):
backup_id = peewee.UUIDField(primary_key=True, default=uuid.uuid4)
backup_id = peewee.CharField(primary_key=True, default=Helpers.create_uuid())
backup_name = peewee.CharField(default="New Backup")
backup_location = peewee.CharField(default="")
excluded_dirs = peewee.CharField(null=True)
@ -62,6 +73,8 @@ def migrate(migrator: Migrator, database, **kwargs):
shutdown = peewee.BooleanField(default=False)
before = peewee.CharField(default="")
after = peewee.CharField(default="")
default = peewee.BooleanField(default=False)
status = peewee.CharField(default='{"status": "Standby", "message": ""}')
enabled = peewee.BooleanField(default=True)
class Meta:
@ -97,11 +110,11 @@ def migrate(migrator: Migrator, database, **kwargs):
for backup in Backups.select():
# Fetch the related server entry from the Servers table
server = Servers.get(Servers.server_id == backup.server_id)
Console.info(f"Migrations: Migrating backup for server {server.server_name}")
# Create a new backup entry with data from the
# old backup entry and related server
NewBackups.create(
backup_name="Default",
backup_name=f"{server.server_name} Backup",
# Set backup_location equal to backup_path
backup_location=server.backup_path,
excluded_dirs=backup.excluded_dirs,
@ -111,19 +124,27 @@ def migrate(migrator: Migrator, database, **kwargs):
shutdown=backup.shutdown,
before=backup.before,
after=backup.after,
default=True,
enabled=True,
)
Console.debug("Migrations: Dropping old backup table")
# Drop the existing backups table
migrator.drop_table("backups")
Console.debug("Migrations: Renaming new_backups to backups")
# Rename the new table to backups
migrator.rename_table("new_backups", "backups")
Console.debug("Migrations: Dropping backup_path from servers table")
migrator.drop_columns("servers", ["backup_path"])
for schedule in Schedules.select():
action_id = None
if schedule.command == "backup_server":
Console.info(
f"Migrations: Adding backup ID to task with name {schedule.name}"
)
backup = NewBackups.get(NewBackups.server_id == schedule.server_id)
action_id = backup.backup_id
NewSchedules.create(
@ -144,9 +165,11 @@ def migrate(migrator: Migrator, database, **kwargs):
next_run=schedule.next_run,
)
Console.debug("Migrations: dropping old schedules table")
# Drop the existing backups table
migrator.drop_table("schedules")
Console.debug("Migrations: renaming new_schedules to schedules")
# Rename the new table to backups
migrator.rename_table("new_schedules", "schedules")

View File

@ -335,7 +335,10 @@
"newBackup": "Create New Backup",
"edit": "Edit",
"run": "Run Backup",
"backups": "Server Backups"
"backups": "Server Backups",
"default": "Default Backup",
"defaultExplain": "The backup that Crafty will use before updates. This cannot be changed or deleted.",
"status": "Status"
},
"serverConfig": {
"bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.",