- Add AlwaysCondition model and evaluation (always returns true) - Add condition type selector (Always/Application) in profile editor - Show condition type pill on profile cards - Fix misleading empty-conditions text (was "never activate", actually always active) - Add i18n keys for Always condition (en + ru) - Add CSS for condition type selector and description Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
105 lines
3.1 KiB
Python
105 lines
3.1 KiB
Python
"""Profile and Condition data models."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from typing import List, Optional
|
|
|
|
|
|
@dataclass
|
|
class Condition:
|
|
"""Base condition — polymorphic via condition_type discriminator."""
|
|
|
|
condition_type: str
|
|
|
|
def to_dict(self) -> dict:
|
|
return {"condition_type": self.condition_type}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict) -> "Condition":
|
|
"""Factory: dispatch to the correct subclass."""
|
|
ct = data.get("condition_type", "")
|
|
if ct == "always":
|
|
return AlwaysCondition.from_dict(data)
|
|
if ct == "application":
|
|
return ApplicationCondition.from_dict(data)
|
|
raise ValueError(f"Unknown condition type: {ct}")
|
|
|
|
|
|
@dataclass
|
|
class AlwaysCondition(Condition):
|
|
"""Always-true condition — profile activates unconditionally when enabled."""
|
|
|
|
condition_type: str = "always"
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict) -> "AlwaysCondition":
|
|
return cls()
|
|
|
|
|
|
@dataclass
|
|
class ApplicationCondition(Condition):
|
|
"""Activate when specified applications are running or topmost."""
|
|
|
|
condition_type: str = "application"
|
|
apps: List[str] = field(default_factory=list)
|
|
match_type: str = "running" # "running" | "topmost"
|
|
|
|
def to_dict(self) -> dict:
|
|
d = super().to_dict()
|
|
d["apps"] = list(self.apps)
|
|
d["match_type"] = self.match_type
|
|
return d
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict) -> "ApplicationCondition":
|
|
return cls(
|
|
apps=data.get("apps", []),
|
|
match_type=data.get("match_type", "running"),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Profile:
|
|
"""Automation profile that activates targets based on conditions."""
|
|
|
|
id: str
|
|
name: str
|
|
enabled: bool
|
|
condition_logic: str # "or" | "and"
|
|
conditions: List[Condition]
|
|
target_ids: List[str]
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"id": self.id,
|
|
"name": self.name,
|
|
"enabled": self.enabled,
|
|
"condition_logic": self.condition_logic,
|
|
"conditions": [c.to_dict() for c in self.conditions],
|
|
"target_ids": list(self.target_ids),
|
|
"created_at": self.created_at.isoformat(),
|
|
"updated_at": self.updated_at.isoformat(),
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict) -> "Profile":
|
|
conditions = []
|
|
for c_data in data.get("conditions", []):
|
|
try:
|
|
conditions.append(Condition.from_dict(c_data))
|
|
except ValueError:
|
|
pass # skip unknown condition types on load
|
|
|
|
return cls(
|
|
id=data["id"],
|
|
name=data["name"],
|
|
enabled=data.get("enabled", True),
|
|
condition_logic=data.get("condition_logic", "or"),
|
|
conditions=conditions,
|
|
target_ids=data.get("target_ids", []),
|
|
created_at=datetime.fromisoformat(data.get("created_at", datetime.utcnow().isoformat())),
|
|
updated_at=datetime.fromisoformat(data.get("updated_at", datetime.utcnow().isoformat())),
|
|
)
|