continuing refactor
This commit is contained in:
+113
-92
@@ -18,6 +18,7 @@ from SYS.config import (
|
||||
count_changed_entries,
|
||||
ConfigSaveConflict,
|
||||
coerce_config_value,
|
||||
_is_multi_instance_plugin_config,
|
||||
)
|
||||
from SYS.database import db
|
||||
from SYS.logger import log, debug
|
||||
@@ -200,7 +201,6 @@ class ConfigModal(ModalScreen):
|
||||
yield Label("Categories", classes="config-label")
|
||||
with ListView(id="category-list"):
|
||||
yield ListItem(Label("Global Settings"), id="cat-globals")
|
||||
yield ListItem(Label("Stores"), id="cat-stores")
|
||||
yield ListItem(Label("Plugins"), id="cat-providers")
|
||||
yield ListItem(Label("Tools"), id="cat-tools")
|
||||
|
||||
@@ -210,14 +210,12 @@ class ConfigModal(ModalScreen):
|
||||
yield Button("Save", variant="success", id="save-btn")
|
||||
# Durable synchronous save: waits and verifies DB persisted critical keys
|
||||
yield Button("Save (durable)", variant="primary", id="save-durable-btn")
|
||||
yield Button("Add Store", variant="primary", id="add-store-btn")
|
||||
yield Button("Add Plugin", variant="primary", id="add-provider-btn")
|
||||
yield Button("Add Tool", variant="primary", id="add-tool-btn")
|
||||
yield Button("Back", id="back-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
|
||||
@@ -267,7 +265,6 @@ class ConfigModal(ModalScreen):
|
||||
|
||||
# Update visibility of buttons
|
||||
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)
|
||||
@@ -467,20 +464,46 @@ class ConfigModal(ModalScreen):
|
||||
providers = self.config_data.get("provider", {})
|
||||
if not providers:
|
||||
container.mount(Static("No plugins configured."))
|
||||
else:
|
||||
for i, (name, _) in enumerate(providers.items()):
|
||||
edit_id = f"edit-provider-{i}"
|
||||
del_id = f"del-provider-{i}"
|
||||
self._button_id_map[edit_id] = ("edit", "provider", name)
|
||||
self._button_id_map[del_id] = ("del", "provider", name)
|
||||
return
|
||||
|
||||
idx = 0
|
||||
for plugin_name, plugin_cfg in providers.items():
|
||||
if isinstance(plugin_cfg, dict) and _is_multi_instance_plugin_config(plugin_cfg):
|
||||
# Multi-instance plugin: show each instance as a separate row
|
||||
for instance_name, instance_cfg in plugin_cfg.items():
|
||||
display_name = instance_name
|
||||
if isinstance(instance_cfg, dict):
|
||||
display_name = (
|
||||
instance_cfg.get("NAME")
|
||||
or instance_cfg.get("name")
|
||||
or instance_name
|
||||
)
|
||||
edit_id = f"edit-provider-{idx}"
|
||||
del_id = f"del-provider-{idx}"
|
||||
self._button_id_map[edit_id] = ("edit", f"plugin-{plugin_name}", instance_name)
|
||||
self._button_id_map[del_id] = ("del", f"plugin-{plugin_name}", instance_name)
|
||||
row = Horizontal(
|
||||
Static(f"{display_name} ({plugin_name})", classes="item-label"),
|
||||
Button("Edit", id=edit_id),
|
||||
Button("Delete", variant="error", id=del_id),
|
||||
classes="item-row"
|
||||
)
|
||||
container.mount(row)
|
||||
idx += 1
|
||||
else:
|
||||
# Single-instance plugin
|
||||
edit_id = f"edit-provider-{idx}"
|
||||
del_id = f"del-provider-{idx}"
|
||||
self._button_id_map[edit_id] = ("edit", "plugin", plugin_name)
|
||||
self._button_id_map[del_id] = ("del", "plugin", plugin_name)
|
||||
row = Horizontal(
|
||||
Static(name, classes="item-label"),
|
||||
Static(plugin_name, classes="item-label"),
|
||||
Button("Edit", id=edit_id),
|
||||
Button("Delete", variant="error", id=del_id),
|
||||
classes="item-row"
|
||||
)
|
||||
container.mount(row)
|
||||
idx += 1
|
||||
|
||||
def render_tools(self, container: ScrollableContainer) -> None:
|
||||
container.mount(Label("Configured Tools", classes="config-label"))
|
||||
@@ -508,11 +531,13 @@ class ConfigModal(ModalScreen):
|
||||
item_schema_map = get_item_schema_map(item_type, item_name)
|
||||
render_state = {"group": None, "mounted_any": False}
|
||||
|
||||
# Parse item_type for store-{stype} or just provider
|
||||
if item_type.startswith("store-"):
|
||||
stype = item_type.replace("store-", "")
|
||||
container.mount(Label(f"Editing Store: {item_name} ({stype})", classes="config-label"))
|
||||
section = self.config_data.get("store", {}).get(stype, {}).get(item_name, {})
|
||||
# Parse item_type: plugin-{ptype} (multi-instance) or flat type
|
||||
if item_type.startswith("plugin-"):
|
||||
ptype = item_type[len("plugin-"):]
|
||||
container.mount(Label(f"Editing {ptype}: {item_name}", classes="config-label"))
|
||||
plugin_block = self.config_data.get("plugin") or self.config_data.get("provider") or {}
|
||||
plugin_instances = plugin_block.get(ptype, {}) if isinstance(plugin_block, dict) else {}
|
||||
section = plugin_instances.get(item_name, {}) if isinstance(plugin_instances, dict) else {}
|
||||
else:
|
||||
container.mount(Label(f"Editing {item_type.capitalize()}: {item_name}", classes="config-label"))
|
||||
section = self.config_data.get(item_type, {}).get(item_name, {})
|
||||
@@ -598,7 +623,7 @@ class ConfigModal(ModalScreen):
|
||||
row.mount(Button("Paste", id=f"paste-{inp_id}", classes="paste-btn"))
|
||||
idx += 1
|
||||
|
||||
if item_type == "plugin" and isinstance(item_name, str):
|
||||
if item_type in ("plugin", "provider") and isinstance(item_name, str):
|
||||
provider = self._instantiate_plugin_for_editor(item_name, self.config_data)
|
||||
if provider is not None:
|
||||
provider_actions = provider.config_actions() or []
|
||||
@@ -626,7 +651,7 @@ class ConfigModal(ModalScreen):
|
||||
)
|
||||
|
||||
if (
|
||||
item_type == "plugin"
|
||||
item_type in ("plugin", "provider")
|
||||
and isinstance(item_name, str)
|
||||
and item_name.strip().lower() == "matrix"
|
||||
):
|
||||
@@ -720,13 +745,11 @@ class ConfigModal(ModalScreen):
|
||||
if not event.item:
|
||||
return
|
||||
item_id = getattr(event.item, "id", None)
|
||||
if item_id not in ("cat-globals", "cat-stores", "cat-providers", "cat-tools"):
|
||||
if item_id not in ("cat-globals", "cat-providers", "cat-tools"):
|
||||
return
|
||||
|
||||
if item_id == "cat-globals":
|
||||
self.current_category = "globals"
|
||||
elif item_id == "cat-stores":
|
||||
self.current_category = "stores"
|
||||
elif item_id == "cat-providers":
|
||||
self.current_category = "providers"
|
||||
elif item_id == "cat-tools":
|
||||
@@ -841,13 +864,23 @@ class ConfigModal(ModalScreen):
|
||||
self.refresh_view()
|
||||
elif action == "del":
|
||||
removed = False
|
||||
if itype.startswith("store-"):
|
||||
if itype.startswith("plugin-"):
|
||||
ptype = itype[len("plugin-"):]
|
||||
plugin_block = self.config_data.get("plugin") or self.config_data.get("provider")
|
||||
if isinstance(plugin_block, dict):
|
||||
instances = plugin_block.get(ptype)
|
||||
if isinstance(instances, dict) and name in instances:
|
||||
del instances[name]
|
||||
if not instances:
|
||||
plugin_block.pop(ptype, None)
|
||||
removed = True
|
||||
elif itype.startswith("store-"):
|
||||
stype = itype.replace("store-", "")
|
||||
if "store" in self.config_data and stype in self.config_data["store"]:
|
||||
if name in self.config_data["store"][stype]:
|
||||
del self.config_data["store"][stype][name]
|
||||
removed = True
|
||||
elif itype == "provider":
|
||||
elif itype in ("provider", "plugin"):
|
||||
if "provider" in self.config_data and name in self.config_data["provider"]:
|
||||
del self.config_data["provider"][name]
|
||||
removed = True
|
||||
@@ -871,9 +904,6 @@ class ConfigModal(ModalScreen):
|
||||
elif bid in self._provider_button_map:
|
||||
provider_name, action_id = self._provider_button_map[bid]
|
||||
self._request_plugin_action(provider_name, action_id)
|
||||
elif bid == "add-store-btn":
|
||||
options = get_configurable_store_types()
|
||||
self.app.push_screen(SelectionModal("Select Store Type", options), callback=self.on_store_type_selected)
|
||||
elif bid == "add-provider-btn":
|
||||
options = get_configurable_plugin_types()
|
||||
self.app.push_screen(SelectionModal("Select Plugin Type", options), callback=self.on_provider_type_selected)
|
||||
@@ -1036,51 +1066,44 @@ class ConfigModal(ModalScreen):
|
||||
|
||||
# Backup/restore helpers removed: forensics/audit mode disabled and restore UI removed.
|
||||
|
||||
def on_store_type_selected(self, stype: str) -> None:
|
||||
if not stype:
|
||||
return
|
||||
|
||||
self._capture_editor_snapshot()
|
||||
|
||||
existing_names: set[str] = set()
|
||||
store_block = self.config_data.get("store")
|
||||
if isinstance(store_block, dict):
|
||||
st_entries = store_block.get(stype)
|
||||
if isinstance(st_entries, dict):
|
||||
existing_names = {str(name) for name in st_entries.keys() if name}
|
||||
|
||||
base_name = f"new_{stype}"
|
||||
new_name = base_name
|
||||
suffix = 1
|
||||
while new_name in existing_names:
|
||||
suffix += 1
|
||||
new_name = f"{base_name}_{suffix}"
|
||||
|
||||
if "store" not in self.config_data:
|
||||
self.config_data["store"] = {}
|
||||
if stype not in self.config_data["store"]:
|
||||
self.config_data["store"][stype] = {}
|
||||
|
||||
# Default config for the new store
|
||||
new_config = build_default_store_config(stype, new_name)
|
||||
|
||||
self.config_data["store"][stype][new_name] = new_config
|
||||
self.editing_item_type = f"store-{stype}"
|
||||
self.editing_item_name = new_name
|
||||
self.refresh_view()
|
||||
|
||||
def on_provider_type_selected(self, ptype: str) -> None:
|
||||
if not ptype: return
|
||||
if not ptype:
|
||||
return
|
||||
self._capture_editor_snapshot()
|
||||
if "provider" not in self.config_data:
|
||||
self.config_data["provider"] = {}
|
||||
|
||||
# Plugins are configured under the top-level 'provider' dict for now.
|
||||
if ptype not in self.config_data["provider"]:
|
||||
self.config_data["provider"][ptype] = build_default_plugin_config(ptype)
|
||||
|
||||
self.editing_item_type = "plugin"
|
||||
self.editing_item_name = ptype
|
||||
|
||||
from ProviderCore.registry import get_plugin_class as _get_cls
|
||||
plugin_class = _get_cls(ptype)
|
||||
is_multi = bool(getattr(plugin_class, 'MULTI_INSTANCE', False)) if plugin_class else False
|
||||
|
||||
if is_multi:
|
||||
# Multi-instance plugin: create a named instance entry in config["plugin"][ptype]
|
||||
plugin_block = self.config_data.setdefault("plugin", {})
|
||||
instances = plugin_block.setdefault(ptype, {})
|
||||
# Also keep config["provider"] in sync (they should be the same dict after normalization,
|
||||
# but if they're not yet, link them)
|
||||
if "provider" in self.config_data and self.config_data["provider"] is not plugin_block:
|
||||
self.config_data["provider"].setdefault(ptype, instances)
|
||||
|
||||
existing_names: set[str] = set(instances.keys())
|
||||
base_name = f"new_{ptype}"
|
||||
new_name = base_name
|
||||
suffix = 1
|
||||
while new_name in existing_names:
|
||||
suffix += 1
|
||||
new_name = f"{base_name}_{suffix}"
|
||||
|
||||
instances[new_name] = build_default_store_config(ptype, new_name)
|
||||
self.editing_item_type = f"plugin-{ptype}"
|
||||
self.editing_item_name = new_name
|
||||
else:
|
||||
# Single-instance plugin
|
||||
if "provider" not in self.config_data:
|
||||
self.config_data["provider"] = {}
|
||||
if ptype not in self.config_data["provider"]:
|
||||
self.config_data["provider"][ptype] = build_default_plugin_config(ptype)
|
||||
self.editing_item_type = "plugin"
|
||||
self.editing_item_name = ptype
|
||||
|
||||
self.refresh_view()
|
||||
|
||||
def on_tool_type_selected(self, tname: str) -> None:
|
||||
@@ -1112,13 +1135,13 @@ class ConfigModal(ModalScreen):
|
||||
if widget_id.startswith("global-"):
|
||||
existing_value = self.config_data.get(key)
|
||||
elif widget_id.startswith("item-") and item_name:
|
||||
if item_type.startswith("store-"):
|
||||
stype = item_type.replace("store-", "")
|
||||
store_block = self.config_data.get("store")
|
||||
if isinstance(store_block, dict):
|
||||
type_block = store_block.get(stype)
|
||||
if isinstance(type_block, dict):
|
||||
section = type_block.get(item_name)
|
||||
if item_type.startswith("plugin-"):
|
||||
ptype = item_type[len("plugin-"):]
|
||||
plugin_block = self.config_data.get("plugin") or self.config_data.get("provider")
|
||||
if isinstance(plugin_block, dict):
|
||||
instances = plugin_block.get(ptype)
|
||||
if isinstance(instances, dict):
|
||||
section = instances.get(item_name)
|
||||
if isinstance(section, dict):
|
||||
existing_value = section.get(key)
|
||||
else:
|
||||
@@ -1137,23 +1160,19 @@ class ConfigModal(ModalScreen):
|
||||
if widget_id.startswith("global-"):
|
||||
self.config_data[key] = processed_value
|
||||
elif widget_id.startswith("item-") and item_name:
|
||||
if item_type.startswith("store-"):
|
||||
stype = item_type.replace("store-", "")
|
||||
if "store" not in self.config_data:
|
||||
self.config_data["store"] = {}
|
||||
if stype not in self.config_data["store"]:
|
||||
self.config_data["store"][stype] = {}
|
||||
if item_name not in self.config_data["store"][stype]:
|
||||
self.config_data["store"][stype][item_name] = {}
|
||||
|
||||
# Special case: Renaming the store via the NAME field
|
||||
if item_type.startswith("plugin-"):
|
||||
ptype = item_type[len("plugin-"):]
|
||||
plugin_block = self.config_data.setdefault("plugin", {})
|
||||
instances = plugin_block.setdefault(ptype, {})
|
||||
if item_name not in instances:
|
||||
instances[item_name] = {}
|
||||
# Special case: rename via the NAME field
|
||||
if key.upper() == "NAME" and processed_value and str(processed_value) != item_name:
|
||||
new_name = str(processed_value)
|
||||
self.config_data["store"][stype][new_name] = self.config_data["store"][stype].pop(item_name)
|
||||
instances[new_name] = instances.pop(item_name)
|
||||
self.editing_item_name = new_name
|
||||
item_name = new_name
|
||||
|
||||
self.config_data["store"][stype][item_name][key] = processed_value
|
||||
instances[item_name][key] = processed_value
|
||||
else:
|
||||
if item_type not in self.config_data:
|
||||
self.config_data[item_type] = {}
|
||||
@@ -1858,11 +1877,13 @@ class ConfigModal(ModalScreen):
|
||||
item_name = str(self.editing_item_name or "")
|
||||
section = {}
|
||||
|
||||
if item_type.startswith("store-"):
|
||||
stype = item_type.replace("store-", "")
|
||||
section = self.config_data.get("store", {}).get(stype, {}).get(item_name, {})
|
||||
if item_type.startswith("plugin-"):
|
||||
ptype = item_type[len("plugin-"):]
|
||||
section = self.config_data.get("plugin", {}).get(ptype, {}).get(item_name, {})
|
||||
elif item_type == "provider":
|
||||
section = self.config_data.get("provider", {}).get(item_name, {})
|
||||
elif item_type == "plugin":
|
||||
section = self.config_data.get("plugin", {}).get(item_name, {}) or self.config_data.get("provider", {}).get(item_name, {})
|
||||
elif item_type == "tool":
|
||||
section = self.config_data.get("tool", {}).get(item_name, {})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user