Phase 2: Tags UI, Custom Fields UI, Notifications Bell

- Tags UI: TagsPage (CRUD, color picker), TagBadge, TagSelector (multi-select, inline creation)
- Custom Fields Backend: model, schema, service, routes, migration 0041
- Custom Fields Frontend: CustomFieldsPage (definitions CRUD), CustomFieldRenderer (dynamic field rendering)
- Custom Fields: _collect_custom_field_definitions() extended to merge DB definitions with plugin definitions
- Notifications Bell: NotificationBell (30s polling, unread badge), NotificationDropdown, NotificationItem
- NotificationBell integrated into TopBar
- Routes: /tags, /settings/custom-fields registered
- Settings nav: Custom Fields entry added
- Menu items: Tags added to automation plugin manifest
This commit is contained in:
Agent Zero
2026-07-26 03:02:25 +02:00
parent 444c7fdb88
commit a7e3890634
22 changed files with 2684 additions and 8 deletions
+2
View File
@@ -21,6 +21,7 @@ from app.models.system_settings import SystemSettings
from app.models.tax import TaxRate
from app.models.tenant import Tenant
from app.models.user import User, UserTenant
from app.models.custom_field_definition import CustomFieldDefinition
from app.models.workflow import Workflow, WorkflowInstance, WorkflowStepHistory
__all__ = [
@@ -54,6 +55,7 @@ __all__ = [
"PluginMigration",
"AIConversation",
"AIMessage",
"CustomFieldDefinition",
"Workflow",
"WorkflowInstance",
"WorkflowStepHistory",
+57
View File
@@ -0,0 +1,57 @@
"""CustomFieldDefinition model — user-defined custom fields stored in DB."""
from __future__ import annotations
import uuid
from typing import Any
from sqlalchemy import Boolean, Integer, JSON, String, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
class CustomFieldDefinition(Base, TenantMixin):
"""User-defined custom field definition stored in the database.
These definitions are merged with plugin-provided custom fields
at query time. Each definition is scoped to a tenant and entity type.
"""
__tablename__ = "custom_field_definitions"
__table_args__ = (
UniqueConstraint(
"tenant_id", "entity", "name",
name="uq_custom_field_def_tenant_entity_name",
),
)
id: Mapped[uuid.UUID] = mapped_column(
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
# ── Identity ──
entity: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(100), nullable=False)
label: Mapped[str] = mapped_column(String(200), nullable=False)
# ── Type & Options ──
field_type: Mapped[str] = mapped_column(
String(20), nullable=False, default="text"
) # text, number, date, select, multiselect, boolean
options: Mapped[list[str] | None] = mapped_column(JSON, nullable=True, default=list)
default_value: Mapped[Any | None] = mapped_column(JSON, nullable=True, default=None)
# ── Behaviour ──
required: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
# ── Audit ──
created_by: Mapped[uuid.UUID | None] = mapped_column(
PGUUID(as_uuid=True), nullable=True
)
updated_by: Mapped[uuid.UUID | None] = mapped_column(
PGUUID(as_uuid=True), nullable=True
)