Add custom login image feature

This commit is contained in:
amcmanu3 2022-11-09 14:13:35 -05:00
parent cd93b468b3
commit bf0255a26b
15 changed files with 249 additions and 62 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ venv.bak/
.idea/
/imports/
/servers/
/app/frontend/static/assets/images/auth/custom/
/backups/
/temp/
/docker/servers/

View File

@ -94,6 +94,14 @@ class ManagementController:
def delete_scheduled_task(schedule_id):
return HelpersManagement.delete_scheduled_task(schedule_id)
@staticmethod
def set_login_image(path):
HelpersManagement.set_login_image(path)
@staticmethod
def get_login_image():
return HelpersManagement.get_login_image()
@staticmethod
def update_scheduled_task(schedule_id, updates):
return HelpersManagement.update_scheduled_task(schedule_id, updates)

View File

@ -43,6 +43,7 @@ class AuditLog(BaseModel):
# **********************************************************************************
class CraftySettings(BaseModel):
secret_api_key = CharField(default="")
login_photo = CharField(default="login_1.jpg")
class Meta:
table_name = "crafty_settings"
@ -254,6 +255,19 @@ class HelpersManagement:
)
return settings[0].secret_api_key
@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()
# **********************************************************************************
# Schedules Methods
# **********************************************************************************

View File

@ -73,6 +73,7 @@ class Controller:
timezone=str(tz)
)
self.first_login = False
self.cached_login = self.management.get_login_image()
self.support_scheduler.start()
@staticmethod

View File

@ -352,6 +352,38 @@ class AjaxHandler(BaseHandler):
self.controller.clear_unexecuted_commands()
return
elif page == "select_photo":
if exec_user["superuser"]:
photo = self.get_argument("photo", None)
if photo == "login_1.jpg":
self.controller.management.set_login_image("login_1.jpg")
self.controller.cached_login = f"{photo}"
else:
self.controller.management.set_login_image(f"custom/{photo}")
self.controller.cached_login = f"custom/{photo}"
return
elif page == "delete_photo":
if exec_user["superuser"]:
photo = self.get_argument("photo", None)
if photo and photo != "login_1.jpg":
os.remove(
os.path.join(
self.controller.project_root,
f"app/frontend/static/assets/images/auth/custom/{photo}",
)
)
current = self.controller.cached_login
split = current.split("/")
if split == 1:
current_photo = current
else:
current_photo = split[1]
if current_photo == photo:
self.controller.management.set_login_image("login_1.jpg")
self.controller.cached_login = "login_1.jpg"
return
elif page == "kill":
if not permissions["Commands"] in user_perms:
if not superuser:

View File

