mirror of
https://github.com/netinvent/npbackup.git
synced 2024-09-20 23:06:17 +08:00
commit
161756a86d
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -15,3 +15,4 @@ en:
|
|||
add_repo: Add repo
|
||||
edit_repo: Edit repo
|
||||
remove_repo: Remove repo
|
||||
no_repo_selected: No repo selected
|
||||
|
|
|
@ -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é
|
Loading…
Reference in a new issue