mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Add custom login image feature
This commit is contained in:
parent
cd93b468b3
commit
bf0255a26b
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,6 +20,7 @@ venv.bak/
|
||||
.idea/
|
||||
/imports/
|
||||
/servers/
|
||||
/app/frontend/static/assets/images/auth/custom/
|
||||
/backups/
|
||||
/temp/
|
||||
/docker/servers/
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
# **********************************************************************************
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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"]
|
||||
|
@ -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:
|
||||
|
@ -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")
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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%"> <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 %}
|
@ -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'] %}
|
||||
|
@ -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">
|
||||
|
18
app/migrations/20221109_login_photo.py
Normal file
18
app/migrations/20221109_login_photo.py
Normal 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.
|
||||
"""
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user