mirror of
https://github.com/netinvent/npbackup.git
synced 2025-10-09 05:01:13 +08:00
WIP: refactor config UI
This commit is contained in:
parent
65d4b4fb60
commit
8874b94676
7 changed files with 62 additions and 93 deletions
|
@ -7,12 +7,12 @@ __intname__ = "npbackup.configuration"
|
||||||
__author__ = "Orsiris de Jong"
|
__author__ = "Orsiris de Jong"
|
||||||
__copyright__ = "Copyright (C) 2022-2023 NetInvent"
|
__copyright__ = "Copyright (C) 2022-2023 NetInvent"
|
||||||
__license__ = "GPL-3.0-only"
|
__license__ = "GPL-3.0-only"
|
||||||
__build__ = "2023121301"
|
__build__ = "2023121501"
|
||||||
__version__ = "2.0.0 for npbackup 2.3.0+"
|
__version__ = "2.0.0 for npbackup 2.3.0+"
|
||||||
|
|
||||||
CONF_VERSION = 2.3
|
CONF_VERSION = 2.3
|
||||||
|
|
||||||
from typing import Tuple, Optional, List, Callable, Any, Union
|
from typing import Tuple, Optional, List, Any, Union
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
@ -304,7 +304,6 @@ def get_repo_config(full_config: dict, repo_name: str = 'default', eval_variable
|
||||||
Returns a dict containing the repo config, with expanded variables
|
Returns a dict containing the repo config, with expanded variables
|
||||||
and a dict containing the repo interitance status
|
and a dict containing the repo interitance status
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def inherit_group_settings(repo_config: dict, group_config: dict) -> Tuple[dict, dict]:
|
def inherit_group_settings(repo_config: dict, group_config: dict) -> Tuple[dict, dict]:
|
||||||
"""
|
"""
|
||||||
iter over group settings, update repo_config, and produce an identical version of repo_config
|
iter over group settings, update repo_config, and produce an identical version of repo_config
|
||||||
|
@ -329,23 +328,30 @@ def get_repo_config(full_config: dict, repo_name: str = 'default', eval_variable
|
||||||
# TODO: Lists containing dicts won't be updated in repo_config here
|
# TODO: Lists containing dicts won't be updated in repo_config here
|
||||||
# we need to have
|
# we need to have
|
||||||
# for elt in list:
|
# for elt in list:
|
||||||
# recurse into elt if elt is dict
|
# recurse into elt if elt is dict
|
||||||
if isinstance(_repo_config.g(key), list):
|
if isinstance(_repo_config.g(key), list):
|
||||||
merged_lists = _repo_config.g(key) + _group_config.g(key)
|
merged_lists = _repo_config.g(key) + _group_config.g(key)
|
||||||
|
# Case where repo config already contains non list info but group config has list
|
||||||
|
elif _repo_config.g(key):
|
||||||
|
merged_lists = [_repo_config.g(key)] + _group_config.g(key)
|
||||||
else:
|
else:
|
||||||
merged_lists = _group_config.g(key)
|
merged_lists = _group_config.g(key)
|
||||||
_repo_config.s(key, merged_lists)
|
_repo_config.s(key, merged_lists)
|
||||||
_config_inheritance.s(key, True)
|
_config_inheritance.s(key, True)
|
||||||
else:
|
else:
|
||||||
# Tricky part
|
# repo_config may or may not already contain data
|
||||||
# repo_config may already contain a struct
|
|
||||||
if not _repo_config:
|
if not _repo_config:
|
||||||
_repo_config = CommentedMap()
|
_repo_config = CommentedMap()
|
||||||
_config_inheritance = CommentedMap()
|
_config_inheritance = CommentedMap()
|
||||||
if not _repo_config.g(key):
|
if not _repo_config.g(key):
|
||||||
_repo_config.s(key, value)
|
_repo_config.s(key, value)
|
||||||
_config_inheritance.s(key, True)
|
_config_inheritance.s(key, True)
|
||||||
|
# Case where repo_config contains list but group info has single str
|
||||||
|
elif isinstance(_repo_config.g(key), list) and value:
|
||||||
|
merged_lists = _repo_config.g(key) + [value]
|
||||||
|
_repo_config.s(key, merged_lists)
|
||||||
else:
|
else:
|
||||||
|
# In other cases, just keep repo confg
|
||||||
_config_inheritance.s(key, False)
|
_config_inheritance.s(key, False)
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -207,7 +207,7 @@ def _gui_update_state(
|
||||||
)
|
)
|
||||||
elif current_state is False:
|
elif current_state is False:
|
||||||
window["state-button"].Update(
|
window["state-button"].Update(
|
||||||
"{}: {}".format(_t("generic.too_old"), backup_tz),
|
"{}: {}".format(_t("generic.too_old"), backup_tz.replace(microsecond=0)),
|
||||||
button_color=GUI_STATE_OLD_BUTTON,
|
button_color=GUI_STATE_OLD_BUTTON,
|
||||||
)
|
)
|
||||||
elif current_state is None:
|
elif current_state is None:
|
||||||
|
@ -570,8 +570,7 @@ def _main_gui():
|
||||||
|
|
||||||
logger.info(f"Using configuration file {config_file}")
|
logger.info(f"Using configuration file {config_file}")
|
||||||
full_config = npbackup.configuration.load_config(config_file)
|
full_config = npbackup.configuration.load_config(config_file)
|
||||||
# TODO add a repo selector
|
repo_config, config_inheritance = npbackup.configuration.get_repo_config(full_config)
|
||||||
repo_config, inherit_config = npbackup.configuration.get_repo_config(full_config)
|
|
||||||
repo_list = npbackup.configuration.get_repo_list(full_config)
|
repo_list = npbackup.configuration.get_repo_list(full_config)
|
||||||
|
|
||||||
backup_destination = _t("main_gui.local_folder")
|
backup_destination = _t("main_gui.local_folder")
|
||||||
|
@ -661,7 +660,12 @@ def _main_gui():
|
||||||
window["snapshot-list"].expand(True, True)
|
window["snapshot-list"].expand(True, True)
|
||||||
|
|
||||||
window.read(timeout=1)
|
window.read(timeout=1)
|
||||||
current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
|
try:
|
||||||
|
current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
|
||||||
|
except ValueError:
|
||||||
|
current_state = None
|
||||||
|
backup_tz = None
|
||||||
|
snapshot_list = []
|
||||||
_gui_update_state(window, current_state, backup_tz, snapshot_list)
|
_gui_update_state(window, current_state, backup_tz, snapshot_list)
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read(timeout=60000)
|
event, values = window.read(timeout=60000)
|
||||||
|
@ -671,7 +675,7 @@ def _main_gui():
|
||||||
if event == "-active_repo-":
|
if event == "-active_repo-":
|
||||||
active_repo = values['-active_repo-']
|
active_repo = values['-active_repo-']
|
||||||
if full_config.g(f"repos.{active_repo}"):
|
if full_config.g(f"repos.{active_repo}"):
|
||||||
repo_config = npbackup.configuration.get_repo_config(full_config, active_repo)
|
repo_config, config_inheriteance = npbackup.configuration.get_repo_config(full_config, active_repo)
|
||||||
current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
|
current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
|
||||||
else:
|
else:
|
||||||
sg.PopupError("Repo not existent in config")
|
sg.PopupError("Repo not existent in config")
|
||||||
|
@ -783,4 +787,8 @@ def main_gui():
|
||||||
_main_gui()
|
_main_gui()
|
||||||
except _tkinter.TclError as exc:
|
except _tkinter.TclError as exc:
|
||||||
logger.critical(f'Tkinter error: "{exc}". Is this a headless server ?')
|
logger.critical(f'Tkinter error: "{exc}". Is this a headless server ?')
|
||||||
sys.exit(250)
|
sys.exit(250)
|
||||||
|
except Exception as exc:
|
||||||
|
sg.Popup(_t("config_gui.bogus_config_file") + f': {exc}')
|
||||||
|
raise #TODO replace with logger
|
||||||
|
sys.exit(251)
|
|
@ -15,11 +15,12 @@ import os
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
from ruamel.yaml.comments import CommentedMap
|
||||||
import npbackup.configuration as configuration
|
import npbackup.configuration as configuration
|
||||||
from ofunctions.misc import get_key_from_value
|
from ofunctions.misc import get_key_from_value
|
||||||
from npbackup.core.i18n_helper import _t
|
from npbackup.core.i18n_helper import _t
|
||||||
from npbackup.path_helper import CURRENT_EXECUTABLE
|
from npbackup.path_helper import CURRENT_EXECUTABLE
|
||||||
from npbackup.core.nuitka_helper import IS_COMPILED
|
from npbackup.customization import INHERITANCE_ICON
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
from npbackup.windows.task import create_scheduled_task
|
from npbackup.windows.task import create_scheduled_task
|
||||||
|
@ -108,7 +109,7 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
"""
|
"""
|
||||||
if key == "backup_admin_password":
|
if key == "backup_admin_password":
|
||||||
return
|
return
|
||||||
if key == "repo_uri":
|
if key in ("repo_uri", "repo_group"):
|
||||||
if object_type == "group":
|
if object_type == "group":
|
||||||
window[key].Disabled = True
|
window[key].Disabled = True
|
||||||
else:
|
else:
|
||||||
|
@ -137,10 +138,10 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
else:
|
else:
|
||||||
window[key].Update(value)
|
window[key].Update(value)
|
||||||
|
|
||||||
# Set inheritance on
|
# Enable inheritance icon when needed
|
||||||
inheritance_key = f'inherited.{key}'
|
inheritance_key = f'inherited.{key}'
|
||||||
if inheritance_key in window.AllKeysDict:
|
if inheritance_key in window.AllKeysDict:
|
||||||
window[f'inherited.{key}'].update(visible=True if inherited else False)
|
window[inheritance_key].update(visible=True if inherited else False)
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.error(f"No GUI equivalent for key {key}.")
|
logger.error(f"No GUI equivalent for key {key}.")
|
||||||
|
@ -177,31 +178,28 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
# First we need to clear the whole GUI to reload new values
|
# First we need to clear the whole GUI to reload new values
|
||||||
for key in window.AllKeysDict:
|
for key in window.AllKeysDict:
|
||||||
# We only clear config keys, wihch have '.' separator
|
# We only clear config keys, wihch have '.' separator
|
||||||
if "." in str(key):
|
if "." in str(key) and not "inherited" in str(key):
|
||||||
window[key]('')
|
window[key]('')
|
||||||
|
|
||||||
object_type, object_name = get_object_from_combo(object_name)
|
object_type, object_name = get_object_from_combo(object_name)
|
||||||
|
|
||||||
if object_type == 'repo':
|
if object_type == 'repo':
|
||||||
object_config, config_inheritance = configuration.get_repo_config(full_config, object_name, eval_variables=False)
|
object_config, config_inheritance = configuration.get_repo_config(full_config, object_name, eval_variables=False)
|
||||||
if object_type == 'group':
|
if object_type == 'group':
|
||||||
object_config = configuration.get_group_config(full_config, object_name, eval_variables=False)
|
object_config = configuration.get_group_config(full_config, object_name, eval_variables=False)
|
||||||
|
config_inheritance = None
|
||||||
|
|
||||||
# Now let's iter over the whole config object and update keys accordingly
|
# Now let's iter over the whole config object and update keys accordingly
|
||||||
iter_over_config(object_config, config_inheritance, object_type, unencrypted, None)
|
iter_over_config(object_config, config_inheritance, object_type, unencrypted, None)
|
||||||
|
|
||||||
|
|
||||||
def update_global_gui(full_config, unencrypted=False):
|
def update_global_gui(full_config, unencrypted=False):
|
||||||
# TODO
|
global_config = CommentedMap()
|
||||||
return
|
|
||||||
global_config = deepcopy(full_config)
|
|
||||||
|
|
||||||
# Only update global options gui with identified global keys
|
# Only update global options gui with identified global keys
|
||||||
for key in global_config.keys():
|
for key in full_config.keys():
|
||||||
if key not in ('identity', 'global_prometheus', 'global_options'):
|
if key in ('identity', 'global_options'):
|
||||||
global_config.pop(key)
|
global_config.s(key, full_config.g(key))
|
||||||
print(global_config)
|
iter_over_config(global_config, None, 'group', unencrypted, None)
|
||||||
|
|
||||||
iter_over_config(global_config, None, None, unencrypted, None)
|
|
||||||
|
|
||||||
|
|
||||||
def update_config_dict(values, full_config):
|
def update_config_dict(values, full_config):
|
||||||
|
@ -239,13 +237,14 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
return full_config
|
return full_config
|
||||||
|
|
||||||
|
|
||||||
def object_layout(object_type: str = "repo") -> List[list]:
|
def object_layout() -> List[list]:
|
||||||
"""
|
"""
|
||||||
Returns the GUI layout depending on the object type
|
Returns the GUI layout depending on the object type
|
||||||
"""
|
"""
|
||||||
backup_col = [
|
backup_col = [
|
||||||
[
|
[
|
||||||
sg.Text(_t("config_gui.compression"), size=(40, 1)),
|
sg.Text(_t("config_gui.compression"), size=(40, 1)),
|
||||||
|
sg.pin(sg.Image(INHERITANCE_ICON, key="inherited.backup_opts.compression", tooltip=_t("config_gui.group_inherited"))),
|
||||||
sg.Combo(
|
sg.Combo(
|
||||||
list(combo_boxes["compression"].values()),
|
list(combo_boxes["compression"].values()),
|
||||||
key="backup_opts.compression",
|
key="backup_opts.compression",
|
||||||
|
@ -259,10 +258,12 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
),
|
),
|
||||||
size=(40, 2),
|
size=(40, 2),
|
||||||
),
|
),
|
||||||
|
sg.pin(sg.Image(INHERITANCE_ICON, expand_x=True, expand_y=True, key="inherited.backup_opts.paths", tooltip=_t("config_gui.group_inherited"))),
|
||||||
sg.Multiline(key="backup_opts.paths", size=(48, 4)),
|
sg.Multiline(key="backup_opts.paths", size=(48, 4)),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
sg.Text(_t("config_gui.source_type"), size=(40, 1)),
|
sg.Text(_t("config_gui.source_type"), size=(40, 1)),
|
||||||
|
sg.pin(sg.Image(INHERITANCE_ICON, expand_x=True, expand_y=True, key="inherited.backup_opts.source_type", tooltip=_t("config_gui.group_inherited"))),
|
||||||
sg.Combo(
|
sg.Combo(
|
||||||
list(combo_boxes["source_type"].values()),
|
list(combo_boxes["source_type"].values()),
|
||||||
key="backup_opts.source_type",
|
key="backup_opts.source_type",
|
||||||
|
@ -276,7 +277,7 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
),
|
),
|
||||||
size=(40, 2),
|
size=(40, 2),
|
||||||
),
|
),
|
||||||
sg.Text("inherited", key="inherited.backup_opts.use_fs_snapshot", visible=False),
|
sg.pin(sg.Image(INHERITANCE_ICON, expand_x=True, expand_y=True, key="inherited.backup_opts.use_fs_snapshot", tooltip=_t("config_gui.group_inherited"))),
|
||||||
sg.Checkbox("", key="backup_opts.use_fs_snapshot", size=(41, 1)),
|
sg.Checkbox("", key="backup_opts.use_fs_snapshot", size=(41, 1)),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -533,7 +534,7 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
[
|
[
|
||||||
sg.Tab(
|
sg.Tab(
|
||||||
_t("config_gui.backup"),
|
_t("config_gui.backup"),
|
||||||
[[sg.Column(backup_col, scrollable=True, vertical_scroll_only=True)]],
|
[[sg.Column(backup_col, scrollable=True, vertical_scroll_only=True, size=(700, 450))]],
|
||||||
font="helvetica 16",
|
font="helvetica 16",
|
||||||
key="--tab-backup--",
|
key="--tab-backup--",
|
||||||
element_justification="L",
|
element_justification="L",
|
||||||
|
@ -542,7 +543,7 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
[
|
[
|
||||||
sg.Tab(
|
sg.Tab(
|
||||||
_t("config_gui.backup_destination"),
|
_t("config_gui.backup_destination"),
|
||||||
repo_col,
|
[[sg.Column(repo_col, scrollable=True, vertical_scroll_only=True, size=(700, 450))]],
|
||||||
font="helvetica 16",
|
font="helvetica 16",
|
||||||
key="--tab-repo--",
|
key="--tab-repo--",
|
||||||
element_justification="L",
|
element_justification="L",
|
||||||
|
@ -590,54 +591,6 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
sg.Input(key="identity.machine_group", size=(50, 1)),
|
sg.Input(key="identity.machine_group", size=(50, 1)),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
prometheus_col = [
|
|
||||||
[sg.Text(_t("config_gui.available_variables"))],
|
|
||||||
[
|
|
||||||
sg.Text(_t("config_gui.enable_prometheus"), size=(40, 1)),
|
|
||||||
sg.Checkbox("", key="global_prometheus.metrics", size=(41, 1)),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
sg.Text(_t("config_gui.job_name"), size=(40, 1)),
|
|
||||||
sg.Input(key="global_prometheus.backup_job", size=(50, 1)),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
sg.Text(_t("config_gui.metrics_destination"), size=(40, 1)),
|
|
||||||
sg.Input(key="global_prometheus.destination", 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.Text(_t("generic.group"), size=(40, 1)),
|
|
||||||
sg.Input(key="global_prometheus.group", size=(50, 1)),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
sg.Text(
|
|
||||||
"{}\n({}\n{})".format(
|
|
||||||
_t("config_gui.additional_labels"),
|
|
||||||
_t("config_gui.one_per_line"),
|
|
||||||
_t("config_gui.format_equals"),
|
|
||||||
),
|
|
||||||
size=(40, 3),
|
|
||||||
),
|
|
||||||
sg.Multiline(key="global_prometheus.additional_labels", size=(48, 3)),
|
|
||||||
],
|
|
||||||
]
|
|
||||||
|
|
||||||
global_options_col = [
|
global_options_col = [
|
||||||
[sg.Text(_t("config_gui.available_variables"))],
|
[sg.Text(_t("config_gui.available_variables"))],
|
||||||
[
|
[
|
||||||
|
@ -691,15 +644,6 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
element_justification="L",
|
element_justification="L",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
[
|
|
||||||
sg.Tab(
|
|
||||||
_t("config_gui.prometheus_config"),
|
|
||||||
prometheus_col,
|
|
||||||
font="helvetica 16",
|
|
||||||
key="--tab-global-prometheus--",
|
|
||||||
element_justification="L",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
sg.Tab(
|
sg.Tab(
|
||||||
_t("generic.options"),
|
_t("generic.options"),
|
||||||
|
@ -743,8 +687,7 @@ def config_gui(full_config: dict, config_file: str):
|
||||||
|
|
||||||
_global_layout = [
|
_global_layout = [
|
||||||
[sg.TabGroup(tab_group_layout, enable_events=True, key="--configtabgroup--")],
|
[sg.TabGroup(tab_group_layout, enable_events=True, key="--configtabgroup--")],
|
||||||
[sg.Column(buttons, element_justification="L")],
|
[sg.Push(), sg.Column(buttons, element_justification="L")],
|
||||||
[sg.Button("trololo")]
|
|
||||||
]
|
]
|
||||||
return _global_layout
|
return _global_layout
|
||||||
|
|
||||||
|
|
|
@ -107,4 +107,9 @@ en:
|
||||||
daily: daily copies
|
daily: daily copies
|
||||||
weekly: weekly copies
|
weekly: weekly copies
|
||||||
monthly: monthly copies
|
monthly: monthly copies
|
||||||
yearly: yearly copies
|
yearly: yearly copies
|
||||||
|
|
||||||
|
group_inherited: Group inherited
|
||||||
|
repo_group_config: Repo group configuration
|
||||||
|
global_config: Global config
|
||||||
|
select_object: Select configuration object
|
||||||
|
|
|
@ -107,4 +107,10 @@ fr:
|
||||||
daily: copies journalières
|
daily: copies journalières
|
||||||
weekly: copies hebdomadaires
|
weekly: copies hebdomadaires
|
||||||
monthly: copies mensuelles
|
monthly: copies mensuelles
|
||||||
yearly: copies annuelles
|
yearly: copies annuelles
|
||||||
|
|
||||||
|
group_inherited: Hérité du groupe
|
||||||
|
repo_group_config: Configuration de groupe de dépots
|
||||||
|
global_config: Configuration globale
|
||||||
|
select_object: Selectionner l'object à configurer
|
||||||
|
|
BIN
resources/inheritance_icon.png
Normal file
BIN
resources/inheritance_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 442 B |
Loading…
Add table
Reference in a new issue