feat: v3.4.0 — OpenPanel plugin public release (42 tools)

OpenPanel product analytics plugin fully reviewed, tested, and published.
Works with both self-hosted and cloud (openpanel.dev) instances.

- 42 tools: event tracking, data export, analytics, project/client management
- All tools use public REST APIs (Track, Export, Insights, Manage)
- Client modes: write (tracking), read (analytics), root (full access)
- Service page with description, setup notes, WordPress plugin download
- Dynamic URL hints in Add Site form
- 62 unit tests
- ENABLED_PLUGINS default now includes openpanel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 17:48:12 +02:00
parent 68c84b3ece
commit 9905a28eb0
33 changed files with 1672 additions and 3207 deletions

View File

@@ -1,4 +1,8 @@
"""Projects Handler - OpenPanel project management (8 tools)"""
"""Projects Handler - OpenPanel project management (5 tools).
Uses Manage API (GET/POST/PATCH/DELETE /manage/projects).
Requires 'root' mode client for write operations.
"""
import json
from typing import Any
@@ -7,22 +11,24 @@ from plugins.openpanel.client import OpenPanelClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (8 tools)"""
"""Return tool specifications for ToolGenerator (5 tools)."""
return [
{
"name": "list_projects",
"method_name": "list_projects",
"description": "List all OpenPanel projects.",
"description": "List all projects via Manage API. Requires 'root' mode client.",
"schema": {"type": "object", "properties": {}},
"scope": "read",
"scope": "admin",
},
{
"name": "get_project",
"method_name": "get_project",
"description": "Get project details including settings and statistics.",
"description": "Get project details via Manage API. Requires 'root' mode client.",
"schema": {
"type": "object",
"properties": {"project_id": {"type": "string", "description": "Project ID"}},
"properties": {
"project_id": {"type": "string", "description": "Project ID"},
},
"required": ["project_id"],
},
"scope": "read",
@@ -30,19 +36,14 @@ def get_tool_specifications() -> list[dict[str, Any]]:
{
"name": "create_project",
"method_name": "create_project",
"description": "Create a new OpenPanel project.",
"description": "Create a new project via Manage API. Requires 'root' mode client.",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Project name"},
"domain": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Primary domain for the project",
},
"timezone": {
"type": "string",
"description": "Project timezone",
"default": "UTC",
"description": "Project domain (e.g., 'example.com')",
},
},
"required": ["name"],
@@ -52,22 +53,18 @@ def get_tool_specifications() -> list[dict[str, Any]]:
{
"name": "update_project",
"method_name": "update_project",
"description": "Update project settings.",
"description": "Update a project via Manage API. Requires 'root' mode client.",
"schema": {
"type": "object",
"properties": {
"project_id": {"type": "string", "description": "Project ID"},
"project_id": {"type": "string", "description": "Project ID to update"},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New project name",
},
"domain": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New primary domain",
},
"timezone": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "New timezone",
"description": "New domain",
},
},
"required": ["project_id"],
@@ -77,85 +74,35 @@ def get_tool_specifications() -> list[dict[str, Any]]:
{
"name": "delete_project",
"method_name": "delete_project",
"description": "Delete a project and all its data.",
"description": "Delete a project via Manage API. WARNING: Permanently deletes all project data. Requires 'root' mode client.",
"schema": {
"type": "object",
"properties": {
"project_id": {"type": "string", "description": "Project ID to delete"},
"confirm": {"type": "boolean", "description": "Confirm deletion (required)"},
},
"required": ["project_id", "confirm"],
},
"scope": "admin",
},
{
"name": "get_project_stats",
"method_name": "get_project_stats",
"description": "Get project statistics (events, users, storage).",
"schema": {
"type": "object",
"properties": {
"project_id": {"type": "string", "description": "Project ID"},
"date_range": {
"type": "string",
"description": "Date range. Common: today, yesterday, 7d, 14d, 30d, 60d, 90d, 6m, 12m, yearToDate, all",
"default": "30d",
},
},
"required": ["project_id"],
},
"scope": "read",
},
{
"name": "get_project_settings",
"method_name": "get_project_settings",
"description": "Get project configuration settings.",
"schema": {
"type": "object",
"properties": {"project_id": {"type": "string", "description": "Project ID"}},
"required": ["project_id"],
},
"scope": "read",
},
{
"name": "update_project_settings",
"method_name": "update_project_settings",
"description": "Update project configuration.",
"schema": {
"type": "object",
"properties": {
"project_id": {"type": "string", "description": "Project ID"},
"settings": {
"type": "object",
"description": "Settings to update",
"properties": {
"cors_domains": {"type": "array", "items": {"type": "string"}},
"ip_anonymization": {"type": "boolean"},
"data_retention_days": {"type": "integer"},
},
},
},
"required": ["project_id", "settings"],
},
"scope": "admin",
},
]
# =====================
# Project Functions (8)
# Project Functions (5)
# =====================
async def list_projects(client: OpenPanelClient) -> str:
"""List all projects"""
"""List all projects via GET /manage/projects."""
try:
result = await client.list_projects()
projects = (
result
if isinstance(result, list)
else result.get("data", []) if isinstance(result, dict) else []
)
return json.dumps(
{
"success": True,
"note": "Project listing requires dashboard tRPC API. Use OpenPanel dashboard to view projects.",
"message": "Project list request processed",
},
{"success": True, "count": len(projects), "projects": projects},
indent=2,
ensure_ascii=False,
)
@@ -164,36 +111,23 @@ async def list_projects(client: OpenPanelClient) -> str:
async def get_project(client: OpenPanelClient, project_id: str) -> str:
"""Get project details"""
"""Get project details via GET /manage/projects/:id."""
try:
return json.dumps(
{
"success": True,
"project_id": project_id,
"note": "Project details require dashboard tRPC API. Use OpenPanel dashboard for full project view.",
"message": "Project details request processed",
},
indent=2,
ensure_ascii=False,
)
result = await client.get_project(project_id)
return json.dumps({"success": True, "project": result}, indent=2, ensure_ascii=False)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def create_project(
client: OpenPanelClient, name: str, domain: str | None = None, timezone: str = "UTC"
) -> str:
"""Create a new project"""
async def create_project(client: OpenPanelClient, name: str, domain: str | None = None) -> str:
"""Create a new project via POST /manage/projects."""
try:
project_config = {"name": name, "domain": domain, "timezone": timezone}
data: dict[str, Any] = {"name": name}
if domain:
data["domain"] = domain
result = await client.create_project(data)
return json.dumps(
{
"success": True,
"project": project_config,
"note": "Project creation requires dashboard tRPC API. Use OpenPanel dashboard to create projects.",
"message": f"Project '{name}' configuration created",
},
{"success": True, "message": f"Project '{name}' created", "project": result},
indent=2,
ensure_ascii=False,
)
@@ -206,55 +140,23 @@ async def update_project(
project_id: str,
name: str | None = None,
domain: str | None = None,
timezone: str | None = None,
) -> str:
"""Update project settings"""
"""Update a project via PATCH /manage/projects/:id."""
try:
updates = {}
data: dict[str, Any] = {}
if name:
updates["name"] = name
data["name"] = name
if domain:
updates["domain"] = domain
if timezone:
updates["timezone"] = timezone
return json.dumps(
{
"success": True,
"project_id": project_id,
"updates": updates,
"note": "Project updates require dashboard tRPC API. Use OpenPanel dashboard to modify projects.",
"message": "Project update configuration created",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def delete_project(client: OpenPanelClient, project_id: str, confirm: bool = False) -> str:
"""Delete a project"""
try:
if not confirm:
data["domain"] = domain
if not data:
return json.dumps(
{
"success": False,
"error": "Deletion not confirmed. Set confirm=true to proceed.",
"warning": "This will permanently delete all project data including events, users, and settings.",
},
{"success": False, "error": "No fields to update. Provide name or domain."},
indent=2,
ensure_ascii=False,
)
result = await client.update_project(project_id, data)
return json.dumps(
{
"success": True,
"project_id": project_id,
"confirmed": confirm,
"note": "Project deletion requires dashboard tRPC API. Use OpenPanel dashboard to delete projects.",
"message": "Project deletion request processed",
},
{"success": True, "message": f"Project '{project_id}' updated", "project": result},
indent=2,
ensure_ascii=False,
)
@@ -262,81 +164,12 @@ async def delete_project(client: OpenPanelClient, project_id: str, confirm: bool
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def get_project_stats(
client: OpenPanelClient, project_id: str, date_range: str = "30d"
) -> str:
"""Get project statistics"""
async def delete_project(client: OpenPanelClient, project_id: str) -> str:
"""Delete a project via DELETE /manage/projects/:id."""
try:
# Get basic stats via export API
metrics = {}
# Total events
events_config = [{"name": "*", "segment": "event"}]
events_result = await client.export_charts(
project_id=project_id, events=events_config, interval="day", date_range=date_range
)
total_events = 0
if isinstance(events_result, dict) and "data" in events_result:
for point in events_result.get("data", []):
total_events += point.get("count", 0)
metrics["total_events"] = total_events
# Unique users
users_config = [{"name": "*", "segment": "user"}]
users_result = await client.export_charts(
project_id=project_id, events=users_config, interval="day", date_range=date_range
)
if isinstance(users_result, dict) and "data" in users_result:
data = users_result.get("data", [])
metrics["unique_users"] = data[-1].get("count", 0) if data else 0
result = await client.delete_project(project_id)
return json.dumps(
{
"success": True,
"project_id": project_id,
"date_range": date_range,
"stats": metrics,
"message": "Project stats retrieved",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def get_project_settings(client: OpenPanelClient, project_id: str) -> str:
"""Get project settings"""
try:
return json.dumps(
{
"success": True,
"project_id": project_id,
"note": "Project settings require dashboard tRPC API. Use OpenPanel dashboard for settings.",
"message": "Project settings request processed",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def update_project_settings(
client: OpenPanelClient, project_id: str, settings: dict[str, Any]
) -> str:
"""Update project settings"""
try:
return json.dumps(
{
"success": True,
"project_id": project_id,
"settings": settings,
"note": "Settings update requires dashboard tRPC API. Use OpenPanel dashboard to configure.",
"message": "Project settings update configuration created",
},
{"success": True, "message": f"Project '{project_id}' deleted", "result": result},
indent=2,
ensure_ascii=False,
)