Merge pull request #68 from netinvent/rc1-bughunt

RC1 bughunt
This commit is contained in:
Orsiris de Jong 2024-06-17 16:37:43 +02:00 committed by GitHub
commit 161756a86d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 188 additions and 126 deletions

View file

@ -7,7 +7,7 @@ __intname__ = "npbackup.configuration"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2022-2024 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "2024061601"
__build__ = "2024061701"
__version__ = "npbackup 3.0.0+"
MIN_CONF_VERSION = 3.0
@ -472,23 +472,24 @@ def extract_permissions_from_full_config(full_config: dict) -> dict:
repo_config objects in memory are always "expanded"
This function is in order to expand when loading config
"""
for repo in full_config.g("repos").keys():
repo_uri = full_config.g(f"repos.{repo}.repo_uri")
if repo_uri:
# Extract permissions and manager password from repo_uri if set as string
if "," in repo_uri:
repo_uri = [item.strip() for item in repo_uri.split(",")]
if isinstance(repo_uri, tuple) or isinstance(repo_uri, list):
repo_uri, permissions, manager_password = repo_uri
# Overwrite existing permissions / password if it was set in repo_uri
full_config.s(f"repos.{repo}.repo_uri", repo_uri)
full_config.s(f"repos.{repo}.permissions", permissions)
full_config.s(f"repos.{repo}.manager_password", manager_password)
else:
logger.info(f"No extra information for repo {repo} found")
# If no permissions are set, we get to use default permissions
full_config.s(f"repos.{repo}.permissions", empty_config_dict["repos"]["default"]["permissions"])
full_config.s(f"repos.{repo}.manager_password", None)
for object_type in ("repos", "groups"):
for object_name in full_config.g(object_type).keys():
repo_uri = full_config.g(f"{object_type}.{object_name}.repo_uri")
if repo_uri:
# Extract permissions and manager password from repo_uri if set as string
if "," in repo_uri:
repo_uri = [item.strip() for item in repo_uri.split(",")]
if isinstance(repo_uri, tuple) or isinstance(repo_uri, list):
repo_uri, permissions, manager_password = repo_uri
# Overwrite existing permissions / password if it was set in repo_uri
full_config.s(f"{object_type}.{object_name}.repo_uri", repo_uri)
full_config.s(f"{object_type}.{object_name}.permissions", permissions)
full_config.s(f"{object_type}.{object_name}.manager_password", manager_password)
else:
logger.info(f"No extra information for {object_type} {object_name} found")
# If no permissions are set, we get to use default permissions
full_config.s(f"repos.{repo}.permissions", empty_config_dict["repos"]["default"]["permissions"])
full_config.s(f"repos.{repo}.manager_password", None)
return full_config
@ -499,24 +500,28 @@ def inject_permissions_into_full_config(full_config: dict) -> Tuple[bool, dict]:
NPF-SEC-00006: Never inject permissions if some are already present unless current manager password equals initial one
"""
for repo in full_config.g("repos").keys():
repo_uri = full_config.g(f"repos.{repo}.repo_uri")
manager_password = full_config.g(f"repos.{repo}.manager_password")
permissions = full_config.g(f"repos.{repo}.permissions")
update_manager_password = full_config.g(f"repos.{repo}.update_manager_password")
if update_manager_password and manager_password:
full_config.s(
f"repos.{repo}.repo_uri", (repo_uri, permissions, manager_password)
)
full_config.s(f"repos.{repo}.is_protected", True)
else:
logger.debug(f"Permissions exist for repo {repo}")
for object_type in ("repos", "groups"):
for object_name in full_config.g(object_type).keys():
repo_uri = full_config.g(f"{object_type}.{object_name}.repo_uri")
manager_password = full_config.g(f"{object_type}.{object_name}.manager_password")
permissions = full_config.g(f"{object_type}.{object_name}.permissions")
update_manager_password = full_config.g(f"{object_type}.{object_name}.update_manager_password")
if update_manager_password and manager_password:
full_config.s(
f"{object_type}.{object_name}.repo_uri", (repo_uri, permissions, manager_password)
)
full_config.s(f"{object_type}.{object_name}.is_protected", True)
elif manager_password:
full_config.s(f"{object_type}.{object_name}.is_protected", True)
logger.debug(f"Permissions exist for {object_type} {object_name}")
else:
full_config.s(f"{object_type}.{object_name}.is_protected", False)
full_config.d(
f"repos.{repo}.update_manager_password"
) # Don't keep decrypted manager password
full_config.d(f"repos.{repo}.permissions")
full_config.d(f"repos.{repo}.manager_password")
full_config.d(
f"{object_type}.{object_name}.update_manager_password"
) # Don't keep decrypted manager password
full_config.d(f"{object_type}.{object_name}.permissions")
full_config.d(f"{object_type}.{object_name}.manager_password")
return full_config

