Index: server.py
===================================================================
--- server.py	(revision d42aac31fe37e6e42a1b0515b54b86e8706d90a8)
+++ server.py	(revision e4c61dd6cd86e06265bc2bd91adba84a0f04044a)
@@ -47,6 +47,12 @@
 
 from flask_cors import CORS
-
-load_dotenv()
+from dotenv import load_dotenv
+from pathlib import Path
+
+BASE_DIR = Path(__file__).resolve().parent
+load_dotenv(BASE_DIR / ".env")
+
+print("GRAFANA_API_TOKEN =", os.environ.get("GRAFANA_API_TOKEN"))
+
 
 # ----------------------------
@@ -61,4 +67,5 @@
 JWT_ISSUER = os.environ.get("JWT_ISSUER", "netintel")
 COOKIE_SECURE = os.environ.get("COOKIE_SECURE", "0") == "1"  # set 1 only on HTTPS
+GRAFANA_TOKEN = os.environ.get("GRAFANA_TOKEN", "")
 
 # CORS origins (add your LAN origins if needed)
@@ -1120,85 +1127,572 @@
 # Simple RAG chat
 # ----------------------------
-def build_rag_context(tenant_id: int, question: str, computer_name=None, limit_per_table=10):
-    conn = db()
-    c = conn.cursor()
-
-    params = [tenant_id]
-    comp_clause = ""
-    if computer_name:
-        comp_clause = "AND c2.name = ?"
-        params.append(computer_name)
-
-    # sysmon
-    c.execute(f"""
-            SELECT s.timestamp, s.event_id, s.event_type, s.message
-            FROM sysmon_events s
-            JOIN computers c2 ON c2.id = s.computer_id
-            WHERE c2.tenant_id = ? {comp_clause}
-            ORDER BY s.timestamp DESC
-            LIMIT ?
-        """, (*params, limit_per_table))
-    sysmon_rows = c.fetchall()
-
-    # perf
-    c.execute(f"""
-            SELECT h.timestamp, h.cpu_usage, h.ram_usage, h.disk_usage, h.network_sent_mb, h.network_recv_mb
-            FROM computer_history h
-            JOIN computers c2 ON c2.id = h.computer_id
-            WHERE c2.tenant_id = ? {comp_clause}
-            ORDER BY h.timestamp DESC
-            LIMIT ?
-        """, (*params, limit_per_table))
-    perf_rows = c.fetchall()
-
-    # proc
-    c.execute(f"""
-            SELECT p.timestamp, p.pid, p.name, p.cpu_percent, p.memory_mb
-            FROM computer_processes_current p
-            JOIN computers c2 ON c2.id = p.computer_id
-            WHERE c2.tenant_id = ? {comp_clause}
-            ORDER BY p.timestamp DESC
-            LIMIT ?
-        """, (*params, limit_per_table))
-    proc_rows = c.fetchall()
-
-    conn.close()
-
-    lines = []
-    for r in sysmon_rows:
-        lines.append(f"[SYSMON] {r['timestamp']} | Event {r['event_id']} ({r['event_type']}): {r['message']}")
-    for r in perf_rows:
-        lines.append(
-            f"[PERF] {r['timestamp']} | CPU {r['cpu_usage']}% | RAM {r['ram_usage']}% | Disk {r['disk_usage']}% | "
-            f"Net sent {r['network_sent_mb']}MB / recv {r['network_recv_mb']}MB"
-        )
-    for r in proc_rows:
-        lines.append(
-            f"[PROC] {r['timestamp']} | PID {r['pid']} | {r['name']} | CPU {r['cpu_percent']}% | RAM {r['memory_mb']}MB"
-        )
-
-    return "\n".join(lines)
-
-
+# def build_rag_context(tenant_id: int, question: str, computer_name=None, limit_per_table=10):
+#     conn = db()
+#     c = conn.cursor()
+#
+#     params = [tenant_id]
+#     comp_clause = ""
+#     if computer_name:
+#         comp_clause = "AND c2.name = ?"
+#         params.append(computer_name)
+#
+#     # sysmon
+#     c.execute(f"""
+#             SELECT s.timestamp, s.event_id, s.event_type, s.message
+#             FROM sysmon_events s
+#             JOIN computers c2 ON c2.id = s.computer_id
+#             WHERE c2.tenant_id = ? {comp_clause}
+#             ORDER BY s.timestamp DESC
+#             LIMIT ?
+#         """, (*params, limit_per_table))
+#     sysmon_rows = c.fetchall()
+#
+#     # perf
+#     c.execute(f"""
+#             SELECT h.timestamp, h.cpu_usage, h.ram_usage, h.disk_usage, h.network_sent_mb, h.network_recv_mb
+#             FROM computer_history h
+#             JOIN computers c2 ON c2.id = h.computer_id
+#             WHERE c2.tenant_id = ? {comp_clause}
+#             ORDER BY h.timestamp DESC
+#             LIMIT ?
+#         """, (*params, limit_per_table))
+#     perf_rows = c.fetchall()
+#
+#     # proc
+#     c.execute(f"""
+#             SELECT p.timestamp, p.pid, p.name, p.cpu_percent, p.memory_mb
+#             FROM computer_processes_current p
+#             JOIN computers c2 ON c2.id = p.computer_id
+#             WHERE c2.tenant_id = ? {comp_clause}
+#             ORDER BY p.timestamp DESC
+#             LIMIT ?
+#         """, (*params, limit_per_table))
+#     proc_rows = c.fetchall()
+#
+#     conn.close()
+#
+#     lines = []
+#     for r in sysmon_rows:
+#         lines.append(f"[SYSMON] {r['timestamp']} | Event {r['event_id']} ({r['event_type']}): {r['message']}")
+#     for r in perf_rows:
+#         lines.append(
+#             f"[PERF] {r['timestamp']} | CPU {r['cpu_usage']}% | RAM {r['ram_usage']}% | Disk {r['disk_usage']}% | "
+#             f"Net sent {r['network_sent_mb']}MB / recv {r['network_recv_mb']}MB"
+#         )
+#     for r in proc_rows:
+#         lines.append(
+#             f"[PROC] {r['timestamp']} | PID {r['pid']} | {r['name']} | CPU {r['cpu_percent']}% | RAM {r['memory_mb']}MB"
+#         )
+#
+#     return "\n".join(lines)
 def ask_llm_with_context(question, context):
     if not client:
         return "Немаш поставено OPENAI_API_KEY на серверот."
 
