Merge branch 'dev' into maintenance/update-translations

This commit is contained in:
Zedifus 2023-10-16 21:25:25 +01:00
commit bfdf31cbb5
9 changed files with 386 additions and 101 deletions

View File

@ -18,6 +18,7 @@
- Fix bug where a reaction loop could be created, but would be cut short by an error when the loop occurred ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/636))
- Use controller on update user call ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/640))
- Move `imports` to `import/upload` in bind mount to better serve users on unraid with limited vdisk storage ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/642))
- Fix bug where everytime a page was loaded user settings would be reset #286 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/643))
### Refactor
- Consolidate remaining frontend functions into API V2, and remove ajax internal API ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/585))
- Replace bleach with nh3 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/628))
@ -33,8 +34,10 @@
- Bump orjson to 3.9.7 for python 3.12 support ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/638))
- Bump all Crafty required python dependancies, maintaining minimum 3.9 support ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/639))
- Better optimize and refactor docker launcher sh ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/642))
- Improve pop-up notifications with Toasts ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/641))
- Move username and password settings to buttons on panel config ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/643))
### Lang
TBD
- fr_FR Translation Updated to latest en_EN ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/646))
<br><br>
## --- [4.1.3] - 2023/07/18

View File

@ -214,14 +214,14 @@ class UsersController:
limit_server_creation = 0
limit_user_creation = 0
limit_role_creation = 0
PermissionsCrafty.add_or_update_user(
user_id,
permissions_mask,
limit_server_creation,
limit_user_creation,
limit_role_creation,
)
if user_crafty_data:
PermissionsCrafty.add_or_update_user(
user_id,
permissions_mask,
limit_server_creation,
limit_user_creation,
limit_role_creation,
)
self.users_helper.delete_user_roles(user_id, removed_roles)

View File

