feat: Add presence heartbeat for Matrix online status

- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
This commit is contained in:
Apple
2025-11-27 00:19:40 -08:00
parent 5bed515852
commit 3de3c8cb36
6371 changed files with 1317450 additions and 932 deletions

284
scripts/node2/execute_cleanup.py Executable file
View File

@@ -0,0 +1,284 @@
#!/usr/bin/env python3
"""
Execute Cleanup Plan for Node-2
Safely executes cleanup actions from node2_cleanup_plan.json
"""
import json
import subprocess
from pathlib import Path
from typing import Dict, List, Any
class CleanupExecutor:
def __init__(self, cleanup_plan_path: str = "node2_cleanup_plan.json"):
self.cleanup_plan_path = Path(cleanup_plan_path)
self.cleanup_plan = {}
self.executed_actions = []
self.errors = []
def load_cleanup_plan(self):
"""Load cleanup plan JSON"""
if not self.cleanup_plan_path.exists():
raise FileNotFoundError(f"Cleanup plan not found: {self.cleanup_plan_path}")
with open(self.cleanup_plan_path, 'r', encoding='utf-8') as f:
self.cleanup_plan = json.load(f)
print(f"✅ Loaded cleanup plan from: {self.cleanup_plan_path}")
def execute_docker_action(self, action: str, container_name: str) -> bool:
"""Execute Docker action (stop, rm)"""
try:
if "stop" in action.lower():
print(f" 🛑 Stopping container: {container_name}")
result = subprocess.run(
["docker", "stop", container_name],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
print(f" ⚠️ Warning: {result.stderr.strip()}")
if "rm" in action.lower():
print(f" 🗑️ Removing container: {container_name}")
result = subprocess.run(
["docker", "rm", container_name],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
print(f" ⚠️ Warning: {result.stderr.strip()}")
return False
return True
except subprocess.TimeoutExpired:
print(f" ❌ Timeout removing container: {container_name}")
return False
except Exception as e:
print(f" ❌ Error: {e}")
return False
def execute_delete_actions(self, dry_run: bool = False):
"""Execute delete actions"""
print("\n🗑️ EXECUTING DELETE ACTIONS")
print("="*60)
delete_items = self.cleanup_plan.get("delete", [])
if not delete_items:
print(" No items to delete")
return
for item in delete_items:
item_type = item.get("type", "")
item_name = item.get("name", "")
action = item.get("action", "")
reason = item.get("reason", "")
print(f"\n 📦 {item_type}: {item_name}")
print(f" Reason: {reason}")
if dry_run:
print(f" [DRY RUN] Would execute: {action}")
continue
if item_type == "docker_container":
success = self.execute_docker_action(action, item_name)
if success:
self.executed_actions.append({
"type": "delete",
"item": item_name,
"status": "success"
})
else:
self.errors.append({
"type": "delete",
"item": item_name,
"error": "Failed to remove container"
})
else:
print(f" ⚠️ Unknown type: {item_type}")
def create_legacy_directory(self, dry_run: bool = False):
"""Create /node2/legacy/ directory for migrations"""
print("\n📁 CREATING LEGACY DIRECTORY")
print("="*60)
legacy_paths = [
Path.home() / "node2" / "legacy",
Path("/node2/legacy")
]
for legacy_path in legacy_paths:
if dry_run:
print(f" [DRY RUN] Would create: {legacy_path}")
else:
try:
legacy_path.mkdir(parents=True, exist_ok=True)
print(f" ✅ Created: {legacy_path}")
self.executed_actions.append({
"type": "create_directory",
"path": str(legacy_path),
"status": "success"
})
except Exception as e:
print(f" ❌ Error creating {legacy_path}: {e}")
self.errors.append({
"type": "create_directory",
"path": str(legacy_path),
"error": str(e)
})
def fix_qdrant_health(self, dry_run: bool = False):
"""Attempt to fix unhealthy Qdrant container"""
print("\n🔧 FIXING QDRANT HEALTH")
print("="*60)
# Find Qdrant container
qdrant_container = None
for container in self.cleanup_plan.get("keep", []):
if "qdrant" in container.get("name", "").lower():
qdrant_container = container
break
if not qdrant_container:
print(" Qdrant container not found in keep list")
return
container_name = qdrant_container.get("path", "").replace("Docker: ", "")
if dry_run:
print(f" [DRY RUN] Would restart: {container_name}")
print(f" [DRY RUN] Would check logs: docker logs {container_name}")
return
try:
# Check container status
result = subprocess.run(
["docker", "ps", "-a", "--filter", f"name={container_name}", "--format", "{{.Status}}"],
capture_output=True,
text=True,
timeout=10
)
if "unhealthy" in result.stdout.lower():
print(f" 🔄 Restarting container: {container_name}")
subprocess.run(
["docker", "restart", container_name],
capture_output=True,
text=True,
timeout=30
)
# Wait a bit and check again
import time
time.sleep(5)
result = subprocess.run(
["docker", "ps", "--filter", f"name={container_name}", "--format", "{{.Status}}"],
capture_output=True,
text=True,
timeout=10
)
if "healthy" in result.stdout.lower() or "up" in result.stdout.lower():
print(f" ✅ Container is now healthy")
self.executed_actions.append({
"type": "fix_health",
"container": container_name,
"status": "success"
})
else:
print(f" ⚠️ Container status: {result.stdout.strip()}")
print(f" 💡 Check logs: docker logs {container_name}")
self.errors.append({
"type": "fix_health",
"container": container_name,
"error": "Container still unhealthy after restart"
})
except Exception as e:
print(f" ❌ Error fixing Qdrant: {e}")
self.errors.append({
"type": "fix_health",
"container": container_name,
"error": str(e)
})
def execute_all(self, dry_run: bool = False):
"""Execute all cleanup actions"""
print("\n" + "="*60)
if dry_run:
print("🔍 DRY RUN MODE - No changes will be made")
else:
print("🚀 EXECUTING CLEANUP PLAN")
print("="*60)
self.create_legacy_directory(dry_run)
self.execute_delete_actions(dry_run)
self.fix_qdrant_health(dry_run)
print("\n" + "="*60)
print("📊 EXECUTION SUMMARY")
print("="*60)
print(f" ✅ Actions executed: {len(self.executed_actions)}")
print(f" ❌ Errors: {len(self.errors)}")
if self.errors:
print("\n ⚠️ ERRORS:")
for error in self.errors:
print(f" - {error.get('type')}: {error.get('error')}")
def save_execution_report(self, output_path: str = "node2_cleanup_execution_report.json"):
"""Save execution report"""
report = {
"executed_actions": self.executed_actions,
"errors": self.errors,
"timestamp": subprocess.run(
["date", "-u", "+%Y-%m-%dT%H:%M:%SZ"],
capture_output=True,
text=True
).stdout.strip()
}
output_file = Path(output_path)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
print(f"\n💾 Execution report saved to: {output_file.absolute()}")
def main():
import sys
dry_run = "--dry-run" in sys.argv or "-d" in sys.argv
# Try to find cleanup plan
cleanup_plan_paths = [
"node2_cleanup_plan.json",
"docs/node2/node2_cleanup_plan.json",
Path.home() / "github-projects" / "microdao-daarion" / "node2_cleanup_plan.json"
]
cleanup_plan_path = None
for path in cleanup_plan_paths:
if Path(path).exists():
cleanup_plan_path = path
break
if not cleanup_plan_path:
print("❌ Cleanup plan not found. Please run cleanup_plan_generator.py first.")
return
executor = CleanupExecutor(cleanup_plan_path)
executor.load_cleanup_plan()
executor.execute_all(dry_run=dry_run)
executor.save_execution_report()
if not dry_run:
print("\n✅ Cleanup execution complete!")
print(" Next step: Proceed to PHASE 3 - Install microDAO Core")
if __name__ == "__main__":
main()