mirror of
https://github.com/netinvent/npbackup.git
synced 2025-10-04 02:36:33 +08:00
GUI: WIP - Refactor operation center
This commit is contained in:
parent
768f3b500b
commit
7827427d7d
3 changed files with 168 additions and 68 deletions
|
@ -7,18 +7,21 @@ __intname__ = "npbackup.gui.operations"
|
||||||
__author__ = "Orsiris de Jong"
|
__author__ = "Orsiris de Jong"
|
||||||
__copyright__ = "Copyright (C) 2023-2024 NetInvent"
|
__copyright__ = "Copyright (C) 2023-2024 NetInvent"
|
||||||
__license__ = "GPL-3.0-only"
|
__license__ = "GPL-3.0-only"
|
||||||
__build__ = "2024061601"
|
__build__ = "2024093001"
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from collections import namedtuple
|
||||||
import FreeSimpleGUI as sg
|
import FreeSimpleGUI as sg
|
||||||
from npbackup.configuration import (
|
from npbackup.configuration import (
|
||||||
get_repo_config,
|
get_repo_config,
|
||||||
|
get_repo_list,
|
||||||
get_group_list,
|
get_group_list,
|
||||||
get_repos_by_group,
|
get_repos_by_group,
|
||||||
get_manager_password,
|
get_manager_password,
|
||||||
)
|
)
|
||||||
|
from npbackup.task import get_scheduled_tasks
|
||||||
from npbackup.core.i18n_helper import _t
|
from npbackup.core.i18n_helper import _t
|
||||||
from npbackup.gui.helpers import get_anon_repo_uri, gui_thread_runner
|
from npbackup.gui.helpers import get_anon_repo_uri, gui_thread_runner
|
||||||
from resources.customization import (
|
from resources.customization import (
|
||||||
|
@ -31,8 +34,10 @@ from npbackup.gui.config import ENCRYPTED_DATA_PLACEHOLDER, ask_manager_password
|
||||||
logger = getLogger(__intname__)
|
logger = getLogger(__intname__)
|
||||||
|
|
||||||
|
|
||||||
|
gui_object = namedtuple("GuiOjbect", ["type", "name", "group", "backend", "uri"])
|
||||||
|
|
||||||
def gui_update_state(window, full_config: dict, unencrypted: str = None) -> list:
|
def gui_update_state(window, full_config: dict, unencrypted: str = None) -> list:
|
||||||
repo_list = []
|
repo_and_group_list = []
|
||||||
try:
|
try:
|
||||||
for repo_name in full_config.g("repos"):
|
for repo_name in full_config.g("repos"):
|
||||||
repo_config, _ = get_repo_config(full_config, repo_name)
|
repo_config, _ = get_repo_config(full_config, repo_name)
|
||||||
|
@ -44,75 +49,111 @@ def gui_update_state(window, full_config: dict, unencrypted: str = None) -> list
|
||||||
repo_group = repo_config.g("repo_group")
|
repo_group = repo_config.g("repo_group")
|
||||||
if not unencrypted and unencrypted != repo_name:
|
if not unencrypted and unencrypted != repo_name:
|
||||||
repo_uri = ENCRYPTED_DATA_PLACEHOLDER
|
repo_uri = ENCRYPTED_DATA_PLACEHOLDER
|
||||||
repo_list.append([repo_name, repo_group, backend_type, repo_uri])
|
repo_and_group_list.append(gui_object("repo", repo_name, repo_group, backend_type, repo_uri))
|
||||||
else:
|
else:
|
||||||
logger.warning("Incomplete URI/password for repo {}".format(repo_name))
|
logger.warning("Incomplete URI/password for repo {}".format(repo_name))
|
||||||
|
for group_name in get_group_list(full_config):
|
||||||
|
repo_and_group_list.append(gui_object("group", group_name, "", "", ""))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.info("No operations repos configured")
|
logger.info("No operations repos configured")
|
||||||
window["repo-list"].update(repo_list)
|
window["repo-and-group-list"].update(repo_and_group_list)
|
||||||
return repo_list
|
return repo_and_group_list
|
||||||
|
|
||||||
|
|
||||||
|
def task_scheduler(repos: list):
|
||||||
|
"""
|
||||||
|
Create tasks for given repo list
|
||||||
|
"""
|
||||||
|
task = namedtuple("Tasks", ["task", "hour", "minute", "day", "month", "weekday"])
|
||||||
|
|
||||||
|
|
||||||
|
def _get_current_tasks():
|
||||||
|
"""
|
||||||
|
mock tasks
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
task("housekeeping", 0, 0, "*", "*", "*"),
|
||||||
|
task("check", 0, 0, "*", "*", "*"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def _update_task_list(window):
|
||||||
|
tasks = _get_current_tasks()
|
||||||
|
task_list = []
|
||||||
|
for task in tasks:
|
||||||
|
task_list.append(task)
|
||||||
|
window["-TASKS-"].update(values=task_list)
|
||||||
|
|
||||||
|
|
||||||
|
actions = [
|
||||||
|
"housekeeping",
|
||||||
|
"check",
|
||||||
|
"repair",
|
||||||
|
"recover",
|
||||||
|
"unlock",
|
||||||
|
"forget",
|
||||||
|
"prune",
|
||||||
|
]
|
||||||
|
|
||||||
|
layout = [
|
||||||
|
[
|
||||||
|
sg.Text(_t("operations_gui.currently_configured_tasks")),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Table(
|
||||||
|
values=[[]],
|
||||||
|
headings=["Task type", "Minute", "Hour", "Day", "Month", "Weekday"],
|
||||||
|
key="-TASKS-",
|
||||||
|
auto_size_columns=True,
|
||||||
|
justification="left",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Text(_t("operations_gui.select_action")),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Combo(values=actions, default_value="housekeeping", key="-ACTION-", size=(20, 1)),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
window = sg.Window(layout=layout, title=_t("operations_gui.task_scheduler"), finalize=True)
|
||||||
|
_update_task_list(window)
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
|
||||||
|
if event in (sg.WIN_CLOSED, sg.WIN_X_EVENT, "--EXIT--"):
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def operations_gui(full_config: dict) -> dict:
|
def operations_gui(full_config: dict) -> dict:
|
||||||
"""
|
"""
|
||||||
Operate on one or multiple repositories
|
Operate on one or multiple repositories, or groups
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _select_groups():
|
def _get_repo_list(selected_rows):
|
||||||
group_list = get_group_list(full_config)
|
if not values["repo-and-group-list"]:
|
||||||
selector_layout = [
|
if sg.popup_yes_no(_t("operations_gui.no_repo_selected"), keep_on_top=True) == "No":
|
||||||
[
|
return False
|
||||||
sg.Table(
|
repos = get_repo_list(full_config)
|
||||||
values=group_list,
|
else:
|
||||||
headings=["Group Name"],
|
repos = []
|
||||||
key="-GROUP_LIST-",
|
for index in values["repo-and-group-list"]:
|
||||||
auto_size_columns=True,
|
gui_object = complete_repo_list[index]
|
||||||
justification="left",
|
if gui_object.type == "group":
|
||||||
expand_x=True,
|
repos += get_repos_by_group(full_config, gui_object.name)
|
||||||
expand_y=True,
|
else:
|
||||||
)
|
repos.append(gui_object.name)
|
||||||
],
|
# Cheap duplicate filter
|
||||||
[
|
repos = list(set(repos))
|
||||||
sg.Push(),
|
return repos
|
||||||
sg.Button(_t("generic.cancel"), key="--CANCEL--"),
|
|
||||||
sg.Button(
|
|
||||||
_t("operations_gui.apply_to_selected_groups"),
|
|
||||||
key="--SELECTED_GROUPS--",
|
|
||||||
),
|
|
||||||
sg.Button(_t("operations_gui.apply_to_all"), key="--APPLY_TO_ALL--"),
|
|
||||||
],
|
|
||||||
]
|
|
||||||
|
|
||||||
select_group_window = sg.Window("Group", selector_layout)
|
|
||||||
while True:
|
|
||||||
event, values = select_group_window.read()
|
|
||||||
if event in (sg.WIN_CLOSED, sg.WIN_X_EVENT, "--CANCEL--"):
|
|
||||||
result = []
|
|
||||||
break
|
|
||||||
if event == "--SELECTED_GROUPS--":
|
|
||||||
if not values["-GROUP_LIST-"]:
|
|
||||||
sg.Popup(_t("operations_gui.no_groups_selected"))
|
|
||||||
continue
|
|
||||||
repo_list = []
|
|
||||||
for group_index in values["-GROUP_LIST-"]:
|
|
||||||
group_name = group_list[group_index]
|
|
||||||
repo_list += get_repos_by_group(full_config, group_name)
|
|
||||||
result = repo_list
|
|
||||||
break
|
|
||||||
if event == "--APPLY_TO_ALL--":
|
|
||||||
result = []
|
|
||||||
for value in complete_repo_list:
|
|
||||||
result.append(value[0])
|
|
||||||
break
|
|
||||||
select_group_window.close()
|
|
||||||
return result
|
|
||||||
|
|
||||||
# This is a stupid hack to make sure uri column is large enough
|
# This is a stupid hack to make sure uri column is large enough
|
||||||
headings = [
|
headings = [
|
||||||
|
"Type ",
|
||||||
"Name ",
|
"Name ",
|
||||||
"Group ",
|
"Group ",
|
||||||
"Backend",
|
"Backend",
|
||||||
"URI ",
|
"URI ",
|
||||||
]
|
]
|
||||||
|
|
||||||
layout = [
|
layout = [
|
||||||
|
@ -138,21 +179,38 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
sg.Table(
|
sg.Table(
|
||||||
values=[[]],
|
values=[[]],
|
||||||
headings=headings,
|
headings=headings,
|
||||||
key="repo-list",
|
key="repo-and-group-list",
|
||||||
auto_size_columns=True,
|
auto_size_columns=True,
|
||||||
justification="left",
|
justification="left",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
sg.Text(_t("operations_gui.select_repositories")),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Button(
|
||||||
|
_t("operations_gui.housekeeping"),
|
||||||
|
key="--HOUSEKEEPING--",
|
||||||
|
size=(45, 1),
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
_t("operations_gui.task_scheduler"),
|
||||||
|
key="--TASK-SCHEDULER--",
|
||||||
|
size=(45, 1),
|
||||||
|
)
|
||||||
|
],
|
||||||
[
|
[
|
||||||
sg.Button(
|
sg.Button(
|
||||||
_t("operations_gui.quick_check"),
|
_t("operations_gui.quick_check"),
|
||||||
key="--QUICK-CHECK--",
|
key="--QUICK-CHECK--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
|
visible=False,
|
||||||
),
|
),
|
||||||
sg.Button(
|
sg.Button(
|
||||||
_t("operations_gui.full_check"),
|
_t("operations_gui.full_check"),
|
||||||
key="--FULL-CHECK--",
|
key="--FULL-CHECK--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
|
visible=False,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -160,11 +218,13 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
_t("operations_gui.repair_index"),
|
_t("operations_gui.repair_index"),
|
||||||
key="--REPAIR-INDEX--",
|
key="--REPAIR-INDEX--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
|
visible=False,
|
||||||
),
|
),
|
||||||
sg.Button(
|
sg.Button(
|
||||||
_t("operations_gui.repair_snapshots"),
|
_t("operations_gui.repair_snapshots"),
|
||||||
key="--REPAIR-SNAPSHOTS--",
|
key="--REPAIR-SNAPSHOTS--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
|
visible=False,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -172,11 +232,13 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
_t("operations_gui.repair_packs"),
|
_t("operations_gui.repair_packs"),
|
||||||
key="--REPAIR-PACKS--",
|
key="--REPAIR-PACKS--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
|
visible=False,
|
||||||
),
|
),
|
||||||
sg.Button(
|
sg.Button(
|
||||||
_t("operations_gui.forget_using_retention_policy"),
|
_t("operations_gui.forget_using_retention_policy"),
|
||||||
key="--FORGET--",
|
key="--FORGET--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
|
visible=False,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -184,21 +246,24 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
_t("operations_gui.standard_prune"),
|
_t("operations_gui.standard_prune"),
|
||||||
key="--STANDARD-PRUNE--",
|
key="--STANDARD-PRUNE--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
|
visible=False,
|
||||||
),
|
),
|
||||||
sg.Button(
|
sg.Button(
|
||||||
_t("operations_gui.max_prune"),
|
_t("operations_gui.max_prune"),
|
||||||
key="--MAX-PRUNE--",
|
key="--MAX-PRUNE--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
|
visible=False,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
sg.Button(
|
sg.Button(
|
||||||
_t("operations_gui.unlock"), key="--UNLOCK--", size=(45, 1)
|
_t("operations_gui.unlock"), key="--UNLOCK--", size=(45, 1), visible=False
|
||||||
),
|
),
|
||||||
sg.Button(
|
sg.Button(
|
||||||
_t("operations_gui.recover"),
|
_t("operations_gui.recover"),
|
||||||
key="--RECOVER--",
|
key="--RECOVER--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
|
visible=False,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -207,6 +272,11 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
key="--STATS--",
|
key="--STATS--",
|
||||||
size=(45, 1),
|
size=(45, 1),
|
||||||
),
|
),
|
||||||
|
sg.Button(
|
||||||
|
_t("operations_gui.show_advanced"),
|
||||||
|
key="--ADVANCED--",
|
||||||
|
size=(45, 1),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
[sg.Button(_t("generic.quit"), key="--EXIT--")],
|
[sg.Button(_t("generic.quit"), key="--EXIT--")],
|
||||||
],
|
],
|
||||||
|
@ -236,7 +306,7 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
complete_repo_list = gui_update_state(window, full_config)
|
complete_repo_list = gui_update_state(window, full_config)
|
||||||
|
|
||||||
# Auto reisze table to window size
|
# Auto reisze table to window size
|
||||||
window["repo-list"].expand(True, True)
|
window["repo-and-group-list"].expand(True, True)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
|
@ -245,7 +315,7 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
break
|
break
|
||||||
if event == _t("config_gui.show_decrypted"):
|
if event == _t("config_gui.show_decrypted"):
|
||||||
try:
|
try:
|
||||||
object_name = complete_repo_list[values["repo-list"][0]][0]
|
object_name = complete_repo_list[values["repo--group-list"][0]][0]
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("Trace:", exc_info=True)
|
logger.debug("Trace:", exc_info=True)
|
||||||
object_name = None
|
object_name = None
|
||||||
|
@ -267,7 +337,24 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
window, full_config, unencrypted=object_name
|
window, full_config, unencrypted=object_name
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
if event == "--ADVANCED--":
|
||||||
|
for button in (
|
||||||
|
"--QUICK-CHECK--",
|
||||||
|
"--FULL-CHECK--",
|
||||||
|
"--REPAIR-INDEX--",
|
||||||
|
"--REPAIR-PACKS--",
|
||||||
|
"--REPAIR-SNAPSHOTS--",
|
||||||
|
"--RECOVER--",
|
||||||
|
"--UNLOCK--",
|
||||||
|
"--FORGET--",
|
||||||
|
"--STANDARD-PRUNE--",
|
||||||
|
"--MAX-PRUNE--",
|
||||||
|
):
|
||||||
|
window[button].update(visible=True)
|
||||||
|
window["--ADVANCED--"].update(disabled=True)
|
||||||
|
continue
|
||||||
if event in (
|
if event in (
|
||||||
|
"--HOUSEKEEPING--",
|
||||||
"--QUICK-CHECK--",
|
"--QUICK-CHECK--",
|
||||||
"--FULL-CHECK--",
|
"--FULL-CHECK--",
|
||||||
"--REPAIR-INDEX--",
|
"--REPAIR-INDEX--",
|
||||||
|
@ -280,22 +367,21 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
"--MAX-PRUNE--",
|
"--MAX-PRUNE--",
|
||||||
"--STATS--",
|
"--STATS--",
|
||||||
):
|
):
|
||||||
if not values["repo-list"]:
|
repos = _get_repo_list(values["repo-and-group-list"])
|
||||||
repos = _select_groups()
|
|
||||||
else:
|
|
||||||
repos = []
|
|
||||||
for value in values["repo-list"]:
|
|
||||||
repos.append(complete_repo_list[value][0])
|
|
||||||
|
|
||||||
repo_config_list = []
|
repo_config_list = []
|
||||||
if not repos:
|
if not repos:
|
||||||
continue
|
continue
|
||||||
for repo_name in repos:
|
for repo_name in repos:
|
||||||
repo_config, __annotations__ = get_repo_config(full_config, repo_name)
|
repo_config, _ = get_repo_config(full_config, repo_name)
|
||||||
repo_config_list.append(repo_config)
|
repo_config_list.append(repo_config)
|
||||||
operation = None
|
operation = None
|
||||||
op_args = None
|
op_args = None
|
||||||
gui_msg = None
|
gui_msg = None
|
||||||
|
if event == "--HOUSEKEEPING--":
|
||||||
|
operation = "housekeeping"
|
||||||
|
op_args = {}
|
||||||
|
gui_msg = _t("operations_gui.housekeeping")
|
||||||
if event == "--FORGET--":
|
if event == "--FORGET--":
|
||||||
operation = "forget"
|
operation = "forget"
|
||||||
op_args = {"use_policy": True}
|
op_args = {"use_policy": True}
|
||||||
|
@ -362,6 +448,12 @@ def operations_gui(full_config: dict) -> dict:
|
||||||
sg.popup_error(f"Bogus operation: {operation}", keep_on_top=True)
|
sg.popup_error(f"Bogus operation: {operation}", keep_on_top=True)
|
||||||
|
|
||||||
event = "---STATE-UPDATE---"
|
event = "---STATE-UPDATE---"
|
||||||
|
if event == "--TASK-SCHEDULER--":
|
||||||
|
repos = _get_repo_list(values["repo-and-group-list"])
|
||||||
|
if not repos:
|
||||||
|
continue
|
||||||
|
task_scheduler(repos)
|
||||||
|
continue
|
||||||
if event == "---STATE-UPDATE---":
|
if event == "---STATE-UPDATE---":
|
||||||
complete_repo_list = gui_update_state(window, full_config)
|
complete_repo_list = gui_update_state(window, full_config)
|
||||||
window.close()
|
window.close()
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
en:
|
en:
|
||||||
|
select_repositories: Select one or more repositories, or groups of repositories
|
||||||
configured_repositories: Configured repositories
|
configured_repositories: Configured repositories
|
||||||
|
housekeeping: Housekeeping and retention operation
|
||||||
|
task_scheduler: Task scheduler
|
||||||
quick_check: Quick repo check
|
quick_check: Quick repo check
|
||||||
full_check: Full repo check
|
full_check: Full repo check
|
||||||
repair_index: Repair repo index
|
repair_index: Repair repo index
|
||||||
|
@ -18,4 +21,5 @@ en:
|
||||||
add_repo: Add repo
|
add_repo: Add repo
|
||||||
edit_repo: Edit repo
|
edit_repo: Edit repo
|
||||||
remove_repo: Remove repo
|
remove_repo: Remove repo
|
||||||
no_repo_selected: No repo selected
|
no_repo_selected: No repo selected, apply to all ?
|
||||||
|
show_advanced: Show advanced options
|
|
@ -1,5 +1,8 @@
|
||||||
fr:
|
fr:
|
||||||
|
select_repositories: Sélectionner un ou plusieurs dépots, ou groupes de dépots
|
||||||
configured_repositories: Dépots configurés
|
configured_repositories: Dépots configurés
|
||||||
|
housekeeping: Opération de maintenance et rétention
|
||||||
|
task_scheduler: Planificateur de tâches
|
||||||
quick_check: Vérification rapide dépot
|
quick_check: Vérification rapide dépot
|
||||||
full_check: Vérification complète dépot
|
full_check: Vérification complète dépot
|
||||||
repair_index: Réparer les index du dépot
|
repair_index: Réparer les index du dépot
|
||||||
|
@ -18,4 +21,5 @@ fr:
|
||||||
add_repo: Ajouter dépot
|
add_repo: Ajouter dépot
|
||||||
edit_repo: Modifier dépot
|
edit_repo: Modifier dépot
|
||||||
remove_repo: Supprimer dépot
|
remove_repo: Supprimer dépot
|
||||||
no_repo_selected: Aucun dépot sélectionné
|
no_repo_selected: Aucun dépot sélectionné, appliquer à tous ?
|
||||||
|
show_advanced: Afficher les options avancées
|
Loading…
Add table
Reference in a new issue