mirror of
https://github.com/netinvent/npbackup.git
synced 2025-10-11 14:06:30 +08:00
1851 lines
70 KiB
Python
1851 lines
70 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-2024 NetInvent"
|
|
__license__ = "GPL-3.0-only"
|
|
__build__ = "2024041501"
|
|
|
|
|
|
from typing import List, Tuple
|
|
import os
|
|
import pathlib
|
|
from logging import getLogger
|
|
import npbackup.gui.PySimpleGUI as sg
|
|
import textwrap
|
|
from ruamel.yaml.comments import CommentedMap
|
|
import npbackup.configuration as configuration
|
|
from ofunctions.misc import get_key_from_value
|
|
from npbackup.core.i18n_helper import _t
|
|
from npbackup.path_helper import CURRENT_EXECUTABLE
|
|
from npbackup.customization import (
|
|
INHERITED_ICON,
|
|
NON_INHERITED_ICON,
|
|
FILE_ICON,
|
|
FOLDER_ICON,
|
|
INHERITED_FILE_ICON,
|
|
INHERITED_FOLDER_ICON,
|
|
TREE_ICON,
|
|
INHERITED_TREE_ICON,
|
|
)
|
|
|
|
if os.name == "nt":
|
|
from npbackup.windows.task import create_scheduled_task
|
|
|
|
logger = getLogger()
|
|
|
|
|
|
# Monkeypatching PySimpleGUI
|
|
# @PySimpleGUI: 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:
|
|
pass
|
|
|
|
|
|
sg.TreeData.delete = delete
|
|
|
|
|
|
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"))
|
|
return False
|
|
return True
|
|
|
|
|
|
def config_gui(full_config: dict, config_file: str):
|
|
logger.info("Launching configuration GUI")
|
|
|
|
# Don't let PySimpleGUI handle key errros 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"),
|
|
},
|
|
"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"),
|
|
"full": _t("config_gui.full_perms"),
|
|
},
|
|
}
|
|
|
|
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
|
|
"""
|
|
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:
|
|
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 = values["-OBJECT-TYPE-"]
|
|
object_name = values["-OBJECT-NAME-"]
|
|
if object_type == "repo":
|
|
if full_config.g(f"repos.{object_name}"):
|
|
sg.PopupError(
|
|
_t("config_gui.repo_already_exists"), keep_on_top=True
|
|
)
|
|
continue
|
|
full_config.s(f"repos.{object_name}", CommentedMap())
|
|
elif object_type == "group":
|
|
if full_config.g(f"groups.{object_name}"):
|
|
sg.PopupError(
|
|
_t("config_gui.group_already_exists"), keep_on_top=True
|
|
)
|
|
continue
|
|
full_config.s(f"groups.{object_name}", CommentedMap())
|
|
else:
|
|
raise ValueError("Bogus object type given")
|
|
window.close()
|
|
update_object_gui(None, unencrypted=False)
|
|
return full_config
|
|
|
|
def delete_object(full_config: dict, object_name: str) -> dict:
|
|
object_type, object_name = get_object_from_combo(object_name)
|
|
result = sg.PopupYesNo(
|
|
_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)
|
|
return full_config
|
|
|
|
def update_object_selector() -> None:
|
|
objects = get_objects()
|
|
window["-OBJECT-SELECT-"].Update(objects)
|
|
window["-OBJECT-SELECT-"].Update(value=objects[0])
|
|
|
|
def get_object_from_combo(combo_value: str) -> Tuple[str, str]:
|
|
"""
|
|
Extracts selected object from combobox
|
|
Returns object type and name
|
|
"""
|
|
|
|
if combo_value.startswith("Repo: "):
|
|
object_type = "repo"
|
|
object_name = combo_value[len("Repo: ") :]
|
|
elif combo_value.startswith("Group: "):
|
|
object_type = "group"
|
|
object_name = combo_value[len("Group: ") :]
|
|
return object_type, object_name
|
|
|
|
def update_gui_values(key, value, inherited, object_type, unencrypted):
|
|
"""
|
|
Update gui values depending on their type
|
|
"""
|
|
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 prometheus_labels_tree
|
|
nonlocal env_variables_tree
|
|
nonlocal encrypted_env_variables_tree
|
|
|
|
if key in ("repo_uri", "repo_group"):
|
|
if object_type == "group":
|
|
window[key].Disabled = True
|
|
else:
|
|
window[key].Disabled = False
|
|
# Update the combo group selector
|
|
window[key].Update(value=value)
|
|
return
|
|
|
|
try:
|
|
# Don't bother to update repo name
|
|
# Also permissions / manager_password are in a separate gui
|
|
# And we don't want to show __current_manager_password
|
|
if key in (
|
|
"name",
|
|
"permissions",
|
|
"manager_password",
|
|
"__current_manager_password",
|
|
"is_protected",
|
|
):
|
|
return
|
|
# Don't show sensible info unless unencrypted requested
|
|
if not unencrypted:
|
|
# Use last part of key only
|
|
if key in configuration.ENCRYPTED_OPTIONS:
|
|
try:
|
|
if value is None or value == "":
|
|
return
|
|
if isinstance(value, dict):
|
|
for k in value.keys():
|
|
value[k] = ENCRYPTED_DATA_PLACEHOLDER
|
|
elif not str(value).startswith(configuration.ID_STRING):
|
|
value = ENCRYPTED_DATA_PLACEHOLDER
|
|
except (KeyError, TypeError):
|
|
pass
|
|
|
|
# Update tree objects
|
|
if key == "backup_opts.paths":
|
|
for val in value:
|
|
if pathlib.Path(val).is_dir():
|
|
if object_type != "group" and inherited[val]:
|
|
icon = INHERITED_FOLDER_ICON
|
|
else:
|
|
icon = FOLDER_ICON
|
|
else:
|
|
if object_type != "group" 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",
|
|
"prometheus.additional_labels",
|
|
):
|
|
if key == "backup_opts.tags":
|
|
tree = tags_tree
|
|
if key == "backup_opts.pre_exec_commands":
|
|
tree = pre_exec_commands_tree
|
|
if key == "backup_opts.post_exec_commands":
|
|
tree = post_exec_commands_tree
|
|
if key == "backup_opts.exclude_files":
|
|
tree = exclude_files_tree
|
|
if key == "backup_opts.exclude_patterns":
|
|
tree = exclude_patterns_tree
|
|
if key == "prometheus.additional_labels":
|
|
tree = prometheus_labels_tree
|
|
|
|
for val in value:
|
|
if object_type != "group" and inherited[val]:
|
|
icon = INHERITED_TREE_ICON
|
|
else:
|
|
icon = TREE_ICON
|
|
tree.insert("", val, val, val, icon=icon)
|
|
window[key].Update(values=tree)
|
|
return
|
|
|
|
if key in ("env.env_variables", "env.encrypted_env_variables"):
|
|
if key == "env.env_variables":
|
|
tree = env_variables_tree
|
|
if key == "env.encrypted_env_variables":
|
|
tree = encrypted_env_variables_tree
|
|
|
|
for skey, val in value.items():
|
|
if object_type != "group" and inherited[skey]:
|
|
icon = INHERITED_TREE_ICON
|
|
else:
|
|
icon = TREE_ICON
|
|
tree.insert("", skey, skey, values=[val], icon=icon)
|
|
window[key].Update(values=tree)
|
|
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",
|
|
):
|
|
# We don't need a better split here since the value string comes from BytesConverter
|
|
# which always provides "0 MiB" or "5 KB" etc.
|
|
value, unit = value.split(" ")
|
|
window[f"{key}_unit"].Update(unit)
|
|
|
|
if isinstance(value, list):
|
|
value = "\n".join(value)
|
|
|
|
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:
|
|
logger.error(f"No GUI equivalent for key {key}.")
|
|
logger.debug("Trace:", exc_info=True)
|
|
except TypeError as exc:
|
|
logger.error(f"Error: {exc} for key {key}.")
|
|
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",
|
|
):
|
|
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(object_name=None, unencrypted=False):
|
|
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 prometheus_labels_tree
|
|
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]
|
|
|
|
# First we need to clear the whole GUI to reload new values
|
|
for key in window.AllKeysDict:
|
|
# We only clear config keys, wihch 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()
|
|
pre_exec_commands_tree = sg.TreeData()
|
|
post_exec_commands_tree = sg.TreeData()
|
|
prometheus_labels_tree = sg.TreeData()
|
|
env_variables_tree = sg.TreeData()
|
|
encrypted_env_variables_tree = sg.TreeData()
|
|
|
|
object_type, object_name = get_object_from_combo(object_name)
|
|
|
|
if object_type == "repo":
|
|
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)
|
|
|
|
if object_type == "group":
|
|
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)
|
|
|
|
# Now let's iter over the whole config object and update keys accordingly
|
|
iter_over_config(
|
|
object_config, config_inheritance, object_type, unencrypted, None
|
|
)
|
|
|
|
def update_global_gui(full_config, unencrypted: bool = False):
|
|
global_config = CommentedMap()
|
|
|
|
# Only update global options gui with identified global keys
|
|
for key in full_config.keys():
|
|
if key in ("identity", "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 == "repo":
|
|
object_group = full_config.g(f"repos.{object_name}.repo_group")
|
|
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
|
|
# @PysimpleGUI: 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",
|
|
"prometheus.additional_labels",
|
|
]
|
|
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",
|
|
]
|
|
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}s.{object_name}.env.encrypted_env_variables.{k}"
|
|
)
|
|
|
|
for key, value in values.items():
|
|
# Don't update placeholders ;)
|
|
# TODO exclude encrypted env vars
|
|
if value == ENCRYPTED_DATA_PLACEHOLDER:
|
|
continue
|
|
if not isinstance(key, str) or (isinstance(key, str) and not "." in key):
|
|
# 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
|
|
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",
|
|
):
|
|
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",
|
|
):
|
|
continue
|
|
|
|
# Don't bother with inheritance on global options and host identity
|
|
if key.startswith("global_options") or key.startswith("identity"):
|
|
active_object_key = f"{key}"
|
|
current_value = full_config.g(active_object_key)
|
|
else:
|
|
active_object_key = f"{object_type}s.{object_name}.{key}"
|
|
current_value = full_config.g(active_object_key)
|
|
|
|
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
|
|
if full_config.g(inheritance_key) == value:
|
|
continue
|
|
|
|
if object_group:
|
|
inherited = full_config.g(inheritance_key)
|
|
else:
|
|
inherited = False
|
|
|
|
# Don't bother to update empty strings, empty lists and None
|
|
if not current_value and not value:
|
|
continue
|
|
# Don't bother to update values which haven't changed
|
|
if current_value == value:
|
|
continue
|
|
|
|
# Finally, update the config dictionary
|
|
# Debug WIP
|
|
# if object_type == "group":
|
|
# 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}")
|
|
full_config.s(active_object_key, value)
|
|
return full_config
|
|
|
|
def set_permissions(full_config: dict, 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"))
|
|
return full_config
|
|
repo_config, _ = configuration.get_repo_config(
|
|
full_config, object_name, eval_variables=False
|
|
)
|
|
permissions = list(combo_boxes["permissions"].values())
|
|
default_perm = repo_config.g("permissions")
|
|
if not default_perm:
|
|
default_perm = permissions[-1]
|
|
manager_password = repo_config.g("manager_password")
|
|
|
|
layout = [
|
|
[
|
|
sg.Text(_t("config_gui.permissions"), size=(40, 1)),
|
|
sg.Combo(permissions, default_value=default_perm, key="permissions"),
|
|
],
|
|
[sg.HorizontalSeparator()],
|
|
[
|
|
sg.Text(_t("config_gui.set_manager_password"), size=(40, 1)),
|
|
sg.Input(
|
|
manager_password,
|
|
key="-MANAGER-PASSWORD-",
|
|
size=(50, 1),
|
|
password_char="*",
|
|
),
|
|
# sg.Button(_t("generic.change"), key="--CHANGE-MANAGER-PASSWORD--") # TODO
|
|
],
|
|
[
|
|
sg.Push(),
|
|
sg.Button(_t("generic.cancel"), key="--CANCEL--"),
|
|
sg.Button(_t("generic.accept"), key="--ACCEPT--"),
|
|
],
|
|
]
|
|
|
|
window = sg.Window(_t("config_gui.permissions"), 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--":
|
|
if not values["-MANAGER-PASSWORD-"]:
|
|
sg.PopupError(
|
|
_t("config_gui.setting_permissions_requires_manager_password"),
|
|
keep_on_top=True,
|
|
)
|
|
continue
|
|
# TODO: Check password strength in a better way than this ^^
|
|
if len(values["-MANAGER-PASSWORD-"]) < 8:
|
|
sg.PopupError(
|
|
_t("config_gui.manager_password_too_short"), keep_on_top=True
|
|
)
|
|
continue
|
|
if not values["permissions"] in permissions:
|
|
sg.PopupError(_t("generic.bogus_data_given"), keep_on_top=True)
|
|
continue
|
|
# Transform translet permission value into key
|
|
permission = get_key_from_value(
|
|
combo_boxes["permissions"], values["permissions"]
|
|
)
|
|
repo_config.s("permissions", permission)
|
|
repo_config.s("manager_password", values["-MANAGER-PASSWORD-"])
|
|
break
|
|
window.close()
|
|
full_config.s(f"repos.{object_name}", repo_config)
|
|
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),
|
|
),
|
|
],
|
|
[
|
|
sg.Tree(
|
|
sg.TreeData(),
|
|
key="backup_opts.paths",
|
|
headings=[],
|
|
col0_heading=_t("generic.paths"),
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
],
|
|
[
|
|
sg.Input(visible=False, key="--ADD-PATHS-FILE--", enable_events=True),
|
|
sg.FilesBrowse(_t("generic.add_files"), target="--ADD-PATHS-FILE--"),
|
|
sg.Input(visible=False, key="--ADD-PATHS-FOLDER--", enable_events=True),
|
|
sg.FolderBrowse(
|
|
_t("generic.add_folder"), target="--ADD-PATHS-FOLDER--"
|
|
),
|
|
sg.Button(_t("generic.remove_selected"), key="--REMOVE-PATHS--"),
|
|
],
|
|
[
|
|
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-TAG--", size=(3, 1))],
|
|
[sg.Button("-", key="--REMOVE-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.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)
|
|
),
|
|
],
|
|
]
|
|
|
|
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("-", 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.Text(
|
|
_t("config_gui.exclude_files_larger_than"),
|
|
size=(40, 1),
|
|
),
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.backup_opts.exclude_files_larger_than",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=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.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)),
|
|
],
|
|
[
|
|
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=(40, 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.Button(_t("config_gui.set_permissions"), key="--SET-PERMISSIONS--")],
|
|
[
|
|
sg.Text(_t("config_gui.repo_group"), size=(40, 1)),
|
|
sg.Combo(
|
|
values=configuration.get_group_list(full_config), key="repo_group"
|
|
),
|
|
],
|
|
[
|
|
sg.Text(
|
|
_t("config_gui.minimum_backup_age"),
|
|
size=(40, 2),
|
|
),
|
|
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.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.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.Input(key="repo_opts.backend_connections", size=(8, 1)),
|
|
],
|
|
[sg.HorizontalSeparator()],
|
|
[sg.Text(_t("config_gui.retention_policy"))],
|
|
[
|
|
sg.Text(_t("config_gui.optional_ntp_server_uri"), size=(40, 1)),
|
|
sg.Input(
|
|
key="repo_opts.retention_strategy.ntp_time_server", size=(50, 1)
|
|
),
|
|
],
|
|
[
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Text(_t("config_gui.keep"), size=(30, 1)),
|
|
]
|
|
]
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_strategy.hourly",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_strategy.hourly", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.hourly"), size=(20, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_strategy.daily",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_strategy.daily", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.daily"), size=(20, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_strategy.weekly",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_strategy.weekly", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.weekly"), size=(20, 1)),
|
|
],
|
|
]
|
|
),
|
|
sg.Column(
|
|
[
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_strategy.monthly",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_strategy.monthly", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.monthly"), size=(20, 1)),
|
|
],
|
|
[
|
|
sg.Image(
|
|
NON_INHERITED_ICON,
|
|
key="inherited.repo_opts.retention_strategy.yearly",
|
|
tooltip=_t("config_gui.group_inherited"),
|
|
pad=1,
|
|
),
|
|
sg.Input(
|
|
key="repo_opts.retention_strategy.yearly", size=(3, 1)
|
|
),
|
|
sg.Text(_t("config_gui.yearly"), size=(20, 1)),
|
|
],
|
|
]
|
|
),
|
|
],
|
|
]
|
|
|
|
prometheus_col = [
|
|
[sg.Text(_t("config_gui.available_variables"))],
|
|
[
|
|
sg.Checkbox(
|
|
_t("config_gui.enable_prometheus"),
|
|
key="prometheus.metrics",
|
|
size=(41, 1),
|
|
),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.job_name"), size=(40, 1)),
|
|
sg.Input(key="prometheus.backup_job", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.metrics_destination"), size=(40, 1)),
|
|
sg.Input(key="prometheus.destination", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.no_cert_verify"), size=(40, 1)),
|
|
sg.Checkbox("", key="prometheus.no_cert_verify", size=(41, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.metrics_username"), size=(40, 1)),
|
|
sg.Input(key="prometheus.http_username", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.metrics_password"), size=(40, 1)),
|
|
sg.Input(key="prometheus.http_password", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("config_gui.instance"), size=(40, 1)),
|
|
sg.Input(key="prometheus.instance", size=(50, 1)),
|
|
],
|
|
[
|
|
sg.Text(_t("generic.group"), size=(40, 1)),
|
|
sg.Input(key="prometheus.group", 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="prometheus.additional_labels",
|
|
headings=[],
|
|
col0_heading=_t("config_gui.additional_labels"),
|
|
num_rows=4,
|
|
expand_x=True,
|
|
expand_y=True,
|
|
)
|
|
]
|
|
],
|
|
pad=0,
|
|
expand_x=True,
|
|
),
|
|
],
|
|
]
|
|
|
|
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.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=(50, 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.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 overrided 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_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()],
|
|
]
|
|
|
|
scheduled_task_col = [
|
|
[
|
|
sg.Text(_t("config_gui.create_scheduled_task_every")),
|
|
sg.Input(key="scheduled_task_interval", size=(4, 1)),
|
|
sg.Text(_t("generic.minutes")),
|
|
sg.Button(_t("generic.create"), key="create_task"),
|
|
],
|
|
[sg.Text(_t("config_gui.scheduled_task_explanation"))],
|
|
]
|
|
|
|
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("generic.scheduled_task"),
|
|
scheduled_task_col,
|
|
font="helvetica 16",
|
|
key="--tab-global-scheduled_task--",
|
|
)
|
|
],
|
|
]
|
|
|
|
_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.create_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)),
|
|
]
|
|
]
|
|
|
|
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,
|
|
)
|
|
],
|
|
]
|
|
|
|
_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(
|
|
"Configuration",
|
|
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,
|
|
)
|
|
|
|
backup_paths_tree = sg.TreeData()
|
|
tags_tree = sg.TreeData()
|
|
exclude_patterns_tree = sg.TreeData()
|
|
exclude_files_tree = sg.TreeData()
|
|
pre_exec_commands_tree = sg.TreeData()
|
|
post_exec_commands_tree = sg.TreeData()
|
|
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)
|
|
update_object_gui(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
|
|
current_object_type = None
|
|
current_object_name = None
|
|
|
|
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--"):
|
|
break
|
|
|
|
if event == "-OBJECT-SELECT-":
|
|
# 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
|
|
update_object_gui(values["-OBJECT-SELECT-"], unencrypted=False)
|
|
update_global_gui(full_config, unencrypted=False)
|
|
continue
|
|
if event == "-OBJECT-DELETE-":
|
|
full_config = delete_object(full_config, values["-OBJECT-SELECT-"])
|
|
update_object_selector()
|
|
continue
|
|
if event == "-OBJECT-CREATE-":
|
|
full_config = create_object(full_config)
|
|
update_object_selector()
|
|
continue
|
|
if event == "--SET-PERMISSIONS--":
|
|
manager_password = configuration.get_manager_password(
|
|
full_config, object_name
|
|
)
|
|
if ask_manager_password(manager_password):
|
|
full_config = set_permissions(full_config, values["-OBJECT-SELECT-"])
|
|
continue
|
|
if event in (
|
|
"--ADD-PATHS-FILE--",
|
|
"--ADD-PATHS-FOLDER--",
|
|
"--ADD-EXCLUDE-FILE--",
|
|
):
|
|
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
|
|
tree.insert("", node, node, node, icon=icon)
|
|
window[key].update(values=tree)
|
|
continue
|
|
if event in (
|
|
"--ADD-TAG--",
|
|
"--ADD-EXCLUDE-PATTERN--",
|
|
"--ADD-PRE-EXEC-COMMAND--",
|
|
"--ADD-POST-EXEC-COMMAND--",
|
|
"--ADD-PROMETHEUS-LABEL--",
|
|
"--ADD-ENV-VARIABLE--",
|
|
"--ADD-ENCRYPTED-ENV-VARIABLE--",
|
|
"--REMOVE-PATHS--",
|
|
"--REMOVE-TAG--",
|
|
"--REMOVE-EXCLUDE-PATTERN--",
|
|
"--REMOVE-EXCLUDE-FILE--",
|
|
"--REMOVE-PRE-EXEC-COMMAND--",
|
|
"--REMOVE-POST-EXEC-COMMAND--",
|
|
"--REMOVE-PROMETHEUS-LABEL--",
|
|
"--REMOVE-ENV-VARIABLE--",
|
|
"--REMOVE-ENCRYPTED-ENV-VARIABLE--",
|
|
):
|
|
if "PATHS" in event:
|
|
option_key = "backup_opts.paths"
|
|
tree = backup_paths_tree
|
|
elif "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 "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 = prometheus_labels_tree
|
|
option_key = "prometheus.additional_labels"
|
|
elif "ENCRYPTED-ENV-VARIABLE" 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:
|
|
tree.insert("", var_name, var_name, [var_value], icon=icon)
|
|
else:
|
|
node = sg.PopupGetText(popup_text)
|
|
if node:
|
|
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 (
|
|
INHERITED_TREE_ICON,
|
|
INHERITED_FILE_ICON,
|
|
INHERITED_FOLDER_ICON,
|
|
):
|
|
sg.PopupError(
|
|
_t("config_gui.cannot_remove_group_inherited_settings")
|
|
)
|
|
continue
|
|
tree.delete(key)
|
|
window[option_key].Update(values=tree)
|
|
continue
|
|
if event == "--ACCEPT--":
|
|
if (
|
|
not values["repo_opts.repo_password"]
|
|
and not values["repo_opts.repo_password_command"]
|
|
):
|
|
sg.PopupError(_t("config_gui.repo_password_cannot_be_empty"))
|
|
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)
|
|
logger.info("Configuration saved successfully.")
|
|
break
|
|
sg.PopupError(_t("config_gui.cannot_save_configuration"), keep_on_top=True)
|
|
logger.info("Could not save configuration")
|
|
continue
|
|
if event == _t("config_gui.show_decrypted"):
|
|
manager_password = configuration.get_manager_password(
|
|
full_config, object_name
|
|
)
|
|
if ask_manager_password(manager_password):
|
|
update_object_gui(values["-OBJECT-SELECT-"], unencrypted=True)
|
|
update_global_gui(full_config, unencrypted=True)
|
|
continue
|
|
if event == "create_task":
|
|
if os.name == "nt":
|
|
result = create_scheduled_task(
|
|
CURRENT_EXECUTABLE, values["scheduled_task_interval"]
|
|
)
|
|
if result:
|
|
sg.Popup(_t("config_gui.scheduled_task_creation_success"))
|
|
else:
|
|
sg.PopupError(_t("config_gui.scheduled_task_creation_failure"))
|
|
else:
|
|
sg.PopupError(_t("config_gui.scheduled_task_creation_failure"))
|
|
continue
|
|
window.close()
|
|
return full_config
|