View file

@ -323,7 +323,7 @@ def ls_window(repo_config: dict, snapshot_id: str) -> bool:
break
if event == "restore_to":
if not values["-TREE-"]:
sg.PopupError(_t("main_gui.select_folder"))
sg.PopupError(_t("main_gui.select_folder"), keep_on_top=True)
continue
restore_window(repo_config, snapshot_id, values["-TREE-"])
@ -451,7 +451,7 @@ def _main_gui(viewer_mode: bool):
sg.Button(_t("generic.accept"), key="--ACCEPT--"),
],
]
window = sg.Window("Configuration File", layout=layout)
window = sg.Window("Configuration File", layout=layout, keep_on_top=True)
while True:
action = None
event, values = window.read()
@ -465,11 +465,11 @@ def _main_gui(viewer_mode: bool):
if event == "--ACCEPT--":
config_file = Path(values["-config_file-"])
if not values["-config_file-"] or not config_file.exists():
sg.PopupError(_t("generic.file_does_not_exist"))
sg.PopupError(_t("generic.file_does_not_exist"), keep_on_top=True)
continue
full_config = npbackup.configuration.load_config(config_file)
if not full_config:
sg.PopupError(_t("generic.bad_file"))
sg.PopupError(_t("generic.bad_file"), keep_on_top=True)
continue
break
window.close()
@ -599,7 +599,7 @@ def _main_gui(viewer_mode: bool):
logger.info(f"Using configuration file {config_file}")
full_config = npbackup.configuration.load_config(config_file)
if not full_config:
sg.PopupError(f"{_t('main_gui.config_error')} {config_file}")
sg.PopupError(f"{_t('main_gui.config_error')} {config_file}", keep_on_top=True)
config_exists = False
else:
config_exists = True
@ -712,7 +712,7 @@ def _main_gui(viewer_mode: bool):
if not os.path.isfile(binary):
msg = f"External backend binary {binary} cannot be found."
logger.critical(msg)
sg.PopupError(msg)
sg.PopupError(msg, keep_on_top=True)
sys.exit(73)
# Let's try to read standard restic repository env variables
@ -916,17 +916,17 @@ def _main_gui(viewer_mode: bool):
current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
gui_update_state()
else:
sg.PopupError("Repo not existent in config")
sg.PopupError("Repo not existent in config", keep_on_top=True)
continue
if event == "--LAUNCH-BACKUP--":
if not full_config:
sg.PopupError(_t("main_gui.no_config"))
sg.PopupError(_t("main_gui.no_config"), keep_on_top=True)
continue
backup(repo_config)
event = "--STATE-BUTTON--"
if event == "--SEE-CONTENT--":
if not repo_config:
sg.PopupError(_t("main_gui.no_config"))
sg.PopupError(_t("main_gui.no_config"), keep_on_top=True)
continue
if not values["snapshot-list"]:
sg.Popup(_t("main_gui.select_backup"), keep_on_top=True)
@ -938,7 +938,7 @@ def _main_gui(viewer_mode: bool):
ls_window(repo_config, snapshot_to_see)
if event == "--FORGET--":
if not full_config:
sg.PopupError(_t("main_gui.no_config"))
sg.PopupError(_t("main_gui.no_config"), keep_on_top=True)
continue
if not values["snapshot-list"]:
sg.Popup(_t("main_gui.select_backup"), keep_on_top=True)
@ -951,13 +951,13 @@ def _main_gui(viewer_mode: bool):
event = "--STATE-BUTTON--"
if event == "--OPERATIONS--":
if not full_config:
sg.PopupError(_t("main_gui.no_config"))
sg.PopupError(_t("main_gui.no_config"), keep_on_top=True)
continue
full_config = operations_gui(full_config)
event = "--STATE-BUTTON--"
if event == "--CONFIGURE--":
if not full_config:
sg.PopupError(_t("main_gui.no_config"))
sg.PopupError(_t("main_gui.no_config"), keep_on_top=True)
continue
full_config = config_gui(full_config, config_file)
# Make sure we trigger a GUI refresh when configuration is changed
@ -993,7 +993,7 @@ def _main_gui(viewer_mode: bool):
repo_uri = _repo_uri
repo_list = _repo_list
else:
sg.PopupError(_t("main_gui.cannot_load_config_keep_current"))
sg.PopupError(_t("main_gui.cannot_load_config_keep_current"), keep_on_top=True)
if not viewer_mode and not config_file and not full_config:
window["-NO-CONFIG-"].Update(visible=True)
elif not viewer_mode:

