Merge branch 'refactor/backups' into refactor/upload-api

This commit is contained in:
amcmanu3 2024-07-08 21:57:59 -04:00
commit d9b9f00e9a
38 changed files with 191 additions and 38 deletions

View File

@ -56,8 +56,8 @@ get_keys "${DIR}/en_EN.json" | sort > "${ref_keys}"
# Iterate over each .json file in the directory # Iterate over each .json file in the directory
for file in "${DIR}"/*.json; do for file in "${DIR}"/*.json; do
# Check if file is a regular file and not en_EN.json, and does not contain "_incomplete" in its name # Check if file is a regular file and not en_EN.json, humanized index and does not contain "_incomplete" in its name
if [[ -f "${file}" && "${file}" != "${DIR}/en_EN.json" && ! "${file}" =~ _incomplete ]]; then if [[ -f "${file}" && "${file}" != "${DIR}/en_EN.json" && "${file}" != "${DIR}/humanized_index.json" && ! "${file}" =~ _incomplete ]]; then
# Get keys and subkeys from the current file # Get keys and subkeys from the current file
current_keys=$(mktemp) current_keys=$(mktemp)

View File

@ -2,17 +2,30 @@
## --- [4.4.1] - 2024/TBD ## --- [4.4.1] - 2024/TBD
### New features ### New features
TBD TBD
### Refactor
- Backups | Allow multiple backup configurations ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/711))
### Bug fixes ### Bug fixes
- Fix zip imports so the root dir selection is functional ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/764)) - Fix zip imports so the root dir selection is functional ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/764))
- Fix bug where full access gives minimal access ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/768)) - Fix bug where full access gives minimal access ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/768))
- Bump tornado & requests for sec advisories ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/774))
- Ensure audit.log exists or create it on Crafty startup ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/771)) - Ensure audit.log exists or create it on Crafty startup ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/771))
- Fix typing issue on ID comparison causing general users to not be able to delete their own API keys ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/775))
- Fix user creation bug where it would fail when a role was selected ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
- Security improvements for general user creations on roles page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
- Security improvements for general user creations on user page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
- Use UTC for tokens_valid_from in user config, to resolve token invalidation on instance TZ change ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/765))
### Tweaks ### Tweaks
- Add info note to default creds file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/760)) - Add info note to default creds file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/760))
- Remove navigation label from sidebar ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/766)) - Remove navigation label from sidebar ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/766))
- Do not allow slashes in server names ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/767))
- Add a thread dump to support logs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/769)) - Add a thread dump to support logs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/769))
- Remove text from status page and use symbols ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/770)) - Remove text from status page and use symbols ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/770))
- Add better feedback on when errors appear on user creation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
- Workaround cpu_freq call catching on obscure cpu architectures ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/776))
### Lang ### Lang
- Show natural language name instead of country code in User Config Lang select list ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/773))
- Add remaining `he_IL`, `th_TH` translations for 4.4.0 Release ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/761)) - Add remaining `he_IL`, `th_TH` translations for 4.4.0 Release ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/761))
- Mark `he_IL` incomplete ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
<br><br> <br><br>
## --- [4.4.0] - 2024/05/11 ## --- [4.4.0] - 2024/05/11

View File

@ -55,6 +55,7 @@ class UsersController:
"minLength": self.helper.minimum_password_length, "minLength": self.helper.minimum_password_length,
"examples": ["crafty"], "examples": ["crafty"],
"title": "Password", "title": "Password",
"error": "passLength",
}, },
"email": { "email": {
"type": "string", "type": "string",

View File

@ -86,7 +86,7 @@ class Stats:
def get_node_stats(self) -> NodeStatsReturnDict: def get_node_stats(self) -> NodeStatsReturnDict:
try: try:
cpu_freq = psutil.cpu_freq() cpu_freq = psutil.cpu_freq()
except (NotImplementedError, FileNotFoundError): except (NotImplementedError, AttributeError, FileNotFoundError):
cpu_freq = None cpu_freq = None
if cpu_freq is None: if cpu_freq is None:
cpu_freq = psutil._common.scpufreq(current=-1, min=-1, max=-1) cpu_freq = psutil._common.scpufreq(current=-1, min=-1, max=-1)

View File

@ -38,7 +38,7 @@ class Users(BaseModel):
superuser = BooleanField(default=False) superuser = BooleanField(default=False)
lang = CharField(default="en_EN") lang = CharField(default="en_EN")
support_logs = CharField(default="") support_logs = CharField(default="")
valid_tokens_from = DateTimeField(default=datetime.datetime.now) valid_tokens_from = DateTimeField(default=Helpers.get_utc_now)
server_order = CharField(default="") server_order = CharField(default="")
preparing = BooleanField(default=False) preparing = BooleanField(default=False)
hints = BooleanField(default=True) hints = BooleanField(default=True)

View File

@ -1,5 +1,6 @@
import logging import logging
import time import time
from datetime import datetime
from typing import Optional, Dict, Any, Tuple from typing import Optional, Dict, Any, Tuple
import jwt import jwt
from jwt import PyJWTError from jwt import PyJWTError
@ -62,7 +63,17 @@ class Authentication:
user = HelperUsers.get_user(user_id) user = HelperUsers.get_user(user_id)
# TODO: Have a cache or something so we don't constantly # TODO: Have a cache or something so we don't constantly
# have to query the database # have to query the database
if int(user.get("valid_tokens_from").timestamp()) < iat: valid_tokens_from_str = user.get("valid_tokens_from")
# It's possible this will be a string or a dt coming from the DB
# We need to account for that
try:
valid_tokens_from_dt = datetime.strptime(
valid_tokens_from_str, "%Y-%m-%d %H:%M:%S.%f%z"
)
except TypeError:
valid_tokens_from_dt = valid_tokens_from_str
# Convert the string to a datetime object
if int(valid_tokens_from_dt.timestamp()) < iat:
# Success! # Success!
return key, data, user return key, data, user
return None return None

View File

@ -19,7 +19,7 @@ import shutil
import shlex import shlex
import subprocess import subprocess
import itertools import itertools
from datetime import datetime from datetime import datetime, timezone
from socket import gethostname from socket import gethostname
from contextlib import redirect_stderr, suppress from contextlib import redirect_stderr, suppress
import libgravatar import libgravatar
@ -639,6 +639,10 @@ class Helpers:
version = f"{major}.{minor}.{sub}" version = f"{major}.{minor}.{sub}"
return str(version) return str(version)
@staticmethod
def get_utc_now() -> datetime:
return datetime.fromtimestamp(time.time(), tz=timezone.utc)
def encode_pass(self, password): def encode_pass(self, password):
return self.passhasher.hash(password) return self.passhasher.hash(password)

View File

@ -20,7 +20,7 @@ class Translation:
def get_language_file(self, language: str): def get_language_file(self, language: str):
return os.path.join(self.translations_path, str(language) + ".json") return os.path.join(self.translations_path, str(language) + ".json")
def translate(self, page, word, language): def translate(self, page, word, language, error=True):
fallback_language = "en_EN" fallback_language = "en_EN"
translated_word = self.translate_inner(page, word, language) translated_word = self.translate_inner(page, word, language)
@ -37,7 +37,9 @@ class Translation:
if hasattr(translated_word, "__iter__"): if hasattr(translated_word, "__iter__"):
# Multiline strings # Multiline strings
return "\n".join(translated_word) return "\n".join(translated_word)
if error:
return "Error while getting translation" return "Error while getting translation"
return word
def translate_inner(self, page, word, language) -> t.Union[t.Any, None]: def translate_inner(self, page, word, language) -> t.Union[t.Any, None]:
language_file = self.get_language_file(language) language_file = self.get_language_file(language)

View File

@ -879,6 +879,8 @@ class PanelHandler(BaseHandler):
os.path.join(self.helper.root_dir, "app", "translations") os.path.join(self.helper.root_dir, "app", "translations")
) )
): ):
if file == "humanized_index.json":
continue
if file.endswith(".json"): if file.endswith(".json"):
if file.split(".")[0] not in self.helper.get_setting( if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files" "disabled_language_files"
@ -1432,6 +1434,8 @@ class PanelHandler(BaseHandler):
for file in sorted( for file in sorted(
os.listdir(os.path.join(self.helper.root_dir, "app", "translations")) os.listdir(os.path.join(self.helper.root_dir, "app", "translations"))
): ):
if file == "humanized_index.json":
continue
if file.endswith(".json"): if file.endswith(".json"):
if file.split(".")[0] not in self.helper.get_setting( if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files" "disabled_language_files"

View File

@ -1,6 +1,6 @@
import datetime
import logging import logging
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
from app.classes.shared.helpers import Helpers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -13,7 +13,7 @@ class ApiAuthInvalidateTokensHandler(BaseApiHandler):
logger.debug(f"Invalidate tokens for user {auth_data[4]['user_id']}") logger.debug(f"Invalidate tokens for user {auth_data[4]['user_id']}")
self.controller.users.raw_update_user( self.controller.users.raw_update_user(
auth_data[4]["user_id"], {"valid_tokens_from": datetime.datetime.now()} auth_data[4]["user_id"], {"valid_tokens_from": Helpers.get_utc_now()}
) )
self.finish_json(200, {"status": "ok"}) self.finish_json(200, {"status": "ok"})

View File

@ -2,6 +2,7 @@ import typing as t
from jsonschema import ValidationError, validate from jsonschema import ValidationError, validate
import orjson import orjson
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
create_role_schema = { create_role_schema = {
@ -71,7 +72,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
return return
( (
_, _,
_, exec_user_permissions_crafty,
_, _,
superuser, superuser,
_, _,
@ -81,7 +82,10 @@ class ApiRolesIndexHandler(BaseApiHandler):
# GET /api/v2/roles?ids=true # GET /api/v2/roles?ids=true
get_only_ids = self.get_query_argument("ids", None) == "true" get_only_ids = self.get_query_argument("ids", None) == "true"
if not superuser: if (
not superuser
and EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json( self.finish_json(
@ -104,14 +108,17 @@ class ApiRolesIndexHandler(BaseApiHandler):
return return
( (
_, _,
_, exec_user_permissions_crafty,
_, _,
superuser, superuser,
user, user,
_, _,
) = auth_data ) = auth_data
if not superuser: if (
not superuser
and EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try: try:
@ -138,6 +145,8 @@ class ApiRolesIndexHandler(BaseApiHandler):
role_name = data["name"] role_name = data["name"]
manager = data.get("manager", None) manager = data.get("manager", None)
if not superuser and not manager:
manager = auth_data[4]["user_id"]
if manager == self.controller.users.get_id_by_name("SYSTEM") or manager == 0: if manager == self.controller.users.get_id_by_name("SYSTEM") or manager == 0:
manager = None manager = None

View File

@ -1,6 +1,7 @@
from jsonschema import ValidationError, validate from jsonschema import ValidationError, validate
import orjson import orjson
from peewee import DoesNotExist from peewee import DoesNotExist, IntegrityError
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
modify_role_schema = { modify_role_schema = {
@ -70,14 +71,17 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
return return
( (
_, _,
_, exec_user_permissions_crafty,
_, _,
superuser, superuser,
_, _,
_, _,
) = auth_data ) = auth_data
if not superuser: if (
not superuser
and EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try: try:
@ -100,8 +104,11 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
user, user,
_, _,
) = auth_data ) = auth_data
role = self.controller.roles.get_role(role_id)
if not superuser: if (
str(role.get("manager", "no manager found")) != str(auth_data[4]["user_id"])
and not superuser
):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"}) return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.controller.roles.remove_role(role_id) self.controller.roles.remove_role(role_id)
@ -124,7 +131,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
return return
( (
_, _,
_, exec_user_permissions_crafty,
_, _,
superuser, superuser,
user, user,
@ -132,7 +139,10 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
) = auth_data ) = auth_data
role = self.controller.roles.get_role(role_id) role = self.controller.roles.get_role(role_id)
if not superuser and user["user_id"] != role["manager"]: if not superuser and (
user["user_id"] != role["manager"]
or EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
):
return self.finish_json( return self.finish_json(
400, 400,
{ {
@ -179,7 +189,10 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
) )
except DoesNotExist: except DoesNotExist:
return self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"}) return self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"})
except IntegrityError:
return self.finish_json(
404, {"status": "error", "error": "ROLE_NAME_EXISTS"}
)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
user["user_id"], user["user_id"],
f"modified role with ID {role_id}", f"modified role with ID {role_id}",

View File

@ -23,6 +23,7 @@ new_server_schema = {
"type": "string", "type": "string",
"examples": ["My Server"], "examples": ["My Server"],
"minLength": 2, "minLength": 2,
"pattern": "^[^/\\\\]*$",
}, },
"roles": {"title": "Roles to add", "type": "array", "examples": [1, 2, 3]}, "roles": {"title": "Roles to add", "type": "array", "examples": [1, 2, 3]},
"stop_command": { "stop_command": {

View File

@ -414,10 +414,13 @@ class ApiServersServerBackupsBackupFilesIndexHandler(BaseApiHandler):
"error_data": str(e), "error_data": str(e),
}, },
) )
try: try:
FileHelpers.del_file( FileHelpers.del_file(
os.path.join(backup_conf["backup_location"], data["filename"]) os.path.join(
backup_conf["backup_location"],
backup_conf["backup_id"],
data["filename"],
)
) )
except Exception as e: except Exception as e:
return self.finish_json( return self.finish_json(

View File

@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
server_patch_schema = { server_patch_schema = {
"type": "object", "type": "object",
"properties": { "properties": {
"server_name": {"type": "string", "minLength": 1}, "server_name": {"type": "string", "minLength": 2, "pattern": "^[^/\\\\]*$"},
"backup_path": {"type": "string"}, "backup_path": {"type": "string"},
"executable": {"type": "string"}, "executable": {"type": "string"},
"log_path": {"type": "string", "minLength": 1}, "log_path": {"type": "string", "minLength": 1},

View File

@ -2,6 +2,7 @@ import logging
import json import json
from jsonschema import validate from jsonschema import validate
from jsonschema.exceptions import ValidationError from jsonschema.exceptions import ValidationError
from app.classes.shared.translation import Translation
from app.classes.models.crafty_permissions import EnumPermissionsCrafty from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.models.roles import Roles, HelperRoles from app.classes.models.roles import Roles, HelperRoles
from app.classes.models.users import PUBLIC_USER_ATTRS from app.classes.models.users import PUBLIC_USER_ATTRS
@ -54,6 +55,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
) )
def post(self): def post(self):
self.translator = Translation(self.helper)
new_user_schema = { new_user_schema = {
"type": "object", "type": "object",
"properties": { "properties": {
@ -87,12 +89,17 @@ class ApiUsersIndexHandler(BaseApiHandler):
try: try:
validate(data, new_user_schema) validate(data, new_user_schema)
except ValidationError as e: except ValidationError as e:
err = self.translator.translate(
"validators",
e.schema["error"],
self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
)
return self.finish_json( return self.finish_json(
400, 400,
{ {
"status": "error", "status": "error",
"error": "INVALID_JSON_SCHEMA", "error": "INVALID_JSON_SCHEMA",
"error_data": str(e), "error_data": f"{str(err)}",
}, },
) )
username = data["username"] username = data["username"]
@ -153,7 +160,11 @@ class ApiUsersIndexHandler(BaseApiHandler):
for role in roles: for role in roles:
role = self.controller.roles.get_role(role) role = self.controller.roles.get_role(role)
if int(role["manager"]) != int(auth_data[4]["user_id"]) and not superuser: if (
str(role.get("manager", "no manager found"))
!= str(auth_data[4]["user_id"])
and not superuser
):
return self.finish_json( return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_CREATE"} 400, {"status": "error", "error": "INVALID_ROLES_CREATE"}
) )

View File

@ -217,7 +217,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
) )
if ( if (
target_key.user_id != auth_data[4]["user_id"] str(target_key.user_id) != str(auth_data[4]["user_id"])
and not auth_data[4]["superuser"] and not auth_data[4]["superuser"]
): ):
return self.finish_json( return self.finish_json(

View File

@ -132,7 +132,6 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
return self.finish_json( return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
) )
try: try:
validate(data, user_patch_schema) validate(data, user_patch_schema)
except ValidationError as e: except ValidationError as e:
@ -144,10 +143,8 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
"error_data": str(e), "error_data": str(e),
}, },
) )
if user_id == "@me": if user_id == "@me":
user_id = user["user_id"] user_id = user["user_id"]
if ( if (
EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions
and str(user["user_id"]) != str(user_id) and str(user["user_id"]) != str(user_id)
@ -215,6 +212,25 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
return self.finish_json( return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"} 400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
) )
user_modify = self.controller.users.get_user_roles_id(user_id)
for role in data["roles"]:
# Check if user is not a super user and that the exec user is the role
# manager or that the role already exists in the user's list
if not superuser and (
str(
self.controller.roles.get_role(role).get(
"manager", "no manager found"
)
)
!= str(auth_data[4]["user_id"])
and role not in user_modify
):
for item in user_modify:
print(type(role), type(item))
return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
)
user_obj = HelperUsers.get_user_model(user_id) user_obj = HelperUsers.get_user_model(user_id)
if "password" in data and str(user["user_id"]) != str(user_id): if "password" in data and str(user["user_id"]) != str(user_id):

View File

@ -428,10 +428,13 @@
if (responseData.status === "ok") { if (responseData.status === "ok") {
window.location.href = "/panel/panel_config"; window.location.href = "/panel/panel_config";
} else { } else {
let errordata = responseData.error;
if (responseData.error_data){
errordata = responseData.error
}
bootbox.alert({ bootbox.alert({
title: responseData.error, title: responseData.error,
message: responseData.error_data message: errordata
}); });
} }
}); });

View File

@ -122,7 +122,7 @@ data['lang']) }}{% end %}
name="lang" form="user_form"> name="lang" form="user_form">
{% for lang in data['languages'] %} {% for lang in data['languages'] %}
{% if not 'incomplete' in lang %} {% if not 'incomplete' in lang %}
<option value="{{lang}}">{{lang}}</option> <option value="{{lang}}" >{{translate('language', lang, 'humanized_index')}}</option>
{% else %} {% else %}
<option value="{{lang}}" disabled>{{lang}}</option> <option value="{{lang}}" disabled>{{lang}}</option>
{% end %} {% end %}
@ -393,6 +393,7 @@ data['lang']) }}{% end %}
} }
function replacer(key, value) { function replacer(key, value) {
if (typeof value == "boolean" || key === "email" || key === "permissions" || key === "roles") { if (typeof value == "boolean" || key === "email" || key === "permissions" || key === "roles") {
console.log(key)
return value return value
} else { } else {
console.log(key, value) console.log(key, value)
@ -433,6 +434,7 @@ data['lang']) }}{% end %}
let disabled_flag = false; let disabled_flag = false;
let roles = null; let roles = null;
if (superuser || userId != edit_id){ if (superuser || userId != edit_id){
console.log("ROLES")
roles = $('.role_check').map(function() { roles = $('.role_check').map(function() {
if ($(this).attr("disabled")){ if ($(this).attr("disabled")){
disabled_flag = true; disabled_flag = true;
@ -457,9 +459,7 @@ data['lang']) }}{% end %}
delete formDataObject.username delete formDataObject.username
} }
if (superuser || userId != edit_id){ if (superuser || userId != edit_id){
if (!disabled_flag){
formDataObject.roles = roles; formDataObject.roles = roles;
}
if ($("#permissions").length){ if ($("#permissions").length){
formDataObject.permissions = permissions; formDataObject.permissions = permissions;
} }

View File

@ -1,10 +1,11 @@
import peewee import peewee
import datetime import datetime
from app.classes.shared.helpers import Helpers
def migrate(migrator, database, **kwargs): def migrate(migrator, database, **kwargs):
migrator.add_columns( migrator.add_columns(
"users", valid_tokens_from=peewee.DateTimeField(default=datetime.datetime.now) "users", valid_tokens_from=peewee.DateTimeField(default=Helpers.get_utc_now)
) )
migrator.drop_columns("users", ["api_token"]) migrator.drop_columns("users", ["api_token"])

View File

@ -688,6 +688,9 @@
"userTheme": "Motiv UI", "userTheme": "Motiv UI",
"uses": "Počet povolených použití (-1==bez omezení)" "uses": "Počet povolených použití (-1==bez omezení)"
}, },
"validators": {
"passLength": "Heslo je příliš krátké. Minimální délka je 8 znaků"
},
"webhooks": { "webhooks": {
"areYouSureDel": "Seš si jistý že chceš smazat tento webhook?", "areYouSureDel": "Seš si jistý že chceš smazat tento webhook?",
"areYouSureRun": "Seš si jistý že chceš otestovat tento webhook?", "areYouSureRun": "Seš si jistý že chceš otestovat tento webhook?",

View File

@ -669,6 +669,9 @@
"userTheme": "Design für die Benutzeroberfläche", "userTheme": "Design für die Benutzeroberfläche",
"uses": "Anzahl der erlaubten Verwendungen (-1==Keine Begrenzung)" "uses": "Anzahl der erlaubten Verwendungen (-1==Keine Begrenzung)"
}, },
"validators": {
"passLength": "Passwort zu kurz. Mindestlänge: 8"
},
"webhooks": { "webhooks": {
"areYouSureDel": "Sind Sie sicher, dass Sie diesen Webhook löschen möchten?", "areYouSureDel": "Sind Sie sicher, dass Sie diesen Webhook löschen möchten?",
"areYouSureRun": "Sind Sie sicher, dass Sie diesen Webhook testen möchten?", "areYouSureRun": "Sind Sie sicher, dass Sie diesen Webhook testen möchten?",

View File

@ -665,6 +665,9 @@
"userTheme": "UI Theme", "userTheme": "UI Theme",
"uses": "Number of uses allowed (-1==No Limit)" "uses": "Number of uses allowed (-1==No Limit)"
}, },
"validators": {
"passLength": "Password Too Short. Minimum Length: 8"
},
"webhooks": { "webhooks": {
"areYouSureDel": "Are you sure you want to delete this webhook?", "areYouSureDel": "Are you sure you want to delete this webhook?",
"areYouSureRun": "Are you sure you want to test this webhook?", "areYouSureRun": "Are you sure you want to test this webhook?",

View File

@ -669,6 +669,9 @@
"userTheme": "Tema de Interfaz", "userTheme": "Tema de Interfaz",
"uses": "Número de usos permitidos. (Sin límite: -1)" "uses": "Número de usos permitidos. (Sin límite: -1)"
}, },
"validators": {
"passLength": "Contraseña demasiado corta. Longitud mínima: 8"
},
"webhooks": { "webhooks": {
"areYouSureDel": "¿Estás seguro de que quieres eliminar este webhook?", "areYouSureDel": "¿Estás seguro de que quieres eliminar este webhook?",
"areYouSureRun": "¿Estás seguro de que quieres probar este webhook?", "areYouSureRun": "¿Estás seguro de que quieres probar este webhook?",

View File

@ -669,6 +669,9 @@
"userTheme": "Theme d'Interface Utilisateur", "userTheme": "Theme d'Interface Utilisateur",
"uses": "Nombre d'utilisation Authorisé (-1 == Illimité)" "uses": "Nombre d'utilisation Authorisé (-1 == Illimité)"
}, },
"validators": {
"passLength": "Mot de passe trop court. Longueur minimum : 8"
},
"webhooks": { "webhooks": {
"areYouSureDel": "Es-tu sûr de vouloir supprimer ce webhook ?", "areYouSureDel": "Es-tu sûr de vouloir supprimer ce webhook ?",
"areYouSureRun": "Es-tu sûr de vouloir tester ce webhook ?", "areYouSureRun": "Es-tu sûr de vouloir tester ce webhook ?",

View File

@ -0,0 +1,19 @@
{
"language": {
"cs_CS": "Čeština",
"de_DE": "Deutsch",
"en_EN": "English (US)",
"es_ES": "Español",
"fr_FR": "Français (France)",
"he_IL": "he_IL",
"it_IT": "Italiano",
"lol_EN": "Lolcatz",
"lv_LV": "Latviešu",
"nl_BE": "nl_BE",
"pl_PL": "Polski",
"th_TH": "ไทย",
"tr_TR": "Türkçe",
"uk_UA": "Українська",
"zh_CN": "中文(中国)"
}
}

View File

@ -669,6 +669,9 @@
"userTheme": "Tema IU", "userTheme": "Tema IU",
"uses": "Numero di usi permessi (-1==Nessun limite)" "uses": "Numero di usi permessi (-1==Nessun limite)"
}, },
"validators": {
"passLength": "La password è troppo corta. Lunghezza minima: 8"
},
"webhooks": { "webhooks": {
"areYouSureDel": "Sei sicuro di voler eliminare questo webhook?", "areYouSureDel": "Sei sicuro di voler eliminare questo webhook?",
"areYouSureRun": "Sei sicuro di voler testare questo webhook?", "areYouSureRun": "Sei sicuro di voler testare questo webhook?",

View File

@ -669,6 +669,9 @@
"userTheme": "THEMEZ", "userTheme": "THEMEZ",
"uses": "NUMBER OV USES ALLOWED (-1==NO LIMIT)" "uses": "NUMBER OV USES ALLOWED (-1==NO LIMIT)"
}, },
"validators": {
"passLength": "PASSWRD TOO SMOL. NEEDZ 8 CATZ PLZ"
},
"webhooks": { "webhooks": {
"areYouSureDel": "U SURE U WANTZ TO EATZ DIS WEBHOOK?", "areYouSureDel": "U SURE U WANTZ TO EATZ DIS WEBHOOK?",
"areYouSureRun": "U SURE U WANTZ TO TESTZ DIS WEBHOOK?", "areYouSureRun": "U SURE U WANTZ TO TESTZ DIS WEBHOOK?",

View File

@ -670,6 +670,9 @@
"userTheme": "UI Tēma", "userTheme": "UI Tēma",
"uses": "Dauzums, cik reizes lietot (-1==Bez Limita)" "uses": "Dauzums, cik reizes lietot (-1==Bez Limita)"
}, },
"validators": {
"passLength": "Parole pārāk īsa. Minimālais Garums: 8"
},
"webhooks": { "webhooks": {
"areYouSureDel": "Vai tiešām vēlies noņemt šo webhook?", "areYouSureDel": "Vai tiešām vēlies noņemt šo webhook?",
"areYouSureRun": "Vai tiešām vēlies testēt šo webhook?", "areYouSureRun": "Vai tiešām vēlies testēt šo webhook?",

View File

@ -669,6 +669,9 @@
"userTheme": "UI-thema", "userTheme": "UI-thema",
"uses": "Aantal toegestane gebruiken (-1==Geen Limiet)" "uses": "Aantal toegestane gebruiken (-1==Geen Limiet)"
}, },
"validators": {
"passLength": "Wachtwoord te kort. Minimumlengte: 8 tekens"
},
"webhooks": { "webhooks": {
"areYouSureDel": "Weet u zeker dat u deze webhook wilt verwijderen?", "areYouSureDel": "Weet u zeker dat u deze webhook wilt verwijderen?",
"areYouSureRun": "Weet u zeker dat u deze webhook wilt testen?", "areYouSureRun": "Weet u zeker dat u deze webhook wilt testen?",

View File

@ -668,6 +668,9 @@
"userTheme": "Wygląd interfejsu", "userTheme": "Wygląd interfejsu",
"uses": "Ilość użyć (-1==Bez limitu)" "uses": "Ilość użyć (-1==Bez limitu)"
}, },
"validators": {
"passLength": "Hasło jest zbyt krótkie. Hasło musi posiadać minimum 8 znaków."
},
"webhooks": { "webhooks": {
"areYouSureDel": "Usunąć ten webhook?", "areYouSureDel": "Usunąć ten webhook?",
"areYouSureRun": "Przetestować ten webhook?", "areYouSureRun": "Przetestować ten webhook?",

View File

@ -668,6 +668,9 @@
"userTheme": "ธีม UI", "userTheme": "ธีม UI",
"uses": "จำนวนการใช้งานที่อนุญาต (-1==ไม่มีขีดจำกัด)" "uses": "จำนวนการใช้งานที่อนุญาต (-1==ไม่มีขีดจำกัด)"
}, },
"validators": {
"passLength": "รหัสผ่านสั้นเกินไป จำนวนตัวอักขระขั้นต่ำ: 8"
},
"webhooks": { "webhooks": {
"areYouSureDel": "คุณแน่ใจหรือไม่ว่าต้องการลบ Webhook นี้?", "areYouSureDel": "คุณแน่ใจหรือไม่ว่าต้องการลบ Webhook นี้?",
"areYouSureRun": "คุณแน่ใจหรือไม่ว่าต้องการทดสอบ Webhook นี้?", "areYouSureRun": "คุณแน่ใจหรือไม่ว่าต้องการทดสอบ Webhook นี้?",

View File

@ -668,6 +668,9 @@
"userTheme": "UI Teması", "userTheme": "UI Teması",
"uses": "İzin verilen kullanım sayısı (-1==Sınır Yok)" "uses": "İzin verilen kullanım sayısı (-1==Sınır Yok)"
}, },
"validators": {
"passLength": "Şifre çok kısa. Şifre en az 8 karakter olmalı."
},
"webhooks": { "webhooks": {
"areYouSureDel": "Bu webhooku silmek istediğinizden emin misiniz?", "areYouSureDel": "Bu webhooku silmek istediğinizden emin misiniz?",
"areYouSureRun": "Bu webhooku test etmek istediğinizden emin misiniz?", "areYouSureRun": "Bu webhooku test etmek istediğinizden emin misiniz?",

View File

@ -668,6 +668,9 @@
"userTheme": "Тема інтерфейсу", "userTheme": "Тема інтерфейсу",
"uses": "Дозволена кількість використань(-1==Без ліміту)" "uses": "Дозволена кількість використань(-1==Без ліміту)"
}, },
"validators": {
"passLength": "Пароль, надто короткий. Мінімальна довжина: 8 символів"
},
"webhooks": { "webhooks": {
"areYouSureDel": "Ви впевнені, що хочете видалити цей Вебхук?", "areYouSureDel": "Ви впевнені, що хочете видалити цей Вебхук?",
"areYouSureRun": "Ви впевнені, що хочете перевірити цей Вебхук?", "areYouSureRun": "Ви впевнені, що хочете перевірити цей Вебхук?",

View File

@ -669,6 +669,9 @@
"userTheme": "UI 主题", "userTheme": "UI 主题",
"uses": "使用次数限制(-1==无限制)" "uses": "使用次数限制(-1==无限制)"
}, },
"validators": {
"passLength": "密码过短。最短长度8"
},
"webhooks": { "webhooks": {
"areYouSureDel": "您确定要删除此 webhook 吗?", "areYouSureDel": "您确定要删除此 webhook 吗?",
"areYouSureRun": "您确定要测试此 webhook 吗?", "areYouSureRun": "您确定要测试此 webhook 吗?",

View File

@ -13,7 +13,7 @@ psutil==5.9.5
pyOpenSSL==24.0.0 pyOpenSSL==24.0.0
pyjwt==2.8.0 pyjwt==2.8.0
PyYAML==6.0.1 PyYAML==6.0.1
requests==2.31.0 requests==2.32.0
termcolor==1.1 termcolor==1.1
tornado==6.4.1 tornado==6.4.1
tzlocal==5.1 tzlocal==5.1