topicgate 1.2.2__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {topicgate-1.2.2/src/topicgate.egg-info → topicgate-1.3.0}/PKG-INFO +7 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/README.md +6 -1
- {topicgate-1.2.2 → topicgate-1.3.0}/pyproject.toml +1 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/app_dependencies.py +18 -35
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/broker_runtime_state.py +1 -10
- topicgate-1.3.0/src/topicgate/app/models/broker_inspection.py +22 -0
- topicgate-1.3.0/src/topicgate/app/services/broker_inspection_service.py +67 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/broker_profile_service.py +5 -45
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/broker_snapshot_service.py +45 -29
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/observation_cache_service.py +33 -57
- topicgate-1.3.0/src/topicgate/app/services/observation_query_service.py +55 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/topicgate_runtime.py +69 -33
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/interfaces/broker_profile_store.py +0 -3
- topicgate-1.3.0/src/topicgate/core/interfaces/current_topic_reader.py +20 -0
- topicgate-1.2.2/src/topicgate/core/interfaces/observer_repository.py → topicgate-1.3.0/src/topicgate/core/interfaces/observer_repo_metadata.py +3 -26
- topicgate-1.3.0/src/topicgate/core/interfaces/observer_repository.py +13 -0
- topicgate-1.3.0/src/topicgate/core/interfaces/stored_observation_administrator.py +21 -0
- topicgate-1.3.0/src/topicgate/core/interfaces/stored_observation_reader.py +36 -0
- topicgate-1.3.0/src/topicgate/core/interfaces/topic_message_recorder.py +23 -0
- topicgate-1.3.0/src/topicgate/core/models/current_topic.py +36 -0
- topicgate-1.3.0/src/topicgate/core/models/message_filter.py +25 -0
- topicgate-1.3.0/src/topicgate/core/models/observation_status.py +8 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/observer_workspace.py +0 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/topic_message.py +0 -1
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/observer_limits.py +0 -5
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/app.py +3 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/stored_observations_dialog.py +295 -10
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/main_view_model.py +201 -6
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/main_window.py +24 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/observer_state_reader.py +0 -4
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/mappers/observer_workspace_mapper.py +1 -8
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/repository/observer_mqtt_repository.py +48 -75
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/repository/topic_message_repository.py +288 -87
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/broker_api.py +31 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/connection_api.py +2 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/mcp_api.py +0 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/publish_api.py +2 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/snapshot_api.py +4 -1
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/subscription_api.py +2 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/topic_api.py +13 -7
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/server.py +19 -8
- {topicgate-1.2.2 → topicgate-1.3.0/src/topicgate.egg-info}/PKG-INFO +7 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate.egg-info/SOURCES.txt +12 -6
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_app.py +20 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_app_dependencies.py +6 -2
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_broker_config_repository.py +1 -12
- topicgate-1.3.0/tests/test_broker_inspection_service.py +69 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_broker_repository_persistence.py +7 -17
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_broker_snapshot_service.py +25 -5
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_dashboard_api.py +28 -16
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_gui.py +145 -10
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_main_view_model.py +211 -53
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_mcp_apis.py +72 -21
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_observation_cache_service.py +3 -1
- topicgate-1.3.0/tests/test_observation_query_service.py +76 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_observer_model_hydration.py +10 -22
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_observer_repository.py +77 -39
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_observer_workspace_mapper.py +1 -9
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_plugin_bundle.py +1 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_retention_administration.py +5 -2
- topicgate-1.3.0/tests/test_topic_message_repository.py +748 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_topic_presentation.py +1 -1
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_topicgate_runtime.py +191 -10
- topicgate-1.2.2/src/topicgate/core/interfaces/topic_message_store.py +0 -13
- topicgate-1.2.2/src/topicgate/core/models/message_filter.py +0 -9
- topicgate-1.2.2/src/topicgate/core/models/observer_model.py +0 -22
- topicgate-1.2.2/src/topicgate/processors/observer_model_mqtt_message_processor.py +0 -79
- topicgate-1.2.2/src/topicgate/processors/observer_model_processor.py +0 -223
- topicgate-1.2.2/tests/test_observer_model_processor.py +0 -151
- topicgate-1.2.2/tests/test_observer_model_service.py +0 -153
- topicgate-1.2.2/tests/test_topic_message_repository.py +0 -386
- {topicgate-1.2.2 → topicgate-1.3.0}/LICENCE +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/setup.cfg +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/__main__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/models/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/models/broker_snapshot.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/models/mcp_setup.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/broker_resolver.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/control_operation_service.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/mcp_setup_service.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/observation_retention_policy_service.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/persistence_lifecycle.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/service_container.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/app/services/service_item.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/assets/icon.png +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/assets/icon.svg +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/config/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/config/app_config.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/config/mqtt_config.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/interfaces/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/interfaces/mqtt_message_processor.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/broker_profile.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/broker_profile_identity.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/broker_summary.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/connection_status.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/mqtt_message.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/mqtt_observation.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/mqtt_state.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/observation_cache_administration.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/observation_deletion_preview.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/observation_retention_policy.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/models/subscription.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/mqtt_topics.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/core/payload_limits.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/broker_state_reader.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/about_dialog.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/add_subscription_dialog.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/application_header.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/broker_settings_dialog.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/connection_controls.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/connection_status.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/log_console.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/mcp_setup_dialog.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/observer_tree.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/onboarding_panel.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/publish_pane.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/snapshot_panel.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/subscription_settings.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/topic_details.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/topic_metadata.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/components/workspace_pane.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/gui.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/settings_migration.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/gui/theme.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/credentials/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/credentials/credential_store.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/credentials/os_credential_store.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/alembic/README +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/alembic/env.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/alembic/script.py.mako +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/alembic/versions/7c3e9f1a2b4d_add_observation_retention_policy.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/alembic/versions/93fa5748f4b5_initial_revision.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/alembic/versions/b84d61a9c2e7_rename_retention_database_bytes.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/alembic/versions/d2a4c6e8f010_add_control_operation_lease.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/alembic/versions/f1490ca3b908_persist_latest_observed_mqtt_state.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/alembic.ini +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/base.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/database_context.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/mappers/broker_profile_mapper.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/mappers/config_mapper.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/mappers/mapper_helper.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/mappers/observation_retention_policy_mapper.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/mappers/subscription_mapper.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/mappers/topic_message_mapper.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/migrations.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/models/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/models/app_config_row.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/models/broker_profile_row.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/models/mqtt_config_row.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/models/mqtt_message_row.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/models/observation_retention_policy_row.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/models/observer_workspace_row.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/database/models/subscription_row.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/mqtt/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/mqtt/async_callback_bridge.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/mqtt/callbacks/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/mqtt/callbacks/basic_callbacks.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/mqtt/callbacks/observer_repository_callbacks.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/mqtt/mqtt_callbacks.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/mqtt/mqtt_client.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/mqtt/mqtt_gate.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/repository/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/repository/broker_config_repository.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/repository/broker_repository.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/repository/observation_retention_policy_repository.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/infrastructure/repository/subscription_repository.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/dashboard_api.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/dashboard_snapshot.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/api/preview/dashboard_preview.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/capabilities.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/instructions.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/middleware/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/middleware/error_handling_middleware.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/middleware/logging_middleware.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/mcp/models.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/paths.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/presentation/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/presentation/retention_presentation.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/presentation/snapshot_presentation.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/presentation/topic_presentation.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/processors/__init__.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/processors/observation_retention_processor.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate/processors/subscription_manager.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate.egg-info/dependency_links.txt +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate.egg-info/entry_points.txt +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate.egg-info/requires.txt +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/src/topicgate.egg-info/top_level.txt +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_async_callback_bridge.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_broker_resolver.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_control_operation_service.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_dashboard_dependencies.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_dashboard_preview.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_database_migrations.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_desktop_snapshot_states.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_github_workflows.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_mcp_disconnected_startup.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_mcp_middleware.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_mcp_server_instructions.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_mcp_setup_service.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_migrate_app_versions.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_mqtt_callbacks.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_mqtt_client.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_mqtt_message_row.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_observation_retention_policy.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_observation_retention_policy_repository.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_observation_retention_policy_service.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_os_credential_store.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_paths.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_persistence_lifecycle.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_service_container.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_settings_migration.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_snapshot_mcp_api.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_snapshot_presentation.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_subscription.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_subscription_manager.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_subscription_mapper.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_subscription_repository.py +0 -0
- {topicgate-1.2.2 → topicgate-1.3.0}/tests/test_verify_distribution.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: topicgate
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Secure local access to your MQTT topics
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/Dumdart/TopicGate
|
|
@@ -49,7 +49,6 @@ Dynamic: license-file
|
|
|
49
49
|
<a href="#get-started">Get started</a> ·
|
|
50
50
|
<a href="#connect-an-agent">Connect an agent</a> ·
|
|
51
51
|
<a href="#how-observations-work">Understand observations</a> ·
|
|
52
|
-
<a href="docs/desktop-workflow.md">Desktop workflow</a>
|
|
53
52
|
</p>
|
|
54
53
|
|
|
55
54
|
<p align="center">
|
|
@@ -60,6 +59,12 @@ TopicGate gives you a local, intentional view of MQTT data. Configure broker pro
|
|
|
60
59
|
|
|
61
60
|
It is built for a practical boundary: broker credentials stay on your machine, observed state is persisted locally, and the MCP server starts in **read-only mode**. MQTT control—connecting, changing subscriptions, refreshing observations, or publishing—requires an explicit opt-in.
|
|
62
61
|
|
|
62
|
+
## Demo
|
|
63
|
+
|
|
64
|
+
See TopicGate connect to an MQTT broker, inspect live topic state, answer an agent query with provenance, and enforce the boundary between observation and authorized control.
|
|
65
|
+
|
|
66
|
+
[Watch the TopicGate demo →](https://www.youtube.com/watch?v=_Qtc01kABkg)
|
|
67
|
+
|
|
63
68
|
## What it does
|
|
64
69
|
|
|
65
70
|
| Desktop | MCP server |
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
<a href="#get-started">Get started</a> ·
|
|
12
12
|
<a href="#connect-an-agent">Connect an agent</a> ·
|
|
13
13
|
<a href="#how-observations-work">Understand observations</a> ·
|
|
14
|
-
<a href="docs/desktop-workflow.md">Desktop workflow</a>
|
|
15
14
|
</p>
|
|
16
15
|
|
|
17
16
|
<p align="center">
|
|
@@ -22,6 +21,12 @@ TopicGate gives you a local, intentional view of MQTT data. Configure broker pro
|
|
|
22
21
|
|
|
23
22
|
It is built for a practical boundary: broker credentials stay on your machine, observed state is persisted locally, and the MCP server starts in **read-only mode**. MQTT control—connecting, changing subscriptions, refreshing observations, or publishing—requires an explicit opt-in.
|
|
24
23
|
|
|
24
|
+
## Demo
|
|
25
|
+
|
|
26
|
+
See TopicGate connect to an MQTT broker, inspect live topic state, answer an agent query with provenance, and enforce the boundary between observation and authorized control.
|
|
27
|
+
|
|
28
|
+
[Watch the TopicGate demo →](https://www.youtube.com/watch?v=_Qtc01kABkg)
|
|
29
|
+
|
|
25
30
|
## What it does
|
|
26
31
|
|
|
27
32
|
| Desktop | MCP server |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "topicgate"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "Secure local access to your MQTT topics"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -62,7 +62,6 @@ test = [
|
|
|
62
62
|
testpaths = ["tests"]
|
|
63
63
|
pythonpath = ["."]
|
|
64
64
|
cache_dir = ".pytest_cache"
|
|
65
|
-
addopts = "--basetemp=.pytest-tmp"
|
|
66
65
|
asyncio_mode = "auto"
|
|
67
66
|
|
|
68
67
|
[project.scripts]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from
|
|
3
|
-
|
|
2
|
+
from topicgate.app.services.observation_query_service import ObservationQueryService
|
|
4
3
|
from topicgate.app.services.service_item import ServiceItem
|
|
5
4
|
from topicgate.app.services.persistence_lifecycle import PersistenceLifecycle
|
|
6
5
|
from topicgate.app.services.broker_profile_service import BrokerProfileService
|
|
@@ -9,14 +8,13 @@ from topicgate.app.services.observation_retention_policy_service import (
|
|
|
9
8
|
ObservationRetentionPolicyService,
|
|
10
9
|
)
|
|
11
10
|
from topicgate.app.services.broker_snapshot_service import BrokerSnapshotService
|
|
11
|
+
from topicgate.app.services.broker_resolver import BrokerResolver
|
|
12
12
|
from topicgate.app.services.control_operation_service import ControlOperationService
|
|
13
13
|
from topicgate.app.services.mcp_setup_service import McpSetupService
|
|
14
14
|
from topicgate.app.broker_runtime_state import BrokerRuntimeState
|
|
15
15
|
from topicgate.app.topicgate_runtime import TopicGateRuntime
|
|
16
16
|
from topicgate.core.interfaces.observer_repository import ObserverRepository
|
|
17
17
|
from topicgate.core.models.broker_profile import BrokerProfile
|
|
18
|
-
from topicgate.core.models.mqtt_observation import MqttObservation
|
|
19
|
-
from topicgate.core.models.topic_message import TopicMessage
|
|
20
18
|
from topicgate.infrastructure.database.database_context import DatabaseContext
|
|
21
19
|
from topicgate.infrastructure.credentials.credential_store import CredentialStore
|
|
22
20
|
from topicgate.infrastructure.credentials.os_credential_store import OSCredentialStore
|
|
@@ -64,7 +62,9 @@ class AppDependencies:
|
|
|
64
62
|
self.observation_cache = ObservationCacheService(
|
|
65
63
|
self.topic_messages,
|
|
66
64
|
self.retention_policy,
|
|
65
|
+
administrator=self.topic_messages,
|
|
67
66
|
)
|
|
67
|
+
self.observation_query = ObservationQueryService(self.topic_messages)
|
|
68
68
|
self.control_operations = ControlOperationService(
|
|
69
69
|
self._db_context,
|
|
70
70
|
control_owner,
|
|
@@ -90,13 +90,19 @@ class AppDependencies:
|
|
|
90
90
|
|
|
91
91
|
self.runtime = TopicGateRuntime(
|
|
92
92
|
self.broker_profiles,
|
|
93
|
-
|
|
93
|
+
self.broker_runtime_state.repositories,
|
|
94
94
|
profile.id,
|
|
95
|
-
self._create_observer_repository,
|
|
96
|
-
self.observation_cache,
|
|
97
|
-
self.
|
|
95
|
+
mqtt_repository_factory=self._create_observer_repository,
|
|
96
|
+
observation_cache=self.observation_cache,
|
|
97
|
+
observation_query=self.observation_query,
|
|
98
|
+
current_topics=self.topic_messages,
|
|
99
|
+
control_operations=self.control_operations,
|
|
100
|
+
)
|
|
101
|
+
self.broker_resolver = BrokerResolver(self.runtime)
|
|
102
|
+
self.snapshot_service = BrokerSnapshotService(
|
|
103
|
+
self.runtime,
|
|
104
|
+
resolver=self.broker_resolver,
|
|
98
105
|
)
|
|
99
|
-
self.snapshot_service = BrokerSnapshotService(self.runtime)
|
|
100
106
|
self.mcp_setup = McpSetupService(
|
|
101
107
|
self.runtime,
|
|
102
108
|
self.snapshot_service,
|
|
@@ -118,31 +124,8 @@ class AppDependencies:
|
|
|
118
124
|
return ObserverMqttRepository(
|
|
119
125
|
profile.config,
|
|
120
126
|
list(profile.workspace.subscriptions),
|
|
121
|
-
profile.workspace.model,
|
|
122
127
|
retention_policy=self.retention_policy.get,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
),
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
def _persist_observation(
|
|
130
|
-
self,
|
|
131
|
-
broker_id: UUID,
|
|
132
|
-
observation: MqttObservation,
|
|
133
|
-
) -> None:
|
|
134
|
-
if observation.observation_id is None:
|
|
135
|
-
raise ValueError("A live observation requires an observation ID.")
|
|
136
|
-
self.topic_messages.update_message(
|
|
137
|
-
TopicMessage(
|
|
138
|
-
broker_id=broker_id,
|
|
139
|
-
topic=observation.topic,
|
|
140
|
-
payload=observation.payload,
|
|
141
|
-
qos=observation.qos,
|
|
142
|
-
retain=observation.retain,
|
|
143
|
-
received_at=observation.received_at,
|
|
144
|
-
payload_size=observation.payload_size or len(observation.payload),
|
|
145
|
-
message_count=observation.message_count,
|
|
146
|
-
observation_id=observation.observation_id,
|
|
147
|
-
)
|
|
128
|
+
broker_id=profile.id,
|
|
129
|
+
message_recorder=self.topic_messages,
|
|
130
|
+
current_topics=self.topic_messages,
|
|
148
131
|
)
|
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
from uuid import UUID
|
|
2
2
|
|
|
3
3
|
from topicgate.core.interfaces.observer_repository import ObserverRepository
|
|
4
|
-
from topicgate.core.models.observer_model import ObserverModel
|
|
5
4
|
from topicgate.core.models.broker_profile import BrokerProfile
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class BrokerRuntimeState:
|
|
9
|
-
"""In-memory
|
|
8
|
+
"""In-memory repositories and profile metadata, keyed by broker identity."""
|
|
10
9
|
|
|
11
10
|
def __init__(self) -> None:
|
|
12
|
-
self._models: dict[UUID, ObserverModel] = {}
|
|
13
11
|
self._repositories: dict[UUID, ObserverRepository] = {}
|
|
14
12
|
self._profile_handles: dict[UUID, BrokerProfile] = {}
|
|
15
13
|
self._config_ids: dict[UUID, int | None] = {}
|
|
16
14
|
|
|
17
|
-
def get_model(self, broker_id: UUID) -> ObserverModel | None:
|
|
18
|
-
return self._models.get(broker_id)
|
|
19
|
-
|
|
20
|
-
def set_model(self, broker_id: UUID, model: ObserverModel) -> None:
|
|
21
|
-
self._models[broker_id] = model
|
|
22
|
-
|
|
23
15
|
def remove(self, broker_id: UUID) -> None:
|
|
24
|
-
self._models.pop(broker_id, None)
|
|
25
16
|
self._repositories.pop(broker_id, None)
|
|
26
17
|
self._profile_handles.pop(broker_id, None)
|
|
27
18
|
self._config_ids.pop(broker_id, None)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from topicgate.app.models.broker_snapshot import BrokerSnapshot
|
|
4
|
+
from topicgate.core.models.broker_summary import BrokerSummary
|
|
5
|
+
from topicgate.core.models.observation_cache_administration import BrokerCacheUsage
|
|
6
|
+
from topicgate.core.models.subscription import Subscription
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class BrokerConnectionState:
|
|
11
|
+
status: str
|
|
12
|
+
dropped_message_count: int
|
|
13
|
+
topic_update_interval: float
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class BrokerInspection:
|
|
18
|
+
identity: BrokerSummary
|
|
19
|
+
connection: BrokerConnectionState
|
|
20
|
+
subscriptions: tuple[Subscription, ...]
|
|
21
|
+
cache: BrokerCacheUsage
|
|
22
|
+
snapshot: BrokerSnapshot | None
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
|
|
3
|
+
from topicgate.app.models.broker_inspection import (
|
|
4
|
+
BrokerConnectionState,
|
|
5
|
+
BrokerInspection,
|
|
6
|
+
)
|
|
7
|
+
from topicgate.app.services.broker_resolver import BrokerResolver
|
|
8
|
+
from topicgate.app.services.broker_snapshot_service import (
|
|
9
|
+
DEFAULT_SNAPSHOT_RESULT_LIMIT,
|
|
10
|
+
BrokerSnapshotService,
|
|
11
|
+
)
|
|
12
|
+
from topicgate.app.topicgate_runtime import TopicGateRuntime
|
|
13
|
+
from topicgate.core.models.observation_cache_administration import BrokerCacheUsage
|
|
14
|
+
from topicgate.core.payload_limits import MAX_RENDERED_PAYLOAD_BYTES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BrokerInspectionService:
|
|
18
|
+
"""Compose passive broker reads into one presentation-neutral response."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
runtime: TopicGateRuntime,
|
|
23
|
+
snapshot_service: BrokerSnapshotService,
|
|
24
|
+
resolver: BrokerResolver,
|
|
25
|
+
) -> None:
|
|
26
|
+
self._runtime = runtime
|
|
27
|
+
self._snapshot_service = snapshot_service
|
|
28
|
+
self._resolver = resolver
|
|
29
|
+
|
|
30
|
+
def inspect(
|
|
31
|
+
self,
|
|
32
|
+
broker: UUID | str,
|
|
33
|
+
*,
|
|
34
|
+
include_snapshot: bool = False,
|
|
35
|
+
snapshot_limit: int = DEFAULT_SNAPSHOT_RESULT_LIMIT,
|
|
36
|
+
payload_limit_bytes: int = MAX_RENDERED_PAYLOAD_BYTES,
|
|
37
|
+
) -> BrokerInspection:
|
|
38
|
+
resolved = self._resolver.resolve(broker)
|
|
39
|
+
cache_summary = self._runtime.get_observation_storage_summary(resolved.id)
|
|
40
|
+
cache = next(
|
|
41
|
+
iter(cache_summary.brokers),
|
|
42
|
+
BrokerCacheUsage(resolved.id, 0, 0, None, None),
|
|
43
|
+
)
|
|
44
|
+
snapshot = (
|
|
45
|
+
self._snapshot_service.build_current(
|
|
46
|
+
resolved.id,
|
|
47
|
+
result_limit=snapshot_limit,
|
|
48
|
+
payload_limit_bytes=payload_limit_bytes,
|
|
49
|
+
)
|
|
50
|
+
if include_snapshot
|
|
51
|
+
else None
|
|
52
|
+
)
|
|
53
|
+
return BrokerInspection(
|
|
54
|
+
identity=resolved,
|
|
55
|
+
connection=BrokerConnectionState(
|
|
56
|
+
status=str(self._runtime.get_connection_status(resolved.id)),
|
|
57
|
+
dropped_message_count=self._runtime.get_dropped_message_count(
|
|
58
|
+
resolved.id
|
|
59
|
+
),
|
|
60
|
+
topic_update_interval=self._runtime.get_topic_update_interval(
|
|
61
|
+
resolved.id
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
subscriptions=self._runtime.list_subscriptions(resolved.id),
|
|
65
|
+
cache=cache,
|
|
66
|
+
snapshot=snapshot,
|
|
67
|
+
)
|
|
@@ -5,12 +5,7 @@ from topicgate.app.broker_runtime_state import BrokerRuntimeState
|
|
|
5
5
|
from topicgate.core.config.app_config import AppConfig
|
|
6
6
|
from topicgate.core.config.mqtt_config import MqttConfig
|
|
7
7
|
from topicgate.core.models.broker_profile import BrokerProfile
|
|
8
|
-
from topicgate.core.
|
|
9
|
-
from topicgate.core.models.mqtt_observation import (
|
|
10
|
-
MqttObservation,
|
|
11
|
-
ObservationSource,
|
|
12
|
-
)
|
|
13
|
-
from topicgate.core.interfaces.topic_message_store import TopicMessageStore
|
|
8
|
+
from topicgate.core.interfaces.topic_message_recorder import TopicMessageRecorder
|
|
14
9
|
from topicgate.core.models.observer_workspace import ObserverWorkspace
|
|
15
10
|
from topicgate.infrastructure.credentials.credential_store import CredentialStore
|
|
16
11
|
from topicgate.infrastructure.database.database_context import DatabaseContext
|
|
@@ -21,7 +16,6 @@ from topicgate.infrastructure.repository.broker_repository import BrokerReposito
|
|
|
21
16
|
from topicgate.infrastructure.repository.subscription_repository import (
|
|
22
17
|
SubscriptionRepository,
|
|
23
18
|
)
|
|
24
|
-
from topicgate.processors.observer_model_processor import ObserverModelProcessor
|
|
25
19
|
|
|
26
20
|
|
|
27
21
|
class BrokerProfileService:
|
|
@@ -34,7 +28,7 @@ class BrokerProfileService:
|
|
|
34
28
|
*,
|
|
35
29
|
credential_store: CredentialStore,
|
|
36
30
|
runtime_state: BrokerRuntimeState | None = None,
|
|
37
|
-
topic_messages:
|
|
31
|
+
topic_messages: TopicMessageRecorder | None = None,
|
|
38
32
|
) -> None:
|
|
39
33
|
if isinstance(settings, DatabaseContext):
|
|
40
34
|
self._db = settings
|
|
@@ -49,7 +43,7 @@ class BrokerProfileService:
|
|
|
49
43
|
self.brokers = BrokerRepository(self._db)
|
|
50
44
|
self.configs = BrokerConfigRepository(self._db)
|
|
51
45
|
self.subscriptions = SubscriptionRepository(self._db)
|
|
52
|
-
self.
|
|
46
|
+
self._topic_message_recorder = topic_messages
|
|
53
47
|
self._settings_id = supplied_settings.id if supplied_settings else None
|
|
54
48
|
|
|
55
49
|
identities = self.brokers.list_profiles()
|
|
@@ -81,13 +75,9 @@ class BrokerProfileService:
|
|
|
81
75
|
id=self._runtime_state.get_config_id(identity.id, config.id),
|
|
82
76
|
)
|
|
83
77
|
subscriptions = self.subscriptions.list_for_workspace(identity.workspace_id)
|
|
84
|
-
model = self._runtime_state.get_model(identity.id)
|
|
85
|
-
if model is None:
|
|
86
|
-
model = self._hydrate_observer_model(identity.id, subscriptions)
|
|
87
78
|
workspace = ObserverWorkspace(
|
|
88
79
|
id=identity.workspace_id,
|
|
89
80
|
profile_id=identity.id,
|
|
90
|
-
model=model,
|
|
91
81
|
subscriptions=subscriptions,
|
|
92
82
|
)
|
|
93
83
|
return BrokerProfile(
|
|
@@ -140,6 +130,8 @@ class BrokerProfileService:
|
|
|
140
130
|
profile_id
|
|
141
131
|
)
|
|
142
132
|
self.brokers.delete_profile(profile_id)
|
|
133
|
+
if self._topic_message_recorder is not None:
|
|
134
|
+
self._topic_message_recorder.remove_current_broker(profile_id)
|
|
143
135
|
self._delete_password(profile_id)
|
|
144
136
|
self._runtime_state.remove(profile_id)
|
|
145
137
|
self.save()
|
|
@@ -164,13 +156,6 @@ class BrokerProfileService:
|
|
|
164
156
|
def update_observer_workspace(self, workspace: ObserverWorkspace) -> None:
|
|
165
157
|
self.replace_subscriptions(workspace.id, workspace.subscriptions)
|
|
166
158
|
|
|
167
|
-
def update_observer_model(self, model: ObserverModel) -> None:
|
|
168
|
-
self._runtime_state.set_model(self.brokers.get_profile().id, model)
|
|
169
|
-
self.save()
|
|
170
|
-
|
|
171
|
-
def get_observer_model(self) -> ObserverModel:
|
|
172
|
-
return self.get_profile().workspace.model
|
|
173
|
-
|
|
174
159
|
def get_observer_workspace(self) -> ObserverWorkspace:
|
|
175
160
|
return self.get_profile().workspace
|
|
176
161
|
|
|
@@ -209,31 +194,6 @@ class BrokerProfileService:
|
|
|
209
194
|
self._store_password(identity.id, config.password)
|
|
210
195
|
self._runtime_state.set_config_id(identity.id, config.id)
|
|
211
196
|
|
|
212
|
-
def _hydrate_observer_model(self, broker_id: UUID, subscriptions) -> ObserverModel:
|
|
213
|
-
model = ObserverModelProcessor.add_topics(
|
|
214
|
-
ObserverModel(root_stats=[]),
|
|
215
|
-
(item.topic_filter for item in subscriptions),
|
|
216
|
-
)
|
|
217
|
-
if self._topic_messages is None:
|
|
218
|
-
return model
|
|
219
|
-
for message in self._topic_messages.get_latest_messages(broker_id):
|
|
220
|
-
node = ObserverModelProcessor.find_or_create_node(model, message.topic)
|
|
221
|
-
state = MqttObservation(
|
|
222
|
-
name=node.segment,
|
|
223
|
-
topic=message.topic,
|
|
224
|
-
payload=message.payload,
|
|
225
|
-
qos=message.qos,
|
|
226
|
-
retain=message.retain,
|
|
227
|
-
recieved_at=message.received_at,
|
|
228
|
-
payload_size=message.payload_size,
|
|
229
|
-
message_count=message.message_count,
|
|
230
|
-
source=ObservationSource.STORED,
|
|
231
|
-
observation_id=message.observation_id,
|
|
232
|
-
)
|
|
233
|
-
node.state = state
|
|
234
|
-
model.topic_states[message.topic] = state
|
|
235
|
-
return model
|
|
236
|
-
|
|
237
197
|
def _store_password(self, profile_id: UUID, password: str) -> None:
|
|
238
198
|
if password:
|
|
239
199
|
self._credentials.set_password(profile_id, password)
|
|
@@ -21,10 +21,14 @@ from topicgate.app.models.broker_snapshot import (
|
|
|
21
21
|
)
|
|
22
22
|
from topicgate.app.services.broker_resolver import BrokerResolver
|
|
23
23
|
from topicgate.app.topicgate_runtime import TopicGateRuntime
|
|
24
|
+
from topicgate.core.interfaces.current_topic_reader import CurrentTopicReader
|
|
24
25
|
from topicgate.core.models.broker_summary import BrokerSummary
|
|
25
26
|
from topicgate.core.models.connection_status import ConnectionStatus
|
|
26
|
-
from topicgate.core.models.
|
|
27
|
+
from topicgate.core.models.current_topic import CurrentTopic
|
|
28
|
+
from topicgate.core.models.mqtt_observation import ObservationSource
|
|
29
|
+
from topicgate.core.models.observation_status import ObservationStatus
|
|
27
30
|
from topicgate.core.models.subscription import Subscription
|
|
31
|
+
from topicgate.core.models.topic_message import TopicMessage
|
|
28
32
|
from topicgate.core.mqtt_topics import mqtt_filter_matches
|
|
29
33
|
from topicgate.core.observer_limits import MAX_OBSERVED_TOPICS
|
|
30
34
|
from topicgate.core.payload_limits import MAX_RENDERED_PAYLOAD_BYTES
|
|
@@ -46,12 +50,14 @@ class BrokerSnapshotService:
|
|
|
46
50
|
self,
|
|
47
51
|
runtime: TopicGateRuntime,
|
|
48
52
|
*,
|
|
53
|
+
current_topics: CurrentTopicReader | None = None,
|
|
49
54
|
resolver: BrokerResolver | None = None,
|
|
50
55
|
clock: Clock | None = None,
|
|
51
56
|
monotonic_clock: MonotonicClock | None = None,
|
|
52
57
|
sleep: Sleep | None = None,
|
|
53
58
|
) -> None:
|
|
54
59
|
self._runtime = runtime
|
|
60
|
+
self._current_topics = current_topics or runtime
|
|
55
61
|
self._resolver = resolver or BrokerResolver(runtime)
|
|
56
62
|
self._clock = clock or (lambda: datetime.now(timezone.utc))
|
|
57
63
|
self._monotonic_clock = monotonic_clock or time.monotonic
|
|
@@ -142,22 +148,24 @@ class BrokerSnapshotService:
|
|
|
142
148
|
actual_wait_seconds: float,
|
|
143
149
|
) -> BrokerSnapshot:
|
|
144
150
|
captured_at = self._as_utc(self._clock())
|
|
145
|
-
model = self._runtime.get_observer_model(resolved.id)
|
|
146
151
|
matching = sorted(
|
|
147
152
|
(
|
|
148
|
-
|
|
149
|
-
for
|
|
150
|
-
if mqtt_filter_matches(topic_filter,
|
|
153
|
+
current
|
|
154
|
+
for current in self._current_topics.get_current_topics(resolved.id)
|
|
155
|
+
if mqtt_filter_matches(topic_filter, current.message.topic)
|
|
151
156
|
),
|
|
152
|
-
key=lambda
|
|
157
|
+
key=lambda current: current.message.topic,
|
|
153
158
|
)
|
|
154
159
|
aged = tuple(
|
|
155
|
-
(
|
|
156
|
-
|
|
160
|
+
(
|
|
161
|
+
current,
|
|
162
|
+
self._age_seconds(captured_at, current.message.received_at),
|
|
163
|
+
)
|
|
164
|
+
for current in matching
|
|
157
165
|
)
|
|
158
166
|
fresh = tuple(
|
|
159
|
-
(
|
|
160
|
-
for
|
|
167
|
+
(current, age)
|
|
168
|
+
for current, age in aged
|
|
161
169
|
if max_age_seconds is None or age <= max_age_seconds
|
|
162
170
|
)
|
|
163
171
|
stale_count = len(aged) - len(fresh)
|
|
@@ -173,12 +181,12 @@ class BrokerSnapshotService:
|
|
|
173
181
|
)
|
|
174
182
|
topics = tuple(
|
|
175
183
|
self._topic_snapshot(
|
|
176
|
-
|
|
184
|
+
current,
|
|
177
185
|
age,
|
|
178
186
|
payload_limit_bytes,
|
|
179
187
|
observation_started_at=observation_started_at,
|
|
180
188
|
)
|
|
181
|
-
for
|
|
189
|
+
for current, age in selected
|
|
182
190
|
)
|
|
183
191
|
|
|
184
192
|
observed_for_seconds = (
|
|
@@ -240,47 +248,53 @@ class BrokerSnapshotService:
|
|
|
240
248
|
@classmethod
|
|
241
249
|
def _topic_snapshot(
|
|
242
250
|
cls,
|
|
243
|
-
|
|
251
|
+
current: CurrentTopic,
|
|
244
252
|
age_seconds: float,
|
|
245
253
|
payload_limit_bytes: int,
|
|
246
254
|
*,
|
|
247
255
|
observation_started_at: datetime | None,
|
|
248
256
|
) -> SnapshotTopicState:
|
|
257
|
+
message = current.message
|
|
249
258
|
return SnapshotTopicState(
|
|
250
|
-
topic=
|
|
251
|
-
payload=cls._render_payload(
|
|
252
|
-
qos=
|
|
253
|
-
retain=
|
|
254
|
-
received_at=cls._as_utc(
|
|
259
|
+
topic=message.topic,
|
|
260
|
+
payload=cls._render_payload(message, payload_limit_bytes),
|
|
261
|
+
qos=message.qos,
|
|
262
|
+
retain=message.retain,
|
|
263
|
+
received_at=cls._as_utc(message.received_at),
|
|
255
264
|
age_seconds=age_seconds,
|
|
256
|
-
message_count=
|
|
257
|
-
source=
|
|
258
|
-
|
|
265
|
+
message_count=message.message_count,
|
|
266
|
+
source=(
|
|
267
|
+
ObservationSource.STORED
|
|
268
|
+
if current.status is ObservationStatus.CACHED
|
|
269
|
+
else ObservationSource.LIVE
|
|
270
|
+
),
|
|
271
|
+
status=cls._topic_status(current, observation_started_at),
|
|
259
272
|
)
|
|
260
273
|
|
|
261
274
|
@classmethod
|
|
262
275
|
def _topic_status(
|
|
263
276
|
cls,
|
|
264
|
-
|
|
277
|
+
current: CurrentTopic,
|
|
265
278
|
observation_started_at: datetime | None,
|
|
266
279
|
) -> SnapshotTopicStatus:
|
|
280
|
+
message = current.message
|
|
267
281
|
if (
|
|
268
282
|
observation_started_at is not None
|
|
269
|
-
and cls._as_utc(
|
|
283
|
+
and cls._as_utc(message.received_at)
|
|
270
284
|
< cls._as_utc(observation_started_at)
|
|
271
285
|
):
|
|
272
286
|
return SnapshotTopicStatus.STALE
|
|
273
|
-
if
|
|
287
|
+
if current.status is ObservationStatus.CACHED:
|
|
274
288
|
return SnapshotTopicStatus.CACHED
|
|
275
289
|
return SnapshotTopicStatus.LIVE
|
|
276
290
|
|
|
277
291
|
@staticmethod
|
|
278
292
|
def _render_payload(
|
|
279
|
-
|
|
293
|
+
message: TopicMessage,
|
|
280
294
|
payload_limit_bytes: int,
|
|
281
295
|
) -> SnapshotPayload:
|
|
282
|
-
available = bytes(
|
|
283
|
-
original_size = max(
|
|
296
|
+
available = bytes(message.payload)
|
|
297
|
+
original_size = max(message.payload_size, len(available))
|
|
284
298
|
rendered = available[:payload_limit_bytes]
|
|
285
299
|
try:
|
|
286
300
|
available.decode("utf-8")
|
|
@@ -310,7 +324,7 @@ class BrokerSnapshotService:
|
|
|
310
324
|
def _limitations(
|
|
311
325
|
*,
|
|
312
326
|
status: object,
|
|
313
|
-
matching: list[
|
|
327
|
+
matching: list[CurrentTopic],
|
|
314
328
|
topics: tuple[SnapshotTopicState, ...],
|
|
315
329
|
observation_started_at: datetime | None,
|
|
316
330
|
dropped_message_count: int,
|
|
@@ -325,7 +339,9 @@ class BrokerSnapshotService:
|
|
|
325
339
|
limitations.append(SnapshotLimitation.BROKER_DISCONNECTED)
|
|
326
340
|
if observation_started_at is None:
|
|
327
341
|
limitations.append(SnapshotLimitation.OBSERVATION_NOT_STARTED)
|
|
328
|
-
if any(
|
|
342
|
+
if any(
|
|
343
|
+
current.status is ObservationStatus.CACHED for current in matching
|
|
344
|
+
):
|
|
329
345
|
limitations.append(
|
|
330
346
|
SnapshotLimitation.STORED_STATE_PREDATES_OBSERVATION
|
|
331
347
|
)
|