stoplight 5.8.3 → 6.0.0.rc2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/LICENSE.md +1 -0
- data/README.md +132 -79
- data/UPGRADING.md +265 -6
- data/lib/stoplight/admin/actions/action.rb +4 -12
- data/lib/stoplight/admin/actions/lock.rb +12 -9
- data/lib/stoplight/admin/actions/lock_all.rb +27 -0
- data/lib/stoplight/admin/actions/remove.rb +12 -9
- data/lib/stoplight/admin/actions/stats.rb +15 -12
- data/lib/stoplight/admin/actions/unlock.rb +10 -9
- data/lib/stoplight/admin/assets/apple-touch-icon.png +0 -0
- data/lib/stoplight/admin/assets/favicon.ico +0 -0
- data/lib/stoplight/admin/assets/flowbite.min.css +2 -0
- data/lib/stoplight/admin/assets/flowbite.min.js +1 -0
- data/lib/stoplight/admin/assets/icon.svg +7 -0
- data/lib/stoplight/admin/assets/turbo.es2017-esm.min.js +34 -0
- data/lib/stoplight/admin/config_registry.rb +40 -0
- data/lib/stoplight/admin/dependencies.rb +29 -28
- data/lib/stoplight/admin/helpers.rb +50 -17
- data/lib/stoplight/admin/light_view.rb +184 -0
- data/lib/stoplight/admin/lights_stats.rb +7 -9
- data/lib/stoplight/admin/views/_card.erb +19 -21
- data/lib/stoplight/admin/views/index.erb +9 -3
- data/lib/stoplight/admin/views/layout.erb +45 -13
- data/lib/stoplight/admin.rb +110 -21
- data/lib/stoplight/data_store.rb +18 -8
- data/lib/stoplight/domain/config.rb +4 -1
- data/lib/stoplight/domain/error_tracking_policy.rb +9 -0
- data/lib/stoplight/domain/failure.rb +4 -0
- data/lib/stoplight/domain/id.rb +13 -0
- data/lib/stoplight/domain/light.rb +35 -77
- data/lib/stoplight/domain/lock_control.rb +55 -0
- data/lib/stoplight/domain/matcher_validator.rb +24 -0
- data/lib/stoplight/domain/state_snapshot.rb +2 -6
- data/lib/stoplight/domain/strategies/green_run_strategy.rb +33 -19
- data/lib/stoplight/domain/strategies/red_run_strategy.rb +8 -2
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +56 -32
- data/lib/stoplight/domain/telemetry/bus.rb +90 -0
- data/lib/stoplight/domain/telemetry/consumer.rb +18 -0
- data/lib/stoplight/domain/telemetry/emitter.rb +45 -0
- data/lib/stoplight/domain/telemetry/envelope.rb +15 -0
- data/lib/stoplight/domain/telemetry/failure.rb +13 -0
- data/lib/stoplight/domain/telemetry/light_registered.rb +12 -0
- data/lib/stoplight/domain/telemetry/lock_changed.rb +19 -0
- data/lib/stoplight/domain/telemetry/metrics.rb +15 -0
- data/lib/stoplight/domain/telemetry/recovery_failed.rb +20 -0
- data/lib/stoplight/domain/telemetry/recovery_probe_completed.rb +15 -0
- data/lib/stoplight/domain/telemetry/recovery_started.rb +18 -0
- data/lib/stoplight/domain/telemetry/recovery_succeeded.rb +19 -0
- data/lib/stoplight/domain/telemetry/run_completed.rb +17 -0
- data/lib/stoplight/domain/telemetry/run_recorder.rb +58 -0
- data/lib/stoplight/domain/telemetry/settings.rb +21 -0
- data/lib/stoplight/domain/telemetry/state_transitioned.rb +11 -0
- data/lib/stoplight/domain/telemetry/subscription.rb +11 -0
- data/lib/stoplight/domain/telemetry/traffic_breached.rb +20 -0
- data/lib/stoplight/domain/tracker/recovery_probe.rb +61 -17
- data/lib/stoplight/domain/tracker/request.rb +17 -8
- data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +13 -3
- data/lib/stoplight/domain/traffic_control/error_rate.rb +16 -12
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +9 -3
- data/lib/stoplight/error.rb +10 -1
- data/lib/stoplight/infrastructure/config_serializer.rb +50 -0
- data/lib/stoplight/infrastructure/fail_safe/error_notifier.rb +25 -0
- data/lib/stoplight/infrastructure/fail_safe/storage/registry.rb +68 -0
- data/lib/stoplight/infrastructure/memory/storage/registry.rb +18 -0
- data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +29 -32
- data/lib/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rb +74 -0
- data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +25 -24
- data/lib/stoplight/infrastructure/notifier/fail_safe.rb +3 -8
- data/lib/stoplight/infrastructure/notifier/generic.rb +3 -3
- data/lib/stoplight/infrastructure/redis/key.rb +25 -0
- data/lib/stoplight/infrastructure/redis/storage/now.lua +9 -0
- data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +2 -4
- data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +1 -1
- data/lib/stoplight/infrastructure/redis/storage/registry.rb +73 -0
- data/lib/stoplight/infrastructure/redis/storage/scripting.rb +54 -3
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +3 -1
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +4 -2
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +3 -1
- data/lib/stoplight/infrastructure/redis/storage/state.rb +8 -8
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +20 -21
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +11 -19
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +20 -16
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/_evict.lua +44 -0
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +14 -21
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +38 -36
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +23 -30
- data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +45 -135
- data/lib/stoplight/infrastructure/system_clock.rb +4 -0
- data/lib/stoplight/telemetry.rb +19 -0
- data/lib/stoplight/types.rb +15 -2
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight/wiring/config_compatibility_validator.rb +25 -2
- data/lib/stoplight/wiring/default.rb +1 -1
- data/lib/stoplight/wiring/default_config.rb +2 -1
- data/lib/stoplight/wiring/default_configuration.rb +3 -2
- data/lib/stoplight/wiring/external_caller.rb +27 -0
- data/lib/stoplight/wiring/fail_safe_config.rb +11 -0
- data/lib/stoplight/wiring/global_state.rb +93 -0
- data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_control_dsl.rb +3 -5
- data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_recovery_dsl.rb +2 -2
- data/lib/stoplight/wiring/light_configuration_dsl.rb +97 -0
- data/lib/stoplight/wiring/light_factory.rb +154 -97
- data/lib/stoplight/wiring/notifier_bridge.rb +33 -0
- data/lib/stoplight/wiring/redis/backend.rb +1 -1
- data/lib/stoplight/wiring/system/storage.rb +69 -0
- data/lib/stoplight/wiring/system.rb +125 -56
- data/lib/stoplight/wiring/{configuration_dsl.rb → system_configuration_dsl.rb} +13 -43
- data/lib/stoplight.rb +50 -194
- data/sig/sinatra/base.rbs +6 -0
- data/sig/stdlib/securerandom.rbs +4 -0
- data/sig/stoplight/admin/actions/action.rbs +10 -0
- data/sig/stoplight/admin/actions/lock.rbs +17 -0
- data/sig/stoplight/admin/actions/lock_all.rbs +17 -0
- data/sig/stoplight/admin/actions/remove.rbs +22 -0
- data/sig/stoplight/admin/actions/stats.rbs +28 -0
- data/sig/stoplight/admin/actions/unlock.rbs +17 -0
- data/sig/stoplight/admin/config_registry.rbs +20 -0
- data/sig/stoplight/admin/dependencies.rbs +16 -0
- data/sig/stoplight/admin/helpers.rbs +17 -0
- data/sig/stoplight/admin/light_view.rbs +39 -0
- data/sig/stoplight/admin/lights_stats.rbs +30 -0
- data/sig/stoplight/admin.rbs +14 -0
- data/sig/stoplight/color.rbs +3 -3
- data/sig/{_private/stoplight/data_store.rbs → stoplight/data_store/base.rbs} +1 -0
- data/sig/stoplight/data_store/memory.rbs +9 -0
- data/sig/stoplight/data_store/redis.rbs +17 -0
- data/sig/{_private/stoplight → stoplight}/domain/config.rbs +28 -24
- data/sig/{_private/stoplight → stoplight}/domain/error_tracking_policy.rbs +4 -0
- data/sig/{_private/stoplight → stoplight}/domain/failure.rbs +2 -0
- data/sig/stoplight/domain/id.rbs +7 -0
- data/sig/stoplight/domain/light.rbs +39 -0
- data/sig/{_private/stoplight → stoplight}/domain/light_info.rbs +1 -5
- data/sig/stoplight/domain/lock_control.rbs +16 -0
- data/sig/stoplight/domain/matcher_validator.rbs +11 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/clock.rbs +6 -0
- data/sig/stoplight/domain/ports/data_store.rbs +9 -0
- data/sig/stoplight/domain/ports/error_notifier.rbs +12 -0
- data/sig/stoplight/domain/ports/exception_matcher.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/metrics_store.rbs +2 -2
- data/sig/stoplight/domain/ports/registry.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/run_strategy.rbs +2 -1
- data/sig/stoplight/domain/ports/state_transition_notifier.rbs +18 -0
- data/sig/stoplight/domain/ports/telemetry.rbs +28 -0
- data/sig/stoplight/domain/ports/telemetry_publisher.rbs +14 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_control.rbs +7 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_recovery.rbs +2 -3
- data/sig/stoplight/domain/strategies/green_run_strategy.rbs +22 -0
- data/sig/stoplight/domain/strategies/red_run_strategy.rbs +19 -0
- data/sig/{_private/stoplight → stoplight}/domain/strategies/yellow_run_strategy.rbs +23 -12
- data/sig/stoplight/domain/telemetry/bus.rbs +49 -0
- data/sig/stoplight/domain/telemetry/consumer.rbs +13 -0
- data/sig/stoplight/domain/telemetry/emitter.rbs +51 -0
- data/sig/stoplight/domain/telemetry/envelope.rbs +30 -0
- data/sig/stoplight/domain/telemetry/failure.rbs +23 -0
- data/sig/stoplight/domain/telemetry/light_registered.rbs +23 -0
- data/sig/stoplight/domain/telemetry/lock_changed.rbs +31 -0
- data/sig/stoplight/domain/telemetry/metrics.rbs +27 -0
- data/sig/stoplight/domain/telemetry/recovery_failed.rbs +36 -0
- data/sig/stoplight/domain/telemetry/recovery_probe_completed.rbs +29 -0
- data/sig/stoplight/domain/telemetry/recovery_started.rbs +26 -0
- data/sig/stoplight/domain/telemetry/recovery_succeeded.rbs +31 -0
- data/sig/stoplight/domain/telemetry/run_completed.rbs +38 -0
- data/sig/stoplight/domain/telemetry/run_recorder.rbs +27 -0
- data/sig/stoplight/domain/telemetry/settings.rbs +47 -0
- data/sig/stoplight/domain/telemetry/state_transitioned.rbs +11 -0
- data/sig/stoplight/domain/telemetry/subscription.rbs +9 -0
- data/sig/stoplight/domain/telemetry/traffic_breached.rbs +36 -0
- data/sig/stoplight/domain/telemetry/types.rbs +32 -0
- data/sig/{_private/stoplight → stoplight}/domain/tracker/recovery_probe.rbs +10 -6
- data/sig/{_private/stoplight → stoplight}/domain/tracker/request.rbs +3 -3
- data/sig/{_private/stoplight → stoplight}/domain/traffic_control/consecutive_errors.rbs +2 -0
- data/sig/{_private/stoplight → stoplight}/domain/traffic_control/error_rate.rbs +3 -2
- data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery/consecutive_successes.rbs +2 -0
- data/sig/stoplight/domain/types.rbs +25 -0
- data/sig/stoplight/error.rbs +8 -0
- data/sig/stoplight/generators/rails_generators_base.rbs +11 -0
- data/sig/stoplight/infrastructure/config_serializer.rbs +12 -0
- data/sig/stoplight/infrastructure/fail_safe/error_notifier.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/metrics.rbs +2 -2
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock.rbs +2 -2
- data/sig/stoplight/infrastructure/fail_safe/storage/registry.rbs +23 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/state.rbs +4 -4
- data/sig/stoplight/infrastructure/memory/storage/registry.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/state.rbs +3 -3
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/unbounded_metrics.rbs +7 -7
- data/sig/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rbs +27 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/window_metrics.rbs +7 -7
- data/sig/stoplight/infrastructure/notifier/fail_safe.rbs +15 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/generic.rbs +1 -1
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/io.rbs +1 -1
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/logger.rbs +1 -1
- data/sig/stoplight/infrastructure/redis/key.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_lock.rbs +2 -2
- data/sig/stoplight/infrastructure/redis/storage/registry.rbs +27 -0
- data/sig/stoplight/infrastructure/redis/storage/scripting.rbs +34 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/state.rbs +4 -4
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/unbounded_metrics.rbs +8 -1
- data/sig/stoplight/infrastructure/redis/storage/window_metrics.rbs +35 -0
- data/sig/stoplight/infrastructure/types.rbs +6 -0
- data/sig/stoplight/notifier.rbs +1 -1
- data/sig/stoplight/ports/system.rbs +16 -10
- data/sig/stoplight/state.rbs +3 -3
- data/sig/stoplight/telemetry.rbs +17 -0
- data/sig/stoplight/types.rbs +9 -0
- data/sig/{_private/stoplight → stoplight}/wiring/config_compatibility_validator.rbs +2 -0
- data/sig/stoplight/wiring/default.rbs +26 -0
- data/sig/stoplight/wiring/default_configuration.rbs +39 -0
- data/sig/stoplight/wiring/external_caller.rbs +13 -0
- data/sig/stoplight/wiring/fail_safe_config.rbs +7 -0
- data/sig/stoplight/wiring/global_state.rbs +36 -0
- data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_control_dsl.rbs +1 -1
- data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_recovery_dsl.rbs +1 -1
- data/sig/stoplight/wiring/light_configuration_dsl.rbs +38 -0
- data/sig/stoplight/wiring/light_factory.rbs +78 -0
- data/sig/stoplight/wiring/notifier_bridge.rbs +15 -0
- data/sig/stoplight/wiring/notifier_factory.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/wiring/redis/backend.rbs +6 -6
- data/sig/stoplight/wiring/system/storage.rbs +35 -0
- data/sig/stoplight/wiring/system.rbs +30 -0
- data/sig/stoplight/wiring/system_configuration_dsl.rbs +39 -0
- data/sig/stoplight/wiring/types.rbs +6 -0
- data/sig/stoplight.rbs +37 -54
- metadata +190 -143
- data/lib/stoplight/admin/actions/lock_all_green.rb +0 -18
- data/lib/stoplight/admin/actions/lock_green.rb +0 -23
- data/lib/stoplight/admin/actions/lock_red.rb +0 -23
- data/lib/stoplight/admin/lights_repository/light.rb +0 -171
- data/lib/stoplight/admin/lights_repository.rb +0 -87
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +0 -236
- data/lib/stoplight/infrastructure/fail_safe/data_store.rb +0 -152
- data/lib/stoplight/infrastructure/memory/data_store/metrics.rb +0 -27
- data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_store.rb +0 -52
- data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_token.rb +0 -17
- data/lib/stoplight/infrastructure/memory/data_store/sliding_window.rb +0 -74
- data/lib/stoplight/infrastructure/memory/data_store/state.rb +0 -21
- data/lib/stoplight/infrastructure/memory/data_store.rb +0 -342
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/get_metrics.lua +0 -26
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_failure.lua +0 -36
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_failure.lua +0 -27
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_success.lua +0 -23
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_success.lua +0 -35
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/release_lock.lua +0 -6
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_green.lua +0 -10
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_red.lua +0 -10
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_yellow.lua +0 -9
- data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_store.rb +0 -60
- data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_token.rb +0 -28
- data/lib/stoplight/infrastructure/redis/data_store/scripting.rb +0 -73
- data/lib/stoplight/infrastructure/redis/data_store.rb +0 -516
- data/lib/stoplight/infrastructure/redis/storage/key_space.rb +0 -51
- data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +0 -41
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +0 -33
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +0 -47
- data/lib/stoplight/infrastructure/storage/compatibility_state.rb +0 -44
- data/lib/stoplight/wiring/light_builder.rb +0 -198
- data/lib/stoplight/wiring/notifier_factory.rb +0 -26
- data/lib/stoplight/wiring/system/light_builder.rb +0 -47
- data/lib/stoplight/wiring/system/light_factory.rb +0 -64
- data/sig/_private/stoplight/domain/light.rbs +0 -25
- data/sig/_private/stoplight/domain/ports/data_store.rbs +0 -76
- data/sig/_private/stoplight/domain/ports/light_factory.rbs +0 -80
- data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +0 -17
- data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +0 -17
- data/sig/_private/stoplight/domain/tracker/base.rbs +0 -8
- data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +0 -26
- data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +0 -25
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +0 -19
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +0 -27
- data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +0 -30
- data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +0 -24
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +0 -21
- data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +0 -34
- data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +0 -67
- data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +0 -19
- data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +0 -13
- data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +0 -34
- data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +0 -13
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +0 -14
- data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +0 -14
- data/sig/_private/stoplight/system/light_builder.rbs +0 -23
- data/sig/_private/stoplight/system/light_factory.rbs +0 -17
- data/sig/_private/stoplight/types.rbs +0 -6
- data/sig/_private/stoplight/wiring/configuration_dsl.rbs +0 -43
- data/sig/_private/stoplight/wiring/default.rbs +0 -26
- data/sig/_private/stoplight/wiring/default_configuration.rbs +0 -29
- data/sig/_private/stoplight/wiring/light_builder.rbs +0 -48
- data/sig/_private/stoplight/wiring/light_factory.rbs +0 -16
- data/sig/_private/stoplight/wiring/notifier_factory.rbs +0 -10
- data/sig/_private/stoplight/wiring/system.rbs +0 -15
- data/sig/_private/stoplight.rbs +0 -48
- data/sig/stoplight/data_store.rbs +0 -19
- data/sig/stoplight/ports/configuration.rbs +0 -19
- data/sig/stoplight/ports/exception_matcher.rbs +0 -8
- data/sig/stoplight/ports/light.rbs +0 -12
- data/sig/stoplight/ports/light_info.rbs +0 -5
- data/sig/stoplight/ports/state_transition_notifier.rbs +0 -15
- /data/sig/{_private/stoplight → stoplight}/common/deprecations.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/compatibility_result.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/metrics_snapshot.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_store.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/state_store.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/state_snapshot.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/storage/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery.rbs +0 -0
- /data/sig/{_private → stoplight}/generators/stoplight/install/install_generator.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_lock.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/system_clock.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/data_store_backend.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/default_config.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/memory/backend.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/storage_set.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/storage_set_builder.rbs +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Stoplight
|
|
6
|
+
class Admin
|
|
7
|
+
class LightView
|
|
8
|
+
COLORS = [
|
|
9
|
+
Stoplight::Color::GREEN,
|
|
10
|
+
Stoplight::Color::YELLOW,
|
|
11
|
+
Stoplight::Color::RED
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
attr_reader :failures
|
|
15
|
+
|
|
16
|
+
def initialize(
|
|
17
|
+
config:,
|
|
18
|
+
state_snapshot:,
|
|
19
|
+
metrics_snapshot:,
|
|
20
|
+
recovery_metrics_snapshot:
|
|
21
|
+
)
|
|
22
|
+
@config = config
|
|
23
|
+
@state_snapshot = state_snapshot
|
|
24
|
+
@recovery_metrics_snapshot = recovery_metrics_snapshot
|
|
25
|
+
@metrics_snapshot = metrics_snapshot
|
|
26
|
+
@failures = [metrics_snapshot.last_error].compact
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def locked?
|
|
30
|
+
!unlocked?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def color = @state_snapshot.color
|
|
34
|
+
def id = @config.id
|
|
35
|
+
def name = @config.name
|
|
36
|
+
def state = @state_snapshot.locked_state
|
|
37
|
+
def latest_failure = @metrics_snapshot.last_error
|
|
38
|
+
|
|
39
|
+
def traffic_metric_label
|
|
40
|
+
if @metrics_snapshot.requests.nil?
|
|
41
|
+
"Consecutive failures"
|
|
42
|
+
else
|
|
43
|
+
"Errors"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def traffic_metric_value
|
|
48
|
+
requests = @metrics_snapshot.requests
|
|
49
|
+
if requests.nil?
|
|
50
|
+
@metrics_snapshot.consecutive_errors.to_s
|
|
51
|
+
else
|
|
52
|
+
error_rate = @metrics_snapshot.error_rate
|
|
53
|
+
raise TypeError, "error_rate must not be nil" if error_rate.nil?
|
|
54
|
+
|
|
55
|
+
"#{@metrics_snapshot.errors!} / #{requests} requests (#{format("%.1f%%", error_rate * 100)})"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def unlocked?
|
|
60
|
+
@state_snapshot.locked_state == Stoplight::State::UNLOCKED
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [Hash]
|
|
64
|
+
def as_json
|
|
65
|
+
{
|
|
66
|
+
id: @config.id,
|
|
67
|
+
name: @config.name,
|
|
68
|
+
color: @state_snapshot.color,
|
|
69
|
+
failures: Array(@metrics_snapshot.last_error),
|
|
70
|
+
locked: locked?
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Array]
|
|
75
|
+
def default_sort_key
|
|
76
|
+
[-COLORS.index(@state_snapshot.color).to_i, @config.name]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def last_check = [
|
|
80
|
+
@metrics_snapshot.last_error_at,
|
|
81
|
+
@metrics_snapshot.last_success_at,
|
|
82
|
+
@recovery_metrics_snapshot.last_error_at,
|
|
83
|
+
@recovery_metrics_snapshot.last_success_at,
|
|
84
|
+
@state_snapshot.breached_at
|
|
85
|
+
].compact.max
|
|
86
|
+
|
|
87
|
+
private def duration_in_words(seconds)
|
|
88
|
+
minutes = seconds / 60
|
|
89
|
+
seconds %= 60
|
|
90
|
+
hours = minutes / 60
|
|
91
|
+
minutes %= 60
|
|
92
|
+
parts = [] #: Array[String]
|
|
93
|
+
parts << "#{hours} #{(hours == 1) ? "hour" : "hours"}" if hours > 0
|
|
94
|
+
parts << "#{minutes} #{(minutes == 1) ? "minute" : "minutes"}" if minutes > 0
|
|
95
|
+
parts << "#{seconds} #{(seconds == 1) ? "second" : "seconds"}" if seconds > 0
|
|
96
|
+
parts.join(", ")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [String]
|
|
100
|
+
def description_title
|
|
101
|
+
case @state_snapshot.color
|
|
102
|
+
when Stoplight::Color::RED
|
|
103
|
+
if locked? && @metrics_snapshot.last_error.nil?
|
|
104
|
+
"Locked Open"
|
|
105
|
+
else
|
|
106
|
+
"Last Error"
|
|
107
|
+
end
|
|
108
|
+
when Stoplight::Color::YELLOW
|
|
109
|
+
"Testing Recovery"
|
|
110
|
+
when Stoplight::Color::GREEN
|
|
111
|
+
if locked?
|
|
112
|
+
"Forced Healthy"
|
|
113
|
+
else
|
|
114
|
+
"Healthy"
|
|
115
|
+
end
|
|
116
|
+
else
|
|
117
|
+
raise T.absurd
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [String]
|
|
122
|
+
def description_message
|
|
123
|
+
case @state_snapshot.color
|
|
124
|
+
when Stoplight::Color::RED
|
|
125
|
+
if (latest_failure = @metrics_snapshot.last_error)
|
|
126
|
+
"#{latest_failure.error_class}: #{latest_failure.error_message}"
|
|
127
|
+
elsif locked?
|
|
128
|
+
"Circuit manually locked open"
|
|
129
|
+
else
|
|
130
|
+
"Not available"
|
|
131
|
+
end
|
|
132
|
+
when Stoplight::Color::YELLOW
|
|
133
|
+
if (latest_failure = @metrics_snapshot.last_error)
|
|
134
|
+
"#{latest_failure.error_class}: #{latest_failure.error_message}"
|
|
135
|
+
else
|
|
136
|
+
"Not available"
|
|
137
|
+
end
|
|
138
|
+
when Stoplight::Color::GREEN
|
|
139
|
+
if locked?
|
|
140
|
+
"Circuit manually locked closed"
|
|
141
|
+
else
|
|
142
|
+
"No recent errors"
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
raise T.absurd
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @return [String]
|
|
150
|
+
def description_comment
|
|
151
|
+
case @state_snapshot.color
|
|
152
|
+
when Stoplight::Color::RED
|
|
153
|
+
if locked?
|
|
154
|
+
"Override active - all requests blocked"
|
|
155
|
+
elsif (recovery_at = @state_snapshot.recovery_scheduled_after)
|
|
156
|
+
recovery_in = [0, (recovery_at - Time.now.utc).to_i].max
|
|
157
|
+
if recovery_in > 0
|
|
158
|
+
"Will attempt recovery in #{duration_in_words(recovery_in)}"
|
|
159
|
+
else
|
|
160
|
+
"Recovery started: awaiting test traffic"
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
"Recovery started: awaiting test traffic"
|
|
164
|
+
end
|
|
165
|
+
when Stoplight::Color::YELLOW
|
|
166
|
+
recovery_metrics_snapshot = @recovery_metrics_snapshot
|
|
167
|
+
if recovery_metrics_snapshot.consecutive_successes.zero?
|
|
168
|
+
"Recovery started: awaiting test traffic"
|
|
169
|
+
else
|
|
170
|
+
"Allowing limited test traffic (#{recovery_metrics_snapshot.consecutive_successes} of #{@config.recovery_threshold} requests)"
|
|
171
|
+
end
|
|
172
|
+
when Stoplight::Color::GREEN
|
|
173
|
+
if locked?
|
|
174
|
+
"Override active - all requests processed"
|
|
175
|
+
else
|
|
176
|
+
"Operating normally"
|
|
177
|
+
end
|
|
178
|
+
else
|
|
179
|
+
raise T.absurd
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -8,8 +8,6 @@ module Stoplight
|
|
|
8
8
|
percent_red: 0, percent_yellow: 0, percent_green: 0
|
|
9
9
|
}.freeze
|
|
10
10
|
|
|
11
|
-
# @!attribute lights
|
|
12
|
-
# @return [<Stoplight::Admin::LightsRepository::Light>]
|
|
13
11
|
attr_reader :lights
|
|
14
12
|
private :lights
|
|
15
13
|
|
|
@@ -19,7 +17,7 @@ module Stoplight
|
|
|
19
17
|
end
|
|
20
18
|
end
|
|
21
19
|
|
|
22
|
-
# @param lights [<Stoplight::Admin::
|
|
20
|
+
# @param lights [<Stoplight::Admin::LightView>]
|
|
23
21
|
def initialize(lights)
|
|
24
22
|
@lights = lights
|
|
25
23
|
end
|
|
@@ -38,27 +36,27 @@ module Stoplight
|
|
|
38
36
|
end
|
|
39
37
|
|
|
40
38
|
private def count_red
|
|
41
|
-
count_lights(RED)
|
|
39
|
+
count_lights(Color::RED)
|
|
42
40
|
end
|
|
43
41
|
|
|
44
42
|
private def percent_red
|
|
45
|
-
percent_lights(RED)
|
|
43
|
+
percent_lights(Color::RED)
|
|
46
44
|
end
|
|
47
45
|
|
|
48
46
|
private def count_green
|
|
49
|
-
count_lights(GREEN)
|
|
47
|
+
count_lights(Color::GREEN)
|
|
50
48
|
end
|
|
51
49
|
|
|
52
50
|
private def percent_green
|
|
53
|
-
percent_lights(GREEN)
|
|
51
|
+
percent_lights(Color::GREEN)
|
|
54
52
|
end
|
|
55
53
|
|
|
56
54
|
private def count_yellow
|
|
57
|
-
count_lights(YELLOW)
|
|
55
|
+
count_lights(Color::YELLOW)
|
|
58
56
|
end
|
|
59
57
|
|
|
60
58
|
private def percent_yellow
|
|
61
|
-
percent_lights(YELLOW)
|
|
59
|
+
percent_lights(Color::YELLOW)
|
|
62
60
|
end
|
|
63
61
|
|
|
64
62
|
private def count_lights(color)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<div class="max-w-xl mr-5 p-6 border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
|
1
|
+
<div class="flex flex-col max-w-xl mr-5 p-6 border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
|
2
2
|
<div class="flex items-center">
|
|
3
|
-
<%
|
|
3
|
+
<% light_id = light.id %>
|
|
4
4
|
|
|
5
5
|
<div class="relative">
|
|
6
6
|
<div class="relative inline-flex items-center justify-center w-10 h-10 overflow-hidden bg-<%= color %>-100 rounded-full dark:bg-<%= color %>-600">
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
<div class="flex-1 min-w-0 ms-4">
|
|
26
26
|
<p class="text-sm font-medium text-gray-900 truncate dark:text-white">
|
|
27
|
-
<%= light.name %>
|
|
27
|
+
<%= CGI.escapeHTML(light.name) %>
|
|
28
28
|
</p>
|
|
29
29
|
<span class="flex items-center text-sm font-medium text-gray-500 dark:text-gray-400 truncate me-3">
|
|
30
30
|
<span class="flex w-2.5 h-2.5 bg-<%= color %>-600 rounded-full me-1.5 shrink-0"></span>
|
|
@@ -50,11 +50,13 @@
|
|
|
50
50
|
</svg>
|
|
51
51
|
</button>
|
|
52
52
|
|
|
53
|
+
<% action_classes = "flex items-center py-2 px-4 text-sm " + (settings.read_only? ? "text-gray-400 dark:text-gray-500 cursor-not-allowed" : "hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white") %>
|
|
54
|
+
|
|
53
55
|
<!-- Dropdown menu -->
|
|
54
56
|
<div id="dropdownDotsHorizontal-<%= light.id %>" class="z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow-sm w-44 dark:bg-gray-700 dark:divide-gray-600">
|
|
55
57
|
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdownMenuIconHorizontalButton">
|
|
56
58
|
<li>
|
|
57
|
-
<a
|
|
59
|
+
<a <%= control_attributes(system_url(system_id, "/lights/#{light_id}/unlock"), verb: "patch") %> class="<%= action_classes %>">
|
|
58
60
|
<svg class="flex w-4 h-4 me-1.5 shrink-0" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
59
61
|
<circle cx="12" cy="16" r="1"/>
|
|
60
62
|
<rect width="18" height="12" x="3" y="10" rx="2"/>
|
|
@@ -64,7 +66,7 @@
|
|
|
64
66
|
</a>
|
|
65
67
|
</li>
|
|
66
68
|
<li>
|
|
67
|
-
<a
|
|
69
|
+
<a <%= control_attributes(system_url(system_id, "/lights/#{light_id}/lock?color=red"), verb: "patch") %> class="<%= action_classes %>">
|
|
68
70
|
<svg class="flex w-4 h-4 me-1.5 text-red-600 shrink-0" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
69
71
|
<circle cx="12" cy="16" r="1"/>
|
|
70
72
|
<rect x="3" y="10" width="18" height="12" rx="2"/>
|
|
@@ -74,7 +76,7 @@
|
|
|
74
76
|
</a>
|
|
75
77
|
</li>
|
|
76
78
|
<li>
|
|
77
|
-
<a
|
|
79
|
+
<a <%= control_attributes(system_url(system_id, "/lights/#{light_id}/lock?color=green"), verb: "patch") %> class="<%= action_classes %>">
|
|
78
80
|
<svg class="flex w-4 h-4 me-1.5 text-green-600 shrink-0" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
79
81
|
<circle cx="12" cy="16" r="1"/>
|
|
80
82
|
<rect x="3" y="10" width="18" height="12" rx="2"/>
|
|
@@ -84,7 +86,7 @@
|
|
|
84
86
|
</a>
|
|
85
87
|
</li>
|
|
86
88
|
<li>
|
|
87
|
-
<a
|
|
89
|
+
<a <%= control_attributes(system_url(system_id, "/lights/#{light_id}"), confirm: "Are you sure you want to remove this light?", verb: "delete") %> class="<%= action_classes %>">
|
|
88
90
|
<svg class="flex w-4 h-4 me-1.5 text-red-600 shrink-0" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
89
91
|
<polyline points="3 6 5 6 21 6"/>
|
|
90
92
|
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
|
|
@@ -101,13 +103,9 @@
|
|
|
101
103
|
|
|
102
104
|
<div class="p-4 my-4 text-sm text-<%= color %>-800 rounded-lg bg-<%= color %>-50 dark:bg-gray-800 dark:text-<%= color %>-400">
|
|
103
105
|
<div class="flex justify-between items-center">
|
|
104
|
-
<
|
|
105
|
-
<p class="font-semibold"><%= light.description_title %></p>
|
|
106
|
-
<p class="font-medium"><%= light.description_message %></p>
|
|
107
|
-
<p><%= light.description_comment %></p>
|
|
108
|
-
</div>
|
|
106
|
+
<p class="font-semibold pr-4"><%= CGI.escapeHTML(light.description_title) %></p>
|
|
109
107
|
<% if light.latest_failure %>
|
|
110
|
-
<div class="whitespace-nowrap text-center">
|
|
108
|
+
<div class="whitespace-nowrap text-center shrink-0">
|
|
111
109
|
<%= light.latest_failure.time.strftime("%F %T") %>
|
|
112
110
|
<span class="text-gray-400 dark:text-gray-500 uppercase tracking-wide">
|
|
113
111
|
UTC
|
|
@@ -115,20 +113,20 @@
|
|
|
115
113
|
</div>
|
|
116
114
|
<% end %>
|
|
117
115
|
</div>
|
|
116
|
+
<p class="font-medium"><%= CGI.escapeHTML(light.description_message) %></p>
|
|
117
|
+
<p><%= CGI.escapeHTML(light.description_comment) %></p>
|
|
118
118
|
</div>
|
|
119
119
|
|
|
120
120
|
<!-- Stats Row -->
|
|
121
|
-
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400 mt-3">
|
|
121
|
+
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400 mt-3" style="margin-top: auto;">
|
|
122
122
|
<div>
|
|
123
|
-
<span class="font-medium"
|
|
123
|
+
<span class="font-medium"><%= light.traffic_metric_label %>:</span> <%= light.traffic_metric_value %>
|
|
124
124
|
</div>
|
|
125
125
|
|
|
126
|
-
<% light.last_check
|
|
127
|
-
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
</div>
|
|
131
|
-
<% end %>
|
|
126
|
+
<% if light.last_check %>
|
|
127
|
+
<div>
|
|
128
|
+
<span class="font-medium">Last Check:</span><%= time_ago_in_words(light.last_check) %>
|
|
129
|
+
</div>
|
|
132
130
|
<% end %>
|
|
133
131
|
</div>
|
|
134
132
|
</div>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
<div class="space-y-4">
|
|
15
15
|
<p class="mb-8 text-lg font-normal text-gray-500 lg:text-xl sm:px-16 lg:px-48 dark:text-gray-400">
|
|
16
|
-
Ensure
|
|
16
|
+
Ensure lights are registered and this Admin instance uses the same data store as your application.
|
|
17
17
|
</p>
|
|
18
18
|
</div>
|
|
19
19
|
|
|
@@ -28,9 +28,15 @@
|
|
|
28
28
|
</div>
|
|
29
29
|
</section>
|
|
30
30
|
<% else %>
|
|
31
|
-
<
|
|
31
|
+
<style nonce="<%= nonce %>">
|
|
32
|
+
.lights-grid { grid-template-columns: 1fr; }
|
|
33
|
+
@media (min-width: 800px) {
|
|
34
|
+
.lights-grid { grid-template-columns: repeat(auto-fill, 500px); }
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
37
|
+
<section class="grid gap-4 lights-grid">
|
|
32
38
|
<% lights.each do |light| %>
|
|
33
|
-
<%= erb :_card, locals: { light:, color: light.color } %>
|
|
39
|
+
<%= erb :_card, locals: { light:, color: light.color, system_id: } %>
|
|
34
40
|
<% end %>
|
|
35
41
|
</section>
|
|
36
42
|
<% end %>
|
|
@@ -6,23 +6,33 @@
|
|
|
6
6
|
|
|
7
7
|
<title>Stoplight Admin</title>
|
|
8
8
|
|
|
9
|
-
<
|
|
9
|
+
<link rel="icon" href="<%= asset_path('favicon.ico') %>" sizes="32x32">
|
|
10
|
+
<link rel="icon" href="<%= asset_path('icon.svg') %>" type="image/svg+xml">
|
|
11
|
+
<link rel="apple-touch-icon" href="<%= asset_path('apple-touch-icon.png') %>">
|
|
12
|
+
|
|
13
|
+
<script nonce="<%= nonce %>" type="module" src="<%= asset_path('turbo.es2017-esm.min.js') %>"></script>
|
|
10
14
|
<script nonce="<%= nonce %>" type="module">
|
|
11
15
|
document.addEventListener("turbo:load", () => {
|
|
12
16
|
window.initFlowbite()
|
|
13
17
|
})
|
|
14
18
|
</script>
|
|
15
|
-
<link rel="stylesheet" href="
|
|
19
|
+
<link rel="stylesheet" href="<%= asset_path('flowbite.min.css') %>" />
|
|
16
20
|
</head>
|
|
17
|
-
<body>
|
|
18
|
-
<div class="antialiased bg-gray-50 dark:bg-gray-900
|
|
19
|
-
<div>
|
|
21
|
+
<body class="bg-gray-50 dark:bg-gray-900">
|
|
22
|
+
<div class="antialiased bg-gray-50 dark:bg-gray-900 flex flex-col" style="min-height: 100vh;">
|
|
23
|
+
<div class="grow">
|
|
20
24
|
<nav class="bg-white border-gray-200 dark:bg-gray-900">
|
|
21
|
-
<div class="
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
<div class="flex flex-wrap items-center justify-between p-4">
|
|
26
|
+
<div class="flex items-center space-x-3 rtl:space-x-reverse">
|
|
27
|
+
<a href="<%= url('/') %>" class="flex items-center space-x-3 rtl:space-x-reverse">
|
|
28
|
+
🚦
|
|
29
|
+
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Stoplight Admin</span>
|
|
30
|
+
</a>
|
|
31
|
+
|
|
32
|
+
<% if settings.read_only? %>
|
|
33
|
+
<span class="bg-gray-100 text-gray-600 text-xs font-medium px-2.5 py-0.5 rounded-sm dark:bg-gray-700 dark:text-gray-300" title="This panel cannot change light states">Read-only</span>
|
|
34
|
+
<% end %>
|
|
35
|
+
</div>
|
|
26
36
|
|
|
27
37
|
<button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-default" aria-expanded="false">
|
|
28
38
|
<span class="sr-only">Open main menu</span>
|
|
@@ -33,9 +43,31 @@
|
|
|
33
43
|
|
|
34
44
|
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
|
|
35
45
|
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
|
46
|
+
<% if show_system_switcher? %>
|
|
47
|
+
<li class="relative me-2 mb-2">
|
|
48
|
+
<button id="systemMenuButton" data-dropdown-toggle="systemMenu" type="button"
|
|
49
|
+
class="flex items-center gap-2 py-2.5 px-4 text-sm font-medium text-gray-900 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-100 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-700">
|
|
50
|
+
<%= CGI.escapeHTML(current_system.name) %>
|
|
51
|
+
<svg class="w-2.5 h-2.5 ms-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 6" fill="none">
|
|
52
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
|
53
|
+
</svg>
|
|
54
|
+
</button>
|
|
55
|
+
<div id="systemMenu" class="z-10 hidden w-60 bg-white divide-y divide-gray-100 rounded-lg shadow-lg border border-gray-100 dark:bg-gray-700 dark:divide-gray-600 dark:border-gray-600">
|
|
56
|
+
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="systemMenuButton">
|
|
57
|
+
<% settings.systems.each do |other_system| %>
|
|
58
|
+
<li>
|
|
59
|
+
<a href="<%= system_url(other_system.config.id, "/lights") %>" class="block px-4 py-2 <%= (other_system.config.id == current_system_id) ? "bg-blue-50 text-blue-700 dark:bg-gray-600 dark:text-white" : "hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white" %>">
|
|
60
|
+
<%= CGI.escapeHTML(other_system.name) %>
|
|
61
|
+
</a>
|
|
62
|
+
</li>
|
|
63
|
+
<% end %>
|
|
64
|
+
</ul>
|
|
65
|
+
</div>
|
|
66
|
+
</li>
|
|
67
|
+
<% end %>
|
|
36
68
|
<% if count_red + count_yellow > 0 %>
|
|
37
69
|
<li>
|
|
38
|
-
<a
|
|
70
|
+
<a <%= control_attributes(system_url(system_id, '/lights/lock?color=green'), verb: "patch") %> class="inline-flex items-center font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 border bg-white dark:bg-gray-800 <%= settings.read_only? ? "text-gray-400 border-gray-200 cursor-not-allowed dark:text-gray-500 dark:border-gray-700" : "text-gray-900 border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700" %>">
|
|
39
71
|
<svg class="flex w-4 h-4 me-1.5 text-green-600 shrink-0" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
40
72
|
<circle cx="12" cy="16" r="1"/>
|
|
41
73
|
<rect x="3" y="10" width="18" height="12" rx="2"/>
|
|
@@ -57,12 +89,12 @@
|
|
|
57
89
|
|
|
58
90
|
<footer class="bg-white rounded-lg shadow-sm dark:bg-gray-900 m-4">
|
|
59
91
|
<div class="w-full max-w-screen-xl mx-auto p-4 md:py-8">
|
|
60
|
-
<p class="block text-sm text-gray-500 sm:text-center dark:text-gray-400 mb-2">All of the Lights
|
|
92
|
+
<p class="block text-sm text-gray-500 sm:text-center dark:text-gray-400 mb-2">All of the Lights — cop lights, flashlights, spotlights, strobe lights…</p>
|
|
61
93
|
<p class="block text-sm text-gray-500 sm:text-center dark:text-gray-400">💡 🔦 🚨 🚥 💫</p>
|
|
62
94
|
</div>
|
|
63
95
|
</footer>
|
|
64
96
|
</div>
|
|
65
97
|
|
|
66
|
-
<script nonce="<%= nonce %>" src="
|
|
98
|
+
<script nonce="<%= nonce %>" src="<%= asset_path('flowbite.min.js') %>"></script>
|
|
67
99
|
</body>
|
|
68
100
|
</html>
|
data/lib/stoplight/admin.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "cgi/escape"
|
|
4
4
|
require "cgi/util" if RUBY_VERSION < "3.5"
|
|
5
|
+
require "digest"
|
|
5
6
|
|
|
6
7
|
begin
|
|
7
8
|
require "sinatra/base"
|
|
@@ -16,61 +17,149 @@ rescue LoadError
|
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
module Stoplight
|
|
20
|
+
# A Sinatra dashboard listing every light, its color, and its recent failures, with
|
|
21
|
+
# controls to lock a light green or red, unlock it, or remove it.
|
|
22
|
+
#
|
|
23
|
+
# Setting +read_only+ deploys it as an observation-only dashboard: every light stays
|
|
24
|
+
# visible, and every control that would change one is refused with 403 rather than merely
|
|
25
|
+
# hidden, so calling the endpoints directly does not get around it. The standalone Docker
|
|
26
|
+
# image reads the same option from +STOPLIGHT_ADMIN_READ_ONLY+, for the exact string +true+.
|
|
27
|
+
#
|
|
28
|
+
# This is not access control - it has no notion of users, and anyone who reaches the panel
|
|
29
|
+
# still reads every light name and failure message. Keep it behind authentication either way.
|
|
30
|
+
#
|
|
31
|
+
# @example Observing without being able to change anything
|
|
32
|
+
# Stoplightt::Admin.conrfigure do |config|
|
|
33
|
+
# config.read_only = true
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# mount Stoplight::Admin => "/stoplights"
|
|
37
|
+
#
|
|
38
|
+
# @example configue multiple systems
|
|
39
|
+
# Core = Stoplight.register_system("Core", data_store:)
|
|
40
|
+
# Analytics = Stoplight.register_system("Analytics", data_store:)
|
|
41
|
+
#
|
|
42
|
+
# Stoplight::Admin.configure do |config|
|
|
43
|
+
# config.add_system Core
|
|
44
|
+
# config.add_system Analytics
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
19
47
|
class Admin < Sinatra::Base
|
|
20
48
|
COLORS = [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
49
|
+
Color::GREEN,
|
|
50
|
+
Color::YELLOW,
|
|
51
|
+
Color::RED
|
|
24
52
|
].freeze
|
|
25
53
|
private_constant :COLORS
|
|
26
54
|
|
|
55
|
+
ASSETS_PATH = File.join(T.must(__dir__), "admin", "assets")
|
|
56
|
+
ASSET_DIGESTS = Dir.children(ASSETS_PATH).to_h do |name|
|
|
57
|
+
[name, T.must(Digest::SHA256.file(File.join(ASSETS_PATH, name)).hexdigest[0, 8])]
|
|
58
|
+
end.freeze
|
|
59
|
+
|
|
60
|
+
ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365
|
|
61
|
+
private_constant :ONE_YEAR_IN_SECONDS
|
|
62
|
+
|
|
27
63
|
helpers Helpers
|
|
64
|
+
@systems = []
|
|
65
|
+
|
|
66
|
+
def self.add_system(system)
|
|
67
|
+
@systems << validate_persistent_system!(system)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.__stoplight__reset_systems!
|
|
71
|
+
@systems = []
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.validate_persistent_system!(system)
|
|
75
|
+
return system if system.persistent?
|
|
76
|
+
|
|
77
|
+
raise TypeError, "Stoplight Admin requires a persistent data store, but the current data store is not persistent. " \
|
|
78
|
+
"Please configure a different data store in your Stoplight configuration."
|
|
79
|
+
end
|
|
80
|
+
private_class_method :validate_persistent_system!
|
|
81
|
+
|
|
82
|
+
set :systems do
|
|
83
|
+
if @systems.empty?
|
|
84
|
+
[validate_persistent_system!(Stoplight.__stoplight__default_system)]
|
|
85
|
+
else
|
|
86
|
+
@systems
|
|
87
|
+
end
|
|
88
|
+
end
|
|
28
89
|
|
|
29
90
|
set :protection, except: %i[json_csrf]
|
|
91
|
+
set :read_only, false
|
|
30
92
|
set :data_store, proc { Stoplight.__stoplight__default_configuration.data_store }
|
|
31
|
-
set :views, File.join(__dir__, "admin", "views")
|
|
93
|
+
set :views, File.join(T.must(__dir__), "admin", "views")
|
|
32
94
|
set :nonce, proc { |request| }
|
|
95
|
+
set :public_folder, ASSETS_PATH
|
|
96
|
+
set :static_cache_control, [:public, max_age: ONE_YEAR_IN_SECONDS, immutable: true]
|
|
97
|
+
|
|
98
|
+
# Gates on the request method rather than on a list of paths, so a write route added
|
|
99
|
+
# later is refused without anyone having to remember this filter exists.
|
|
100
|
+
before do
|
|
101
|
+
if settings.read_only? && !request.get? && !request.head?
|
|
102
|
+
halt 403, "Stoplight Admin is running in read-only mode."
|
|
103
|
+
end
|
|
104
|
+
end
|
|
33
105
|
|
|
34
106
|
get "/" do
|
|
107
|
+
system = settings.systems.first
|
|
108
|
+
|
|
109
|
+
redirect system_url(system.config.id, "/lights")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
get "/systems/:system_id/lights" do
|
|
35
113
|
lights, stats = dependencies.stats_action.call
|
|
36
114
|
|
|
37
|
-
erb :index, locals: stats.merge(
|
|
115
|
+
erb :index, locals: stats.merge(
|
|
116
|
+
lights: lights,
|
|
117
|
+
nonce: settings.nonce(request),
|
|
118
|
+
system_id: current_system_id
|
|
119
|
+
)
|
|
38
120
|
end
|
|
39
121
|
|
|
122
|
+
# Always reports the first configured system, regardless of which system is "current" -
|
|
123
|
+
# for external monitors that already poll this unscoped path.
|
|
40
124
|
get "/stats" do
|
|
41
|
-
lights, stats =
|
|
125
|
+
lights, stats = Dependencies.new(system: settings.systems.first).stats_action.call
|
|
42
126
|
|
|
43
127
|
json({stats: stats, lights: lights.map(&:as_json)})
|
|
44
128
|
end
|
|
45
129
|
|
|
46
|
-
|
|
47
|
-
dependencies.
|
|
130
|
+
get "/systems/:system_id/lights.json" do
|
|
131
|
+
lights, stats = dependencies.stats_action.call
|
|
48
132
|
|
|
49
|
-
|
|
133
|
+
json({stats: stats, lights: lights.map(&:as_json)})
|
|
50
134
|
end
|
|
51
135
|
|
|
52
|
-
|
|
53
|
-
|
|
136
|
+
patch "/systems/:system_id/lights/:light_id/unlock" do
|
|
137
|
+
light_id = T.must(params[:light_id])
|
|
138
|
+
dependencies.unlock_action.call(light_id:)
|
|
54
139
|
|
|
55
|
-
redirect
|
|
140
|
+
redirect system_url(current_system_id, "/lights")
|
|
56
141
|
end
|
|
57
142
|
|
|
58
|
-
|
|
59
|
-
|
|
143
|
+
patch "/systems/:system_id/lights/:light_id/lock" do
|
|
144
|
+
light_id = T.must(params[:light_id])
|
|
145
|
+
color = T.must(params[:color])
|
|
146
|
+
dependencies.lock_action.call(light_id:, color:)
|
|
60
147
|
|
|
61
|
-
redirect
|
|
148
|
+
redirect system_url(current_system_id, "/lights")
|
|
62
149
|
end
|
|
63
150
|
|
|
64
|
-
|
|
65
|
-
|
|
151
|
+
patch "/systems/:system_id/lights/lock" do
|
|
152
|
+
color = T.must(params[:color])
|
|
153
|
+
dependencies.lock_all_action.call(color:)
|
|
66
154
|
|
|
67
|
-
redirect
|
|
155
|
+
redirect system_url(current_system_id, "/lights")
|
|
68
156
|
end
|
|
69
157
|
|
|
70
|
-
|
|
71
|
-
|
|
158
|
+
delete "/systems/:system_id/lights/:light_id" do
|
|
159
|
+
light_id = T.must(params[:light_id])
|
|
160
|
+
dependencies.remove_action.call(light_id:)
|
|
72
161
|
|
|
73
|
-
redirect
|
|
162
|
+
redirect system_url(current_system_id, "/lights")
|
|
74
163
|
end
|
|
75
164
|
end
|
|
76
165
|
end
|