Files
APIClient-Agent/app/ui/environment_dialog.py
2026-03-28 17:42:37 +05:30

239 lines
9.1 KiB
Python

"""APIClient - Agent - Environment Manager Dialog."""
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QListWidget, QListWidgetItem,
QPushButton, QTableWidget, QTableWidgetItem, QHeaderView,
QLabel, QInputDialog, QMessageBox, QSplitter, QWidget
)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QBrush, QColor
from app.ui.theme import Colors
from app.core import storage
from app.models import Environment
class EnvironmentDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Manage Environments")
self.setMinimumSize(760, 520)
self._current_env: Environment | None = None
self._dirty = False
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# ── Title bar ─────────────────────────────────────────────────────────
title_bar = QWidget()
title_bar.setObjectName("panelHeader")
title_bar.setFixedHeight(48)
tl = QHBoxLayout(title_bar)
tl.setContentsMargins(16, 0, 16, 0)
title = QLabel("Manage Environments")
title.setObjectName("panelTitle")
tl.addWidget(title)
layout.addWidget(title_bar)
# ── Splitter ──────────────────────────────────────────────────────────
splitter = QSplitter(Qt.Orientation.Horizontal)
splitter.setHandleWidth(1)
# Left: environment list
left = QWidget()
left.setObjectName("sidebarPanel")
left.setFixedWidth(220)
ll = QVBoxLayout(left)
ll.setContentsMargins(0, 0, 0, 0)
ll.setSpacing(0)
list_header = QWidget()
list_header.setObjectName("sectionHeader")
list_header.setFixedHeight(36)
lh = QHBoxLayout(list_header)
lh.setContentsMargins(12, 0, 8, 0)
env_heading = QLabel("ENVIRONMENTS")
env_heading.setObjectName("sectionLabel")
lh.addWidget(env_heading)
lh.addStretch()
add_env_btn = QPushButton("+")
add_env_btn.setObjectName("ghost")
add_env_btn.setFixedSize(26, 26)
add_env_btn.setToolTip("Add Environment")
add_env_btn.clicked.connect(self._add_env)
lh.addWidget(add_env_btn)
ll.addWidget(list_header)
self.env_list = QListWidget()
self.env_list.setObjectName("sidebarList")
self.env_list.currentItemChanged.connect(self._on_env_selected)
ll.addWidget(self.env_list)
btn_row = QHBoxLayout()
btn_row.setContentsMargins(8, 6, 8, 6)
btn_row.setSpacing(6)
self.activate_btn = QPushButton("Set Active")
self.activate_btn.clicked.connect(self._set_active)
self.del_btn = QPushButton("Delete")
self.del_btn.setObjectName("danger")
self.del_btn.clicked.connect(self._delete_env)
btn_row.addWidget(self.activate_btn)
btn_row.addWidget(self.del_btn)
ll.addLayout(btn_row)
splitter.addWidget(left)
# Right: variable table
right = QWidget()
right.setObjectName("panelBody")
rl = QVBoxLayout(right)
rl.setContentsMargins(0, 0, 0, 0)
rl.setSpacing(0)
var_header = QWidget()
var_header.setObjectName("panelHeader")
var_header.setFixedHeight(36)
vh = QHBoxLayout(var_header)
vh.setContentsMargins(16, 0, 12, 0)
var_label = QLabel("Variables")
var_label.setObjectName("fieldLabel")
vh.addWidget(var_label)
vh.addStretch()
add_var_btn = QPushButton("+ Add Variable")
add_var_btn.setObjectName("ghost")
add_var_btn.clicked.connect(self._add_var_row)
vh.addWidget(add_var_btn)
rl.addWidget(var_header)
self.var_table = QTableWidget(0, 2)
self.var_table.setHorizontalHeaderLabels(["Variable", "Value"])
self.var_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self.var_table.verticalHeader().setVisible(False)
self.var_table.setAlternatingRowColors(True)
self.var_table.itemChanged.connect(self._on_var_changed)
rl.addWidget(self.var_table, 1)
splitter.addWidget(right)
splitter.setSizes([220, 520])
layout.addWidget(splitter, 1)
# ── Bottom bar ────────────────────────────────────────────────────────
bottom = QWidget()
bottom.setObjectName("panelFooter")
bottom.setFixedHeight(52)
bl = QHBoxLayout(bottom)
bl.setContentsMargins(16, 0, 16, 0)
bl.addStretch()
save_btn = QPushButton("Save & Close")
save_btn.setObjectName("accent")
save_btn.setFixedWidth(120)
save_btn.clicked.connect(self._save_and_close)
bl.addWidget(save_btn)
layout.addWidget(bottom)
self._load_envs()
# ── Data loading ──────────────────────────────────────────────────────────
def _load_envs(self):
self.env_list.clear()
for env in storage.get_environments():
label = f"{'' if env.is_active else ' '} {env.name}"
item = QListWidgetItem(label)
item.setData(Qt.ItemDataRole.UserRole, env)
if env.is_active:
item.setForeground(QBrush(QColor(Colors.ACCENT)))
self.env_list.addItem(item)
def _on_env_selected(self, current, _previous):
if self._current_env and self._dirty:
self._current_env.variables = self._get_vars()
if not current:
return
self._current_env = current.data(Qt.ItemDataRole.UserRole)
self._dirty = False
self._load_vars(self._current_env.variables)
def _load_vars(self, variables: dict):
self.var_table.blockSignals(True)
self.var_table.setRowCount(0)
for k, v in variables.items():
row = self.var_table.rowCount()
self.var_table.insertRow(row)
self.var_table.setItem(row, 0, QTableWidgetItem(k))
self.var_table.setItem(row, 1, QTableWidgetItem(str(v)))
self.var_table.blockSignals(False)
def _on_var_changed(self):
if self._current_env:
self._dirty = True
def _get_vars(self) -> dict:
result = {}
for row in range(self.var_table.rowCount()):
k = self.var_table.item(row, 0)
v = self.var_table.item(row, 1)
if k and k.text().strip():
result[k.text().strip()] = v.text() if v else ""
return result
# ── Actions ───────────────────────────────────────────────────────────────
def _add_var_row(self):
if not self._current_env:
QMessageBox.information(
self, "No Environment Selected",
"Select or create an environment first."
)
return
row = self.var_table.rowCount()
self.var_table.insertRow(row)
self.var_table.setItem(row, 0, QTableWidgetItem(""))
self.var_table.setItem(row, 1, QTableWidgetItem(""))
self.var_table.editItem(self.var_table.item(row, 0))
def _add_env(self):
name, ok = QInputDialog.getText(self, "New Environment", "Name:")
if not ok or not name.strip():
return
env = Environment(name=name.strip())
env_id = storage.save_environment(env)
env.id = env_id
self._load_envs()
for i in range(self.env_list.count()):
item = self.env_list.item(i)
if item.data(Qt.ItemDataRole.UserRole).id == env_id:
self.env_list.setCurrentItem(item)
break
def _delete_env(self):
item = self.env_list.currentItem()
if not item:
return
env = item.data(Qt.ItemDataRole.UserRole)
reply = QMessageBox.question(
self, "Delete Environment",
f"Delete '{env.name}'? This cannot be undone.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel
)
if reply == QMessageBox.StandardButton.Yes:
storage.delete_environment(env.id)
self._current_env = None
self._dirty = False
self.var_table.setRowCount(0)
self._load_envs()
def _set_active(self):
item = self.env_list.currentItem()
if not item:
return
env = item.data(Qt.ItemDataRole.UserRole)
storage.set_active_environment(env.id)
self._load_envs()
def _save_and_close(self):
if self._current_env:
self._current_env.variables = self._get_vars()
storage.save_environment(self._current_env)
self.accept()