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/ .idea/
/imports/ /imports/
/servers/ /servers/
/app/frontend/static/assets/images/auth/custom/
/backups/ /backups/
/temp/ /temp/
/docker/servers/ /docker/servers/

View File

@ -94,6 +94,14 @@ class ManagementController:
def delete_scheduled_task(schedule_id): def delete_scheduled_task(schedule_id):
return HelpersManagement.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 @staticmethod
def update_scheduled_task(schedule_id, updates): def update_scheduled_task(schedule_id, updates):
return HelpersManagement.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): class CraftySettings(BaseModel):
secret_api_key = CharField(default="") secret_api_key = CharField(default="")
login_photo = CharField(default="login_1.jpg")
class Meta: class Meta:
table_name = "crafty_settings" table_name = "crafty_settings"
@ -254,6 +255,19 @@ class HelpersManagement:
) )
return settings[0].secret_api_key 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 # Schedules Methods
# ********************************************************************************** # **********************************************************************************

View File

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

View File

@ -352,6 +352,38 @@ class AjaxHandler(BaseHandler):
self.controller.clear_unexecuted_commands() self.controller.clear_unexecuted_commands()
return 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": elif page == "kill":
if not permissions["Commands"] in user_perms: if not permissions["Commands"] in user_perms:
if not superuser: if not superuser:

View File

@ -849,6 +849,25 @@ class PanelHandler(BaseHandler):
page_data["roles"] = self.controller.roles.get_all_roles() page_data["roles"] = self.controller.roles.get_all_roles()
page_data["auth-servers"][user.user_id] = super_auth_servers page_data["auth-servers"][user.user_id] = super_auth_servers
page_data["managed_users"] = [] 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: else:
page_data["managed_users"] = self.controller.users.get_managed_users( page_data["managed_users"] = self.controller.users.get_managed_users(
exec_user["user_id"] exec_user["user_id"]

View File

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

View File

@ -152,65 +152,46 @@ class UploadHandler(BaseHandler):
return return
self.do_upload = True self.do_upload = True
if superuser: if not superuser:
exec_user_server_permissions = ( self.helper.websocket_helper.broadcast_user(
self.controller.server_perms.list_defined_permissions() 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: return
exec_user_server_permissions = ( if not self.request.headers.get("X-Content-Type", None).startswith(
self.controller.server_perms.get_api_key_permissions_list( "image/"
api_key, server_id ):
) 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: return
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
if user_id is None: if user_id is None:
logger.warning("User ID not found in upload handler call") logger.warning("User ID not found in upload handler call")
Console.warning("User ID not found in upload handler call") Console.warning("User ID not found in upload handler call")
self.do_upload = False self.do_upload = False
if EnumPermissionsServer.FILES not in exec_user_server_permissions: path = os.path.join(
logger.warning( self.controller.project_root,
f"User {user_id} tried to upload a file to " "app/frontend/static/assets/images/auth/custom",
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)
filename = self.request.headers.get("X-FileName", None) filename = self.request.headers.get("X-FileName", None)
full_path = os.path.join(path, filename) 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: if self.do_upload:
try: try:
self.f = open(full_path, "wb") self.f = open(full_path, "wb")

View File

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

View File

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

View File

@ -229,6 +229,59 @@
</div> </div>
</div> </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 %} {% end %}
</div> </div>
</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> </script>
{% end %} {% end %}

View File

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

View File

@ -60,6 +60,11 @@
.login-input:focus { .login-input:focus {
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4); 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> </style>
{% if data['query'] %} {% if data['query'] %}
<form action="/public/login?{{ data['query'] }}" method="post"> <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?", "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.", "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: {}", "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": { "footer": {
"allRightsReserved": "All rights reserved", "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.", "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?", "superConfirmTitle": "Enable superuser? Are you sure?",
"user": "User", "user": "User",
"users": "Users" "users": "Users",
"loginImage": "Upload a background image for the login screen."
}, },
"rolesConfig": { "rolesConfig": {
"config": "Role Config", "config": "Role Config",