@ -849,6 +849,25 @@ class PanelHandler(BaseHandler):
page_data["roles"] = self.controller.roles.get_all_roles()
page_data["auth-servers"][user.user_id] = super_auth_servers
page_data["managed_users"] = []
page_data["backgrounds"] = []
cached_split = self.controller.cached_login.split("/")
if len(cached_split) == 1:
page_data["backgrounds"].append(
self.controller.cached_login
)
else:
page_data["backgrounds"].append(cached_split[1])
if "login_1.jpg" not in page_data["backgrounds"]:
page_data["backgrounds"].append("login_1.jpg")
for item in os.listdir(
os.path.join(
self.controller.project_root,
"app/frontend/static/assets/images/auth/custom",
)
):
if item not in page_data["backgrounds"]:
page_data["backgrounds"].append(item)
else:
page_data["managed_users"] = self.controller.users.get_managed_users(
exec_user["user_id"]

View File

@ -48,6 +48,7 @@ class PublicHandler(BaseHandler):
template = "public/404.html"
if page == "login":
page_data["background"] = self.controller.cached_login
template = "public/login.html"
elif page == 404:

View File

@ -152,65 +152,46 @@ class UploadHandler(BaseHandler):
return
self.do_upload = True
if superuser:
exec_user_server_permissions = (
self.controller.server_perms.list_defined_permissions()
if not superuser:
self.helper.websocket_helper.broadcast_user(
user_id,
"send_start_error",
{
"error": self.helper.translation.translate(
"error",
"superError",
self.controller.users.get_user_lang_by_id(user_id),
),
},
)
elif api_key is not None:
exec_user_server_permissions = (
self.controller.server_perms.get_api_key_permissions_list(
api_key, server_id
)
return
if not self.request.headers.get("X-Content-Type", None).startswith(
"image/"
):
self.helper.websocket_helper.broadcast_user(
user_id,
"send_start_error",
{
"error": self.helper.translation.translate(
"error",
"fileError",
self.controller.users.get_user_lang_by_id(user_id),
),
},
)
else:
exec_user_server_permissions = (
self.controller.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server_id
)
)
server_id = self.request.headers.get("X-ServerId", None)
if server_id is None:
logger.warning("Server ID not found in upload handler call")
Console.warning("Server ID not found in upload handler call")
self.do_upload = False
return
if user_id is None:
logger.warning("User ID not found in upload handler call")
Console.warning("User ID not found in upload handler call")
self.do_upload = False
if EnumPermissionsServer.FILES not in exec_user_server_permissions:
logger.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
Console.warning(
f"User {user_id} tried to upload a file to "
f"{server_id} without permissions!"
)
self.do_upload = False
path = self.request.headers.get("X-Path", None)
path = os.path.join(
self.controller.project_root,
"app/frontend/static/assets/images/auth/custom",
)
filename = self.request.headers.get("X-FileName", None)
full_path = os.path.join(path, filename)
if not Helpers.in_path(
Helpers.get_os_understandable_path(
self.controller.servers.get_server_data_by_id(server_id)["path"]
),
full_path,
):
logger.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
Console.warning(
f"User {user_id} tried to upload a file to {server_id} "
f"but the path is not inside of the server!"
)
self.do_upload = False
if self.do_upload:
try:
self.f = open(full_path, "wb")

View File

@ -28362,11 +28362,6 @@ div.tagsinput span.tag a {
min-height: 100vh;
}
.auth.auth-bg-1 {
background: url("../../images/auth/login_1.jpg");
background-size: cover;
}
.auth.register-bg-1 {
background: url("../../images/auth/register.jpg") center center no-repeat;
background-size: cover;

View File

@ -26992,11 +26992,6 @@ div.tagsinput span.tag a {
min-height: 100vh;
}
.auth.auth-bg-1 {
background: url("../../images/auth/login_1.jpg");
background-size: cover;
}
.auth.register-bg-1 {
background: url("../../images/auth/register.jpg") center center no-repeat;
background-size: cover;

View File

@ -229,6 +229,59 @@
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4>{{ translate('panelConfig', 'loginImage', data['lang']) }}</h4>
<br />
<p class="card-description">
<form name="zip" method="post" class="server-wizard" onSubmit="wait_msg(true)">
{% raw xsrf_form_html() %}
<input type="hidden" value="import_zip" name="create_type">
<div class="row">
<div class="col-sm-12">
<div class="col-sm-12">
<div class="form-group">
<label for="server">Background Upload</label><br>
<span id="upload_input">
<input type="file" multiple="false" class="form-control" id="file" name="file" required
style="width: 70%;">
<button type="button" class="btn btn-info" onclick="sendFile()">UPLOAD</button>
</span>
</div>
</div>
</div>
</div>
</div>
</form>
</p>
</div>
</div>
<div class="col-sm-6 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4>Login Background Image</h4><br /><br>
<form id="photo_form">
<select class="form-select form-control form-control-lg select-css" id="photo" name="photo"
form="photo_form">
{% for image in data["backgrounds"] %}
<option value="{{image}}">{{image}}</option>
{% end %}
</select>
<br />
<br />
<button class="btn btn-outline-success select-photo" type="button">Select</button>
<button class="btn btn-outline-danger delete-photo" type="button">Delete</button>
</form>
</div>
</div>
</div>
</div>
{% end %}
</div>
</div>
@ -307,6 +360,67 @@
},
});
})
$('.delete-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/delete_photo?photo=' + photo,
success: function (data) {
location.reload();
},
});
})
$('.select-photo').click(function () {
var token = getCookie("_xsrf")
let photo = $('#photo').find(":selected").val();
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: '/ajax/select_photo?photo=' + photo,
success: function (data) {
},
});
})
var file;
function sendFile() {
file = $("#file")[0].files[0]
document.getElementById("upload_input").innerHTML = '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">&nbsp;<i class="fa-solid fa-spinner"></i></div></div>'
let xmlHttpRequest = new XMLHttpRequest();
let token = getCookie("_xsrf")
let fileName = file.name
let target = '/upload'
let mimeType = file.type
let size = file.size
let type = 'background'
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('X-Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('X-XSRFToken', token);
xmlHttpRequest.setRequestHeader('X-Content-Length', size);
xmlHttpRequest.setRequestHeader('X-Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.setRequestHeader('X-Content-Upload-Type', type);
xmlHttpRequest.setRequestHeader('X-FileName', fileName);
xmlHttpRequest.addEventListener('load', (event) => {
if (event.target.responseText == 'success') {
console.log('Upload for file', file.name, 'was successful!')
document.getElementById("upload_input").innerHTML = '<div class="card-header header-sm d-flex justify-content-between align-items-center"><span id="file-uploaded" style="color: gray;">' + fileName + '</span> 🔒</div>';
document.getElementById("lower_half").style.visibility = "visible";
}
else {
alert('Upload failed with response: ' + event.target.responseText);
doUpload = false;
}
}, false);
xmlHttpRequest.addEventListener('error', (e) => {
console.error('Error while uploading file', file.name + '.', 'Event:', e)
}, false);
xmlHttpRequest.send(file);
}
</script>
{% end %}

View File

@ -124,7 +124,7 @@ data['lang']) }}{% end %}
<div class="form-group">
<label class="form-label" for="theme">{{ translate('userConfig', 'userTheme', data['lang'])
}}</label>
<select class="form-select form-control form-control-lg select-css" id="language"
<select class="form-select form-control form-control-lg select-css" id="theme"
name="theme" form="user_form">
<option value="{{data['user'].get('theme', 'default')}}">{{data['user'].get('theme', 'default')}}</option>
{% for theme in data['themes'] %}