@ -4,10 +4,7 @@ import typing as t
from jsonschema import ValidationError, validate
from app.classes.controllers.users_controller import UsersController
from app.classes.models.crafty_permissions import (
EnumPermissionsCrafty,
PermissionsCrafty,
)
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.models.roles import HelperRoles
from app.classes.models.users import HelperUsers
from app.classes.web.base_api_handler import BaseApiHandler
@ -247,31 +244,25 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
or data["manager"] == 0
):
data["manager"] = None
crafty_perms = None
if "permissions" in data:
permissions: t.List[UsersController.ApiPermissionDict] = data.pop(
"permissions"
)
permissions_mask = "0" * len(EnumPermissionsCrafty)
limit_server_creation = 0
limit_user_creation = 0
limit_role_creation = 0
for permission in permissions:
permissions_mask = self.controller.crafty_perms.set_permission(
permissions_mask,
EnumPermissionsCrafty.__members__[permission["name"]],
"1" if permission["enabled"] else "0",
)
PermissionsCrafty.add_or_update_user(
user_id,
permissions_mask,
limit_server_creation,
limit_user_creation,
limit_role_creation,
)
if permissions is not None:
server_quantity = {}
permissions_mask = list(permissions_mask)
for permission in permissions:
server_quantity[permission["name"]] = permission["quantity"]
permissions_mask[
EnumPermissionsCrafty[permission["name"]].value
] = ("1" if permission["enabled"] else "0")
permissions_mask = "".join(permissions_mask)
crafty_perms = {
"permissions_mask": permissions_mask,
"server_quantity": server_quantity,
}
# TODO: make this more efficient
if len(data) != 0:
for key in data:
@ -280,7 +271,11 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
if key == "password":
value = self.helper.encode_pass(value)
setattr(user_obj, key, value)
self.controller.users.update_user(auth_data[4]["user_id"], data)
self.controller.users.update_user(
user_id,
data,
crafty_perms,
)
self.controller.management.add_to_audit_log(
user["user_id"],

View File

@ -124,19 +124,32 @@
.notification {
position: relative;
box-sizing: border-box;
padding: 0.5rem;
padding-left: 0.7rem;
width: 180px;
margin-left: 10px;
margin-right: 10px;
margin-right: 1rem;
background: var(--card-banner-bg);
-webkit-transition: right 0.75s, opacity 0.75s, top 0.75s;
-moz-transition: right 0.75s, opacity 0.75s, top 0.75s;
-o-transition: right 0.75s, opacity 0.75s, top 0.75s;
transition: right 0.75s, opacity 0.75s, top 0.75s;
right: -6rem;
right: -20rem;
opacity: 0.1;
margin-bottom: 1rem;
z-index: 999;
top: 0px;
}
.toast-header {
background-color: var(--card-banner-bg);
color: var(--base-text);
}
.toast-body {
background-color: var(--dropdown-bg);
color: var(--base-text);
}
.notification img {
max-height: 20px;
}
.notification strong {
line-height: 20px;
}
.notification.active {
@ -150,27 +163,17 @@
top: -2rem;
}
.notification p {
margin: 0px;
width: calc(160.8px - 16px);
z-index: inherit;
}
.notification span {
position: absolute;
right: 0.5rem;
top: 0.46rem;
cursor: pointer;
font-weight: bold;
line-height: 20px;
font-size: 22px;
font-size: 15px;
user-select: none;
z-index: inherit;
cursor: pointer;
}
</style>
<div class="notifications"></div>
<div class="notifications"></div>
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
<script src="/static/assets/js/shared/off-canvas.js"></script>
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
@ -502,30 +505,55 @@
function notify(message) {
console.log(`notify(${message})`);
var paragraphEl = document.createElement('p');
var closeEl = document.createElement('span');
var intId = getRandomInt(0, 100);
var toastDiv = document.createElement('div');
toastDiv.setAttribute("id", "toast_" + intId);
toastDiv.setAttribute("class", "notification toast");
toastDiv.setAttribute("role", "alert");
toastDiv.setAttribute("aria-lived", "assertive");
toastDiv.setAttribute("aria-atomic", "true");
toastDiv.setAttribute("data-delay", "3000");
toastDiv.setAttribute("data-animation", "true");
toastDiv.setAttribute("data-autohide", "false");
paragraphEl.textContent = message;
var toastHeaderDiv = document.createElement('div');
toastHeaderDiv.setAttribute("class", "toast-header");
closeEl.innerHTML = '&times;';
closeEl.addEventListener('click', function () { closeNotification(this) });
var toastHeaderImg = document.createElement('img');
toastHeaderImg.setAttribute("src", "/static/assets/images/logo_small.svg");
toastHeaderImg.setAttribute("class", "mr-auto");
toastHeaderImg.setAttribute("alt", "logo");
toastHeaderDiv.appendChild(toastHeaderImg);
var parentEl = document.createElement('div');
parentEl.appendChild(paragraphEl);
parentEl.appendChild(closeEl);
var toastHeaderTitle = document.createElement('strong');
toastHeaderTitle.setAttribute("class", "mr-auto");
toastHeaderTitle.innerHTML = " Crafty Controller ";
toastHeaderDiv.appendChild(toastHeaderTitle);
parentEl.classList.add('notification');
var toastHeaderCloseSpan = document.createElement('span');
toastHeaderCloseSpan.setAttribute("class", "fa-solid fa-xmark");
toastHeaderCloseSpan.setAttribute("aria-hidden", "true");
toastHeaderCloseSpan.addEventListener('click', function () { closeNotification(this.parentElement) });
toastHeaderDiv.appendChild(toastHeaderCloseSpan);
document.querySelector('.notifications').appendChild(parentEl);
var toastBodyDiv = document.createElement('div');
toastBodyDiv.setAttribute("class", "toast-body");
toastBodyDiv.textContent = message;
toastDiv.appendChild(toastHeaderDiv);
toastDiv.appendChild(toastBodyDiv);
document.querySelector('.notifications').appendChild(toastDiv);
$('#toast_' + intId).toast('show');
setTimeout(function () {
parentEl.classList.add('active');
toastDiv.classList.add('active');
}, 200);
setTimeout(function (element) {
closeNotification(element);
}, 7500, closeEl);
closeNotification(element.parentElement);
}, 7500, toastHeaderCloseSpan);
`
<div class="notification">

View File

@ -992,23 +992,28 @@
});
$(document).ready(function () {
function sendOrder(id_string) {
async function sendOrder(id_string) {
const token = getCookie("_xsrf")
$.ajax({
type: "PATCH",
headers: { 'X-XSRFToken': token },
url: `/api/v2/users/@me`,
data: JSON.stringify({
let res = await fetch(`/api/v2/users/@me`, {
method: 'PATCH',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({
server_order: id_string,
}),
success: function (data) {
console.log("got response:");
console.log(data);
},
});
let responseData = await res.json();
if (responseData.status === "ok") {
return
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
}
// Inits the sortable
$("table#servers_table tbody")
.sortable({

View File

@ -79,7 +79,7 @@
<tbody>
{% for user in data['users'] %}
<tr>
<td><i class="fas fa-user"></i> {{ user.username }}</td>
<td><i class="fas fa-user"></i><span id="user_{{user.user_id}}">{{ user.username }}</span></td>
<td>
{% if user.enabled %}
<span class="text-success">
@ -106,7 +106,10 @@
{% end %}
</ul>
</td>
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
<td><span data-translate="{{translate('userConfig', 'userName', data['lang'])}}" data-toggle="tooltip" title="{{ translate('userConfig', 'userName', data['lang'])}}" id="username_{{user.user_id}}" class="edit_user clickable" data-name="{{user.username}}" data-id="{{user.user_id}}"><i class="fa-solid fa-user"></i></span>
&nbsp;&nbsp;<span data-translate1="{{translate('userConfig', 'password', data['lang'])}}" data-translate2="{{translate('userConfig', 'repeat', data['lang'])}}" data-toggle="tooltip" title="{{ translate('userConfig', 'password', data['lang'])}}" class="edit_password clickable" data-id="{{user.user_id}}"><i class="fa-solid fa-lock"></i></span>
&nbsp;&nbsp;<a data-toggle="tooltip" title="{{ translate('userConfig', 'pageTitle', data['lang'])}}" href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a>
</td>
</tr>
{% end %}
{% for user in data['managed_users'] %}
@ -138,7 +141,10 @@
{% end %}
</ul>
</td>
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
<td><span data-translate="{{translate('userConfig', 'userName', data['lang'])}}" data-toggle="tooltip" title="{{ translate('userConfig', 'userName', data['lang'])}}" id="username_{{user.user_id}}" class="edit_user clickable" data-name="{{user.username}}" data-id="{{user.user_id}}"><i class="fa-solid fa-user"></i></span>
&nbsp;&nbsp;<span data-translate1="{{translate('userConfig', 'password', data['lang'])}}" data-translate2="{{translate('userConfig', 'repeat', data['lang'])}}" data-toggle="tooltip" title="{{ translate('userConfig', 'password', data['lang'])}}" class="edit_password clickable" data-id="{{user.user_id}}"><i class="fa-solid fa-lock"></i></span>
&nbsp;&nbsp;<a data-toggle="tooltip" title="{{ translate('userConfig', 'pageTitle', data['lang'])}}" href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a>
</td>
</tr>
{% end %}
</tbody>
@ -274,6 +280,12 @@
</div>
<style>
.clickable {
color: #007bff;
}
.clickable:hover {
cursor: pointer;
}
.custom-picker {
border: 1px solid var(--outline);
}
@ -312,6 +324,99 @@
{% block js %}
<script>
function validateForm() {
let password0 = document.getElementById("password0").value;
let password1 = document.getElementById("password1").value;
if (password0 != password1) {
$('.passwords-match').popover('show');
$('.popover-body').click(function () {
$('.passwords-match').popover("hide");
});
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
$("#password0").css("outline", "1px solid red");
$("#password1").css("outline", "1px solid red");
return false;
} else {
return password1;
}
}
$(".edit_password").on("click", async function(){
const token = getCookie("_xsrf");
let user_id = $(this).data('id');
bootbox.confirm(`<form class="form" id='infos' action=''>\
<div class="form-group">
<label for="new_password">${$(this).data("translate1")}</label>
<input class="form-control" type='password' id="password0" name='new_password' /></br>\
</div>
<div class="form-group">
<label for="confirm_password">${$(this).data("translate2")}</label>
<input class="form-control" type='password' id="password1" name='confirm_password' />\
</div>
</form>`, async function(result) {
if(result){
password = validateForm();
if (!password){
return;
}
let res = await fetch(`/api/v2/users/${user_id}`, {
method: 'PATCH',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({"password": password}),
});
let responseData = await res.json();
if (responseData.status === "ok") {
console.log(responseData.data)
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
}
});
});
$(document).on("submit", ".bootbox form", function(e) {
e.preventDefault();
$(".bootbox .btn-primary").click();
});
$(".edit_user").on("click", function(){
const token = getCookie("_xsrf");
let username = $(this).data('name');
let user_id = $(this).data('id');
bootbox.confirm(`<form class="form" id='infos' action=''>\
<div class="form-group">
<label for="username">${$(this).data("translate")}</label>
<input class="form-control" type='text' name='username' id="username_field" value=${username} /><br/>\
</div>
</form>`, async function(result) {
if(result){
let new_username = $("#username_field").val();
let res = await fetch(`/api/v2/users/${user_id}`, {
method: 'PATCH',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({"username": new_username}),
});
let responseData = await res.json();
if (responseData.status === "ok") {
$(`#user_${user_id}`).html(` ${new_username}`)
$(`#username_${user_id}`).data('name', new_username);
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
}
});
});
if (webSocket) {
webSocket.on('move_status', function (message) {
if (message === "done") {

View File

@ -71,6 +71,7 @@ data['lang']) }}{% end %}
data['lang']) }}</h4>
</div>
<div class="card-body">
{% if data['new_user'] %}
<div class="form-group">
<label class="form-label" for="username">{{ translate('userConfig', 'userName', data['lang'])
}}<small class="text-muted ml-1"> - {{ translate('userConfig', 'userNameDesc', data['lang'])
@ -98,6 +99,15 @@ data['lang']) }}{% end %}
data-content="{{ translate('panelConfig', 'match', data['lang']) }}" ,
data-placement="right"></span>
</div>
{% else %}
<div class="form-group">
<label class="form-label" for="username">{{ translate('userConfig', 'userName', data['lang'])
}}<small class="text-muted ml-1"> - {{ translate('userConfig', 'userNameDesc', data['lang'])
}}</small> </label>
<input type="text" class="form-control" name="username" id="username" autocomplete="off"
data-lpignore="true" value="{{ data['user']['username'] }}" placeholder="User Name" disabled>
</div>
{% end %}
<div class="form-group">
<label class="form-label" for="email">{{ translate('userConfig', 'gravEmail', data['lang'])
}}<small class="text-muted ml-1"> - {{ translate('userConfig', 'gravDesc', data['lang'])
@ -388,18 +398,43 @@ data['lang']) }}{% end %}
return (isNaN(value) ? value : +value);
}
}
const userId = new URLSearchParams(document.location.search).get('id')
$("#user_form").on("submit", async function (e) {
const userId = new URLSearchParams(document.location.search).get('id');
console.log(userId)
e.preventDefault();
let password = validateForm();
if (!password){
return;
let password = null;
if(!userId){
password = validateForm();
if (!password){
return;
}
}
const token = getCookie("_xsrf")
let userRes = await fetch(`/api/v2/users/@me`, {
method: "GET",
headers: {
'X-XSRFToken': token
},
});
let userData = await userRes.json();
let superuser = null;
if (userData.status === "ok") {
superuser = userData.data["superuser"];
edit_id = userData.data["user_id"];
} else {
bootbox.alert({
title: userData.error,
message: userData.error
});
}
let userForm = document.getElementById("user_form");
let disabled_flag = false;
let roles = $('.role_check').map(function() {
let roles = null;
if (superuser || userId != edit_id){
roles = $('.role_check').map(function() {
if ($(this).attr("disabled")){
disabled_flag = true;
}
@ -407,7 +442,6 @@ data['lang']) }}{% end %}
return $(this).val();
}
}).get();
let avail_permissions = $('.perm-name').map(function() {
return $(this).data("perm");
}).get();
@ -416,20 +450,26 @@ data['lang']) }}{% end %}
for(i=0; i < avail_permissions.length; i++){
permissions.push({"name": avail_permissions[i], "quantity": $(`#quantity_${avail_permissions[i]}`).val(), "enabled": $(`#permission_${avail_permissions[i]}`).is(':checked')})
}
console.log(permissions);
}
let formData = new FormData(userForm);
//Create an object from the form data entries
let formDataObject = Object.fromEntries(formData.entries());
if(userId){
delete formDataObject.username
}
if (superuser || userId != edit_id){
if (!disabled_flag){
formDataObject.roles = roles;
}
if ($("#permissions").length){
formDataObject.permissions = permissions;
}
if(typeof password === "string"){
if(!userId){
if(typeof password === "string"){
formDataObject.password = password;
}
}
}
formDataObject.enabled = $("#enabled").is(":checked");
if ($("#superuser").is(":enabled")){
formDataObject.superuser = $("#superuser").is(":checked");

View File

@ -615,7 +615,7 @@
"pageTitleNew": "Create User",
"password": "New Password",
"permName": "Permission Name",
"repeat": "Repeat Password",
"repeat": "Confirm Password",
"roleName": "Role Name",
"selectManager": "Select Manager for User",
"super": "Super User",

View File

@ -53,6 +53,20 @@
"translationTitle": "Traductions",
"translator": "Traducteurs"
},
"customLogin": {
"apply": "Appliquer",
"backgroundUpload": "Charger l'arrière plan",
"customLoginPage": "Personnaliser la page de Connexion",
"delete": "Supprimer",
"labelLoginImage": "Choisis l'arrière plan de Connexion",
"loginBackground": "Image d'arrière plan de Login",
"loginImage": "Charger une image de fond pour l'écran de Connexion.",
"loginOpacity": "Selectionner l'opacité de la fenêtre de Connexion",
"pageTitle": "Page Personalisée de Connexion",
"preview": "Aperçu",
"select": "Sélectionner",
"selectImage": "Sélectionner une image"
},
"dashboard": {
"actions": "Actions",
"allServers": "Tous les Serverus",
@ -165,20 +179,33 @@
}
},
"error": {
"agree": "Agree",
"bedrockError": "Téléchargement Bedrock non disponible. Merci de vérifier",
"cancel": "Annuler",
"contact": "Contacter le Support de Crafty Control via Discord",
"craftyStatus": "Page de statut de Crafty",
"cronFormat": "Format Cron invalide détecté",
"embarassing": "Oulà, c'est embarrassant.",
"error": "Erreur !",
"eulaAgree": "Êtes-vous d'accord?",
"eulaMsg": "Vous devez accepter le EULA. Une copie du CLUF de Minecraft est liée sous ce message.",
"eulaTitle": "Accepter le EULA",
"fileError": "Le type de fichier doit être une image.",
"fileTooLarge": "Echec du chargement. Le fichier est trop gros. Demande de l'aide à l'administrateur système.",
"hereIsTheError": "Il y a une erreur",
"installerJava": "Echec de l'installation {} : l'installation d'un server Forge nécessite Java. Nous avons détecté que Java n'est pas installé. Merci d'installer Java, puis le serveur.",
"internet": "Nous avons détecté que la machine exécutant Crafty n'a pas de connexion à Internet. Les connexions client au serveur peuvent être limitées.",
"migration": "Le stockage principal de serverurs de Crafty est en migration vers la nouvelle localisation. Tous les serveurs ont été suspendu pour l'opération. Merci de patienter pendant que nous finissons la migration.",
"no-file": "Nous ne parvenons pas à localiser le fichier demandé. Vérifier le chemin du fichier. Les permissions authorisent elles Crafty ?",
"noInternet": "Crafty a des diggiculté à accéder à Internet. La création de serveur a été désactivée. Merci de vérifier ta connexion internet et de rafraichir cette page.",
"noJava": "Le Démarrage du Serveur {} a échoué avec le code d'erreur : Nous avons détecté que Java n'est pas installé. Merci d'installer java avant de démarrer le serveur.",
"not-downloaded": "Nous ne parvenons pas à trouver le fichier exécutable. A-t-il fini de Télécharger ? Les permissions permettent elles l'exécution ?",
"portReminder": "Nous avons détecté que c'est la première fois que {} est exécuté. Assurez-vous de transférer le port {} via votre routeur/pare-feu pour le rendre accessible à distance depuis Internet.",
"privMsg": "et le ",
"serverJars1": "l'API Server JARs est inaccessible. Merci de vérifier",
"serverJars2": "pour les informations les plus à jour.",
"start-error": "Le serveur {} n'a pas pu démarrer avec le code d'erreur : {}",
"superError": "Tu dois être un super utilisateur pour effectuer cette action.",
"terribleFailure": "C'est une Terrible Erreur !"
},
"footer": {
@ -190,7 +217,8 @@
"forgotPassword": "Mot de Passe Oublié",
"login": "Connexion",
"password": "Mot de Passe",
"username": "Nom d'Utilisateur"
"username": "Nom d'Utilisateur",
"viewStatus": "Voir la page de statut publique"
},
"notify": {
"activityLog": "Logs d'Activité",
@ -202,24 +230,38 @@
"preparingLogs": " Merci d'attendre pendant que nous préparons les logs ... Nous enverrons une notification quand ils seront prêts. Cela peut prendre du temps s'il y a beaucoup de serveurs.",
"supportLogs": "Logs de Support"
},
"offline": {
"offline": "Hors ligne",
"pleaseConnect": "Merci de se connecter à Internet pour utiliser Crafty."
},
"panelConfig": {
"adminControls": "Controls de l'Admin",
"allowedServers": "Serveurs Authorisés",
"apply": "Appliquer",
"assignedRoles": "Rôles Assignés",
"cancel": "Annuler",
"clearComms": "Nettoyer les Commandes Non-Exécutées",
"custom": "Personnaliser Crafty",
"delete": "Supprimer",
"edit": "Modifier",
"enableLang": "Activer toutes les langues",
"enabled": "Activé",
"globalExplain": "L'endroit où Crafty stocke tous les fichiers des serveurs. (Nous suffixer le chemin avec /servers/[uuid of server])",
"globalServer": "Dossier Global des Serveurs",
"json": "Config.json",
"match": "Les mots de passe doivent correspondre",
"newRole": "Ajouter un nouveau Rôle",
"newUser": "Ajouter un Nouvel Utilisateur",
"noMounts": "Ne montrer aucun point de montage sur le tableau de bord",
"pageTitle": "Panneau de Configuration",
"role": "Rôle",
"roleUsers": "Rôles Utilisteurs",
"roles": "Roles",
"save": "Enregister",
"select": "Sélectionner",
"superConfirm": "Utiliser seulement si tu veux que cet utilisateur ait accès à ABSOLUMENT TOUT (tous les comptes, tous les serveurs, panneau de configuration, etc). Ils peuvent même supprimer tes droits superuser.",
"superConfirmTitle": "Activer le superuser ? Es-tu sûr ?",
"title": "Configuration de Crafty",
"user": "Utilisateur",
"users": "Utilisateurs"
},
@ -243,14 +285,17 @@
"roleTitle": "Paramètres du Rôle",
"roleUserName": "Nom d'Utilisateur",
"roleUsers": "Les utilisateurs du Rôle: ",
"selectManager": "Sélectionne un gestionnaire pour ce role",
"serverAccess": "Accès ?",
"serverName": "Nom du Serveur",
"serversDesc": "Les serveurs auquels ce rôle a accès"
},
"serverBackups": {
"after": "Exécuter une commande après la sauvegarde",
"backupAtMidnight": "Sauvegarde Automatique à minuit ?",
"backupNow": "Sauvegarder Maintenant !",
"backupTask": "Une sauvegarde vient de démarrer.",
"before": "Exécuter une commande avant la sauvegarde",
"cancel": "Annuler",
"clickExclude": "Cliquer pour sélectionner les Exclusions",
"compress": "Compresser la Sauvegarde",
@ -281,7 +326,7 @@
"bePatientDeleteFiles": "Merci de patienter pendant la suppression du serveur du tableau de bord de Crafty et des fichiers de la machine hôte. Cet écran se fermera dans quelques instants.",
"bePatientUpdate": "Merci de patienter pendant la mise à jour du Serveur. La durée de téléchargement dépend de votre vitesse de connexion internet.<br /> Cet écran se mettra à jour dans quelques instants",
"cancel": "Annuler",
"crashTime": "Crash Timeout",
"crashTime": "Délai de plantage",
"crashTimeDesc": "Combien de temps attendre avant de considérer que le serveur a crash ?",
"deleteFilesQuestion": "Supprimer les fichiers de la machine ?",
"deleteFilesQuestionMessage": "Veux-tu que Crafty supprime tous les fichiers du serveur de la machine hôte? <br><br><strong>Cela inclut les sauvegardes du serveur.</strong>",
@ -290,6 +335,8 @@
"deleteServerQuestionMessage": "Es-tu sur de vouloir supprimer ce Serveur ? Après ça, il n'y aura pas de retour en arrière ...",
"exeUpdateURL": "Lien URL de mise à jour de l'exécutable du Serveur",
"exeUpdateURLDesc": "Lien URL de Téléchargement Direct pour les mises à jour.",
"ignoredExits": "Codes d'Erreurs de Plantage Ignorés",
"ignoredExitsExplain": "Codes d'Erreurs de sortie que Crafty doit considérer comme un arrêt nominal (séparés par une virgule)",
"javaNoChange": "Ne Pas Remplacer",
"javaVersion": "Remplace la version actuelle de Java",
"javaVersionDesc": "Si tu veux remplacer la version de Java, assure toi que le chemin vers la version de Java dans 'execution command' est bien entre guillements (par défaut sans la variable 'java')",
@ -303,7 +350,7 @@
"serverAutoStart": "Autodémarrage du Serveur",
"serverAutostartDelay": "Délai d'Autodémarrage du Serveur",
"serverAutostartDelayDesc": "Délai avant autodémarrage (Si activé ci-dessous)",
"serverCrashDetection": "Détaction de Plantage du Serveur",
"serverCrashDetection": "Détection de Plantage du Serveur",
"serverExecutable": "Exécutable du Serveur",
"serverExecutableDesc": "Le fichier exécutable du serveur",
"serverExecutionCommand": "Commande d'Exécution du Serveur",
@ -320,7 +367,13 @@
"serverPortDesc": "Port auquel Crafty doit se connecter pour les stats",
"serverStopCommand": "Commande d'Arrêt du Serveur",
"serverStopCommandDesc": "Commande à envoyer pour l'arrêter",
"showStatus": "Montrer sur la page de statut publique",
"shutdownTimeout": "Délai d'arret",
"statsHint1": "Le port sur lequel le serveur tourne doit aller ici. Ce n'est que pour que Crafty puisse se connecter pour récupérer les statistiques du serveur.",
"statsHint2": "Cela ne change pas le port de ton serveur. Tu doit quand même changer le port dans le fichier de configuration du serveur.",
"stopBeforeDeleting": "Merci d'arrêter le serveur avant de le supprimer",
"timeoutExplain1": "La durée pendant laquelle Crafty va attendre l'arret du server après exécution de la",
"timeoutExplain2": "commande avant de forcer la fin du processus.",
"update": "Mettre à Jour l'Exécutable",
"yesDelete": "Oui, Supprimer",
"yesDeleteFiles": "Oui, Supprimer les fichier"
@ -346,8 +399,12 @@
"backup": "Sauvegardes",
"config": "Configuration",
"files": "Fichiers",
"filter": "Filtrer les Logs",
"filterList": "Mots Filtrés",
"logs": "Logs",
"metrics": "Statistiques",
"playerControls": "Gestion des Joueurs",
"reset": "Réinitialiser le défilement",
"schedule": "Tâches Planifiées",
"serverDetails": "Détails Serveur",
"terminal": "Terminal"
@ -416,18 +473,36 @@
"parent-explain": "Quelle tâche doit déclencher celle-ci ?",
"reaction": "Réaction",
"restart": "Redémarrer le Serveur",
"select": "Sélection basique de la chaine de réaction / Cron /",
"start": "Démarrer le Serveur",
"stop": "Arrêter le Server",
"time": "Temps",
"time-explain": "A quelle heure exécuter la tâche planifiée ?"
},
"serverSchedules": {
"action": "Action",
"areYouSure": "Supprimer la Tâche Planifiée ?",
"cancel": "Annuler",
"cannotSee": "Tu ne peux pas tou voir ?",
"cannotSee": "Tu ne peux pas tout voir ?",
"cannotSeeOnMobile": "Essaie de clicker sur une tâche planifiée pour voir tous les détails.",
"child": "Enfant de la tâche planifiée avec l'ID ",
"close": "Fermer",
"command": "Commande",
"confirm": "Confirmer",
"confirmDelete": "Es-tu sûr de vouloir supprimer cette tâche planifiée ? Il n'y aura pas de retour en arrière."
"confirmDelete": "Es-tu sûr de vouloir supprimer cette tâche planifiée ? Il n'y aura pas de retour en arrière.",
"create": "Créer une nouvelle tâche planifiée",
"cron": "Crong String",
"delete": "Supprimer",
"details": "Détails de la tâche planifiée",
"edit": "Modifier",
"enabled": "Activer",
"every": "Toutes",
"interval": "Intervalle",
"name": "Nom",
"nextRun": "Exécution suivante",
"no": "Non",
"scheduledTasks": "Tâches Planifiées",
"yes": "Oui"
},
"serverStats": {
"cpuUsage": "Utilisation CPU",
@ -450,6 +525,7 @@
"commandInput": "Entre ta commande",
"delay-explained": "Le service/agent a récemment démarré et retarde le démarrage de l'instance du serveur minecraft",
"downloading": "Téléchargement ...",
"importing": "Importation ...",
"installing": "Installation ...",
"restart": "Redémarrer",
"sendCommand": "Envoiyer commande",
@ -475,6 +551,7 @@
"importServerButton": "Importer Serveur !",
"importZip": "Importer depuis un Fichier Zip",
"importing": "Importation du Serveur ...",
"labelZipFile": "Choisis ton Fichier Zip",
"maxMem": "Mémoire Maximum",
"minMem": "Mémoire Minimum",
"myNewServer": "Mon Nouveau Serveur",
@ -485,6 +562,7 @@
"save": "Sauvegarder",
"selectRole": "Sélectionnez le rôle(s)",
"selectRoot": "Selectionner le Dossier Racine de l'Archive",
"selectServer": "Sélectionner un Serveur",
"selectType": "Selectionner un Type",
"selectVersion": "Selectionner une Version",
"selectZipDir": "Selectionner le dossier de l'archive depuis lequel extraire les fichiers",
@ -492,9 +570,13 @@
"serverName": "Non du Serveur",
"serverPath": "Chemin du Serveur",
"serverPort": "Port du Serveur",
"serverSelect": "Sélectionner un Serveur",
"serverType": "Type du Serveur",
"serverUpload": "Charger le fichier Zippé",
"serverVersion": "Version du Serveur",
"sizeInGB": "Taille en GB",
"uploadButton": "Chargement",
"uploadZip": "Charger le fichier pour l'importation du Serveur",
"zipPath": "Chemin du Serveur"
},
"sidebar": {
@ -502,6 +584,7 @@
"credits": "Crédits",
"dashboard": "Tableau de Bord",
"documentation": "Documentation",
"inApp": "Documentation Interne",
"navigation": "Navigation",
"newServer": "Créer un Nouveau Serveur",
"servers": "Serveurs"
@ -526,6 +609,7 @@
"lastLogin": "Dernière Connexion : ",
"lastUpdate": "Dernière Mise à Jour: ",
"leaveBlank": "Pour modifier l'Utilisateur sans changer le MOt de Passe, laisser vide.",
"manager": "Gestionnaire",
"member": "Membre ?",
"notExist": "Tu ne peux pas supprimer quelquechose qui n'existe pas !",
"pageTitle": "Modifier Utilisateur",
@ -534,6 +618,7 @@
"permName": "Nom de la Permission",
"repeat": "Confirmation du Mot de Passe",
"roleName": "Nom du Rôle",
"selectManager": "Sélectionner un gestionnaire pour l'Utilisateur",
"super": "Super Utilisateur",
"userLang": "Langue de l'Utilisateur",
"userName": "Nom d'Utilisateur",
@ -541,6 +626,30 @@
"userRoles": "Rôles Utilisateur",
"userRolesDesc": "L'utilisateur est membre de ces rôles.",
"userSettings": "Paramètres Utilisateur",
"userTheme": "Theme d'Interface Utilisateur",
"uses": "Nombre d'utilisation Authorisé (-1 == Illimité)"
},
"webhooks": {
"areYouSureDel": "Es-tu sûr de vouloir supprimer ce webhook ?",
"areYouSureRun": "Es-tu sûr de vouloir tester ce webhook ?",
"backup_server": "Sauvegarde du serveur terminée",
"bot_name": "Nom du Bot",
"color": "Sélectionner une couleur d'accentuation",
"crash_detected": "Le serveur a planté",
"edit": "Modifier",
"enabled": "Activer",
"jar_update": "Executable du Serveur mis à jour",
"kill": "Serveur Interrompu",
"name": "Nom",
"new": "Nouveau Webhook",
"run": "Test l'exécution du Webhook",
"send_command": "Commande du Serveur reçue",
"start_server": "Le serveur a démarré",
"stop_server": "Le serveur d'est arrêté",
"trigger": "Déclencheur",
"type": "Type de Webhook",
"url": "Lien URL du Webhook",
"webhook_body": "Corps du Webhook",
"webhooks": "Webhooks"
}
}