View file

@ -64,13 +64,16 @@ def delete(self, key):
sg.TreeData.delete = delete
ENCRYPTED_DATA_PLACEHOLDER = "<{}>".format(_t("config_gui.encrypted_data"))
def ask_manager_password(manager_password: str) -> bool:
if manager_password:
if sg.PopupGetText(
_t("config_gui.set_manager_password"), password_char="*"
) == str(manager_password):
return True
sg.PopupError(_t("config_gui.wrong_password"))
sg.PopupError(_t("config_gui.wrong_password"), keep_on_top=True)
return False
return True
@ -111,8 +114,6 @@ def config_gui(full_config: dict, config_file: str):
byte_units = ["B", "KB", "KiB", "MB", "MiB", "GB", "GiB", "TB", "TiB", "PB", "PiB"]
ENCRYPTED_DATA_PLACEHOLDER = "<{}>".format(_t("config_gui.encrypted_data"))
def get_objects() -> List[str]:
"""
Adds repos and groups in a list for combobox
@ -149,15 +150,18 @@ def config_gui(full_config: dict, config_file: str):
if event == "--ACCEPT--":
object_type = values["-OBJECT-TYPE-"]
object_name = values["-OBJECT-NAME-"]
if object_type == "repo":
if full_config.g(f"repos.{object_name}"):
if object_type == "repos":
if full_config.g(f"{object_type}.{object_name}"):
sg.PopupError(
_t("config_gui.repo_already_exists"), keep_on_top=True
)
continue
full_config.s(f"repos.{object_name}", configuration.get_default_repo_config())
elif object_type == "group":
if full_config.g(f"groups.{object_name}"):
full_config.s(f"{object_type}.{object_name}", CommentedMap())
elif object_type == "groups":
if full_config.g(f"{object_type}.{object_name}"):
full_config.s(f"{object_type}.{object_name}", configuration.get_default_repo_config())
elif object_type == "groups":
if full_config.g(f"{object_type}.{object_name}"):
sg.PopupError(
_t("config_gui.group_already_exists"), keep_on_top=True
)
@ -167,7 +171,7 @@ def config_gui(full_config: dict, config_file: str):
raise ValueError("Bogus object type given")
break
window.close()
update_object_gui(None, unencrypted=False)
update_object_gui(full_config, None, unencrypted=False)
return full_config, object_name, object_type
def delete_object(full_config: dict, object_name: str) -> dict:
@ -176,8 +180,8 @@ def config_gui(full_config: dict, config_file: str):
_t("config_gui.are_you_sure_to_delete") + f" {object_type} {object_name} ?"
)
if result:
full_config.d(f"{object_type}s.{object_name}")
update_object_gui(None, unencrypted=False)
full_config.d(f"{object_type}.{object_name}")
update_object_gui(full_config, None, unencrypted=False)
return full_config
def update_object_selector(object_name: str = None, object_type: str = None) -> None:
@ -199,10 +203,10 @@ def config_gui(full_config: dict, config_file: str):
"""
if combo_value.startswith("Repo: "):
object_type = "repo"
object_type = "repos"
object_name = combo_value[len("Repo: ") :]
elif combo_value.startswith("Group: "):
object_type = "group"
object_type = "groups"
object_name = combo_value[len("Group: ") :]
else:
object_type = None
@ -229,16 +233,24 @@ def config_gui(full_config: dict, config_file: str):
# Also, don't update global prometheus options here but in global options
if key in (
"name",
"permissions",
"manager_password",
"is_protected",
"prometheus.metrics",
"prometheus.destination",
"prometheus.instance",
"prometheus.http_username",
"prometheus.http_password",
"update_manager_password"
) or key.startswith("prometheus.additional_labels"):
return
if key == "permissions":
window["current_permissions"].Update(combo_boxes["permissions"][value])
return
if key == "manager_password":
if value:
window["manager_password_set"].Update(_t("generic.yes"))
else:
window["manager_password_set"].Update(_t("generic.no"))
return
# NPF-SEC-00009
# Don't show sensible info unless unencrypted requested
@ -257,7 +269,7 @@ def config_gui(full_config: dict, config_file: str):
pass
if key in ("repo_uri", "repo_group"):
if object_type == "group":
if object_type == "groups":
window[key].Disabled = True
else:
window[key].Disabled = False
@ -270,12 +282,12 @@ def config_gui(full_config: dict, config_file: str):
if value:
for val in value:
if pathlib.Path(val).is_dir():
if object_type != "group" and inherited[val]:
if object_type != "groups" and inherited[val]:
icon = INHERITED_FOLDER_ICON
else:
icon = FOLDER_ICON
else:
if object_type != "group" and inherited[val]:
if object_type != "groups" and inherited[val]:
icon = INHERITED_FILE_ICON
else:
icon = FILE_ICON
@ -310,7 +322,7 @@ def config_gui(full_config: dict, config_file: str):
for val in value:
if val is None:
continue
if object_type != "group" and inherited[val]:
if object_type != "groups" and inherited[val]:
icon = INHERITED_TREE_ICON
else:
icon = TREE_ICON
@ -336,7 +348,7 @@ def config_gui(full_config: dict, config_file: str):
if value:
if isinstance(value, dict):
for skey, val in value.items():
if object_type != "group" and inherited[skey]:
if object_type != "groups" and inherited[skey]:
icon = INHERITED_TREE_ICON
else:
icon = TREE_ICON
@ -425,7 +437,7 @@ def config_gui(full_config: dict, config_file: str):
_iter_over_config(object_config, root_key)
def update_object_gui(object_name=None, unencrypted=False):
def update_object_gui(full_config: dict, object_name: str = None, unencrypted: bool = False):
nonlocal backup_paths_tree
nonlocal tags_tree
nonlocal exclude_files_tree
@ -436,6 +448,7 @@ def config_gui(full_config: dict, config_file: str):
nonlocal env_variables_tree
nonlocal encrypted_env_variables_tree
# Load fist available repo or group if none given
if not object_name:
object_name = get_objects()[0]
@ -462,7 +475,7 @@ def config_gui(full_config: dict, config_file: str):
object_type, object_name = get_object_from_combo(object_name)
if object_type == "repo":
if object_type == "repos":
object_config, config_inheritance = configuration.get_repo_config(
full_config, object_name, eval_variables=False
)
@ -471,7 +484,7 @@ def config_gui(full_config: dict, config_file: str):
window["repo_uri"].Update(visible=True)
window["--SET-PERMISSIONS--"].Update(visible=True)
elif object_type == "group":
elif object_type == "groups":
object_config = configuration.get_group_config(
full_config, object_name, eval_variables=False
)
@ -485,6 +498,7 @@ def config_gui(full_config: dict, config_file: str):
object_config = None
config_inheritance = None
# Now let's iter over the whole config object and update keys accordingly
iter_over_config(
object_config, config_inheritance, object_type, unencrypted, None
@ -508,8 +522,8 @@ def config_gui(full_config: dict, config_file: str):
Update full_config with keys from GUI
keys should always have form section.name or section.subsection.name
"""
if object_type == "repo":
object_group = full_config.g(f"repos.{object_name}.repo_group")
if object_type == "repos":
object_group = full_config.g(f"{object_type}.{object_name}.repo_group")
else:
object_group = None
@ -549,7 +563,7 @@ def config_gui(full_config: dict, config_file: str):
for k, v in values["env.encrypted_env_variables"].items():
if v == ENCRYPTED_DATA_PLACEHOLDER:
values["env.encrypted_env_variables"][k] = full_config.g(
f"{object_type}s.{object_name}.env.encrypted_env_variables.{k}"
f"{object_type}.{object_name}.env.encrypted_env_variables.{k}"
)
for key, value in values.items():
@ -610,21 +624,21 @@ def config_gui(full_config: dict, config_file: str):
active_object_key = f"{key}"
current_value = full_config.g(active_object_key)
else:
active_object_key = f"{object_type}s.{object_name}.{key}"
active_object_key = f"{object_type}.{object_name}.{key}"
current_value = full_config.g(active_object_key)
# Special case for nested retention_policy dict which may not exist, we need to create it
if key.startswith("repo_opts.retention_policy"):
if not full_config.g(
f"{object_type}s.{object_name}.repo_opts.retention_policy"
f"{object_type}.{object_name}.repo_opts.retention_policy"
):
full_config.s(
f"{object_type}s.{object_name}.repo_opts.retention_policy",
f"{object_type}.{object_name}.repo_opts.retention_policy",
CommentedMap(),
)
if object_group:
inheritance_key = f"groups.{object_group}.{key}"
inheritance_key = f"{object_type}.{object_group}.{key}"
# If object is a list, check which values are inherited from group and remove them
if isinstance(value, list):
inheritance_list = full_config.g(inheritance_key)
@ -651,7 +665,7 @@ def config_gui(full_config: dict, config_file: str):
# Finally, update the config dictionary
# Debug WIP
# if object_type == "group":
# if object_type == "groups":
# print(f"UPDATING {active_object_key} curr={current_value} new={value}")
# else:
# print(f"UPDATING {active_object_key} curr={current_value} inherited={inherited} new={value}")
@ -675,24 +689,21 @@ def config_gui(full_config: dict, config_file: str):
full_config.d(f"repos.{object_name}.prometheus.{prom_key}")
return full_config
def set_permissions(full_config: dict, object_name: str) -> dict:
def set_permissions(full_config: dict, object_type: str, object_name: str) -> dict:
"""
Sets repo wide repo_uri / password / permissions
"""
object_type, object_name = get_object_from_combo(object_name)
if object_type == "group":
sg.PopupError(_t("config_gui.permissions_only_for_repos"))
if object_type == "groups":
sg.PopupError(_t("config_gui.permissions_only_for_repos"), keep_on_top=True)
return full_config
repo_config, _ = configuration.get_repo_config(
full_config, object_name, eval_variables=False
)
permissions = list(combo_boxes["permissions"].values())
current_perm = repo_config.g("permissions")
current_perm = full_config.g(f"{object_type}.{object_name}.permissions")
if not current_perm:
current_perm = permissions[-1]
else:
current_perm = combo_boxes["permissions"][current_perm]
manager_password = repo_config.g("manager_password")
manager_password = full_config.g(f"{object_type}.{object_name}.manager_password")
layout = [
[
@ -742,12 +753,11 @@ def config_gui(full_config: dict, config_file: str):
permission = get_key_from_value(
combo_boxes["permissions"], values["permissions"]
)
repo_config.s("permissions", permission)
repo_config.s("manager_password", values["-MANAGER-PASSWORD-"])
repo_config.s("update_manager_password", True)
full_config.s(f"{object_type}.{object_name}.permissions", permission)
full_config.s(f"{object_type}.{object_name}.manager_password", values["-MANAGER-PASSWORD-"])
full_config.s(f"{object_type}.{object_name}.update_manager_password", True)
break
window.close()
full_config.s(f"repos.{object_name}", repo_config)
return full_config
def object_layout() -> List[list]:
@ -1225,7 +1235,18 @@ def config_gui(full_config: dict, config_file: str):
),
sg.Input(key="repo_opts.repo_password_command", size=(95, 1)),
],
[sg.Button(_t("config_gui.set_permissions"), key="--SET-PERMISSIONS--")],
[
sg.Text(_t("config_gui.current_permissions"), size=(40, 1)),
sg.Text("Default", key="current_permissions", size=(25, 1))
],
[
sg.Text(_t("config_gui.manager_password_set"), size=(40, 1)),
sg.Text(_t("generic.no"), key="manager_password_set", size=(25, 1))
],
[
sg.Button(_t("config_gui.set_permissions"), key="--SET-PERMISSIONS--")
],
[
sg.Text(_t("config_gui.repo_group"), size=(40, 1)),
sg.Combo(
@ -1881,7 +1902,7 @@ Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\
encrypted_env_variables_tree = sg.TreeData()
# Update gui with first default object (repo or group)
update_object_gui(get_objects()[0], unencrypted=False)
update_object_gui(full_config, get_objects()[0], unencrypted=False)
update_global_gui(full_config, unencrypted=False)
# These contain object name/type so on object change we can update the current object before loading new one
@ -1908,7 +1929,7 @@ Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\
full_config, current_object_type, current_object_name, values
)
current_object_type, current_object_name = object_type, object_name
update_object_gui(values["-OBJECT-SELECT-"], unencrypted=False)
update_object_gui(full_config, values["-OBJECT-SELECT-"], unencrypted=False)
update_global_gui(full_config, unencrypted=False)
continue
if event == "-OBJECT-DELETE-":
@ -1924,7 +1945,10 @@ Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\
full_config, object_name
)
if not manager_password or ask_manager_password(manager_password):
full_config = set_permissions(full_config, values["-OBJECT-SELECT-"])
# We need to update full_config with current GUI values before using modifying it
full_config = update_config_dict(full_config, current_object_type, current_object_name, values)
full_config = set_permissions(full_config, object_type=object_type, object_name=values["-OBJECT-SELECT-"])
update_object_gui(full_config, values["-OBJECT-SELECT-"])
continue
if event in (
"--ADD-PATHS-FILE--",
@ -2046,21 +2070,21 @@ Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\
tree.insert("", node, node, node, icon=icon)
if event.startswith("--REMOVE-"):
for key in values[option_key]:
if object_type != "group" and tree.tree_dict[key].icon in (
if object_type != "groups" and tree.tree_dict[key].icon in (
INHERITED_TREE_ICON,
INHERITED_FILE_ICON,
INHERITED_FOLDER_ICON,
):
sg.PopupError(
_t("config_gui.cannot_remove_group_inherited_settings")
_t("config_gui.cannot_remove_group_inherited_settings"), keep_on_top=True
)
continue
tree.delete(key)
window[option_key].Update(values=tree)
continue
if event == "--ACCEPT--":
if object_type != "group" and not values["repo_uri"]:
sg.PopupError(_t("config_gui.repo_uri_cannot_be_empty"))
if object_type != "groups" and not values["repo_uri"]:
sg.PopupError(_t("config_gui.repo_uri_cannot_be_empty"), keep_on_top=True)
continue
full_config = update_config_dict(
full_config, current_object_type, current_object_name, values
@ -2080,12 +2104,12 @@ Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\
# NPF-SEC-00009
env_manager_password = os.environ.get("NPBACKUP_MANAGER_PASSWORD", None)
if not manager_password:
sg.PopupError(_t("config_gui.no_manager_password_defined"))
sg.PopupError(_t("config_gui.no_manager_password_defined"), keep_on_top=True)
continue
if (
env_manager_password and env_manager_password == manager_password
) or ask_manager_password(manager_password):
update_object_gui(values["-OBJECT-SELECT-"], unencrypted=True)
update_object_gui(full_config, values["-OBJECT-SELECT-"], unencrypted=True)
update_global_gui(full_config, unencrypted=True)
continue
if event in ("create_interval_task", "create_daily_task"):
@ -2099,10 +2123,10 @@ Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\
if result:
sg.Popup(_t("config_gui.scheduled_task_creation_success"))
else:
sg.PopupError(_t("config_gui.scheduled_task_creation_failure"))
sg.PopupError(_t("config_gui.scheduled_task_creation_failure"), keep_on_top=True)
except ValueError as exc:
sg.PopupError(
_t("config_gui.scheduled_task_creation_failure") + f": {exc}"
_t("config_gui.scheduled_task_creation_failure") + f": {exc}", keep_on_top=True
)
continue
window.close()

View file

@ -7,7 +7,7 @@ __intname__ = "npbackup.gui.helpers"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2023-2024 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "2024050901"
__build__ = "2024061501"
from typing import Tuple
@ -144,9 +144,10 @@ def gui_thread_runner(
[
sg.Multiline(
key="-OPERATIONS-PROGRESS-STDOUT-",
size=(70, 5),
size=(70, 15),
visible=not __compact,
autoscroll=False, # Setting autoscroll=True on not visible Multiline takes seconds on updates
# Setting autoscroll=True on not visible Multiline takes seconds on updates
autoscroll=False if __fn_name == "backup" else True,
)
],
[
@ -161,7 +162,7 @@ def gui_thread_runner(
[
sg.Multiline(
key="-OPERATIONS-PROGRESS-STDERR-",
size=(70, 10),
size=(70, 5),
visible=not __compact,
autoscroll=True,
)
@ -258,10 +259,11 @@ def gui_thread_runner(
if stdout_data is None:
logger.debug("gui_thread_runner got stdout queue close signal")
read_stdout_queue = False
progress_window["-OPERATIONS-PROGRESS-STDOUT-"].Update(
"\n", append=True
)
#progress_window["-OPERATIONS-PROGRESS-STDOUT-"].Update(
# "\n", append=True
#)
else:
stdout_data = stdout_data.strip("\r\n")
progress_window["-OPERATIONS-PROGRESS-STDOUT-"].Update(
f"\n{stdout_data}", append=True
)
@ -276,16 +278,11 @@ def gui_thread_runner(
if stderr_data is None:
logger.debug("gui_thread_runner got stderr queue close signal")
read_stderr_queue = False
progress_window["-OPERATIONS-PROGRESS-STDERR-"].Update(
"\n", append=True
)
else:
stderr_has_messages = True
# if __compact:
# for key in progress_window.AllKeysDict:
# progress_window[key].Update(visible=True)
stderr_data = stderr_data.strip("\r\n")
progress_window["-OPERATIONS-PROGRESS-STDERR-"].Update(
f"{stderr_data}", append=True
f"\n{stderr_data}", append=True
)
read_queues = read_stdout_queue or read_stderr_queue

View file

@ -7,34 +7,38 @@ __intname__ = "npbackup.gui.operations"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2023-2024 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "2024010301"
__build__ = "2024061601"
import os
from logging import getLogger
import npbackup.gui.PySimpleGUI as sg
import npbackup.configuration as configuration
from npbackup.configuration import get_repo_config, get_group_list, get_repos_by_group, get_manager_password
from npbackup.core.i18n_helper import _t
from npbackup.gui.helpers import get_anon_repo_uri, gui_thread_runner
from resources.customization import (
OEM_STRING,
OEM_LOGO,
)
from npbackup.gui.config import ENCRYPTED_DATA_PLACEHOLDER, ask_manager_password
logger = getLogger(__intname__)
def gui_update_state(window, full_config: dict) -> list:
def gui_update_state(window, full_config: dict, unencrypted: str = None) -> list:
repo_list = []
try:
for repo_name in full_config.g("repos"):
repo_config, _ = configuration.get_repo_config(full_config, repo_name)
repo_config, _ = get_repo_config(full_config, repo_name)
if repo_config.g(f"repo_uri") and (
repo_config.g(f"repo_opts.repo_password")
or repo_config.g(f"repo_opts.repo_password_command")
):
backend_type, repo_uri = get_anon_repo_uri(repo_config.g(f"repo_uri"))
repo_group = repo_config.g("repo_group")
if not unencrypted and unencrypted != repo_name:
repo_uri = ENCRYPTED_DATA_PLACEHOLDER
repo_list.append([repo_name, repo_group, backend_type, repo_uri])
else:
logger.warning("Incomplete operations repo {}".format(repo_name))
@ -50,7 +54,7 @@ def operations_gui(full_config: dict) -> dict:
"""
def _select_groups():
group_list = configuration.get_group_list(full_config)
group_list = get_group_list(full_config)
selector_layout = [
[
sg.Table(
@ -87,7 +91,7 @@ def operations_gui(full_config: dict) -> dict:
repo_list = []
for group_index in values["-GROUP_LIST-"]:
group_name = group_list[group_index]
repo_list += configuration.get_repos_by_group(
repo_list += get_repos_by_group(
full_config, group_name
)
result = repo_list
@ -195,6 +199,8 @@ def operations_gui(full_config: dict) -> dict:
)
]
]
right_click_menu = ["", [_t("config_gui.show_decrypted")]]
window = sg.Window(
"Configuration",
@ -208,6 +214,7 @@ def operations_gui(full_config: dict) -> dict:
keep_on_top=False,
alpha_channel=1.0,
default_button_element_size=(20, 1),
right_click_menu=right_click_menu,
finalize=True,
)
@ -221,6 +228,29 @@ def operations_gui(full_config: dict) -> dict:
if event in (sg.WIN_CLOSED, sg.WIN_X_EVENT, "--EXIT--"):
break
if event == _t("config_gui.show_decrypted"):
try:
object_name = complete_repo_list[values["repo-list"][0]][0]
except Exception as exc:
logger.debug("Trace:", exc_info=True)
object_name = None
finally:
if not object_name:
sg.PopupError(_t("operations_gui.no_repo_selected"), keep_on_top=True)
continue
manager_password = get_manager_password(
full_config, object_name
)
# NPF-SEC-00009
env_manager_password = os.environ.get("NPBACKUP_MANAGER_PASSWORD", None)
if not manager_password:
sg.PopupError(_t("config_gui.no_manager_password_defined"), keep_on_top=True)
continue
if (
env_manager_password and env_manager_password == manager_password
) or ask_manager_password(manager_password):
complete_repo_list = gui_update_state(window, full_config, unencrypted=object_name)
continue
if event in (
"--QUICK-CHECK--",
"--FULL-CHECK--",
@ -243,7 +273,7 @@ def operations_gui(full_config: dict) -> dict:
if not repos:
continue
for repo_name in repos:
repo_config, __annotations__ = configuration.get_repo_config(
repo_config, __annotations__ = get_repo_config(
full_config, repo_name
)
repo_config_list.append(repo_config)

View file

@ -143,6 +143,8 @@ en:
full_perms: Full permissions
setting_permissions_requires_manager_password: Setting permissions requires manager password
manager_password_too_short: Manager password is too short
current_permissions: Current permissions
manager_password_set: Manager password initialized
unknown_error_see_logs: Unknown error, please check logs

View file

@ -144,7 +144,9 @@ fr:
full_perms: Accès total
setting_permissions_requires_manager_password: Un mot de passe gestionnaire est requis pour définir des permissions
manager_password_too_short: Le mot de passe gestionnaire est trop court
current_permissions: Permissions actives
manager_password_set: Mot de passe gestionnaire initialisé
unknown_error_see_logs: Erreur inconnue, merci de vérifier les journaux
enter_tag: Entrer tag

View file

@ -15,3 +15,4 @@ en:
add_repo: Add repo
edit_repo: Edit repo
remove_repo: Remove repo
no_repo_selected: No repo selected

View file

@ -14,4 +14,5 @@ fr:
no_groups_selected: Aucun groupe sélectionné
add_repo: Ajouter dépot
edit_repo: Modifier dépot
remove_repo: Supprimer dépot
remove_repo: Supprimer dépot
no_repo_selected: Aucun dépot sélectionné