mirror of
https://github.com/netinvent/npbackup.git
synced 2025-10-06 19:46:58 +08:00
2795 lines
110 KiB
Python
2795 lines
110 KiB
Python
#! /usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# This file is part of npbackup
|
|
|
|
__intname__ = "npbackup.gui.config"
|
|
__author__ = "Orsiris de Jong"
|
|
__copyright__ = "Copyright (C) 2022-2025 NetInvent"
|
|
__license__ = "GPL-3.0-only"
|
|
__build__ = "2025022301"
|
|
|
|
|
|
from typing import List, Tuple
|
|
import os
|
|
import re
|
|
import pathlib
|
|
from logging import getLogger
|
|
import FreeSimpleGUI as sg
|
|
import textwrap
|
|
from ruamel.yaml.comments import CommentedMap
|
|
from npbackup import configuration
|
|
from ofunctions.misc import get_key_from_value, BytesConverter
|
|
from npbackup.core.i18n_helper import _t
|
|
from resources.customization import (
|
|
INHERITED_ICON,
|
|
NON_INHERITED_ICON,
|
|
FILE_ICON,
|
|
FOLDER_ICON,
|
|
INHERITED_FILE_ICON,
|
|
INHERITED_FOLDER_ICON,
|
|
TREE_ICON,
|
|
INHERITED_TREE_ICON,
|
|
)
|
|
from npbackup.task import create_scheduled_task
|
|
|
|
logger = getLogger()
|
|
|
|
|
|
# Monkeypatching SimpleGUI
|
|
# @SimpleGUI: Why is there no delete method for TreeData ?
|
|
def delete(self, key):
|
|
if key == "":
|
|
return False
|
|
try:
|
|
node = self.tree_dict[key]
|
|
key_list = [
|
|
key,
|
|
]
|
|
parent_node = self.tree_dict[node.parent]
|
|
parent_node.children.remove(node)
|
|
while key_list != []:
|
|
temp = []
|
|
for item in key_list:
|
|
temp += self.tree_dict[item].children
|
|
del self.tree_dict[item]
|
|
key_list = temp
|
|
return True
|
|
except KeyError:
|
|
return False
|
|
|
|
|
|
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"), keep_on_top=True)
|
|
return False
|
|
return True
|
|
|
|
|
|
def config_gui(full_config: dict, config_file: str):
|
|
logger.info("Launching configuration GUI")
|
|
|
|
# Don't let SimpleGUI handle key errors since we might have new keys in config file
|
|
sg.set_options(
|
|
suppress_raise_key_errors=True,
|
|
suppress_error_popups=True,
|
|
suppress_key_guessing=True,
|
|
)
|
|
|
|
combo_boxes = {
|
|
"backup_opts.compression": {
|
|
"auto": _t("config_gui.auto"),
|
|
"max": _t("config_gui.max"),
|
|
"off": _t("config_gui.off"),
|
|
},
|
|
"backup_opts.source_type": {
|
|
"folder_list": _t("config_gui.folder_list"),
|
|
"files_from": _t("config_gui.files_from"),
|
|
"files_from_verbatim": _t("config_gui.files_from_verbatim"),
|
|
"files_from_raw": _t("config_gui.files_from_raw"),
|
|
"stdin_from_command": _t("config_gui.stdin_from_command"),
|
|
},
|
|
"backup_opts.priority": {
|
|
"low": _t("config_gui.low"),
|
|
"normal": _t("config_gui.normal"),
|
|
"high": _t("config_gui.high"),
|
|
},
|
|
"permissions": {
|
|
"backup": _t("config_gui.backup_perms"),
|
|
"restore": _t("config_gui.restore_perms"),
|
|
"restore_only": _t("config_gui.restore_only_perms"),
|
|
"full": _t("config_gui.full_perms"),
|
|
},
|
|
}
|
|
|
|
byte_units = ["B", "KB", "KiB", "MB", "MiB", "GB", "GiB", "TB", "TiB", "PB", "PiB"]
|
|
|
|
def get_objects() -> List[str]:
|
|
"""
|
|
Adds repos and groups in a list for combobox
|
|
"""
|
|
object_list = []
|
|
for repo in configuration.get_repo_list(full_config):
|
|
object_list.append(f"Repo: {repo}")
|
|
for group in configuration.get_group_list(full_config):
|
|
object_list.append(f"Group: {group}")
|
|
return object_list
|
|
|
|
def create_object(full_config: dict) -> dict:
|
|
object_type = None
|
|
object_name = None
|
|
layout = [
|
|
[
|
|
sg.Text(_t("generic.type")),
|
|
sg.Combo(["repo", "group"], default_value="repo", key="-OBJECT-TYPE-"),
|
|
sg.Text(_t("generic.name")),
|
|
sg.Input(key="-OBJECT-NAME-"),
|
|
],
|
|
[
|
|
sg.Push(),
|
|
sg.Button(_t("generic.cancel"), key="--CANCEL--"),
|
|
sg.Button(_t("generic.accept"), key="--ACCEPT--"),
|
|
],
|
|
]
|
|
|
|
window = sg.Window(
|
|
_t("config_gui.create_object"), layout=layout, keep_on_top=True
|
|
)
|
|
while True:
|
|
event, values = window.read()
|
|
if event in (sg.WIN_CLOSED, sg.WIN_X_EVENT, "--CANCEL--"):
|
|
break
|
|
if event == "--ACCEPT--":
|
|
object_type = (
|
|
"groups" if values["-OBJECT-TYPE-"] == "group" else "repos"
|
|
)
|
|
object_name = values["-OBJECT-NAME-"]
|
|
if object_name is None or object_name == "" or "." in object_name:
|
|
sg.PopupError(
|
|
_t("config_gui.object_name_cannot_be_empty"), keep_on_top=True
|
|
)
|
|
continue
|
|
if object_name == "__all__":
|
|
sg.PopupError(
|
|
_t("config_gui.object_name_cannot_be_all"), keep_on_top=True
|
|
)
|
|
continue
|
|
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"{object_type}.{object_name}", CommentedMap())
|
|
full_config.s(
|
|
f"{object_type}.{object_name}",
|
|
configuration.get_default_repo_config(),
|
|
)
|
|
break
|
|
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
|
|
)
|
|
continue
|
|
full_config.s(
|
|
f"{object_type}.{object_name}",
|
|
configuration.get_default_group_config(),
|
|
)
|
|
break
|
|
else:
|
|
raise ValueError("Bogus object type given")
|
|
window.close()
|
|
if object_type and object_name:
|
|
full_config = update_object_gui(
|
|
full_config, object_type, object_name, unencrypted=False
|
|
)
|
|
update_global_gui(full_config, unencrypted=False)
|
|
return full_config, object_type, object_name
|
|
|
|
def delete_object(full_config: dict, full_object_name: str) -> dict:
|
|
object_type, object_name = get_object_from_combo(full_object_name)
|
|
if not object_type and not object_name:
|
|
sg.popup_error(_t("config_gui.no_object_to_delete"), keep_on_top=True)
|
|
return full_config
|
|
result = sg.popup_yes_no(
|
|
_t("config_gui.are_you_sure_to_delete") + f" {object_type} {object_name} ?"
|
|
)
|
|
if result == "Yes":
|
|
full_config.d(f"{object_type}.{object_name}")
|
|
full_config = update_object_gui(full_config, None, unencrypted=False)
|
|
update_global_gui(full_config, unencrypted=False)
|
|
return full_config
|
|
|
|
def update_object_selector(
|
|
object_name: str = None, object_type: str = None
|
|
) -> Tuple[str, str]:
|
|
object_list = get_objects()
|
|
if not object_name or not object_type:
|
|
if len(object_list) > 0:
|
|
obj = object_list[0]
|
|
else:
|
|
obj = None
|
|
else:
|
|
# We need to remove the "s" and the end if we want our combobox name to be usable later
|
|
obj = f"{object_type.rstrip('s').capitalize()}: {object_name}"
|
|
|
|
window["-OBJECT-SELECT-"].Update(values=object_list)
|
|
window["-OBJECT-SELECT-"].Update(value=obj)
|
|
|
|
# Also update task object selector
|
|
window["-OBJECT-SELECT-TASKS-"].Update(values=object_list)
|
|
window["-OBJECT-SELECT-TASKS-"].Update(value=obj)
|
|
|
|
return get_object_from_combo(obj)
|
|
|
|
def get_object_from_combo(combo_value: str) -> Tuple[str, str]:
|
|
"""
|
|
Extracts selected object from combobox
|
|
Returns object type and name
|
|
"""
|
|
try:
|
|
if combo_value.startswith("Repo: "):
|
|
object_type = "repos"
|
|
object_name = combo_value[len("Repo: ") :]
|
|
elif combo_value.startswith("Group: "):
|
|
object_type = "groups"
|
|
object_name = combo_value[len("Group: ") :]
|
|
else:
|
|
object_type = None
|
|
object_name = None
|
|
logger.error(
|
|
f"Could not obtain object_type and object_name from string {combo_value}"
|
|
)
|
|
except AttributeError:
|
|
object_type = None
|
|
object_name = None
|
|
logger.error(
|
|
f"Could not obtain object_type and object_name from {combo_value}"
|
|
)
|
|
return object_type, object_name
|
|
|
|
def update_source_layout(source_type: str):
|
|
if source_type == "stdin_from_command":
|
|
window["backup_opts.paths"].update(visible=False)
|
|
window["--ADD-PATHS-FILE-BUTTON--"].update(visible=False)
|
|
window["--ADD-PATHS-FOLDER-BUTTON--"].update(visible=False)
|
|
window["--ADD-PATHS-MANUALLY--"].update(visible=False)
|
|
window["--REMOVE-PATHS--"].update(visible=False)
|
|
window["backup_opts.stdin_from_command"].update(visible=True)
|
|
window["inherited.backup_opts.stdin_from_command"].update(visible=True)
|
|
window["text_stdin_from_command"].update(visible=True)
|
|
window["backup_opts.stdin_filename"].update(visible=True)
|
|
window["inherited.backup_opts.stdin_filename"].update(visible=True)
|
|
window["text_stdin_filename"].update(visible=True)
|
|
elif source_type == "folder_list":
|
|
window["backup_opts.paths"].update(visible=True)
|
|
window["--ADD-PATHS-FILE-BUTTON--"].update(visible=True)
|
|
window["--ADD-PATHS-FOLDER-BUTTON--"].update(visible=True)
|
|
window["--ADD-PATHS-MANUALLY--"].update(visible=True)
|
|
window["--REMOVE-PATHS--"].update(visible=True)
|
|
window["backup_opts.stdin_from_command"].update(visible=False)
|
|
window["inherited.backup_opts.stdin_from_command"].update(visible=False)
|
|
window["text_stdin_from_command"].update(visible=False)
|
|
window["backup_opts.stdin_filename"].update(visible=False)
|
|
window["inherited.backup_opts.stdin_filename"].update(visible=False)
|
|
window["text_stdin_filename"].update(visible=False)
|
|
elif source_type in ("files_from", "files_from_verbatim", "files_from_raw"):
|
|
window["backup_opts.paths"].update(visible=True)
|
|
window["--ADD-PATHS-FILE-BUTTON--"].update(visible=True)
|
|
window["--ADD-PATHS-FOLDER-BUTTON--"].update(visible=False)
|
|
window["--ADD-PATHS-MANUALLY--"].update(visible=True)
|
|
window["--REMOVE-PATHS--"].update(visible=True)
|
|
window["backup_opts.stdin_from_command"].update(visible=False)
|
|
window["inherited.backup_opts.stdin_from_command"].update(visible=False)
|
|
window["text_stdin_from_command"].update(visible=False)
|
|
window["backup_opts.stdin_filename"].update(visible=False)
|
|
window["inherited.backup_opts.stdin_filename"].update(visible=False)
|
|
window["text_stdin_filename"].update(visible=False)
|
|
|
|
def update_gui_values(key, value, inherited, object_type, unencrypted):
|
|
"""
|
|
Update gui values depending on their type
|
|
This not called directly, but rather from update_object_gui which calls iter_over_config which calls this function
|
|
"""
|
|
# Do not redefine those variables here since they're not modified, fixes flake8 F824
|
|
# nonlocal BAD_KEYS_FOUND_IN_CONFIG
|
|
|
|
# nonlocal backup_paths_tree
|
|
# nonlocal tags_tree
|
|
# nonlocal exclude_files_tree
|
|
# nonlocal exclude_patterns_tree
|
|
# nonlocal pre_exec_commands_tree
|
|
# nonlocal post_exec_commands_tree
|
|
# nonlocal global_prometheus_labels_tree
|
|
# nonlocal env_variables_tree
|
|
# nonlocal encrypted_env_variables_tree
|
|
|
|
try:
|
|
# Don't bother to update repo name
|
|
# Also permissions / manager_password are in a separate gui
|
|
# Also, don't update global prometheus options here but in global options
|
|
if key in (
|
|
"name",
|
|
"is_protected",
|
|
"prometheus.metrics",
|
|
"prometheus.destination",
|
|
"prometheus.instance",
|
|
"prometheus.http_username",
|
|
"prometheus.http_password",
|
|
"prometheus.no_cert_verify",
|
|
"current_manager_password",
|
|
) or key.startswith("prometheus.additional_labels"):
|
|
return
|
|
# Note that keys with "new" must be processed after "current" keys
|
|
# This will happen automatically since adding new values are at the end of the config
|
|
if key in ("permissions", "new_permissions"):
|
|
# So we need to represent no permission as full in GUI
|
|
if value is None:
|
|
value = "full"
|
|
window["current_permissions"].Update(combo_boxes["permissions"][value])
|
|
return
|
|
if key in ("manager_password", "new_manager_password"):
|
|
if value:
|
|
window["manager_password_set"].Update(_t("generic.yes"))
|
|
window["--SET-PERMISSIONS--"].Update(button_color="green")
|
|
else:
|
|
window["manager_password_set"].Update(_t("generic.no"))
|
|
window["--SET-PERMISSIONS--"].Update(button_color="red")
|
|
return
|
|
# Since FreeSimpleGUI does not allow to suppress the debugger anymore in v5.1.0, we need to handle KeyError
|
|
if key not in window.AllKeysDict:
|
|
raise KeyError
|
|
|
|
# NPF-SEC-00009
|
|
# Don't show sensitive info unless unencrypted requested
|
|
if not unencrypted:
|
|
# Use last part of key only
|
|
if key in configuration.ENCRYPTED_OPTIONS:
|
|
try:
|
|
if isinstance(value, dict):
|
|
for k in value.keys():
|
|
value[k] = ENCRYPTED_DATA_PLACEHOLDER
|
|
elif value is not None and not str(value).startswith(
|
|
configuration.ID_STRING
|
|
):
|
|
value = ENCRYPTED_DATA_PLACEHOLDER
|
|
except (KeyError, TypeError):
|
|
pass
|
|
|
|
if key in ("repo_uri", "repo_group"):
|
|
if object_type == "groups":
|
|
window[key].Disabled = True
|
|
window[key].Update(value=None)
|
|
else:
|
|
window[key].Disabled = False
|
|
# Update the combo group selector
|
|
if value is None:
|
|
window[key].Update(value="")
|
|
else:
|
|
# Update possible values for repo group combobox after a new group is created
|
|
if key == "repo_group":
|
|
window[key].Update(
|
|
values=configuration.get_group_list(full_config)
|
|
)
|
|
window[key].Update(value=value)
|
|
return
|
|
|
|
# Update tree objects
|
|
if key == "backup_opts.paths":
|
|
if value:
|
|
for val in value:
|
|
if pathlib.Path(val).is_dir():
|
|
if object_type != "groups" and inherited[val]:
|
|
icon = INHERITED_FOLDER_ICON
|
|
else:
|
|
icon = FOLDER_ICON
|
|
else:
|
|
if object_type != "groups" and inherited[val]:
|
|
icon = INHERITED_FILE_ICON
|
|
else:
|
|
icon = FILE_ICON
|
|
backup_paths_tree.insert("", val, val, val, icon=icon)
|
|
window["backup_opts.paths"].update(values=backup_paths_tree)
|
|
return
|
|
elif key in (
|
|
"backup_opts.tags",
|
|
"backup_opts.pre_exec_commands",
|
|
"backup_opts.post_exec_commands",
|
|
"backup_opts.exclude_files",
|
|
"backup_opts.exclude_patterns",
|
|
"repo_opts.retention_policy.tags",
|
|
):
|
|
if key == "backup_opts.tags":
|
|
tree = tags_tree
|
|
elif key == "backup_opts.pre_exec_commands":
|
|
tree = pre_exec_commands_tree
|
|
elif key == "backup_opts.post_exec_commands":
|
|
tree = post_exec_commands_tree
|
|
elif key == "backup_opts.exclude_files":
|
|
tree = exclude_files_tree
|
|
elif key == "backup_opts.exclude_patterns":
|
|
tree = exclude_patterns_tree
|
|
elif key == "repo_opts.retention_policy.tags":
|
|
tree = retention_policy_tags_tree
|
|
else:
|
|
tree = None
|
|
|
|
if value:
|
|
if isinstance(value, list):
|
|
for val in value:
|
|
if val is None:
|
|
continue
|
|
if object_type != "groups" and inherited[val]:
|
|
icon = INHERITED_TREE_ICON
|
|
else:
|
|
icon = TREE_ICON
|
|
if tree:
|
|
tree.insert("", val, val, val, icon=icon)
|
|
window[key].Update(values=tree)
|
|
else:
|
|
logger.error(rf"Bgous configuration value for {key}: {value}")
|
|
return
|
|
|
|
if key in (
|
|
"env.env_variables",
|
|
"env.encrypted_env_variables",
|
|
"global_prometheus.additional_labels",
|
|
):
|
|
if key == "env.env_variables":
|
|
tree = env_variables_tree
|
|
if key == "env.encrypted_env_variables":
|
|
tree = encrypted_env_variables_tree
|
|
if key == "global_prometheus.additional_labels":
|
|
tree = global_prometheus_labels_tree
|
|
|
|
if value:
|
|
if isinstance(value, dict):
|
|
for skey, val in value.items():
|
|
if (
|
|
object_type != "groups"
|
|
and inherited
|
|
and inherited[skey]
|
|
):
|
|
icon = INHERITED_TREE_ICON
|
|
else:
|
|
icon = TREE_ICON
|
|
tree.insert("", skey, skey, values=[val], icon=icon)
|
|
window[key].Update(values=tree)
|
|
else:
|
|
logger.error(f"Bogus configuration value for {key}: {value}")
|
|
return
|
|
|
|
# Update units into separate value and unit combobox
|
|
if key in (
|
|
"backup_opts.minimum_backup_size_error",
|
|
"backup_opts.exclude_files_larger_than",
|
|
"repo_opts.upload_speed",
|
|
"repo_opts.download_speed",
|
|
"repo_opts.prune_max_unused",
|
|
"repo_opts.prune_max_repack_size",
|
|
):
|
|
# We don't need a better split here since the value string comes from BytesConverter
|
|
# which always provides "0 MiB" or "5 KB" etc.
|
|
# except repo_opts.prune_max_unused which also allows percent, eg "5%" or "5 %"
|
|
if value is not None:
|
|
unit = None
|
|
try:
|
|
|
|
matches = re.search(r"(\d+(?:\.\d+)?)\s*([\w%]*)", value)
|
|
if matches:
|
|
value = str(matches.group(1))
|
|
unit = str(matches.group(2))
|
|
except (TypeError, IndexError, AttributeError):
|
|
logger.error(
|
|
f"Error decoding value {value} of key {key}. Setting default value"
|
|
)
|
|
value = "0"
|
|
unit = "MiB"
|
|
window[key].Update(value)
|
|
window[f"{key}_unit"].Update(unit)
|
|
|
|
if key in combo_boxes.keys() and value:
|
|
window[key].Update(value=combo_boxes[key][value])
|
|
else:
|
|
window[key].Update(value=value)
|
|
|
|
# Enable inheritance icon when needed
|
|
inheritance_key = f"inherited.{key}"
|
|
if inheritance_key in window.AllKeysDict:
|
|
if inherited:
|
|
window[inheritance_key].update(INHERITED_ICON)
|
|
else:
|
|
window[inheritance_key].update(NON_INHERITED_ICON)
|
|
|
|
except KeyError:
|
|
msg = f"{_t('config_gui.key_error')}: {key}"
|
|
sg.PopupError(msg)
|
|
logger.error(msg)
|
|
logger.debug("Trace:", exc_info=True)
|
|
BAD_KEYS_FOUND_IN_CONFIG.add(key)
|
|
except TypeError as exc:
|
|
logger.error(
|
|
f"Error: Trying to update GUI with key {key} produced error: {exc}"
|
|
)
|
|
logger.debug("Trace:", exc_info=True)
|
|
|
|
def iter_over_config(
|
|
object_config: dict,
|
|
config_inheritance: dict = None,
|
|
object_type: str = None,
|
|
unencrypted: bool = False,
|
|
root_key: str = "",
|
|
):
|
|
"""
|
|
Iter over a dict while retaining the full key path to current object
|
|
"""
|
|
base_object = object_config
|
|
|
|
def _iter_over_config(object_config: dict, root_key=""):
|
|
# We need to handle a special case here where env variables are dicts but shouldn't itered over here
|
|
# but handled in in update_gui_values
|
|
if isinstance(object_config, dict) and root_key not in (
|
|
"env.env_variables",
|
|
"env.encrypted_env_variables",
|
|
"global_prometheus.additional_labels",
|
|
):
|
|
for key in object_config.keys():
|
|
if root_key:
|
|
_iter_over_config(
|
|
object_config[key], root_key=f"{root_key}.{key}"
|
|
)
|
|
else:
|
|
_iter_over_config(object_config[key], root_key=f"{key}")
|
|
else:
|
|
if config_inheritance:
|
|
inherited = config_inheritance.g(root_key)
|
|
else:
|
|
inherited = False
|
|
update_gui_values(
|
|
root_key,
|
|
base_object.g(root_key),
|
|
inherited,
|
|
object_type,
|
|
unencrypted,
|
|
)
|
|
|
|
_iter_over_config(object_config, root_key)
|
|
|
|
def update_object_gui(
|
|
full_config: dict,
|
|
object_type: str = None,
|
|
object_name: str = None,
|
|
unencrypted: bool = False,
|
|
) -> dict:
|
|
"""
|
|
Reload current object configuration settings to GUI
|
|
"""
|
|
nonlocal backup_paths_tree
|
|
nonlocal tags_tree
|
|
nonlocal exclude_files_tree
|
|
nonlocal exclude_patterns_tree
|
|
nonlocal retention_policy_tags_tree
|
|
nonlocal pre_exec_commands_tree
|
|
nonlocal post_exec_commands_tree
|
|
nonlocal env_variables_tree
|
|
nonlocal encrypted_env_variables_tree
|
|
|
|
# Load fist available repo or group if none given
|
|
if not object_name:
|
|
try:
|
|
object_type, object_name = get_object_from_combo(get_objects()[0])
|
|
except IndexError:
|
|
object_type = None
|
|
object_name = None
|
|
|
|
# First we need to clear the whole GUI to reload new values
|
|
for key in window.AllKeysDict:
|
|
# We only clear config keys, which have '.' separator
|
|
if "." in str(key) and not "inherited" in str(key):
|
|
if isinstance(window[key], sg.Tree):
|
|
window[key].Update(sg.TreeData())
|
|
else:
|
|
window[key]("")
|
|
|
|
# We also need to clear tree objects
|
|
backup_paths_tree = sg.TreeData()
|
|
tags_tree = sg.TreeData()
|
|
exclude_patterns_tree = sg.TreeData()
|
|
exclude_files_tree = sg.TreeData()
|
|
retention_policy_tags_tree = sg.TreeData()
|
|
pre_exec_commands_tree = sg.TreeData()
|
|
post_exec_commands_tree = sg.TreeData()
|
|
env_variables_tree = sg.TreeData()
|
|
encrypted_env_variables_tree = sg.TreeData()
|
|
|
|
if object_type == "repos":
|
|
object_config, config_inheritance = configuration.get_repo_config(
|
|
full_config, object_name, eval_variables=False
|
|
)
|
|
|
|
# Enable settings only valid for repos
|
|
window["repo_uri"].Update(visible=True)
|
|
window["--SET-PERMISSIONS--"].Update(visible=True)
|
|
window["current_permissions"].Update(visible=True)
|
|
window["manager_password_set"].Update(visible=True)
|
|
window["repo_group"].Update(visible=True)
|
|
|
|
elif object_type == "groups":
|
|
object_config = configuration.get_group_config(
|
|
full_config, object_name, eval_variables=False
|
|
)
|
|
config_inheritance = None
|
|
|
|
# Disable settings only valid for repos
|
|
window["repo_uri"].Update(visible=False)
|
|
window["--SET-PERMISSIONS--"].Update(visible=False)
|
|
window["current_permissions"].Update(visible=False)
|
|
window["manager_password_set"].Update(visible=False)
|
|
window["repo_group"].Update(visible=False)
|
|
|
|
else:
|
|
object_config = None
|
|
config_inheritance = None
|
|
logger.error(f"Bogus object {object_type}.{object_name}")
|
|
return full_config
|
|
|
|
# Now let's iter over the whole config object and update keys accordingly
|
|
iter_over_config(
|
|
object_config, config_inheritance, object_type, unencrypted, None
|
|
)
|
|
|
|
# Special case when no source type is set
|
|
if window["backup_opts.source_type"].Get() == "":
|
|
window["backup_opts.source_type"].Update(
|
|
value=combo_boxes["backup_opts.source_type"]["folder_list"]
|
|
)
|
|
source_type = get_key_from_value(
|
|
combo_boxes["backup_opts.source_type"],
|
|
window["backup_opts.source_type"].Get(),
|
|
)
|
|
update_source_layout(source_type)
|
|
|
|
if BAD_KEYS_FOUND_IN_CONFIG:
|
|
answer = sg.popup_yes_no(
|
|
_t("config_gui.delete_bad_keys")
|
|
+ f": {','.join(BAD_KEYS_FOUND_IN_CONFIG)}",
|
|
keep_on_top=True,
|
|
)
|
|
if answer == "Yes":
|
|
for key in BAD_KEYS_FOUND_IN_CONFIG:
|
|
full_key_path = f"{object_type}.{object_name}.{key}"
|
|
logger.info(f"Deleting bogus key {full_key_path}")
|
|
full_config.d(full_key_path)
|
|
return full_config
|
|
|
|
def update_global_gui(full_config, unencrypted: bool = False):
|
|
nonlocal global_prometheus_labels_tree
|
|
|
|
global_config = CommentedMap()
|
|
|
|
global_prometheus_labels_tree = sg.TreeData()
|
|
|
|
# Only update global options gui with identified global keys
|
|
for key in full_config.keys():
|
|
if key in ("identity", "global_prometheus", "global_options"):
|
|
global_config.s(key, full_config.g(key))
|
|
iter_over_config(global_config, None, "group", unencrypted, None)
|
|
|
|
def update_config_dict(full_config, object_type, object_name, values: dict) -> dict:
|
|
"""
|
|
Update full_config with keys from GUI
|
|
keys should always have form section.name or section.subsection.name
|
|
"""
|
|
if object_type == "repos":
|
|
object_group = full_config.g(f"{object_type}.{object_name}.repo_group")
|
|
if not object_group:
|
|
logger.error(
|
|
f"Current repo {object_name} has no group. Cannot upgrade config"
|
|
)
|
|
return full_config
|
|
else:
|
|
object_group = None
|
|
|
|
# We need to patch values since sg.Tree() only returns selected data from TreeData()
|
|
# Hence we'll fill values with a list or a dict depending on our TreeData data structure
|
|
# @simpleGUI: there should be a get_all_values() method or something
|
|
list_tree_data_keys = [
|
|
"backup_opts.paths",
|
|
"backup_opts.tags",
|
|
"backup_opts.pre_exec_commands",
|
|
"backup_opts.post_exec_commands",
|
|
"backup_opts.exclude_files",
|
|
"backup_opts.exclude_patterns",
|
|
"repo_opts.retention_policy.tags",
|
|
]
|
|
for tree_data_key in list_tree_data_keys:
|
|
values[tree_data_key] = []
|
|
# pylint: disable=E1101 (no-member)
|
|
for node in window[tree_data_key].TreeData.tree_dict.values():
|
|
if node.values:
|
|
values[tree_data_key].append(node.values)
|
|
|
|
dict_tree_data_keys = [
|
|
"env.env_variables",
|
|
"env.encrypted_env_variables",
|
|
"global_prometheus.additional_labels",
|
|
]
|
|
for tree_data_key in dict_tree_data_keys:
|
|
values[tree_data_key] = CommentedMap()
|
|
# pylint: disable=E1101 (no-member)
|
|
for key, node in window[tree_data_key].TreeData.tree_dict.items():
|
|
if key and node.values:
|
|
values[tree_data_key][key] = node.values[0]
|
|
|
|
# Special treatment for env.encrypted_env_variables since they might contain an ENCRYPTED_DATA_PLACEHOLDER
|
|
# We need to update the placeholder to the actual value if exists
|
|
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}.{object_name}.env.encrypted_env_variables.{k}"
|
|
)
|
|
|
|
# Now that we have dealt with data preparation, let's loop over the key, value sets
|
|
for key, value in values.items():
|
|
# Don't update placeholders ;)
|
|
if value == ENCRYPTED_DATA_PLACEHOLDER:
|
|
continue
|
|
if not isinstance(key, str) or (
|
|
isinstance(key, str)
|
|
and (not "." in key and not key in ("repo_uri", "repo_group"))
|
|
):
|
|
# Don't bother with keys that don't contain with "." since they're not in the YAML config file
|
|
# but are most probably for GUI events
|
|
# Still, we need to handle repo_uri and repo_group which do not have dot notations since they're root keys
|
|
continue
|
|
|
|
# Handle combo boxes first to transform translation into key
|
|
if key in combo_boxes.keys():
|
|
value = get_key_from_value(combo_boxes[key], value)
|
|
# check whether we need to split into list
|
|
elif not isinstance(value, bool) and not isinstance(value, list):
|
|
# Try to convert ints and floats before committing
|
|
if "." in value:
|
|
try:
|
|
value = float(value)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
try:
|
|
value = int(value)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
# Glue value and units back together for config file
|
|
if key in (
|
|
"backup_opts.minimum_backup_size_error",
|
|
"backup_opts.exclude_files_larger_than",
|
|
"repo_opts.upload_speed",
|
|
"repo_opts.download_speed",
|
|
"repo_opts.prune_max_unused",
|
|
"repo_opts.prune_max_repack_size",
|
|
):
|
|
if value:
|
|
value = f"{value} {values[f'{key}_unit']}"
|
|
|
|
# Don't update unit keys
|
|
if key in (
|
|
"backup_opts.minimum_backup_size_error_unit",
|
|
"backup_opts.exclude_files_larger_than_unit",
|
|
"repo_opts.upload_speed_unit",
|
|
"repo_opts.download_speed_unit",
|
|
"repo_opts.prune_max_unused_unit",
|
|
"repo_opts.prune_max_repack_size_unit",
|
|
):
|
|
continue
|
|
# Don't bother with inheritance on global options and host identity
|
|
if (
|
|
key.startswith("global_options")
|
|
or key.startswith("identity")
|
|
or key.startswith("global_prometheus")
|
|
):
|
|
active_object_key = f"{key}"
|
|
current_value = full_config.g(active_object_key)
|
|
else:
|
|
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}.{object_name}.repo_opts.retention_policy"
|
|
):
|
|
full_config.s(
|
|
f"{object_type}.{object_name}.repo_opts.retention_policy",
|
|
CommentedMap(),
|
|
)
|
|
|
|
if object_group:
|
|
inheritance_key = f"groups.{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)
|
|
if inheritance_list:
|
|
for entry in inheritance_list:
|
|
if entry in value:
|
|
value.remove(entry)
|
|
|
|
# check if value is inherited from group, and if so, delete it from repo config
|
|
# Also add a foolproof test since people could add repo_group to a group config
|
|
# We're not allowing recursive groups !
|
|
if full_config.g(inheritance_key) == value and key != "repo_group":
|
|
full_config.d(active_object_key)
|
|
continue
|
|
# we also need to compare inherited values with current values for BytesConverter values
|
|
if key in (
|
|
"backup_opts.minimum_backup_size_error",
|
|
"backup_opts.exclude_files_larger_than",
|
|
"repo_opts.upload_speed",
|
|
"repo_opts.download_speed",
|
|
):
|
|
if (
|
|
full_config.g(inheritance_key) is not None
|
|
and value is not None
|
|
and (
|
|
BytesConverter(full_config.g(inheritance_key)).bytes
|
|
== BytesConverter(value).bytes
|
|
)
|
|
):
|
|
continue
|
|
|
|
# Don't bother to update empty strings, empty lists and None
|
|
# unless we have False, or 0, which or course need to be updated
|
|
if not current_value and value in (None, "", []):
|
|
continue
|
|
# Don't bother to update values which haven't changed
|
|
if current_value == value:
|
|
continue
|
|
|
|
try:
|
|
full_config.s(active_object_key, value)
|
|
except KeyError:
|
|
parent_key = ".".join(active_object_key.split(".")[:-1])
|
|
full_config.s(parent_key, CommentedMap())
|
|
full_config.s(active_object_key, value)
|
|
|
|
# Remove injected global prometheus config
|
|
for prom_key in (
|
|
"metrics",
|
|
"destination",
|
|
"additional_labels",
|
|
"instance",
|
|
"http_username",
|
|
"http_password",
|
|
"no_cert_verify",
|
|
):
|
|
full_config.d(f"repos.{object_name}.prometheus.{prom_key}")
|
|
return full_config
|
|
|
|
def set_permissions(full_config: dict, object_type: str, object_name: str) -> dict:
|
|
"""
|
|
Sets repo wide repo_uri / password / permissions
|
|
"""
|
|
if object_type == "groups":
|
|
sg.PopupError(_t("config_gui.permissions_only_for_repos"), keep_on_top=True)
|
|
return full_config
|
|
permissions = list(combo_boxes["permissions"].values())
|
|
current_perm = full_config.g(f"{object_type}.{object_name}.permissions")
|
|
if not current_perm:
|
|
# So we need to represent no permission as full in GUI, so if not set, let's take highest permission
|
|
current_perm = permissions[-1]
|
|
else:
|
|
current_perm = combo_boxes["permissions"][current_perm]
|
|
manager_password = full_config.g(
|
|
f"{object_type}.{object_name}.manager_password"
|
|
)
|
|
|
|
layout = [
|
|
[
|
|
sg.Text(_t("config_gui.permissions"), size=(40, 1)),
|
|
sg.Combo(permissions, default_value=current_perm, key="permissions"),
|
|
],
|
|
[sg.HorizontalSeparator()],
|
|
[
|
|
sg.Text(_t("config_gui.set_manager_password"), size=(40, 1)),
|
|
sg.Input(
|
|
None,
|
|
key="-MANAGER-PASSWORD-",
|
|
size=(50, 1),
|
|
password_char="*",
|
|
),
|
|
],
|
|
[
|
|
sg.Push(),
|
|
sg.Button(
|
|
_t("config_gui.remove_password"),
|
|
key="--SUPPRESS-PASSWORD--",
|
|
button_color="red",
|
|
),
|
|
sg.Button(_t("generic.cancel"), key="--CANCEL--"),
|
|
sg.Button(_t("generic.accept"), key="--ACCEPT--"),
|
|
],
|
|
]
|
|
|
|
# We need to set current_manager_password variable to make sure we have sufficient permissions to modify settings
|
|
full_config.s(
|
|
f"{object_type}.{object_name}.current_manager_password",
|
|
full_config.g(f"{object_type}.{object_name}.manager_password"),
|
|
)
|
|
|
|
window = sg.Window(_t("config_gui.permissions"), layout, keep_on_top=True)
|
|
window.finalize()
|
|
# Stupid fix because using window update method will fill input with "0" if False is given
|
|
window["-MANAGER-PASSWORD-"].Update(
|
|
manager_password if manager_password else ""
|
|
)
|
|
while True:
|
|
event, values = window.read()
|
|
if event in (sg.WIN_CLOSED, sg.WIN_X_EVENT, "--CANCEL--"):
|
|
break
|
|
if event == "--ACCEPT--":
|
|
if not values["-MANAGER-PASSWORD-"]:
|
|
sg.PopupError(
|
|
_t("config_gui.setting_permissions_requires_manager_password"),
|
|
keep_on_top=True,
|
|
)
|
|
continue
|
|
# Courtesy of https://uibakery.io/regex-library/password-regex-python
|
|
if not re.findall(
|
|
r"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$",
|
|
values["-MANAGER-PASSWORD-"],
|
|
):
|
|
sg.PopupError(
|
|
_t("config_gui.manager_password_too_simple"), keep_on_top=True
|
|
)
|
|
continue
|
|
if not values["permissions"] in permissions:
|
|
sg.PopupError(_t("generic.bogus_data_given"), keep_on_top=True)
|
|
continue
|
|
# Transform translated permission value into key
|
|
permission = get_key_from_value(
|
|
combo_boxes["permissions"], values["permissions"]
|
|
)
|
|
full_config.s(
|
|
f"{object_type}.{object_name}.new_permissions", permission
|
|
)
|
|
full_config.s(
|
|
f"{object_type}.{object_name}.new_manager_password",
|
|
values["-MANAGER-PASSWORD-"],
|
|
)
|
|
break
|
|
if event == "--SUPPRESS-PASSWORD--":
|
|
full_config.s(f"{object_type}.{object_name}.new_permissions", "full")
|
|
full_config.s(
|
|
f"{object_type}.{object_name}.new_manager_password", False
|
|
)
|
|
break
|
|
window.close()
|
|
return full_config
|
|
|
|
def object_layout() -> List[list]:
|
|
"""
|
|
Returns the GUI layout depending on the object type
|
|
"""
|
|
backup_col = [
|
|
[
|
|
sg.Text(
|
|
textwrap.fill(f"{_t('config_gui.backup_paths')}"),
|
|
size=(None, None),
|
|
expand_x=True,
|
|
),
|
|
sg.Text(
|
|
textwrap.fill(f"{_t('config_gui.source_type')}"),
|
|
size=(None, None),
|
|
expand_x=True,
|
|
justification="R",
|
|
),
|
|
sg.Combo(
|
|
list(combo_boxes["backup_opts.source_type"].values()),
|
|
key="backup_opts.source_type",
|
|
size=(48, 1),
|
|
enable_events=True,
|
|
),
|
|
],
|
|
[
|
|
sg.Input(visible=False, key="--ADD-PATHS-FILE--", enable_events=True),
|
|
sg.FilesBrowse(
|
|
_t("generic.add_files"),
|
|
target="--ADD-PATHS-FILE--",
|
|
key="--ADD-PATHS-FILE-BUTTON--",
|
|
),
|
|
sg.Input(visible=False, key="--ADD-PATHS-FOLDER--", enable_events=True),
|
|
sg.FolderBrowse(
|
|
_t("generic.add_folder"),
|
|
target="--ADD-PATHS-FOLDER--",
|
|
key="--ADD-PATHS-FOLDER-BUTTON--",
|
|
),
|
|
sg.Button(_t("generic.add_manually"), key="--ADD-PATHS-MANUALLY--"),
|
|
sg.Button(_t("generic.remove_selected"), key="--REMOVE-PATHS--"),
|
|
],
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="backup_opts.paths",
|
|
headings=[],
|
|
col0_heading=_t("generic.paths"),
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.stdin_from_command"),
|
|
key="text_stdin_from_command",
|
|
visible=False,
|
|
)
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.stdin_from_command",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
visible=False,
|
|
),
|
|
sg.Input(
|
|
key="backup_opts.stdin_from_command", size=(100, 1), visible=False
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.stdin_filename"),
|
|
key="text_stdin_filename",
|
|
visible=False,
|
|
)
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.stdin_filename",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
visible=False,
|
|
),
|
|
sg.Input(
|
|
key="backup_opts.stdin_filename", size=(100, 1), visible=False
|
|
),
|
|
],
|
|
[
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Text(_t("config_gui.compression"), size=(20, None)),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.compression",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Combo(
|
|
list(combo_boxes["backup_opts.compression"].values()),
|
|
key="backup_opts.compression",
|
|
size=(20, 1),
|
|
pad=0,
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.backup_priority"), size=(20, 1)),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.priority",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Combo(
|
|
list(combo_boxes["backup_opts.priority"].values()),
|
|
key="backup_opts.priority",
|
|
size=(20, 1),
|
|
pad=0,
|
|
),
|
|
],
|
|
[
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Button(
|
|
"+", key="--ADD-BACKUP-TAG--", size=(3, 1)
|
|
)
|
|
],
|
|
[
|
|
sg.Button(
|
|
"-",
|
|
key="--REMOVE-BACKUP-TAG--",
|
|
size=(3, 1),
|
|
)
|
|
],
|
|
],
|
|
pad=0,
|
|
size=(40, 80),
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="backup_opts.tags",
|
|
headings=[],
|
|
col0_heading="Tags",
|
|
col0_width=30,
|
|
num_rows=3,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
size=(300, 80),
|
|
),
|
|
],
|
|
],
|
|
pad=0,
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.minimum_backup_size_error"), size=(40, 2)
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.minimum_backup_size_error",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="backup_opts.minimum_backup_size_error", size=(8, 1)
|
|
),
|
|
sg.Combo(
|
|
byte_units,
|
|
default_value=byte_units[3],
|
|
key="backup_opts.minimum_backup_size_error_unit",
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.use_fs_snapshot",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
textwrap.fill(
|
|
f'{_t("config_gui.use_fs_snapshot")}', width=34
|
|
),
|
|
key="backup_opts.use_fs_snapshot",
|
|
size=(40, 1),
|
|
pad=0,
|
|
),
|
|
],
|
|
]
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.additional_backup_only_parameters"), size=(40, 1)
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.additional_backup_only_parameters",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="backup_opts.additional_backup_only_parameters", size=(100, 1)
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.additional_restore_only_parameters"), size=(40, 1)
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.additional_restore_only_parameters",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="backup_opts.additional_restore_only_parameters", size=(100, 1)
|
|
),
|
|
],
|
|
]
|
|
|
|
exclusions_col = [
|
|
[
|
|
sg.Column(
|
|
[
|
|
[sg.Button("+", key="--ADD-EXCLUDE-PATTERN--", size=(3, 1))],
|
|
[sg.Button("-", key="--REMOVE-EXCLUDE-PATTERN--", size=(3, 1))],
|
|
],
|
|
pad=0,
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="backup_opts.exclude_patterns",
|
|
headings=[],
|
|
col0_heading=_t("config_gui.exclude_patterns"),
|
|
num_rows=4,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
expand_x=True,
|
|
),
|
|
],
|
|
[sg.HSeparator()],
|
|
[
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Input(
|
|
visible=False,
|
|
key="--ADD-EXCLUDE-FILE--",
|
|
enable_events=True,
|
|
),
|
|
sg.FilesBrowse(
|
|
"+", target="--ADD-EXCLUDE-FILE--", size=(3, 1)
|
|
),
|
|
],
|
|
[
|
|
sg.Button(
|
|
"M", key="--ADD-EXCLUDE-FILE-MANUALLY--", size=(3, 1)
|
|
)
|
|
],
|
|
[sg.Button("-", key="--REMOVE-EXCLUDE-FILE--", size=(3, 1))],
|
|
],
|
|
pad=0,
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="backup_opts.exclude_files",
|
|
headings=[],
|
|
col0_heading=_t("config_gui.exclude_files"),
|
|
num_rows=4,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
expand_x=True,
|
|
),
|
|
],
|
|
[sg.HSeparator()],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.exclude_files_larger_than",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Text(
|
|
_t("config_gui.exclude_files_larger_than"),
|
|
size=(40, 1),
|
|
),
|
|
sg.Input(key="backup_opts.exclude_files_larger_than", size=(8, 1)),
|
|
sg.Combo(
|
|
byte_units,
|
|
default_value=byte_units[3],
|
|
key="backup_opts.exclude_files_larger_than_unit",
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.ignore_cloud_files",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
f'{_t("config_gui.ignore_cloud_files")} ({_t("config_gui.windows_only")})',
|
|
key="backup_opts.ignore_cloud_files",
|
|
size=(None, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.excludes_case_ignore",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
f'{_t("config_gui.excludes_case_ignore")} ({_t("config_gui.windows_always")})',
|
|
key="backup_opts.excludes_case_ignore",
|
|
size=(None, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.exclude_caches",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
_t("config_gui.exclude_cache_dirs"),
|
|
key="backup_opts.exclude_caches",
|
|
size=(None, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.one_file_system",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
_t("config_gui.one_file_system"),
|
|
key="backup_opts.one_file_system",
|
|
size=(None, 1),
|
|
),
|
|
],
|
|
]
|
|
|
|
pre_post_col = [
|
|
[
|
|
sg.Column(
|
|
[
|
|
[sg.Button("+", key="--ADD-PRE-EXEC-COMMAND--", size=(3, 1))],
|
|
[
|
|
sg.Button(
|
|
"-", key="--REMOVE-PRE-EXEC-COMMAND--", size=(3, 1)
|
|
)
|
|
],
|
|
],
|
|
pad=0,
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="backup_opts.pre_exec_commands",
|
|
headings=[],
|
|
col0_heading=_t("config_gui.pre_exec_commands"),
|
|
num_rows=4,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
expand_x=True,
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.pre_exec_per_command_timeout",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Text(_t("config_gui.maximum_exec_time"), size=(40, 1)),
|
|
sg.Input(key="backup_opts.pre_exec_per_command_timeout", size=(8, 1)),
|
|
sg.Text(_t("generic.seconds")),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.pre_exec_failure_is_fatal",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
_t("config_gui.exec_failure_is_fatal"),
|
|
key="backup_opts.pre_exec_failure_is_fatal",
|
|
size=(41, 1),
|
|
),
|
|
],
|
|
[sg.HorizontalSeparator()],
|
|
[
|
|
sg.Column(
|
|
[
|
|
[sg.Button("+", key="--ADD-POST-EXEC-COMMAND--", size=(3, 1))],
|
|
[
|
|
sg.Button(
|
|
"-", key="--REMOVE-POST-EXEC-COMMAND--", size=(3, 1)
|
|
)
|
|
],
|
|
],
|
|
pad=0,
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="backup_opts.post_exec_commands",
|
|
headings=[],
|
|
col0_heading=_t("config_gui.post_exec_commands"),
|
|
num_rows=4,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
expand_x=True,
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.post_exec_per_command_timeout",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Text(_t("config_gui.maximum_exec_time"), size=(40, 1)),
|
|
sg.Input(key="backup_opts.post_exec_per_command_timeout", size=(8, 1)),
|
|
sg.Text(_t("generic.seconds")),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.post_exec_failure_is_fatal",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
_t("config_gui.exec_failure_is_fatal"),
|
|
key="backup_opts.post_exec_failure_is_fatal",
|
|
size=(41, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.post_exec_execute_even_on_backup_error",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
_t("config_gui.execute_even_on_backup_error"),
|
|
key="backup_opts.post_exec_execute_even_on_backup_error",
|
|
size=(41, 1),
|
|
),
|
|
],
|
|
]
|
|
|
|
repo_col = [
|
|
[
|
|
sg.Text(_t("config_gui.backup_repo_uri"), size=(40, 1)),
|
|
],
|
|
[
|
|
sg.Image(NON_INHERITED_ICON, pad=1),
|
|
sg.Input(key="repo_uri", size=(95, 1), enable_events=True),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.repo_uri_cloud_hint"),
|
|
key="repo_uri_cloud_hint",
|
|
visible=False,
|
|
)
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.backup_repo_password"), size=(40, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.repo_password",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="repo_opts.repo_password", size=(95, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.backup_repo_password_command"), size=(95, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.repo_password_command",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="repo_opts.repo_password_command", size=(95, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.current_permissions"), size=(40, 1)),
|
|
sg.Image(NON_INHERITED_ICON, pad=1),
|
|
sg.Text("Default", key="current_permissions", size=(25, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.manager_password_set"), size=(40, 1)),
|
|
sg.Image(NON_INHERITED_ICON, pad=1),
|
|
sg.Text(_t("generic.no"), key="manager_password_set", size=(25, 1)),
|
|
],
|
|
[
|
|
sg.Text(" ", size=(40, 1)),
|
|
sg.Image(NON_INHERITED_ICON, pad=1),
|
|
sg.Button(
|
|
_t("config_gui.set_permissions"),
|
|
key="--SET-PERMISSIONS--",
|
|
size=(35, 1),
|
|
button_color="red",
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.repo_group"), size=(40, 1)),
|
|
sg.Image(NON_INHERITED_ICON, pad=1),
|
|
sg.Combo(
|
|
values=configuration.get_group_list(full_config),
|
|
key="repo_group",
|
|
enable_events=True,
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.minimum_backup_age"),
|
|
size=(40, 2),
|
|
),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.minimum_backup_age",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="repo_opts.minimum_backup_age", size=(8, 1)),
|
|
sg.Text(_t("generic.minutes")),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.upload_speed"), size=(40, 1)),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.upload_speed",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="repo_opts.upload_speed", size=(8, 1)),
|
|
sg.Combo(
|
|
byte_units,
|
|
default_value=byte_units[3],
|
|
key="repo_opts.upload_speed_unit",
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.download_speed"), size=(40, 1)),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.download_speed",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="repo_opts.download_speed", size=(8, 1)),
|
|
sg.Combo(
|
|
byte_units,
|
|
default_value=byte_units[3],
|
|
key="repo_opts.download_speed_unit",
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.backend_connections"), size=(40, 1)),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.backend_connections",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="repo_opts.backend_connections", size=(8, 1)),
|
|
],
|
|
]
|
|
|
|
retention_col = [
|
|
[
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Text(_t("config_gui.keep"), size=(30, 1)),
|
|
]
|
|
]
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.last",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_policy.last", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.last"), size=(20, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.hourly",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_policy.hourly", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.hourly"), size=(20, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.daily",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_policy.daily", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.daily"), size=(20, 1)),
|
|
],
|
|
],
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.weekly",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_policy.weekly", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.weekly"), size=(20, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.monthly",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_policy.monthly", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.monthly"), size=(20, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.yearly",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_policy.yearly", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.yearly"), size=(20, 1)),
|
|
],
|
|
]
|
|
),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.keep_within",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
_t("config_gui.keep_within"),
|
|
key="repo_opts.retention_policy.keep_within",
|
|
size=(100, 1),
|
|
),
|
|
],
|
|
[sg.Text(_t("config_gui.policiy_group_by"))],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.group_by_host",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
_t("config_gui.group_by_host"),
|
|
key="repo_opts.retention_policy.group_by_host",
|
|
),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.group_by_paths",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
_t("config_gui.group_by_paths"),
|
|
key="repo_opts.retention_policy.group_by_paths",
|
|
),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.group_by_tags",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Checkbox(
|
|
_t("config_gui.group_by_tags"),
|
|
key="repo_opts.retention_policy.group_by_tags",
|
|
),
|
|
],
|
|
[sg.Text(_t("config_gui.policiy_group_by_explanation"))],
|
|
[sg.HorizontalSeparator()],
|
|
[
|
|
sg.Column(
|
|
[
|
|
[sg.Button("+", key="--ADD-RETENTION-TAG--", size=(3, 1))],
|
|
[sg.Button("-", key="--REMOVE-RETENTION-TAG--", size=(3, 1))],
|
|
],
|
|
pad=0,
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="repo_opts.retention_policy.tags",
|
|
headings=[],
|
|
col0_heading=_t("config_gui.keep_tags"),
|
|
num_rows=4,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
expand_x=True,
|
|
),
|
|
],
|
|
[sg.HorizontalSeparator()],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.post_backup_housekeeping_percent_chance"),
|
|
size=(40, 1),
|
|
),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.post_backup_housekeeping_percent_chance",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="backup_opts.post_backup_housekeeping_percent_chance",
|
|
size=(8, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t(
|
|
"config_gui.post_backup_housekeeping_percent_chance_explanation"
|
|
),
|
|
size=(100, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.post_backup_housekeeping_interval"),
|
|
size=(40, 1),
|
|
),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.post_backup_housekeeping_interval",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="backup_opts.post_backup_housekeeping_interval",
|
|
size=(8, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.post_backup_housekeeping_interval_explanation"),
|
|
size=(100, 1),
|
|
),
|
|
],
|
|
[sg.HorizontalSeparator()],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_policy.ntp_server",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Text(_t("config_gui.optional_ntp_server_uri"), size=(40, 1)),
|
|
sg.Input(key="repo_opts.retention_policy.ntp_server", size=(50, 1)),
|
|
],
|
|
[sg.HorizontalSeparator()],
|
|
[
|
|
sg.Text(_t("config_gui.prune_max_unused"), size=(40, 1)),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.prune_max_unused",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="repo_opts.prune_max_unused", size=(8, 1)),
|
|
sg.Combo(
|
|
byte_units + ["%"],
|
|
default_value=byte_units[3],
|
|
key="repo_opts.prune_max_unused_unit",
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.prune_max_unused_explanation"), size=(100, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.prune_max_repack_size"), size=(40, 1)),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.prune_max_repack_size",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="repo_opts.prune_max_repack_size", size=(8, 1)),
|
|
sg.Combo(
|
|
byte_units,
|
|
default_value=byte_units[3],
|
|
key="repo_opts.prune_max_repack_size_unit",
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.prune_max_repack_size_explanation"), size=(100, 1)
|
|
),
|
|
],
|
|
]
|
|
|
|
prometheus_col = [
|
|
[sg.Text(_t("config_gui.available_variables"))],
|
|
[
|
|
sg.Text(_t("config_gui.job_name"), size=(40, 1)),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.prometheus.backup_job",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="prometheus.backup_job", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("generic.group"), size=(40, 1)),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.prometheus.group",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="prometheus.group", size=(50, 1)),
|
|
],
|
|
]
|
|
|
|
env_col = [
|
|
[
|
|
sg.Column(
|
|
[
|
|
[sg.Button("+", key="--ADD-ENV-VARIABLE--", size=(3, 1))],
|
|
[sg.Button("-", key="--REMOVE-ENV-VARIABLE--", size=(3, 1))],
|
|
],
|
|
pad=0,
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="env.env_variables",
|
|
headings=[_t("generic.value")],
|
|
col0_heading=_t("config_gui.env_variables"),
|
|
col0_width=1,
|
|
auto_size_columns=True,
|
|
justification="L",
|
|
num_rows=4,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
expand_x=True,
|
|
),
|
|
],
|
|
[
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Button(
|
|
"+", key="--ADD-ENCRYPTED-ENV-VARIABLE--", size=(3, 1)
|
|
)
|
|
],
|
|
[
|
|
sg.Button(
|
|
"-",
|
|
key="--REMOVE-ENCRYPTED-ENV-VARIABLE--",
|
|
size=(3, 1),
|
|
)
|
|
],
|
|
],
|
|
pad=0,
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="env.encrypted_env_variables",
|
|
headings=[_t("generic.value")],
|
|
col0_heading=_t("config_gui.encrypted_env_variables"),
|
|
col0_width=1,
|
|
auto_size_columns=True,
|
|
justification="L",
|
|
num_rows=4,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
expand_x=True,
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.add_identity")),
|
|
sg.Button("S3", key="--ADD-S3-IDENTITY--"),
|
|
sg.Button("Azure", key="--ADD-AZURE-IDENTITY--"),
|
|
sg.Button("B2", key="--ADD-B2-IDENTITY--"),
|
|
sg.Button("Google Cloud Storage", key="--ADD-GCS-IDENTITY--"),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.suggested_encrypted_env_variables"), size=(40, 1)
|
|
),
|
|
],
|
|
[
|
|
sg.Multiline(
|
|
"\
|
|
AWS / S3: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY\n\
|
|
AZURE: AZURE_ACCOUNT_KEY AZURE_ACCOUNT_SAS AZURE_ACCOUNT_NAME\n\
|
|
B2: B2_ACCOUNT_ID B2_ACCOUNT_KEY\n\
|
|
Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\
|
|
",
|
|
size=(80, 4),
|
|
disabled=True,
|
|
font=("Courier", 12),
|
|
no_scrollbar=True,
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.additional_parameters"), size=(40, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.additional_parameters",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(key="backup_opts.additional_parameters", size=(100, 1)),
|
|
],
|
|
]
|
|
|
|
object_list = get_objects()
|
|
object_selector = [
|
|
[
|
|
sg.Text(_t("config_gui.select_object")),
|
|
sg.Combo(
|
|
object_list,
|
|
default_value=object_list[0] if object_list else None,
|
|
key="-OBJECT-SELECT-",
|
|
enable_events=True,
|
|
),
|
|
]
|
|
]
|
|
|
|
tab_group_layout = [
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.backup"),
|
|
backup_col,
|
|
font="helvetica 16",
|
|
key="--tab-backup--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.backup_destination"),
|
|
repo_col,
|
|
font="helvetica 16",
|
|
key="--tab-repo--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.exclusions"),
|
|
exclusions_col,
|
|
font="helvetica 16",
|
|
key="--tab-exclusions--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.retention_policy"),
|
|
retention_col,
|
|
font="helvetica 16",
|
|
key="--tab-retention--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.pre_post"),
|
|
pre_post_col,
|
|
font="helvetica 16",
|
|
key="--tab-hooks--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.prometheus_config"),
|
|
prometheus_col,
|
|
font="helvetica 16",
|
|
key="--tab-prometheus--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.env_variables"),
|
|
env_col,
|
|
font="helvetica 16",
|
|
key="--tab-env--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
],
|
|
]
|
|
|
|
_layout = [
|
|
[
|
|
sg.Column(
|
|
object_selector,
|
|
)
|
|
],
|
|
[
|
|
sg.TabGroup(
|
|
tab_group_layout, enable_events=True, key="--object-tabgroup--"
|
|
)
|
|
],
|
|
]
|
|
return _layout
|
|
|
|
def global_options_layout():
|
|
""" "
|
|
Returns layout for global options that can't be overridden by group / repo settings
|
|
"""
|
|
identity_col = [
|
|
[sg.Text(_t("config_gui.available_variables_id"))],
|
|
[
|
|
sg.Text(_t("config_gui.machine_id"), size=(40, 1)),
|
|
sg.Input(key="identity.machine_id", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.machine_group"), size=(40, 1)),
|
|
sg.Input(key="identity.machine_group", size=(50, 1)),
|
|
],
|
|
]
|
|
global_options_col = [
|
|
[sg.Text(_t("config_gui.available_variables"))],
|
|
[
|
|
sg.Text(_t("config_gui.auto_upgrade"), size=(40, 1)),
|
|
sg.Checkbox("", key="global_options.auto_upgrade", size=(41, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.auto_upgrade_server_url"), size=(40, 1)),
|
|
sg.Input(key="global_options.auto_upgrade_server_url", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.auto_upgrade_server_username"), size=(40, 1)),
|
|
sg.Input(
|
|
key="global_options.auto_upgrade_server_username", size=(50, 1)
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.auto_upgrade_server_password"), size=(40, 1)),
|
|
sg.Input(
|
|
key="global_options.auto_upgrade_server_password", size=(50, 1)
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.auto_upgrade_percent_chance"), size=(40, 1)),
|
|
sg.Input(
|
|
key="global_options.auto_upgrade_percent_chance", size=(50, 1)
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.auto_upgrade_interval"), size=(40, 1)),
|
|
sg.Input(key="global_options.auto_upgrade_interval", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("generic.identity"), size=(40, 1)),
|
|
sg.Input(key="global_options.auto_upgrade_host_identity", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("generic.group"), size=(40, 1)),
|
|
sg.Input(key="global_options.auto_upgrade_group", size=(50, 1)),
|
|
],
|
|
[sg.HorizontalSeparator()],
|
|
]
|
|
|
|
global_prometheus_col = [
|
|
[sg.Text(_t("config_gui.available_variables"))],
|
|
[
|
|
sg.Checkbox(
|
|
_t("config_gui.enable_prometheus"),
|
|
key="global_prometheus.metrics",
|
|
size=(41, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.metrics_destination"), size=(40, 1)),
|
|
sg.Input(key="global_prometheus.destination", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text("", size=(40, 1)),
|
|
sg.Text(
|
|
"Ex: /var/lib/node_exporter/textfile_collector/npbackup.prom",
|
|
size=(50, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Text("", size=(40, 1)),
|
|
sg.Text(
|
|
"Ex: https://push.domain.tld/metrics/job/${BACKUP_JOB}",
|
|
size=(50, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.no_cert_verify"), size=(40, 1)),
|
|
sg.Checkbox("", key="global_prometheus.no_cert_verify", size=(41, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.metrics_username"), size=(40, 1)),
|
|
sg.Input(key="global_prometheus.http_username", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.metrics_password"), size=(40, 1)),
|
|
sg.Input(key="global_prometheus.http_password", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.instance"), size=(40, 1)),
|
|
sg.Input(key="global_prometheus.instance", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Column(
|
|
[
|
|
[sg.Button("+", key="--ADD-PROMETHEUS-LABEL--", size=(3, 1))],
|
|
[
|
|
sg.Button(
|
|
"-", key="--REMOVE-PROMETHEUS-LABEL--", size=(3, 1)
|
|
)
|
|
],
|
|
],
|
|
pad=0,
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="global_prometheus.additional_labels",
|
|
headings=[_t("generic.value")],
|
|
col0_heading=_t("config_gui.additional_labels"),
|
|
col0_width=1,
|
|
auto_size_columns=True,
|
|
justification="L",
|
|
num_rows=4,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
expand_x=True,
|
|
),
|
|
],
|
|
]
|
|
|
|
tab_group_layout = [
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.machine_identification"),
|
|
identity_col,
|
|
font="helvetica 16",
|
|
key="--tab-global-identification--",
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("generic.options"),
|
|
global_options_col,
|
|
font="helvetica 16",
|
|
key="--tab-global-options--",
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.prometheus_config"),
|
|
global_prometheus_col,
|
|
font="helvetica 16",
|
|
key="--tab-global-prometheus--",
|
|
)
|
|
],
|
|
]
|
|
|
|
_layout = [
|
|
[
|
|
sg.TabGroup(
|
|
tab_group_layout, enable_events=True, key="--global-tabgroup--"
|
|
)
|
|
],
|
|
]
|
|
return _layout
|
|
|
|
def config_layout() -> List[list]:
|
|
buttons = [
|
|
[
|
|
sg.Push(),
|
|
sg.Button(
|
|
_t("config_gui.add_object"), key="-OBJECT-CREATE-", size=(28, 1)
|
|
),
|
|
sg.Button(
|
|
_t("config_gui.delete_object"), key="-OBJECT-DELETE-", size=(28, 1)
|
|
),
|
|
sg.Button(_t("generic.cancel"), key="--CANCEL--", size=(13, 1)),
|
|
sg.Button(_t("generic.accept"), key="--ACCEPT--", size=(13, 1)),
|
|
]
|
|
]
|
|
|
|
object_list = get_objects()
|
|
scheduled_task_col = [
|
|
[
|
|
sg.Text(
|
|
textwrap.fill(
|
|
f"{_t('config_gui.scheduled_task_explanation')}", width=120
|
|
),
|
|
size=(100, 4),
|
|
)
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.select_object")),
|
|
sg.Combo(
|
|
object_list,
|
|
default_value=object_list[0] if object_list else None,
|
|
key="-OBJECT-SELECT-TASKS-",
|
|
enable_events=True,
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.create_backup_scheduled_task_every"), size=(40, 1)
|
|
),
|
|
sg.Input("15", key="scheduled_backup_task_interval", size=(4, 1)),
|
|
sg.Text(_t("generic.minutes"), size=(10, 1)),
|
|
sg.Button(_t("generic.create"), key="create_backup_interval_task"),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.create_backup_scheduled_task_at"), size=(40, 1)),
|
|
sg.Input("22", key="scheduled_backup_task_hour", size=(4, 1)),
|
|
sg.Text(_t("generic.hours"), size=(10, 1)),
|
|
sg.Input("00", key="scheduled_backup_task_minute", size=(4, 1)),
|
|
sg.Text(_t("generic.minutes"), size=(10, 1)),
|
|
sg.Button(_t("generic.create"), key="create_backup_daily_task"),
|
|
],
|
|
[
|
|
sg.HorizontalSeparator(),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.create_housekeeping_scheduled_task_at"), size=(40, 1)
|
|
),
|
|
sg.Input("22", key="scheduled_housekeeping_task_hour", size=(4, 1)),
|
|
sg.Text(_t("generic.hours"), size=(10, 1)),
|
|
sg.Input("00", key="scheduled_housekeeping_task_minute", size=(4, 1)),
|
|
sg.Text(_t("generic.minutes"), size=(10, 1)),
|
|
sg.Button(_t("generic.create"), key="create_housekeeping_daily_task"),
|
|
],
|
|
]
|
|
|
|
tab_group_layout = [
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.repo_group_config"),
|
|
object_layout(),
|
|
key="--repo-group-config--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
pad=0,
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("config_gui.global_config"),
|
|
global_options_layout(),
|
|
key="--global-config--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
pad=0,
|
|
)
|
|
],
|
|
[
|
|
sg.Tab(
|
|
_t("generic.scheduled_task"),
|
|
scheduled_task_col,
|
|
font="helvetica 16",
|
|
key="--tab-global-scheduled_task--",
|
|
)
|
|
],
|
|
]
|
|
|
|
_global_layout = [
|
|
[
|
|
sg.TabGroup(
|
|
tab_group_layout,
|
|
enable_events=True,
|
|
key="--configtabgroup--",
|
|
expand_x=True,
|
|
expand_y=True,
|
|
pad=0,
|
|
)
|
|
],
|
|
[
|
|
sg.Push(),
|
|
sg.Column(
|
|
buttons,
|
|
),
|
|
],
|
|
]
|
|
return _global_layout
|
|
|
|
right_click_menu = ["", [_t("config_gui.show_decrypted")]]
|
|
window = sg.Window(
|
|
title="Configuration",
|
|
layout=config_layout(),
|
|
# size=(800, 650),
|
|
auto_size_text=True,
|
|
auto_size_buttons=False,
|
|
no_titlebar=False,
|
|
grab_anywhere=True,
|
|
keep_on_top=False,
|
|
alpha_channel=1.0,
|
|
default_button_element_size=(16, 1),
|
|
right_click_menu=right_click_menu,
|
|
finalize=True,
|
|
enable_close_attempted_event=True,
|
|
)
|
|
|
|
# Init fresh config objects
|
|
BAD_KEYS_FOUND_IN_CONFIG = set()
|
|
backup_paths_tree = sg.TreeData()
|
|
tags_tree = sg.TreeData()
|
|
exclude_patterns_tree = sg.TreeData()
|
|
exclude_files_tree = sg.TreeData()
|
|
retention_policy_tags_tree = sg.TreeData()
|
|
pre_exec_commands_tree = sg.TreeData()
|
|
post_exec_commands_tree = sg.TreeData()
|
|
global_prometheus_labels_tree = sg.TreeData()
|
|
env_variables_tree = sg.TreeData()
|
|
encrypted_env_variables_tree = sg.TreeData()
|
|
|
|
# Update gui with first default object (repo or group)
|
|
full_config = update_object_gui(full_config, 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
|
|
current_object_type = None
|
|
current_object_name = None
|
|
|
|
if config_file:
|
|
window.set_title(f"Configuration - {config_file}")
|
|
|
|
while True:
|
|
event, values = window.read()
|
|
# Get object type for various delete operations
|
|
object_type, object_name = get_object_from_combo(values["-OBJECT-SELECT-"])
|
|
if not current_object_type and not current_object_name:
|
|
current_object_type, current_object_name = object_type, object_name
|
|
if event in (
|
|
sg.WIN_CLOSED,
|
|
sg.WIN_X_EVENT,
|
|
"--CANCEL--",
|
|
"-WINDOW CLOSE ATTEMPTED-",
|
|
):
|
|
break
|
|
|
|
if event == "-OBJECT-SELECT-" or event == "repo_group":
|
|
# Update full_config with current object before updating
|
|
full_config = update_config_dict(
|
|
full_config, current_object_type, current_object_name, values
|
|
)
|
|
current_object_type, current_object_name = object_type, object_name
|
|
full_config = update_object_gui(
|
|
full_config, current_object_type, current_object_name, unencrypted=False
|
|
)
|
|
update_global_gui(full_config, unencrypted=False)
|
|
continue
|
|
if event == "-OBJECT-DELETE-":
|
|
object_type, object_name = get_object_from_combo(values["-OBJECT-SELECT-"])
|
|
# if object_type == "repos" and object_name == "default":
|
|
# sg.popup_error(_t("config_gui.cannot_delete_default_repo"))
|
|
# continue
|
|
# if object_type == "groups" and object_name == "default_group":
|
|
# sg.popup_error(_t("config_gui.cannot_delete_default_group"))
|
|
# continue
|
|
full_config = delete_object(full_config, values["-OBJECT-SELECT-"])
|
|
current_object_type, current_object_name = update_object_selector()
|
|
continue
|
|
if event == "-OBJECT-CREATE-":
|
|
full_config, _object_type, _object_name = create_object(full_config)
|
|
if _object_type and _object_name:
|
|
object_type = _object_type
|
|
object_name = _object_name
|
|
update_object_selector(object_name, object_type)
|
|
current_object_type = object_type
|
|
current_object_name = object_name
|
|
continue
|
|
if event == "--SET-PERMISSIONS--":
|
|
manager_password = configuration.get_manager_password(
|
|
full_config, object_name
|
|
)
|
|
if not manager_password or ask_manager_password(manager_password):
|
|
# We need to update full_config with current GUI values before using or modifying it
|
|
full_config = update_config_dict(
|
|
full_config, current_object_type, current_object_name, values
|
|
)
|
|
full_config = set_permissions(
|
|
full_config,
|
|
object_type=current_object_type,
|
|
object_name=current_object_name,
|
|
)
|
|
full_config = update_object_gui(
|
|
full_config,
|
|
current_object_type,
|
|
current_object_name,
|
|
unencrypted=False,
|
|
)
|
|
update_global_gui(full_config, unencrypted=False)
|
|
continue
|
|
if event == "backup_opts.source_type":
|
|
source_type = get_key_from_value(
|
|
combo_boxes["backup_opts.source_type"],
|
|
values["backup_opts.source_type"],
|
|
)
|
|
update_source_layout(source_type)
|
|
continue
|
|
if event in (
|
|
"--ADD-PATHS-FILE--",
|
|
"--ADD-PATHS-FOLDER--",
|
|
"--ADD-PATHS-MANUALLY--",
|
|
"--ADD-EXCLUDE-FILE--",
|
|
"--ADD-EXCLUDE-FILE-MANUALLY--",
|
|
):
|
|
tree = None
|
|
node = None
|
|
icon = None
|
|
key = None
|
|
if event in ("--ADD-PATHS-FILE--", "--ADD-EXCLUDE-FILE--"):
|
|
if event == "--ADD-PATHS-FILE--":
|
|
key = "backup_opts.paths"
|
|
tree = backup_paths_tree
|
|
if event == "--ADD-EXCLUDE-FILE--":
|
|
key = "backup_opts.exclude_files"
|
|
tree = exclude_files_tree
|
|
node = values[event]
|
|
icon = FILE_ICON
|
|
elif event == "--ADD-PATHS-FOLDER--":
|
|
key = "backup_opts.paths"
|
|
tree = backup_paths_tree
|
|
node = values[event]
|
|
icon = FOLDER_ICON
|
|
elif event == "--ADD-PATHS-MANUALLY--":
|
|
key = "backup_opts.paths"
|
|
tree = backup_paths_tree
|
|
node = sg.PopupGetText(_t("generic.add_manually"))
|
|
if node and os.path.exists(node) and os.path.isdir(node):
|
|
icon = FOLDER_ICON
|
|
else:
|
|
icon = FILE_ICON
|
|
elif event == "--ADD-EXCLUDE-FILE-MANUALLY--":
|
|
key = "backup_opts.exclude_files"
|
|
tree = exclude_files_tree
|
|
node = sg.PopupGetText(_t("generic.add_manually"))
|
|
icon = FILE_ICON
|
|
if tree and node:
|
|
# Check if node is ADD-PATH-FILES which can contain multiple elements separated by semicolon
|
|
if key == "backup_opts.paths" and ";" in node:
|
|
for path in node.split(";"):
|
|
tree.insert("", path, path, path, icon=icon)
|
|
else:
|
|
tree.insert("", node, node, node, icon=icon)
|
|
window[key].update(values=tree)
|
|
continue
|
|
if event in (
|
|
"--ADD-BACKUP-TAG--",
|
|
"--ADD-EXCLUDE-PATTERN--",
|
|
"--ADD-RETENTION-TAG--",
|
|
"--ADD-PRE-EXEC-COMMAND--",
|
|
"--ADD-POST-EXEC-COMMAND--",
|
|
"--ADD-PROMETHEUS-LABEL--",
|
|
"--ADD-ENV-VARIABLE--",
|
|
"--ADD-ENCRYPTED-ENV-VARIABLE--",
|
|
"--ADD-S3-IDENTITY--",
|
|
"--ADD-AZURE-IDENTITY--",
|
|
"--ADD-B2-IDENTITY--",
|
|
"--ADD-GCS-IDENTITY--",
|
|
"--REMOVE-PATHS--",
|
|
"--REMOVE-BACKUP-TAG--",
|
|
"--REMOVE-EXCLUDE-PATTERN--",
|
|
"--REMOVE-EXCLUDE-FILE--",
|
|
"--REMOVE-RETENTION-TAG--",
|
|
"--REMOVE-PRE-EXEC-COMMAND--",
|
|
"--REMOVE-POST-EXEC-COMMAND--",
|
|
"--REMOVE-PROMETHEUS-LABEL--",
|
|
"--REMOVE-ENV-VARIABLE--",
|
|
"--REMOVE-ENCRYPTED-ENV-VARIABLE--",
|
|
):
|
|
popup_text = None
|
|
option_key = None
|
|
if "PATHS" in event:
|
|
option_key = "backup_opts.paths"
|
|
tree = backup_paths_tree
|
|
elif "BACKUP-TAG" in event:
|
|
popup_text = _t("config_gui.enter_tag")
|
|
tree = tags_tree
|
|
option_key = "backup_opts.tags"
|
|
elif "EXCLUDE-PATTERN" in event:
|
|
popup_text = _t("config_gui.enter_pattern")
|
|
tree = exclude_patterns_tree
|
|
option_key = "backup_opts.exclude_patterns"
|
|
elif "EXCLUDE-FILE" in event:
|
|
popup_text = None
|
|
tree = exclude_files_tree
|
|
option_key = "backup_opts.exclude_files"
|
|
elif "RETENTION-TAG" in event:
|
|
popup_text = _t("config_gui.enter_tag")
|
|
tree = retention_policy_tags_tree
|
|
option_key = "repo_opts.retention_policy.tags"
|
|
elif "PRE-EXEC-COMMAND" in event:
|
|
popup_text = _t("config_gui.enter_command")
|
|
tree = pre_exec_commands_tree
|
|
option_key = "backup_opts.pre_exec_commands"
|
|
elif "POST-EXEC-COMMAND" in event:
|
|
popup_text = _t("config_gui.enter_command")
|
|
tree = post_exec_commands_tree
|
|
option_key = "backup_opts.post_exec_commands"
|
|
elif "PROMETHEUS-LABEL" in event:
|
|
popup_text = _t("config_gui.enter_label")
|
|
tree = global_prometheus_labels_tree
|
|
option_key = "global_prometheus.additional_labels"
|
|
elif (
|
|
"ENCRYPTED-ENV-VARIABLE" in event
|
|
or "S3-IDENTITY--" in event
|
|
or "AZURE-IDENTITY--" in event
|
|
or "B2-IDENTITY--" in event
|
|
or "GCS-IDENTITY--" in event
|
|
):
|
|
tree = encrypted_env_variables_tree
|
|
option_key = "env.encrypted_env_variables"
|
|
elif "ENV-VARIABLE" in event:
|
|
tree = env_variables_tree
|
|
option_key = "env.env_variables"
|
|
|
|
if event.startswith("--ADD-"):
|
|
icon = TREE_ICON
|
|
if "ENV-VARIABLE" in event or "ENCRYPTED-ENV-VARIABLE" in event:
|
|
var_name = sg.PopupGetText(_t("config_gui.enter_var_name"))
|
|
var_value = sg.PopupGetText(_t("config_gui.enter_var_value"))
|
|
if var_name and var_value:
|
|
if tree.tree_dict.get(var_name):
|
|
tree.delete(var_name)
|
|
tree.insert("", var_name, var_name, [var_value], icon=icon)
|
|
elif "PROMETHEUS-LABEL" in event:
|
|
var_name = sg.PopupGetText(_t("config_gui.enter_label_name"))
|
|
var_value = sg.PopupGetText(_t("config_gui.enter_label_value"))
|
|
if var_name and var_value:
|
|
if tree.tree_dict.get(var_name):
|
|
tree.delete(var_name)
|
|
tree.insert("", var_name, var_name, [var_value], icon=icon)
|
|
elif (
|
|
"S3-IDENTITY--" in event
|
|
or "AZURE-IDENTITY--" in event
|
|
or "B2-IDENTITY--" in event
|
|
or "GCS-IDENTITY--" in event
|
|
):
|
|
if "S3-IDENTITY--" in event:
|
|
var_names = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
|
|
elif "AZURE-IDENTITY--" in event:
|
|
var_names = [
|
|
"AZURE_ACCOUNT_KEY",
|
|
"AZURE_ACCOUNT_SAS",
|
|
"AZURE_ACCOUNT_NAME",
|
|
]
|
|
elif "B2-IDENTITY--" in event:
|
|
var_names = ["B2_ACCOUNT_ID", "B2_ACCOUNT_KEY"]
|
|
elif "GCS-IDENTITY--" in event:
|
|
var_names = [
|
|
"GOOGLE_PROJECT_ID",
|
|
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
]
|
|
else:
|
|
sg.popup_error("Bad identity given")
|
|
break
|
|
for var_name in var_names:
|
|
var_value = sg.PopupGetText(var_name)
|
|
if var_value:
|
|
if tree.tree_dict.get(var_name):
|
|
tree.delete(var_name)
|
|
tree.insert("", var_name, var_name, [var_value], icon=icon)
|
|
else:
|
|
sg.Popup_error(_t("config_gui.value_cannot_be_empty"))
|
|
break
|
|
else:
|
|
node = sg.PopupGetText(popup_text)
|
|
if node:
|
|
if tree.tree_dict.get(node):
|
|
tree.delete(node)
|
|
tree.insert("", node, node, node, icon=icon)
|
|
if event.startswith("--REMOVE-"):
|
|
for key in values[option_key]:
|
|
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"),
|
|
keep_on_top=True,
|
|
)
|
|
continue
|
|
tree.delete(key)
|
|
window[option_key].Update(values=tree)
|
|
continue
|
|
if event == "--ACCEPT--":
|
|
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
|
|
)
|
|
result = configuration.save_config(config_file, full_config)
|
|
if result:
|
|
sg.Popup(_t("config_gui.configuration_saved"), keep_on_top=True)
|
|
break
|
|
sg.PopupError(_t("config_gui.cannot_save_configuration"), keep_on_top=True)
|
|
continue
|
|
if event == _t("config_gui.show_decrypted"):
|
|
manager_password = configuration.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):
|
|
full_config = update_object_gui(
|
|
full_config,
|
|
current_object_type,
|
|
current_object_name,
|
|
unencrypted=True,
|
|
)
|
|
update_global_gui(full_config, unencrypted=True)
|
|
continue
|
|
if event in (
|
|
"create_backup_interval_task",
|
|
"create_backup_daily_task",
|
|
"create_housekeeping_daily_task",
|
|
):
|
|
object_type, object_name = get_object_from_combo(
|
|
values["-OBJECT-SELECT-TASKS-"]
|
|
)
|
|
if object_type == "groups":
|
|
task_repo_group = object_name
|
|
task_repo_name = None
|
|
else:
|
|
task_repo_name = object_name
|
|
task_repo_group = None
|
|
|
|
try:
|
|
interval = None
|
|
hour = None
|
|
minute = None
|
|
if event == "create_housekeeping_daily_task":
|
|
task_type = "housekeeping"
|
|
hour = values["scheduled_housekeeping_task_hour"]
|
|
minute = values["scheduled_housekeeping_task_minute"]
|
|
else:
|
|
task_type = "backup"
|
|
if event == "create_backup_interval_task":
|
|
interval = values["scheduled_backup_task_interval"]
|
|
else:
|
|
hour = values["scheduled_backup_task_hour"]
|
|
minute = values["scheduled_backup_task_minute"]
|
|
|
|
result = create_scheduled_task(
|
|
config_file=config_file,
|
|
task_type=task_type,
|
|
repo=task_repo_name,
|
|
group=task_repo_group,
|
|
interval_minutes=interval,
|
|
hour=hour,
|
|
minute=minute,
|
|
)
|
|
if result:
|
|
sg.Popup(
|
|
_t("config_gui.scheduled_task_creation_success"),
|
|
keep_on_top=True,
|
|
)
|
|
else:
|
|
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}",
|
|
keep_on_top=True,
|
|
)
|
|
continue
|
|
if event == "repo_uri":
|
|
for cloud_provider in ["s3", "azure", "b2", "gs"]:
|
|
if values["repo_uri"].startswith(cloud_provider + ":"):
|
|
window["repo_uri_cloud_hint"].Update(visible=True)
|
|
break
|
|
else:
|
|
window["repo_uri_cloud_hint"].Update(visible=False)
|
|
window.close()
|
|
return full_config
|