Compare commits

..

2 Commits

Author SHA1 Message Date
chrome-storm-c442
6a2db542c3 v1.9.41: fix Grafana/Prometheus tabs — pass server dict to clients
- GrafanaTab/PrometheusTab: use store.get_server(alias) to get server
  dict instead of passing (alias, store) to client constructors
- Fix PrometheusTab: targets() and alerts() method names + parse response
- Fix GrafanaTab: build dashboard URL from client.base_url

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:08:59 -05:00
chrome-storm-c442
d49fa9ce90 v1.9.40: Grafana basic auth support (user/password + api_token)
- Add user/password fields to Grafana in server dialog FIELD_MAP
- GrafanaClient: support basic auth when no api_token is set
- ssh.py: _grafana_request supports basic auth fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:04:09 -05:00
8 changed files with 37 additions and 21 deletions

View File

@@ -20,19 +20,25 @@ class GrafanaClient:
Initialize the Grafana client. Initialize the Grafana client.
Args: Args:
server: dict with keys: ip, port, api_token, use_ssl server: dict with keys: ip, port, api_token (or user+password), use_ssl
""" """
self.ip: str = server["ip"] self.ip: str = server["ip"]
self.port: int = int(server["port"]) self.port: int = int(server["port"])
self.api_token: str = server["api_token"] self.api_token: str = server.get("api_token", "")
self.user: str = server.get("user", "")
self.password: str = server.get("password", "")
self.use_ssl: bool = bool(server.get("use_ssl", False)) self.use_ssl: bool = bool(server.get("use_ssl", False))
scheme = "https" if self.use_ssl else "http" scheme = "https" if self.use_ssl else "http"
self.base_url: str = f"{scheme}://{self.ip}:{self.port}" self.base_url: str = f"{scheme}://{self.ip}:{self.port}"
self.headers: dict[str, str] = { self.headers: dict[str, str] = {"Content-Type": "application/json"}
"Authorization": f"Bearer {self.api_token}", self.auth: tuple[str, str] | None = None
"Content-Type": "application/json",
} if self.api_token:
self.headers["Authorization"] = f"Bearer {self.api_token}"
elif self.user and self.password:
self.auth = (self.user, self.password)
self.timeout: int = 10 self.timeout: int = 10
def _get(self, path: str, params: dict | None = None) -> Any: def _get(self, path: str, params: dict | None = None) -> Any:
@@ -42,7 +48,7 @@ class GrafanaClient:
url = f"{self.base_url}{path}" url = f"{self.base_url}{path}"
log.debug("Grafana GET %s", url) log.debug("Grafana GET %s", url)
resp = requests.get( resp = requests.get(
url, headers=self.headers, params=params, timeout=self.timeout url, headers=self.headers, params=params, auth=self.auth, timeout=self.timeout
) )
resp.raise_for_status() resp.raise_for_status()
return resp.json() return resp.json()
@@ -54,7 +60,7 @@ class GrafanaClient:
url = f"{self.base_url}{path}" url = f"{self.base_url}{path}"
log.debug("Grafana POST %s", url) log.debug("Grafana POST %s", url)
resp = requests.post( resp = requests.post(
url, headers=self.headers, json=json_data, timeout=self.timeout url, headers=self.headers, json=json_data, auth=self.auth, timeout=self.timeout
) )
resp.raise_for_status() resp.raise_for_status()
return resp.json() return resp.json()

View File

