from datetime import datetime from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from .database import Base class Category(Base): __tablename__ = "categories" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(100), unique=True, index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) image_path: Mapped[str | None] = mapped_column(String(255), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) assets: Mapped[list["Asset"]] = relationship(back_populates="category") visible_fields: Mapped[list["CategoryField"]] = relationship( back_populates="category", cascade="all, delete-orphan", order_by="CategoryField.sort_order" ) class FieldDefinition(Base): __tablename__ = "field_definitions" id: Mapped[int] = mapped_column(Integer, primary_key=True) field_name: Mapped[str] = mapped_column(String(80), unique=True, index=True) label: Mapped[str] = mapped_column(String(120)) description: Mapped[str | None] = mapped_column(Text, nullable=True) data_type: Mapped[str] = mapped_column(String(30), default="short_text") readonly: Mapped[bool] = mapped_column(Boolean, default=False) is_system: Mapped[bool] = mapped_column(Boolean, default=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True) translation_key: Mapped[str | None] = mapped_column(String(180), nullable=True) searchable: Mapped[bool] = mapped_column(Boolean, default=True) filterable: Mapped[bool] = mapped_column(Boolean, default=True) chart_enabled: Mapped[bool] = mapped_column(Boolean, default=True) excel_import: Mapped[bool] = mapped_column(Boolean, default=True) excel_export: Mapped[bool] = mapped_column(Boolean, default=True) bulk_edit: Mapped[bool] = mapped_column(Boolean, default=True) is_default: Mapped[bool] = mapped_column(Boolean, default=False) list_width: Mapped[int | None] = mapped_column(Integer, nullable=True) sort_order: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) category_links: Mapped[list["CategoryField"]] = relationship(back_populates="definition", cascade="all, delete-orphan") values: Mapped[list["AssetFieldValue"]] = relationship(back_populates="definition", cascade="all, delete-orphan") class CategoryField(Base): __tablename__ = "category_fields" __table_args__ = (UniqueConstraint("category_id", "field_name"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) category_id: Mapped[int] = mapped_column(ForeignKey("categories.id", ondelete="CASCADE")) field_name: Mapped[str] = mapped_column(String(80)) label: Mapped[str] = mapped_column(String(100)) field_definition_id: Mapped[int | None] = mapped_column(ForeignKey("field_definitions.id", ondelete="CASCADE"), nullable=True, index=True) active: Mapped[bool] = mapped_column(Boolean, default=True) required: Mapped[bool] = mapped_column(Boolean, default=False) show_in_list: Mapped[bool] = mapped_column(Boolean, default=False) readonly: Mapped[bool] = mapped_column(Boolean, default=False) sort_order: Mapped[int] = mapped_column(Integer, default=0) category: Mapped[Category] = relationship(back_populates="visible_fields") definition: Mapped[FieldDefinition | None] = relationship(back_populates="category_links") @property def effective_label(self) -> str: return self.definition.label if self.definition else self.label @property def effective_readonly(self) -> bool: return bool(self.definition.readonly) if self.definition else bool(getattr(self, "readonly", False)) class Asset(Base): __tablename__ = "assets" id: Mapped[int] = mapped_column(Integer, primary_key=True) category_id: Mapped[int] = mapped_column(ForeignKey("categories.id"), index=True) asset_tag: Mapped[str | None] = mapped_column(String(100), unique=True, nullable=True, index=True) name: Mapped[str] = mapped_column(String(150), index=True) manufacturer: Mapped[str | None] = mapped_column(String(150), nullable=True) model: Mapped[str | None] = mapped_column(String(150), nullable=True) serial_number: Mapped[str | None] = mapped_column(String(150), nullable=True, index=True) status: Mapped[str | None] = mapped_column(String(60), nullable=True) assigned_to: Mapped[str | None] = mapped_column(String(150), nullable=True) assigned_on: Mapped[str | None] = mapped_column(String(20), nullable=True) location: Mapped[str | None] = mapped_column(String(150), nullable=True) room_number: Mapped[str | None] = mapped_column(String(80), nullable=True, index=True) parent_asset_id: Mapped[int | None] = mapped_column(ForeignKey("assets.id", ondelete="SET NULL"), nullable=True, index=True) department: Mapped[str | None] = mapped_column(String(150), nullable=True) purchase_date: Mapped[str | None] = mapped_column(String(20), nullable=True) warranty_until: Mapped[str | None] = mapped_column(String(20), nullable=True) notes: Mapped[str | None] = mapped_column(Text, nullable=True) image_path: Mapped[str | None] = mapped_column(String(255), nullable=True) hostname: Mapped[str | None] = mapped_column(String(150), nullable=True, index=True) operating_system: Mapped[str | None] = mapped_column(String(150), nullable=True) os_patch_level: Mapped[str | None] = mapped_column(String(150), nullable=True) mac_address: Mapped[str | None] = mapped_column(String(50), nullable=True) ip_address: Mapped[str | None] = mapped_column(String(50), nullable=True) cpu: Mapped[str | None] = mapped_column(String(200), nullable=True) memory_gb: Mapped[str | None] = mapped_column(String(40), nullable=True) storage_gb: Mapped[str | None] = mapped_column(String(40), nullable=True) storage_details: Mapped[str | None] = mapped_column(Text, nullable=True) gpu_name: Mapped[str | None] = mapped_column(Text, nullable=True) tpm_version: Mapped[str | None] = mapped_column(String(50), nullable=True) memory_details: Mapped[str | None] = mapped_column(Text, nullable=True) antivirus: Mapped[str | None] = mapped_column(Text, nullable=True) mesh_group: Mapped[str | None] = mapped_column(String(180), nullable=True) mesh_mtype: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True) mesh_node_id: Mapped[str | None] = mapped_column(String(180), unique=True, nullable=True, index=True) mesh_last_sync: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) mesh_last_seen: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) mesh_online: Mapped[bool | None] = mapped_column(Boolean, nullable=True, index=True) mesh_online_state: Mapped[str] = mapped_column(String(20), default="unknown", index=True) mesh_presence_updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) mesh_last_online_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) mesh_presence_source: Mapped[str | None] = mapped_column(String(40), nullable=True) mesh_sync_status: Mapped[str | None] = mapped_column(String(30), nullable=True, index=True) mesh_sync_message: Mapped[str | None] = mapped_column(Text, nullable=True) mesh_conflicts: Mapped[dict] = mapped_column(JSON, default=dict) mesh_source_data: Mapped[dict] = mapped_column(JSON, default=dict) mesh_hardware_data: Mapped[dict] = mapped_column(JSON, default=dict) mesh_software_data: Mapped[list | dict] = mapped_column(JSON, default=list) mesh_inventory_updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) extra_data: Mapped[dict] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) category: Mapped[Category] = relationship(back_populates="assets") parent_asset: Mapped["Asset | None"] = relationship("Asset", remote_side="Asset.id", back_populates="child_assets", foreign_keys=[parent_asset_id]) child_assets: Mapped[list["Asset"]] = relationship("Asset", back_populates="parent_asset", foreign_keys=[parent_asset_id]) class AssetFieldValue(Base): __tablename__ = "asset_field_values" __table_args__ = (UniqueConstraint("asset_id", "field_definition_id"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True) field_definition_id: Mapped[int] = mapped_column(ForeignKey("field_definitions.id", ondelete="CASCADE"), index=True) value_text: Mapped[str | None] = mapped_column(Text, nullable=True) value_number: Mapped[str | None] = mapped_column(String(100), nullable=True) value_boolean: Mapped[bool | None] = mapped_column(Boolean, nullable=True) value_date: Mapped[str | None] = mapped_column(String(20), nullable=True) value_datetime: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) value_json: Mapped[dict | list | None] = mapped_column(JSON, nullable=True) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) asset: Mapped[Asset] = relationship() definition: Mapped[FieldDefinition] = relationship(back_populates="values") class MeshFieldMapping(Base): __tablename__ = "mesh_field_mappings" id: Mapped[int] = mapped_column(Integer, primary_key=True) source_path: Mapped[str] = mapped_column(String(255), index=True) target_field_name: Mapped[str] = mapped_column(String(80), index=True) transform: Mapped[str] = mapped_column(String(40), default="raw") multi_value_mode: Mapped[str] = mapped_column(String(30), default="first") separator: Mapped[str] = mapped_column(String(20), default="\n") enabled: Mapped[bool] = mapped_column(Boolean, default=True) priority: Mapped[int] = mapped_column(Integer, default=0) description: Mapped[str | None] = mapped_column(Text, nullable=True) update_rule: Mapped[str] = mapped_column(String(30), default="fill_empty") is_system: Mapped[bool] = mapped_column(Boolean, default=False) class AssetHistory(Base): __tablename__ = "asset_history" id: Mapped[int] = mapped_column(Integer, primary_key=True) asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True) changed_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) source: Mapped[str] = mapped_column(String(50), default="manual") changed_by: Mapped[str | None] = mapped_column(String(150), nullable=True) changes: Mapped[dict] = mapped_column(JSON, default=dict) class StatusOption(Base): __tablename__ = "status_options" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(60), unique=True, index=True) sort_order: Mapped[int] = mapped_column(Integer, default=0) active: Mapped[bool] = mapped_column(Boolean, default=True) use_for_issue: Mapped[bool] = mapped_column(Boolean, default=False) use_for_return: Mapped[bool] = mapped_column(Boolean, default=False) class SyncRun(Base): __tablename__ = "sync_runs" id: Mapped[int] = mapped_column(Integer, primary_key=True) source: Mapped[str] = mapped_column(String(50), default="meshcentral") started_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) status: Mapped[str] = mapped_column(String(30), default="running") devices_found: Mapped[int] = mapped_column(Integer, default=0) created_count: Mapped[int] = mapped_column(Integer, default=0) updated_count: Mapped[int] = mapped_column(Integer, default=0) conflict_count: Mapped[int] = mapped_column(Integer, default=0) error_count: Mapped[int] = mapped_column(Integer, default=0) message: Mapped[str | None] = mapped_column(Text, nullable=True) class ChartDefinition(Base): __tablename__ = "chart_definitions" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(150), unique=True, index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) chart_type: Mapped[str] = mapped_column(String(30), default="bar") group_field: Mapped[str] = mapped_column(String(80), default="category") stack_field: Mapped[str | None] = mapped_column(String(80), nullable=True) dashboard_visible: Mapped[bool] = mapped_column(Boolean, default=False) show_empty: Mapped[bool] = mapped_column(Boolean, default=False) max_items: Mapped[int] = mapped_column(Integer, default=12) sort_mode: Mapped[str] = mapped_column(String(30), default="count_desc") value_mode: Mapped[str] = mapped_column(String(30), default="raw") value_mappings: Mapped[list] = mapped_column(JSON, default=list) excluded_statuses: Mapped[list] = mapped_column(JSON, default=list) is_system: Mapped[bool] = mapped_column(Boolean, default=False) created_by: Mapped[str | None] = mapped_column(String(150), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class Language(Base): __tablename__ = "languages" code: Mapped[str] = mapped_column(String(10), primary_key=True) name: Mapped[str] = mapped_column(String(80)) native_name: Mapped[str] = mapped_column(String(80)) is_active: Mapped[bool] = mapped_column(Boolean, default=True) is_default: Mapped[bool] = mapped_column(Boolean, default=False) sort_order: Mapped[int] = mapped_column(Integer, default=0) class Translation(Base): __tablename__ = "translations" __table_args__ = (UniqueConstraint("translation_key", "language_code"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) translation_key: Mapped[str] = mapped_column(String(180), index=True) language_code: Mapped[str] = mapped_column(ForeignKey("languages.code", ondelete="CASCADE"), index=True) text_value: Mapped[str] = mapped_column(Text) description: Mapped[str | None] = mapped_column(String(255), nullable=True) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True) username: Mapped[str] = mapped_column(String(120), unique=True, index=True) display_name: Mapped[str | None] = mapped_column(String(180), nullable=True) email: Mapped[str | None] = mapped_column(String(180), nullable=True) password_hash: Mapped[str | None] = mapped_column(String(255), nullable=True) auth_source: Mapped[str] = mapped_column(String(30), default="local") is_active: Mapped[bool] = mapped_column(Boolean, default=True) is_admin: Mapped[bool] = mapped_column(Boolean, default=False) is_protected: Mapped[bool] = mapped_column(Boolean, default=False) allowed_mesh_groups: Mapped[list] = mapped_column(JSON, default=list) asset_access_scopes: Mapped[list] = mapped_column(JSON, default=list) profile_department: Mapped[str | None] = mapped_column(String(150), nullable=True) profile_location: Mapped[str | None] = mapped_column(String(150), nullable=True) language_code: Mapped[str] = mapped_column(String(10), default="en") persist_browser_state: Mapped[bool] = mapped_column(Boolean, default=False) toast_position: Mapped[str] = mapped_column(String(30), default="top-right") toast_duration_seconds: Mapped[int] = mapped_column(Integer, default=6) theme_mode: Mapped[str] = mapped_column(String(20), default="light") last_login: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class JobDefinition(Base): __tablename__ = "job_definitions" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(180), unique=True, index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) job_kind: Mapped[str] = mapped_column(String(30), default="script", index=True) configuration: Mapped[dict] = mapped_column(JSON, default=dict) system_key: Mapped[str | None] = mapped_column(String(120), unique=True, nullable=True, index=True) platform: Mapped[str] = mapped_column(String(30), default="windows", index=True) interpreter: Mapped[str] = mapped_column(String(30), default="powershell") source_type: Mapped[str] = mapped_column(String(30), default="inline") source_path: Mapped[str | None] = mapped_column(Text, nullable=True) inline_script: Mapped[str | None] = mapped_column(Text, nullable=True) arguments: Mapped[str | None] = mapped_column(Text, nullable=True) timeout_seconds: Mapped[int] = mapped_column(Integer, default=300) success_codes: Mapped[str] = mapped_column(String(120), default="0") enabled: Mapped[bool] = mapped_column(Boolean, default=True, index=True) is_system: Mapped[bool] = mapped_column(Boolean, default=False) revision: Mapped[int] = mapped_column(Integer, default=1) created_by: Mapped[str | None] = mapped_column(String(150), nullable=True) updated_by: Mapped[str | None] = mapped_column(String(150), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) revisions: Mapped[list["JobDefinitionRevision"]] = relationship(back_populates="definition", cascade="all, delete-orphan", order_by="JobDefinitionRevision.revision.desc()") parameters: Mapped[list["JobDefinitionParameter"]] = relationship(back_populates="definition", cascade="all, delete-orphan", order_by="JobDefinitionParameter.sort_order") class JobDefinitionParameter(Base): __tablename__ = "job_definition_parameters" __table_args__ = (UniqueConstraint("definition_id", "parameter_key", name="uq_job_definition_parameter_key"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) definition_id: Mapped[int] = mapped_column(ForeignKey("job_definitions.id", ondelete="CASCADE"), index=True) parameter_key: Mapped[str] = mapped_column(String(80)) label: Mapped[str] = mapped_column(String(140)) description: Mapped[str | None] = mapped_column(Text, nullable=True) data_type: Mapped[str] = mapped_column(String(30), default="text") default_value: Mapped[str | None] = mapped_column(Text, nullable=True) required: Mapped[bool] = mapped_column(Boolean, default=False) sort_order: Mapped[int] = mapped_column(Integer, default=0) definition: Mapped["JobDefinition"] = relationship(back_populates="parameters") class JobDefinitionAssetParameter(Base): __tablename__ = "job_definition_asset_parameters" __table_args__ = (UniqueConstraint("definition_parameter_id", "asset_id", name="uq_job_definition_asset_parameter"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) definition_parameter_id: Mapped[int] = mapped_column(ForeignKey("job_definition_parameters.id", ondelete="CASCADE"), index=True) asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True) value_text: Mapped[str | None] = mapped_column(Text, nullable=True) updated_by: Mapped[str | None] = mapped_column(String(150), nullable=True) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) parameter: Mapped["JobDefinitionParameter"] = relationship() asset: Mapped["Asset"] = relationship() class JobDefinitionRevision(Base): __tablename__ = "job_definition_revisions" __table_args__ = (UniqueConstraint("definition_id", "revision", name="uq_job_definition_revision"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) definition_id: Mapped[int] = mapped_column(ForeignKey("job_definitions.id", ondelete="CASCADE"), index=True) revision: Mapped[int] = mapped_column(Integer) snapshot: Mapped[dict] = mapped_column(JSON, default=dict) changed_by: Mapped[str | None] = mapped_column(String(150), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) definition: Mapped["JobDefinition"] = relationship(back_populates="revisions") class SoftwarePackage(Base): __tablename__ = "software_packages" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(180), unique=True, index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) package_type: Mapped[str] = mapped_column(String(40), default="command") enabled: Mapped[bool] = mapped_column(Boolean, default=True) is_system: Mapped[bool] = mapped_column(Boolean, default=False) command_windows: Mapped[str | None] = mapped_column(Text, nullable=True) command_linux: Mapped[str | None] = mapped_column(Text, nullable=True) command_macos: Mapped[str | None] = mapped_column(Text, nullable=True) command_android: Mapped[str | None] = mapped_column(Text, nullable=True) callback_timeout_minutes: Mapped[int] = mapped_column(Integer, default=30) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) parameters: Mapped[list["SoftwarePackageParameter"]] = relationship(back_populates="package", cascade="all, delete-orphan", order_by="SoftwarePackageParameter.sort_order") jobs: Mapped[list["SoftwareJob"]] = relationship(back_populates="package") class SoftwarePackageParameter(Base): __tablename__ = "software_package_parameters" __table_args__ = (UniqueConstraint("package_id", "name"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) package_id: Mapped[int] = mapped_column(ForeignKey("software_packages.id", ondelete="CASCADE"), index=True) name: Mapped[str] = mapped_column(String(80)) label: Mapped[str] = mapped_column(String(120)) data_type: Mapped[str] = mapped_column(String(30), default="text") required: Mapped[bool] = mapped_column(Boolean, default=False) secret: Mapped[bool] = mapped_column(Boolean, default=False) default_value: Mapped[str | None] = mapped_column(Text, nullable=True) validation_regex: Mapped[str | None] = mapped_column(String(255), nullable=True) sort_order: Mapped[int] = mapped_column(Integer, default=0) package: Mapped[SoftwarePackage] = relationship(back_populates="parameters") class SoftwareJob(Base): __tablename__ = "software_jobs" id: Mapped[int] = mapped_column(Integer, primary_key=True) asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True) package_id: Mapped[int] = mapped_column(ForeignKey("software_packages.id"), index=True) status: Mapped[str] = mapped_column(String(30), default="created", index=True) job_type: Mapped[str] = mapped_column(String(50), default="software_inventory", index=True) action: Mapped[str] = mapped_column(String(30), default="run", index=True) priority: Mapped[int] = mapped_column(Integer, default=100, index=True) platform: Mapped[str] = mapped_column(String(30), default="unknown") parameters: Mapped[dict] = mapped_column(JSON, default=dict) result_data: Mapped[dict] = mapped_column(JSON, default=dict) attempt_count: Mapped[int] = mapped_column(Integer, default=0) max_attempts: Mapped[int] = mapped_column(Integer, default=1) scheduled_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True) callback_token_hash: Mapped[str] = mapped_column(String(64), index=True) callback_expires_at: Mapped[datetime] = mapped_column(DateTime) callback_completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) command_preview: Mapped[str | None] = mapped_column(Text, nullable=True) resolved_script: Mapped[str | None] = mapped_column(Text, nullable=True) meshctrl_stdout: Mapped[str | None] = mapped_column(Text, nullable=True) meshctrl_stderr: Mapped[str | None] = mapped_column(Text, nullable=True) exit_code: Mapped[int | None] = mapped_column(Integer, nullable=True) message: Mapped[str | None] = mapped_column(Text, nullable=True) log_text: Mapped[str] = mapped_column(Text, default="") created_by: Mapped[str | None] = mapped_column(String(150), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) sent_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) asset: Mapped[Asset] = relationship() package: Mapped[SoftwarePackage] = relationship(back_populates="jobs") events: Mapped[list["JobEvent"]] = relationship(back_populates="job", cascade="all, delete-orphan", order_by="JobEvent.created_at") class AssetJobState(Base): __tablename__ = "asset_job_states" __table_args__ = ( UniqueConstraint("asset_id", "state_key", name="uq_asset_job_state_asset_key"), Index("ix_asset_job_states_key_status", "state_key", "last_status"), Index("ix_asset_job_states_key_success", "state_key", "last_success_at"), Index("ix_asset_job_states_asset_key", "asset_id", "state_key"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True) asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True) state_key: Mapped[str] = mapped_column(String(120), index=True) job_type: Mapped[str] = mapped_column(String(50), index=True) job_definition_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True) definition_revision: Mapped[int | None] = mapped_column(Integer, nullable=True) last_success_revision: Mapped[int | None] = mapped_column(Integer, nullable=True) last_job_id: Mapped[int | None] = mapped_column(ForeignKey("software_jobs.id", ondelete="SET NULL"), nullable=True, index=True) last_status: Mapped[str] = mapped_column(String(30), default="created", index=True) last_status_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) last_created_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) last_sent_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) last_started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) last_completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) last_success_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True) execution_count: Mapped[int] = mapped_column(Integer, default=0) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) asset: Mapped[Asset] = relationship() last_job: Mapped[SoftwareJob | None] = relationship() class JobEvent(Base): __tablename__ = "job_events" id: Mapped[int] = mapped_column(Integer, primary_key=True) job_id: Mapped[int] = mapped_column(ForeignKey("software_jobs.id", ondelete="CASCADE"), index=True) attempt: Mapped[int] = mapped_column(Integer, default=0) event_type: Mapped[str] = mapped_column(String(40), default="status", index=True) status: Mapped[str | None] = mapped_column(String(30), nullable=True, index=True) message: Mapped[str | None] = mapped_column(Text, nullable=True) created_by: Mapped[str | None] = mapped_column(String(150), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) job: Mapped["SoftwareJob"] = relationship(back_populates="events") class InstalledSoftware(Base): __tablename__ = "installed_software" __table_args__ = (UniqueConstraint("asset_id", "name", "version", "publisher", "architecture", name="uq_installed_software_identity"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) asset_id: Mapped[int] = mapped_column(ForeignKey("assets.id", ondelete="CASCADE"), index=True) name: Mapped[str] = mapped_column(String(300), index=True) version: Mapped[str] = mapped_column(String(160), default="") publisher: Mapped[str] = mapped_column(String(220), default="") architecture: Mapped[str] = mapped_column(String(40), default="") platform: Mapped[str] = mapped_column(String(40), default="unknown", index=True) install_date: Mapped[str | None] = mapped_column(String(40), nullable=True) source: Mapped[str] = mapped_column(String(50), default="meshcentral-inventory") first_seen_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) last_seen_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) asset: Mapped[Asset] = relationship()