View File

@ -60,6 +60,11 @@
.login-input:focus {
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
}
.auth.auth-bg-1 {
background: url("../../static/assets/images/auth/{{data['background']}}");
background-size: cover;
}
</style>
{% if data['query'] %}
<form action="/public/login?{{ data['query'] }}" method="post">

View File

@ -0,0 +1,18 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns(
"crafty_settings", login_photo=peewee.CharField(default="login_1.jpg")
)
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("crafty_settings", ["login_photo"])
"""
Write your rollback migrations here.
"""

View File

@ -181,7 +181,9 @@
"not-downloaded": "We can't seem to find your executable file. Has it finished downloading? Are the permissions set to executable?",
"portReminder": "We have detected this is the first time {} has been run. Make sure to forward port {} through your router/firewall to make this remotely accessible from the internet.",
"start-error": "Server {} failed to start with error code: {}",
"terribleFailure": "What a Terrible Failure!"
"terribleFailure": "What a Terrible Failure!",
"superError": "You must be a super user to complete this action.",
"fileError": "File type must be an image."
},
"footer": {
"allRightsReserved": "All rights reserved",
@ -224,7 +226,8 @@
"superConfirm": "Proceed only if you want this user to have access to EVERYTHING (all user accounts, servers, panel settings, etc.). They can even revoke your superuser rights.",
"superConfirmTitle": "Enable superuser? Are you sure?",
"user": "User",
"users": "Users"
"users": "Users",
"loginImage": "Upload a background image for the login screen."
},
"rolesConfig": {
"config": "Role Config",