@@ -20,7 +20,7 @@ FIELD_MAP = {
"mssql": ["user", "password", "database"], "mssql": ["user", "password", "database"],
"postgresql": ["user", "password", "database"], "postgresql": ["user", "password", "database"],
"redis": ["password", "db_index", "use_ssl"], "redis": ["password", "db_index", "use_ssl"],
"grafana": ["api_token", "use_ssl"], "grafana": ["user", "password", "api_token", "use_ssl"],
"prometheus": ["use_ssl"], "prometheus": ["use_ssl"],
"rdp": ["user", "password", "rdp_resolution", "rdp_quality", "rdp_clipboard", "rdp_drives", "rdp_printers"], "rdp": ["user", "password", "rdp_resolution", "rdp_quality", "rdp_clipboard", "rdp_drives", "rdp_printers"],
"vnc": ["password"], "vnc": ["password"],

View File

@@ -140,7 +140,10 @@ class GrafanaTab(ctk.CTkFrame):
def _get_client(self) -> GrafanaClient: def _get_client(self) -> GrafanaClient:
if self._client is None: if self._client is None:
self._client = GrafanaClient(self._current_alias, self.store) server = self.store.get_server(self._current_alias)
if not server:
raise ValueError(f"Server '{self._current_alias}' not found")
self._client = GrafanaClient(server)
return self._client return self._client
# ── Table population ── # ── Table population ──
@@ -194,10 +197,9 @@ class GrafanaTab(ctk.CTkFrame):
if url: if url:
try: try:
client = self._get_client() client = self._get_client()
full_url = client.get_dashboard_url(url) full_url = f"{client.base_url}{url}"
webbrowser.open(full_url) webbrowser.open(full_url)
except Exception: except Exception:
# Fallback: just open relative URL
webbrowser.open(url) webbrowser.open(url)
break break

View File

@@ -189,8 +189,10 @@ class PrometheusTab(ctk.CTkFrame):
try: try:
client = self._get_client() client = self._get_client()
targets = client.get_targets() targets_resp = client.targets()
alerts = client.get_alerts() targets = targets_resp.get("data", {}).get("activeTargets", [])
alerts_resp = client.alerts()
alerts = alerts_resp.get("data", {}).get("alerts", [])
self.after(0, lambda: self._populate_targets(targets)) self.after(0, lambda: self._populate_targets(targets))
self.after(0, lambda: self._populate_alerts(alerts)) self.after(0, lambda: self._populate_alerts(alerts))
@@ -210,7 +212,10 @@ class PrometheusTab(ctk.CTkFrame):
def _get_client(self) -> PrometheusClient: def _get_client(self) -> PrometheusClient:
if self._client is None: if self._client is None:
self._client = PrometheusClient(self._current_alias, self.store) server = self.store.get_server(self._current_alias)
if not server:
raise ValueError(f"Server '{self._current_alias}' not found")
self._client = PrometheusClient(server)
return self._client return self._client
# ── Table population ── # ── Table population ──

View File

@@ -1511,16 +1511,19 @@ def _grafana_request(server: dict, endpoint: str) -> dict:
import requests import requests
host = server["ip"] host = server["ip"]
port = server.get("port", 3000) port = server.get("port", 3000)
protocol = "https" if server.get("ssl", False) else "http" protocol = "https" if server.get("use_ssl", server.get("ssl", False)) else "http"
base_url = server.get("base_url", f"{protocol}://{host}:{port}") base_url = server.get("base_url", f"{protocol}://{host}:{port}")
api_key = server.get("api_key", server.get("password", "")) api_token = server.get("api_token", server.get("api_key", ""))
headers = {} headers = {}
if api_key: auth = None
headers["Authorization"] = f"Bearer {api_key}" if api_token:
headers["Authorization"] = f"Bearer {api_token}"
elif server.get("user") and server.get("password"):
auth = (server["user"], server["password"])
url = f"{base_url.rstrip('/')}/api/{endpoint.lstrip('/')}" url = f"{base_url.rstrip('/')}/api/{endpoint.lstrip('/')}"
resp = requests.get(url, headers=headers, timeout=15, verify=server.get("ssl_verify", True)) resp = requests.get(url, headers=headers, auth=auth, timeout=15, verify=server.get("ssl_verify", True))
resp.raise_for_status() resp.raise_for_status()
return resp.json() return resp.json()

View File

@@ -1,6 +1,6 @@
"""Version info for ServerManager.""" """Version info for ServerManager."""
__version__ = "1.9.39" __version__ = "1.9.41"
__app_name__ = "ServerManager" __app_name__ = "ServerManager"
__author__ = "aibot777" __author__ = "aibot777"
__description__ = "Desktop GUI for managing remote servers" __description__ = "Desktop GUI for managing remote servers"