lklk
This commit is contained in:
@@ -154,6 +154,7 @@ class ConfigModal(ModalScreen):
|
||||
yield ListItem(Label("Global Settings"), id="cat-globals")
|
||||
yield ListItem(Label("Stores"), id="cat-stores")
|
||||
yield ListItem(Label("Providers"), id="cat-providers")
|
||||
yield ListItem(Label("Tools"), id="cat-tools")
|
||||
|
||||
with Vertical(id="config-content"):
|
||||
yield ScrollableContainer(id="fields-container")
|
||||
@@ -161,14 +162,17 @@ class ConfigModal(ModalScreen):
|
||||
yield Button("Save", variant="success", id="save-btn")
|
||||
yield Button("Add Store", variant="primary", id="add-store-btn")
|
||||
yield Button("Add Provider", variant="primary", id="add-provider-btn")
|
||||
yield Button("Add Tool", variant="primary", id="add-tool-btn")
|
||||
yield Button("Back", id="back-btn")
|
||||
yield Button("Restore Backup", id="restore-backup-btn")
|
||||
yield Button("Copy DB Path", id="copy-db-btn")
|
||||
yield Button("Close", variant="error", id="cancel-btn")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.query_one("#add-store-btn", Button).display = False
|
||||
self.query_one("#add-provider-btn", Button).display = False
|
||||
try:
|
||||
self.query_one("#add-tool-btn", Button).display = False
|
||||
except Exception:
|
||||
pass
|
||||
# Update DB path and last-saved on mount
|
||||
try:
|
||||
self.query_one("#config-db-path", Static).update(self._db_path)
|
||||
@@ -213,6 +217,7 @@ class ConfigModal(ModalScreen):
|
||||
try:
|
||||
self.query_one("#add-store-btn", Button).display = (self.current_category == "stores" and self.editing_item_name is None)
|
||||
self.query_one("#add-provider-btn", Button).display = (self.current_category == "providers" and self.editing_item_name is None)
|
||||
self.query_one("#add-tool-btn", Button).display = (self.current_category == "tools" and self.editing_item_name is None)
|
||||
self.query_one("#back-btn", Button).display = (self.editing_item_name is not None)
|
||||
self.query_one("#save-btn", Button).display = (self.editing_item_name is not None or self.current_category == "globals")
|
||||
except Exception:
|
||||
@@ -238,6 +243,8 @@ class ConfigModal(ModalScreen):
|
||||
self.render_stores(container)
|
||||
elif self.current_category == "providers":
|
||||
self.render_providers(container)
|
||||
elif self.current_category == "tools":
|
||||
self.render_tools(container)
|
||||
|
||||
self.call_after_refresh(do_mount)
|
||||
|
||||
@@ -354,6 +361,26 @@ class ConfigModal(ModalScreen):
|
||||
)
|
||||
container.mount(row)
|
||||
|
||||
def render_tools(self, container: ScrollableContainer) -> None:
|
||||
container.mount(Label("Configured Tools", classes="config-label"))
|
||||
tools = self.config_data.get("tool", {})
|
||||
if not tools:
|
||||
container.mount(Static("No tools configured."))
|
||||
else:
|
||||
for i, (name, _) in enumerate(tools.items()):
|
||||
edit_id = f"edit-tool-{i}"
|
||||
del_id = f"del-tool-{i}"
|
||||
self._button_id_map[edit_id] = ("edit", "tool", name)
|
||||
self._button_id_map[del_id] = ("del", "tool", name)
|
||||
|
||||
row = Horizontal(
|
||||
Static(name, classes="item-label"),
|
||||
Button("Edit", id=edit_id),
|
||||
Button("Delete", variant="error", id=del_id),
|
||||
classes="item-row"
|
||||
)
|
||||
container.mount(row)
|
||||
|
||||
def render_item_editor(self, container: ScrollableContainer) -> None:
|
||||
item_type = str(self.editing_item_type or "")
|
||||
item_name = str(self.editing_item_name or "")
|
||||
@@ -392,6 +419,18 @@ class ConfigModal(ModalScreen):
|
||||
provider_schema_map[k.upper()] = field_def
|
||||
except Exception:
|
||||
pass
|
||||
# Fetch Tool schema
|
||||
if item_type == "tool":
|
||||
try:
|
||||
import importlib
|
||||
mod = importlib.import_module(f"tool.{item_name}")
|
||||
if hasattr(mod, "config_schema") and callable(mod.config_schema):
|
||||
for field_def in mod.config_schema():
|
||||
k = field_def.get("key")
|
||||
if k:
|
||||
provider_schema_map[k.upper()] = field_def
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Use columns for better layout of inputs with paste buttons
|
||||
container.mount(Label("Edit Settings"))
|
||||
@@ -569,6 +608,8 @@ class ConfigModal(ModalScreen):
|
||||
self.current_category = "stores"
|
||||
elif event.item.id == "cat-providers":
|
||||
self.current_category = "providers"
|
||||
elif event.item.id == "cat-tools":
|
||||
self.current_category = "tools"
|
||||
|
||||
self.editing_item_name = None
|
||||
self.editing_item_type = None
|
||||
@@ -645,6 +686,10 @@ class ConfigModal(ModalScreen):
|
||||
removed = True
|
||||
if str(name).strip().lower() == "alldebrid":
|
||||
self._remove_alldebrid_store_entry()
|
||||
elif itype == "tool":
|
||||
if "tool" in self.config_data and name in self.config_data["tool"]:
|
||||
del self.config_data["tool"][name]
|
||||
removed = True
|
||||
if removed:
|
||||
try:
|
||||
saved = self.save_all()
|
||||
@@ -676,31 +721,33 @@ class ConfigModal(ModalScreen):
|
||||
except Exception:
|
||||
pass
|
||||
self.app.push_screen(SelectionModal("Select Provider Type", options), callback=self.on_provider_type_selected)
|
||||
elif bid == "add-tool-btn":
|
||||
# Discover tool modules that advertise a config_schema()
|
||||
options = []
|
||||
try:
|
||||
import pkgutil
|
||||
import importlib
|
||||
import tool as _tool_pkg
|
||||
|
||||
for _mod in pkgutil.iter_modules(_tool_pkg.__path__):
|
||||
try:
|
||||
mod = importlib.import_module(f"tool.{_mod.name}")
|
||||
if hasattr(mod, "config_schema") and callable(mod.config_schema):
|
||||
options.append(_mod.name)
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
# Fallback to known entry
|
||||
options = ["ytdlp"]
|
||||
|
||||
if options:
|
||||
options.sort()
|
||||
self.app.push_screen(SelectionModal("Select Tool", options), callback=self.on_tool_type_selected)
|
||||
elif bid == "matrix-test-btn":
|
||||
self._request_matrix_test()
|
||||
elif bid == "matrix-rooms-btn":
|
||||
self._open_matrix_room_picker()
|
||||
elif bid == "restore-backup-btn":
|
||||
try:
|
||||
backup = self._get_last_backup_path()
|
||||
if not backup:
|
||||
self.notify("No backups available", severity="warning")
|
||||
else:
|
||||
# Ask for confirmation via a simple notification and perform restore
|
||||
self.notify(f"Restoring {backup.name}...", timeout=2)
|
||||
self._restore_backup_background(str(backup))
|
||||
except Exception as exc:
|
||||
self.notify(f"Restore failed: {exc}", severity="error")
|
||||
elif bid == "copy-db-btn":
|
||||
try:
|
||||
if hasattr(self.app, "copy_to_clipboard"):
|
||||
self.app.copy_to_clipboard(str(db.db_path))
|
||||
self.notify("DB path copied to clipboard")
|
||||
else:
|
||||
# Fall back to a visible notification
|
||||
self.notify(str(db.db_path))
|
||||
except Exception:
|
||||
self.notify("Failed to copy DB path", severity="warning")
|
||||
# Restore UI removed: backups/audit remain available programmatically
|
||||
elif bid.startswith("paste-"):
|
||||
# Programmatic paste button
|
||||
target_id = bid.replace("paste-", "")
|
||||
@@ -735,64 +782,7 @@ class ConfigModal(ModalScreen):
|
||||
else:
|
||||
self.notify("Clipboard not supported in this terminal", severity="warning")
|
||||
|
||||
def _get_last_backup_path(self):
|
||||
try:
|
||||
backup_dir = Path(db.db_path).with_name("config_backups")
|
||||
if not backup_dir.exists():
|
||||
return None
|
||||
files = sorted(backup_dir.glob("medios-backup-*.db"), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
return files[0] if files else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@work(thread=True)
|
||||
def _restore_backup_background(self, backup_path: str) -> None:
|
||||
try:
|
||||
import sqlite3, json
|
||||
cfg = {}
|
||||
with sqlite3.connect(backup_path) as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT category, subtype, item_name, key, value FROM config")
|
||||
rows = cur.fetchall()
|
||||
for cat, sub, name, key, val in rows:
|
||||
try:
|
||||
parsed = json.loads(val)
|
||||
except Exception:
|
||||
parsed = val
|
||||
if cat == 'global':
|
||||
cfg[key] = parsed
|
||||
elif cat in ('provider', 'tool'):
|
||||
cd = cfg.setdefault(cat, {})
|
||||
sd = cd.setdefault(sub, {})
|
||||
sd[key] = parsed
|
||||
elif cat == 'store':
|
||||
cd = cfg.setdefault('store', {})
|
||||
sd = cd.setdefault(sub, {})
|
||||
nd = sd.setdefault(name, {})
|
||||
nd[key] = parsed
|
||||
else:
|
||||
cfg.setdefault(cat, {})[key] = parsed
|
||||
|
||||
# Persist restored config using save_config
|
||||
from SYS.config import save_config, reload_config
|
||||
saved = save_config(cfg)
|
||||
# Reload and update UI from main thread
|
||||
self.app.call_from_thread(self._on_restore_complete, True, backup_path, saved)
|
||||
except Exception as exc:
|
||||
self.app.call_from_thread(self._on_restore_complete, False, backup_path, str(exc))
|
||||
|
||||
def _on_restore_complete(self, success: bool, backup_path: str, saved_or_error):
|
||||
if success:
|
||||
# Refresh our in-memory view and UI
|
||||
try:
|
||||
from SYS.config import reload_config
|
||||
self.config_data = reload_config()
|
||||
self.refresh_view()
|
||||
except Exception:
|
||||
pass
|
||||
self.notify(f"Restore complete: re-saved {saved_or_error} entries from {Path(backup_path).name}")
|
||||
else:
|
||||
self.notify(f"Restore failed: {saved_or_error}", severity="error")
|
||||
# Backup/restore helpers removed: forensics/audit mode disabled and restore UI removed.
|
||||
|
||||
def on_store_type_selected(self, stype: str) -> None:
|
||||
if not stype:
|
||||
@@ -878,6 +868,31 @@ class ConfigModal(ModalScreen):
|
||||
self.editing_item_name = ptype
|
||||
self.refresh_view()
|
||||
|
||||
def on_tool_type_selected(self, tname: str) -> None:
|
||||
if not tname:
|
||||
return
|
||||
self._capture_editor_snapshot()
|
||||
if "tool" not in self.config_data:
|
||||
self.config_data["tool"] = {}
|
||||
|
||||
if tname not in self.config_data["tool"]:
|
||||
new_config = {}
|
||||
try:
|
||||
import importlib
|
||||
mod = importlib.import_module(f"tool.{tname}")
|
||||
if hasattr(mod, "config_schema") and callable(mod.config_schema):
|
||||
for field_def in mod.config_schema():
|
||||
key = field_def.get("key")
|
||||
if key:
|
||||
new_config[key] = field_def.get("default", "")
|
||||
except Exception:
|
||||
pass
|
||||
self.config_data["tool"][tname] = new_config
|
||||
|
||||
self.editing_item_type = "tool"
|
||||
self.editing_item_name = tname
|
||||
self.refresh_view()
|
||||
|
||||
def _update_config_value(self, widget_id: str, value: Any) -> None:
|
||||
if widget_id not in self._input_id_map:
|
||||
return
|
||||
@@ -1282,6 +1297,19 @@ class ConfigModal(ModalScreen):
|
||||
except Exception:
|
||||
pass
|
||||
section = self.config_data.get("provider", {}).get(item_name, {})
|
||||
elif item_type == "tool":
|
||||
try:
|
||||
import importlib
|
||||
mod = importlib.import_module(f"tool.{item_name}")
|
||||
if hasattr(mod, "config_schema") and callable(mod.config_schema):
|
||||
for field_def in mod.config_schema():
|
||||
if field_def.get("required"):
|
||||
k = field_def.get("key")
|
||||
if k and k not in required_keys:
|
||||
required_keys.append(k)
|
||||
except Exception:
|
||||
pass
|
||||
section = self.config_data.get("tool", {}).get(item_name, {})
|
||||
|
||||
# Check required keys
|
||||
for rk in required_keys:
|
||||
|
||||
Reference in New Issue
Block a user