-    system_msg = (
-        "Ти си асистент за безбедност и систем мониторинг. "
-        "Одговараш базирано ИСКЛУЧИВО на дадените логови. "
-        "Ако нема доволно податоци, кажи дека нема доволно информации. "
-        "Кога корисникот прашува за процеси и нивна потрошувачка, користи ги [PROC] редовите."
+    prompt = f"""Ти си асистент за безбедност и систем мониторинг.
+Одговарај базирано ИСКЛУЧИВО на дадените логови.
+Ако нема доволно податоци, кажи: 'Нема доволно информации во логовите.'.
+
+Прашање: {question}
+
+Логови:
+{context or 'Нема логови.'}
+"""
+
+    resp = client.responses.create(
+        model="gpt-4.1-mini",
+        input=[{"role": "user", "content": [{"type": "input_text", "text": prompt}]}],
     )
+
+    text = getattr(resp, "output_text", "") or ""
+    return text.strip() or "Нема доволно информации во логовите."
+
+def require_grafana():
+    def deco(fn):
+        @wraps(fn)
+        def wrap(*args, **kwargs):
+            tok = request.headers.get("X-Grafana-Token", "")
+            if not GRAFANA_TOKEN or tok != GRAFANA_TOKEN:
+                return jsonify({"error": "Unauthorized"}), 401
+            return fn(*args, **kwargs)
+        return wrap
+    return deco
+@app.route("/api/public/stats", methods=["GET"])
+@require_grafana()
+def grafana_stats():
+    # ако сакаш само една околина:
+    env = request.args.get("env", "default")
+
+    conn = db()
+    c = conn.cursor()
+
+    # вкупно sysmon во 24h (за сите tenants, или ако сакаш само 1 tenant додади филтер)
+    c.execute("""
+        SELECT COUNT(*) AS n
+        FROM sysmon_events s
+        JOIN computers c2 ON c2.id = s.computer_id
+        WHERE s.timestamp > datetime('now','-24 hours')
+          AND c2.env_name = ?
+    """, (env,))
+    total_sysmon = int(c.fetchone()["n"] or 0)
+
+    c.execute("""
+        SELECT COUNT(*) AS n
+        FROM network_connections n
+        JOIN computers c2 ON c2.id = n.computer_id
+        WHERE n.timestamp > datetime('now','-24 hours')
+          AND c2.env_name = ?
+    """, (env,))
+    total_net = int(c.fetchone()["n"] or 0)
+
+    conn.close()
+    return jsonify({
+        "total_sysmon_events": total_sysmon,
+        "total_network_connections": total_net,
+        "server_time": datetime.now().isoformat(),
+        "env": env,
+    })
+
+def require_grafana_token(fn):
+    @wraps(fn)
+    def wrapper(*args, **kwargs):
+        expected = os.environ.get("GRAFANA_API_TOKEN") or ""
+
+        # 1) пробај Authorization: Bearer <token>
+        auth = request.headers.get("Authorization", "")
+        token = ""
+        if auth.startswith("Bearer "):
+            token = auth.replace("Bearer ", "").strip()
+
+        # 2) ако нема header, пробај query param ?token=
+        if not token:
+            token = (request.args.get("token") or "").strip()
+
+        if not expected or token != expected:
+            return jsonify({"error": "Invalid Grafana token"}), 401
+
+        return fn(*args, **kwargs)
+    return wrapper
+
+@app.route("/api/public/ips", methods=["GET"])
+@require_grafana_token
+def api_public_ips():
+    tenant_id = request.args.get("tenant_id", type=int)
+    env = request.args.get("env", "default")
+    hours = int(request.args.get("hours", "24"))
+    limit = int(request.args.get("limit", "200"))
+
+    if not tenant_id:
+        return jsonify({"error": "Missing tenant_id"}), 400
+
+    conn = db()
+    c = conn.cursor()
+
+    c.execute("""
+        SELECT DISTINCT
+            substr(nc.remote_address, 1,
+                   CASE
+                       WHEN instr(nc.remote_address, ':') > 0
+                       THEN instr(nc.remote_address, ':') - 1
+                       ELSE length(nc.remote_address)
+                   END
+            ) AS ip
+        FROM network_connections nc
+        JOIN computers c2 ON c2.id = nc.computer_id
+        WHERE c2.tenant_id = ?
+          AND c2.env_name = ?
+          AND nc.timestamp > datetime('now', ?)
+          AND nc.remote_address IS NOT NULL
+          AND nc.remote_address != ''
+        ORDER BY ip
+        LIMIT ?
+    """, (tenant_id, env, f"-{hours} hours", limit))
+
+    ips = [r["ip"] for r in c.fetchall()]
+    conn.close()
+
+    return jsonify({"ips": ips})
+
+
+
+@app.route("/api/public/sankey", methods=["GET"])
+@require_grafana_token
+def api_public_sankey():
+    tenant_id = int(request.args.get("tenant_id", "1"))
+    env = request.args.get("env", "office1")
+    hours = int(request.args.get("hours", "24"))
+    limit = int(request.args.get("limit", "20"))
+
+    computers = (request.args.get("computers") or "all").strip()
+    ips = (request.args.get("ips") or "all").strip()
+
+    # normalize grafana All values
+    if computers in ("", "__all"):
+        computers = "all"
+    if ips in ("", "__all"):
+        ips = "all"
+
+    comp_list = [c.strip() for c in computers.split(",") if c.strip()] if computers != "all" else []
+    ip_list = [i.strip() for i in ips.split(",") if i.strip()] if ips != "all" else []
+
+    comp_clause = ""
+    ip_clause = ""
+    params = [tenant_id, env, f"-{hours} hours"]
+
+    # ✅ target без port за IPv4: "1.2.3.4:443" -> "1.2.3.4"
+    # ⚠️ за IPv6 ќе го остави како што е (за да не го расипеме со split по ':')
+    target_expr = """
+    CASE
+      WHEN nc.remote_address LIKE '%:%' AND nc.remote_address NOT LIKE '%:%:%'
+      THEN substr(nc.remote_address, 1, instr(nc.remote_address, ':') - 1)
+      ELSE nc.remote_address
+    END
+    """
+
+    if comp_list:
+        comp_clause = "AND comp.name IN (" + ",".join(["?"] * len(comp_list)) + ")"
+        params.extend(comp_list)
+
+    # ✅ Филтер по IP без порт (истиот expr)
+    if ip_list:
+        ip_clause = f"AND ({target_expr}) IN (" + ",".join(["?"] * len(ip_list)) + ")"
+        params.extend(ip_list)
+
+    # LIMIT на крај
+    params.append(limit)
+
+    conn = db()
+    c = conn.cursor()
+
+    c.execute(f"""
+        SELECT
+            comp.name AS source,
+            ({target_expr}) AS target,
+            COUNT(*) AS value
+        FROM network_connections nc
+        JOIN computers comp ON comp.id = nc.computer_id
+        WHERE comp.tenant_id = ?
+          AND comp.env_name = ?
+          AND nc.timestamp > datetime('now', ?)
+          AND nc.remote_address IS NOT NULL
+          AND nc.remote_address != ''
+
+          -- ✅ тргни loopback spam (овие прават да изгледа како да нема линии)
+          AND nc.remote_address NOT LIKE '127.0.0.1%'
+          AND nc.remote_address NOT LIKE '::1%'
+
+          {comp_clause}
+          {ip_clause}
+        GROUP BY comp.name, target
+        ORDER BY value DESC
+        LIMIT ?
+    """, params)
+
+    rows = [dict(r) for r in c.fetchall()]
+    conn.close()
+    return jsonify(rows)
+
+from urllib.parse import urlparse
+
+def _ip_only(addr: str):
+    # addr може да е "1.2.3.4:443" или "hostname:port"
+    if not addr:
+        return None
+    # ако има повеќе ':' (ipv6), ќе го оставиме целото; за ipv4 split е ок
+    if addr.count(":") == 1:
+        return addr.split(":")[0]
+    return addr
+
+@app.route("/api/graph/net", methods=["GET"])
+@require_user()
+def api_graph_net():
+    tenant_id = request.user["tenant_id"]
+    env = request.args.get("env") or request.headers.get("X-Env", "default")
+    computer = request.args.get("computer")  # optional
+    since_hours = int(request.args.get("since_hours") or 24)
+
+    conn = db()
+    c = conn.cursor()
+
+    params = [tenant_id, env, since_hours]
+    comp_clause = ""
+    if computer:
+        comp_clause = "AND c2.name = ?"
+        params.append(computer)
+
+    # Вади top remote IP по број на конекции
+    c.execute(f"""
+        SELECT
+          c2.name AS computer_name,
+          n.remote_address AS remote_address,
+          COUNT(*) AS cnt
+        FROM network_connections n
+        JOIN computers c2 ON c2.id = n.computer_id
+        WHERE c2.tenant_id = ?
+          AND c2.env_name = ?
+          AND n.timestamp > datetime('now', '-' || ? || ' hours')
+          {comp_clause}
+          AND n.remote_address IS NOT NULL
+          AND n.remote_address <> ''
+        GROUP BY c2.name, n.remote_address
+        ORDER BY cnt DESC
+        LIMIT 200
+    """, params)
+
+    rows = c.fetchall()
+    conn.close()
+
+    # Build NodeGraph-like structure
+    nodes = {}
+    edges = []
+
+    def add_node(node_id, title, ntype):
+        if node_id not in nodes:
+            nodes[node_id] = {"id": node_id, "title": title, "subTitle": ntype}
+
+    for r in rows:
+        comp_name = r["computer_name"]
+        remote = _ip_only(r["remote_address"]) or r["remote_address"]
+        cnt = int(r["cnt"] or 0)
+
+        comp_id = f"comp:{comp_name}"
+        ip_id = f"ip:{remote}"
+
+        add_node(comp_id, comp_name, "computer")
+        add_node(ip_id, remote, "remote_ip")
+
+        edges.append({
+            "id": f"{comp_id}->{ip_id}",
+            "source": comp_id,
+            "target": ip_id,
+            "mainStat": str(cnt),
+        })
+
+    return jsonify({
+        "nodes": list(nodes.values()),
+        "edges": edges,
+        "meta": {"env": env, "computer": computer, "since_hours": since_hours}
+    })
+
+@app.route("/api/public/netgraph", methods=["GET"])
+@require_grafana_token
+def api_public_netgraph():
+    tenant_id = int(request.args.get("tenant_id", "1"))
+    env = request.args.get("env", "default")
+    hours = int(request.args.get("hours", "6"))
+    limit_edges = int(request.args.get("limit", "200"))
+    computer = request.args.get("computer")  # <-- NEW (optional)
+
+    conn = db()
+    c = conn.cursor()
+
+    comp_clause = ""
+    params = [tenant_id, env, f"-{hours} hours"]
+
+    if computer and computer != "all":
+        comp_clause = "AND comp.name = ?"
+        params.append(computer)
+
+    params.append(limit_edges)
+
+    c.execute(f"""
+        SELECT
+            comp.name AS src,
+            nc.remote_address AS remote,
+            COUNT(*) AS cnt
+        FROM network_connections nc
+        JOIN computers comp ON comp.id = nc.computer_id
+        WHERE comp.tenant_id = ?
+          AND comp.env_name = ?
+          AND nc.timestamp > datetime('now', ?)
+          {comp_clause}
+          AND nc.remote_address IS NOT NULL
+          AND nc.remote_address != ''
+        GROUP BY comp.name, nc.remote_address
+        ORDER BY cnt DESC
+        LIMIT ?
+    """, params)
+
+    rows = c.fetchall()
+    conn.close()
+
+    def ip_only(addr: str) -> str:
+        if not addr:
+            return ""
+        if addr.startswith("[") and "]" in addr:
+            return addr[1:addr.index("]")]
+        if ":" in addr:
+            return addr.split(":")[0]
+        return addr
+
+    nodes = {}
+    edges = []
+
+    for r in rows:
+        src = r["src"]
+        remote_ip = ip_only(r["remote"])
+        cnt = int(r["cnt"] or 0)
+        if not remote_ip:
+            continue
+
+        nodes[src] = {"id": src, "title": src, "subTitle": env, "mainStat": "PC"}
+        ip_id = f"IP:{remote_ip}"
+        if ip_id not in nodes:
+            nodes[ip_id] = {"id": ip_id, "title": remote_ip, "subTitle": "remote", "mainStat": "IP"}
+
+        edges.append({
+            "id": f"{src}->{ip_id}",
+            "source": src,
+            "target": ip_id,
+            "mainStat": cnt
+        })
+
+    return jsonify({"nodes": list(nodes.values()), "edges": edges})
+@app.route("/api/public/computers", methods=["GET"])
+@require_grafana_token
+def api_public_computers():
+    tenant_id = int(request.args.get("tenant_id", "1"))
+    env = request.args.get("env", "default")
+
+    conn = db()
+    c = conn.cursor()
+    c.execute("""
+        SELECT name
+        FROM computers
+        WHERE tenant_id = ? AND env_name = ?
+        ORDER BY name
+    """, (tenant_id, env))
+    names = [r["name"] for r in c.fetchall()]
+    conn.close()
+
+    return jsonify({"computers": names})
+
+@app.route("/api/public/stats", methods=["GET"])
+@require_grafana_token
+def public_stats():
+    # ⚠️ ако сакаш tenant по tenant, ќе мора да додадеш tenant_id како query param,
+    # или да имаш 1 tenant во проект за сега.
+    tenant_id = request.args.get("tenant_id", type=int)
+    if not tenant_id:
+        return jsonify({"error": "Missing tenant_id"}), 400
+
+    conn = db()
+    c = conn.cursor()
+
+    c.execute("""
+        SELECT COUNT(*) AS n
+        FROM sysmon_events s
+        JOIN computers c2 ON c2.id = s.computer_id
+        WHERE s.timestamp > datetime('now','-24 hours')
+          AND c2.tenant_id = ?
+    """, (tenant_id,))
+    total_sysmon = int(c.fetchone()["n"] or 0)
+
+    c.execute("""
+        SELECT COUNT(*) AS n
+        FROM network_connections n
+        JOIN computers c2 ON c2.id = n.computer_id
+        WHERE n.timestamp > datetime('now','-24 hours')
+          AND c2.tenant_id = ?
+    """, (tenant_id,))
+    total_net = int(c.fetchone()["n"] or 0)
+
+    conn.close()
+
+    return jsonify({
+        "total_sysmon_events": total_sysmon,
+        "total_network_connections": total_net,
+        "server_time": datetime.now().isoformat(),
+    })
+
+
+def ask_llm_sql_generator(question: str, tenant_id: int, env: str, computer_name: str | None):
+    """
+    Returns dict: { "queries": [...], "notes": "...", "scope": {...} }
+    Only SELECT/WITH queries, no modifications.
+    """
+    if not client:
+        return {
+            "queries": [],
+            "notes": "Немаш поставено OPENAI_API_KEY на серверот.",
+            "scope": {"tenant_id": tenant_id, "env": env, "computer_name": computer_name},
+        }
+
+    schema = """
+SQLite schema (важни табели):
+- computers(id, tenant_id, env_name, name, user, ip, os, first_seen, last_seen, sysmon_available)
+- computer_history(computer_id, cpu_usage, ram_usage, disk_usage, network_sent_mb, network_recv_mb, timestamp)
+- computer_processes_current(computer_id, pid, name, cpu_percent, memory_mb, username, cmdline, timestamp)
+- computer_processes_history(computer_id, pid, name, cpu_percent, memory_mb, username, cmdline, timestamp)
+- sysmon_events(computer_id, event_id, event_type, message, timestamp, details)
+- network_connections(computer_id, pid, local_address, remote_address, status, process_name, timestamp)
+
+Правила:
+- Врати САМО валиден JSON.
+- JSON формат: {"queries":[...], "notes":"..."}.
+- queries мора да бидат само SELECT или WITH (никако INSERT/UPDATE/DELETE/ALTER/DROP).
+- максимум 3 queries.
+- користи placeholders: :tenant_id, :env, :computer_name (ако е дадено).
+- ако нема доволно инфо -> queries празно и notes објаснува.
+""".strip()
+
+    user_scope = {
+        "tenant_id": tenant_id,
+        "env": env,
+        "computer_name": computer_name,
+    }
+
+    user_msg = f"""
+Прашање од корисник: {question}
+
+Scope (вметни во WHERE):
+- tenant_id = :tenant_id
+- env = :env
+- computer_name = :computer_name (ако не е null)
+
+Врати 1-3 SELECT/WITH queries за да се добијат потребните податоци.
+""".strip()
 
     completion = client.chat.completions.create(
         model="gpt-4.1-mini",
         messages=[
-            {"role": "system", "content": system_msg},
-            {"role": "user", "content": f"Прашање: {question}\n\nЛогови:\n{context or 'Нема логови.'}"},
+            {"role": "system", "content": schema},
+            {"role": "user", "content": user_msg},
         ],
-        temperature=0.2,
+        temperature=0.1,
     )
-    return completion.choices[0].message.content
+
+    raw = completion.choices[0].message.content or ""
+
+    # Safe parse JSON
+    try:
+        out = json.loads(raw)
+        if not isinstance(out, dict):
+            raise ValueError("Not an object")
+        queries = out.get("queries") or []
+        notes = out.get("notes") or ""
+
+        # hard safety filter: keep only SELECT/WITH
+        safe_queries = []
+        for q in queries:
+            if not isinstance(q, str):
+                continue
+            qs = q.strip().lower()
+            if qs.startswith("select") or qs.startswith("with"):
+                # block obvious multi-statement
+                if ";" in q.strip().rstrip(";"):
+                    continue
+                safe_queries.append(q.strip())
+
+        return {"queries": safe_queries[:3], "notes": notes, "scope": user_scope}
+
+    except Exception:
+        # fallback ако AI не врати JSON
+        return {
+            "queries": [],
+            "notes": "AI не врати валиден JSON. Обиди се повторно (или намали прашање).",
+            "scope": user_scope,
+            "raw": raw[:1200],
+        }
 
 
@@ -1208,5 +1702,7 @@
     try:
         tenant_id = request.user["tenant_id"]
+        env = request.headers.get("X-Env", "default")  # важно!
         data = request.get_json(force=True, silent=True) or {}
+
         question = (data.get("question") or "").strip()
         computer_name = data.get("computer_name") or None
@@ -1215,11 +1711,24 @@
             return jsonify({"error": "No question provided"}), 400
 
-        ctx = build_rag_context(tenant_id, question, computer_name=computer_name, limit_per_table=10)
-        ans = ask_llm_with_context(question, ctx)
-        return jsonify({"answer": ans or ""}), 200
+        out = ask_llm_sql_generator(
+            question=question,
+            tenant_id=tenant_id,
+            env=env,
+            computer_name=computer_name,
+        )
+
+        # Тука не извршуваме SQL, само враќаме query за копирање.
+        return jsonify({
+            "ok": True,
+            "queries": out.get("queries", []),
+            "notes": out.get("notes", ""),
+            "scope": out.get("scope", {}),
+            # "raw": out.get("raw")  # ако сакаш debug
+        }), 200
 
     except Exception as e:
         traceback.print_exc()
         return jsonify({"error": str(e)}), 500
+
 
 
