import json from html import escape from pathlib import Path from typing import Dict, List def _safe(value: str) -> str: return escape(str(value or "")) def _folder_tree(rows: List[dict]) -> dict: root = {"name": "ROOT", "path": "", "count": 0, "children": {}} for row in rows: p = row.get("path") or "" parts = [x for x in p.split("/") if x] if not parts: continue # Folder-only tree: for file items exclude last segment. folder_depth = len(parts) if row.get("item_kind") == "FOLDER" else len(parts) - 1 if folder_depth <= 0: continue node = root node["count"] += 1 current = [] for i in range(folder_depth): part = parts[i] current.append(part) key = "/".join(current) if part not in node["children"]: node["children"][part] = { "name": part, "path": key, "count": 0, "children": {}, } node = node["children"][part] node["count"] += 1 return root def _render_tree(node: dict) -> str: if not node["children"]: return "" out = ["") return "".join(out) def render_explorer(curated_rows: List[dict], output_html: Path, build_id: str = "") -> None: rows = sorted(curated_rows, key=lambda x: (x.get("path") or "", x.get("name") or "")) tree_html = _render_tree(_folder_tree(rows)) payload_rows = [] for r in rows: payload_rows.append( { "item_id": r.get("item_id", ""), "name": r.get("name", ""), "path": r.get("path", ""), "root_path": r.get("root_path", ""), "item_kind": r.get("item_kind", ""), "file_category": r.get("file_category", ""), "original_owner_email": r.get("original_owner_email", ""), "shared_by_email": r.get("shared_by_email", ""), "access_scope": r.get("access_scope", ""), "shared_with_count": int(r.get("shared_with_count", 0) or 0), "access_targets": r.get("access_targets", ""), "permission_entries": r.get("permission_entries", ""), "risk_level": r.get("risk_level", "LOW"), "risk_reason": r.get("risk_reason", ""), "risk_score": int(r.get("risk_score", 0) or 0), "risk_flags": r.get("risk_flags", ""), "external_target_count": int(r.get("external_target_count", 0) or 0), "writer_target_count": int(r.get("writer_target_count", 0) or 0), } ) rows_json = json.dumps(payload_rows, ensure_ascii=True) html = f""" GDrive Explorer + Diagram
Sort: path/name | click selects + highlights
Global Security Score -
Rules: public/domain/external/writer/mass-sharing
Total: - High: - Medium: - Low: - Public/Domain: - External share: -
HIGH: score 70+ | MEDIUM: 35-69 | LOW: 0-34
Path: All
Path Tree (Folders)
{tree_html}
Sunburst (Drive Structure)
Selected item: none
View: Folder map
Scroll: zoom | Click: select + highlight
Hover any segment for details.
Share Network
Selected item: none
Live Context
Loading context...
Selection details appear here in plain English. Use this panel to understand what you selected and where access risk is concentrated.
Item Access Matrix (hidden helper)
Lazy matrix loading
Path/Name Type Owner Access # Risk Copy
""" output_html.write_text(html, encoding="utf-8")