karafka-web 0.10.4 → 0.11.0
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 +62 -176
- data/Gemfile +4 -0
- data/Gemfile.lock +88 -44
- data/LICENSE +6 -2
- data/Rakefile +4 -0
- data/bin/verify_kafka_warnings +35 -0
- data/bin/verify_topics_naming +35 -0
- data/config/locales/pro_errors.yml +1 -0
- data/config/locales/slogans.yml +1 -1
- data/docker-compose.yml +1 -1
- data/gulpfile.js +0 -2
- data/karafka-web.gemspec +2 -7
- data/lib/karafka/web/config.rb +80 -9
- data/lib/karafka/web/contracts/config.rb +44 -5
- data/lib/karafka/web/errors.rb +10 -12
- data/lib/karafka/web/management/actions/create_initial_states.rb +6 -6
- data/lib/karafka/web/management/actions/create_topics.rb +30 -64
- data/lib/karafka/web/management/actions/delete_topics.rb +5 -5
- data/lib/karafka/web/management/actions/enable.rb +5 -5
- data/lib/karafka/web/pro/commanding/commands/base.rb +37 -13
- data/lib/karafka/web/pro/commanding/commands/consumers/quiet.rb +33 -0
- data/lib/karafka/web/pro/commanding/commands/consumers/stop.rb +32 -0
- data/lib/karafka/web/pro/commanding/commands/consumers/trace.rb +37 -0
- data/lib/karafka/web/pro/commanding/commands/partitions/pause.rb +30 -0
- data/lib/karafka/web/pro/commanding/commands/partitions/resume.rb +30 -0
- data/lib/karafka/web/pro/commanding/commands/partitions/seek.rb +30 -0
- data/lib/karafka/web/pro/commanding/config.rb +6 -10
- data/lib/karafka/web/pro/commanding/contracts/config.rb +2 -10
- data/lib/karafka/web/pro/commanding/dispatcher.rb +45 -24
- data/lib/karafka/web/pro/commanding/handlers/partitions/commands/base.rb +67 -0
- data/lib/karafka/web/pro/commanding/handlers/partitions/commands/pause.rb +44 -0
- data/lib/karafka/web/pro/commanding/handlers/partitions/commands/resume.rb +29 -0
- data/lib/karafka/web/pro/commanding/handlers/partitions/commands/seek.rb +86 -0
- data/lib/karafka/web/pro/commanding/handlers/partitions/executor.rb +56 -0
- data/lib/karafka/web/pro/commanding/handlers/partitions/listener.rb +55 -0
- data/lib/karafka/web/pro/commanding/handlers/partitions/tracker.rb +62 -0
- data/lib/karafka/web/pro/commanding/listener.rb +4 -12
- data/lib/karafka/web/pro/commanding/manager.rb +36 -24
- data/lib/karafka/web/pro/commanding/matcher.rb +7 -17
- data/lib/karafka/web/pro/commanding/request.rb +39 -0
- data/lib/karafka/web/pro/commanding.rb +2 -10
- data/lib/karafka/web/pro/loader.rb +13 -10
- data/lib/karafka/web/pro/ui/app.rb +31 -390
- data/lib/karafka/web/pro/ui/controllers/base_controller.rb +8 -10
- data/lib/karafka/web/pro/ui/controllers/cluster_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/consumers/base_controller.rb +21 -0
- data/lib/karafka/web/pro/ui/controllers/consumers/commanding_controller.rb +148 -0
- data/lib/karafka/web/pro/ui/controllers/consumers/commands_controller.rb +96 -0
- data/lib/karafka/web/pro/ui/controllers/consumers/consumers_controller.rb +99 -0
- data/lib/karafka/web/pro/ui/controllers/consumers/controls_controller.rb +36 -0
- data/lib/karafka/web/pro/ui/controllers/consumers/jobs_controller.rb +57 -0
- data/lib/karafka/web/pro/ui/controllers/consumers/partitions/base_controller.rb +86 -0
- data/lib/karafka/web/pro/ui/controllers/consumers/partitions/offsets_controller.rb +75 -0
- data/lib/karafka/web/pro/ui/controllers/consumers/partitions/pauses_controller.rb +110 -0
- data/lib/karafka/web/pro/ui/controllers/dashboard_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/dlq_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/errors_controller.rb +11 -15
- data/lib/karafka/web/pro/ui/controllers/explorer/base_controller.rb +21 -0
- data/lib/karafka/web/pro/ui/controllers/explorer/explorer_controller.rb +225 -0
- data/lib/karafka/web/pro/ui/controllers/explorer/messages_controller.rb +145 -0
- data/lib/karafka/web/pro/ui/controllers/explorer/search_controller.rb +68 -0
- data/lib/karafka/web/pro/ui/controllers/health_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/jobs_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/recurring_tasks_controller.rb +12 -13
- data/lib/karafka/web/pro/ui/controllers/routing_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/scheduled_messages/base_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/scheduled_messages/explorer_controller.rb +8 -16
- data/lib/karafka/web/pro/ui/controllers/scheduled_messages/messages_controller.rb +9 -15
- data/lib/karafka/web/pro/ui/controllers/scheduled_messages/schedules_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/status_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/support_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/controllers/topics/base_controller.rb +21 -0
- data/lib/karafka/web/pro/ui/controllers/topics/configs_controller.rb +86 -0
- data/lib/karafka/web/pro/ui/controllers/topics/distributions_controller.rb +91 -0
- data/lib/karafka/web/pro/ui/controllers/topics/offsets_controller.rb +55 -0
- data/lib/karafka/web/pro/ui/controllers/topics/replications_controller.rb +37 -0
- data/lib/karafka/web/pro/ui/controllers/topics/topics_controller.rb +101 -0
- data/lib/karafka/web/pro/ui/controllers/ux_controller.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/branding/config.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/branding/contracts/config.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/branding.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/features.rb +53 -0
- data/lib/karafka/web/pro/ui/lib/patterns_detector.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/policies/config.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/policies/contracts/config.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/policies/messages.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/policies/requests.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/policies.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/safe_runner.rb +5 -0
- data/lib/karafka/web/pro/ui/lib/search/config.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/search/contracts/config.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/search/contracts/form.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/search/matchers/base.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/search/matchers/raw_header_includes.rb +10 -11
- data/lib/karafka/web/pro/ui/lib/search/matchers/raw_key_includes.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/search/matchers/raw_payload_includes.rb +23 -11
- data/lib/karafka/web/pro/ui/lib/search/normalizer.rb +2 -10
- data/lib/karafka/web/pro/ui/lib/search/runner.rb +3 -11
- data/lib/karafka/web/pro/ui/lib/search.rb +2 -10
- data/lib/karafka/web/pro/ui/routes/base.rb +19 -0
- data/lib/karafka/web/pro/ui/routes/cluster.rb +37 -0
- data/lib/karafka/web/pro/ui/routes/consumers.rb +145 -0
- data/lib/karafka/web/pro/ui/routes/dashboard.rb +25 -0
- data/lib/karafka/web/pro/ui/routes/dlq.rb +24 -0
- data/lib/karafka/web/pro/ui/routes/errors.rb +39 -0
- data/lib/karafka/web/pro/ui/routes/explorer.rb +118 -0
- data/lib/karafka/web/pro/ui/routes/health.rb +47 -0
- data/lib/karafka/web/pro/ui/routes/jobs.rb +33 -0
- data/lib/karafka/web/pro/ui/routes/recurring_tasks.rb +59 -0
- data/lib/karafka/web/pro/ui/routes/routing.rb +31 -0
- data/lib/karafka/web/pro/ui/routes/scheduled_messages.rb +75 -0
- data/lib/karafka/web/pro/ui/routes/status.rb +24 -0
- data/lib/karafka/web/pro/ui/routes/support.rb +24 -0
- data/lib/karafka/web/pro/ui/routes/topics.rb +90 -0
- data/lib/karafka/web/pro/ui/routes/ux.rb +24 -0
- data/lib/karafka/web/pro/ui/views/cluster/_breadcrumbs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/cluster/_broker.erb +3 -0
- data/lib/karafka/web/pro/ui/views/cluster/_config.erb +3 -0
- data/lib/karafka/web/pro/ui/views/cluster/_tabs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/cluster/index.erb +4 -1
- data/lib/karafka/web/pro/ui/views/cluster/show.erb +3 -0
- data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_backtrace.erb +3 -0
- data/lib/karafka/web/pro/ui/views/consumers/commands/_breadcrumbs.erb +24 -0
- data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_command.erb +22 -6
- data/lib/karafka/web/pro/ui/views/consumers/commands/_command_details.erb +4 -0
- data/lib/karafka/web/pro/ui/views/consumers/commands/_empty.erb +6 -0
- data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_incompatible_schema.erb +3 -0
- data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_metadata.erb +4 -1
- data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/_table.erb +5 -2
- data/lib/karafka/web/pro/ui/views/{commands → consumers/commands}/index.erb +7 -4
- data/lib/karafka/web/pro/ui/views/consumers/commands/show.erb +32 -0
- data/lib/karafka/web/pro/ui/views/consumers/consumers/_breadcrumbs.erb +46 -0
- data/lib/karafka/web/pro/ui/views/consumers/{_consumer.erb → consumers/_consumer.erb} +4 -1
- data/lib/karafka/web/pro/ui/views/consumers/{_consumer_performance.erb → consumers/_consumer_performance.erb} +4 -1
- data/lib/karafka/web/pro/ui/views/consumers/consumers/_tabs.erb +38 -0
- data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_commands.erb +80 -0
- data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_consumer_group.erb +11 -0
- data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_metrics.erb +3 -0
- data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_no_subscriptions.erb +10 -0
- data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_partition.erb +16 -0
- data/lib/karafka/web/pro/ui/views/consumers/consumers/consumer/_partition_edit_options.erb +33 -0
- data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_stopped.erb +3 -0
- data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_subscription_group.erb +7 -3
- data/lib/karafka/web/pro/ui/views/consumers/{consumer → consumers/consumer}/_tabs.erb +7 -4
- data/lib/karafka/web/pro/ui/views/consumers/consumers/details.erb +15 -0
- data/lib/karafka/web/pro/ui/views/consumers/{index.erb → consumers/index.erb} +6 -3
- data/lib/karafka/web/pro/ui/views/consumers/{performance.erb → consumers/performance.erb} +6 -3
- data/lib/karafka/web/pro/ui/views/consumers/consumers/subscriptions.erb +24 -0
- data/lib/karafka/web/pro/ui/views/consumers/controls/_breadcrumbs.erb +16 -0
- data/lib/karafka/web/pro/ui/views/consumers/{_consumer_controls.erb → controls/_controls.erb} +10 -7
- data/lib/karafka/web/pro/ui/views/consumers/{controls.erb → controls/index.erb} +8 -5
- data/lib/karafka/web/pro/ui/views/consumers/jobs/_breadcrumbs.erb +36 -0
- data/lib/karafka/web/pro/ui/views/consumers/{consumer → jobs}/_job.erb +4 -2
- data/lib/karafka/web/pro/ui/views/consumers/jobs/_no_jobs.erb +6 -0
- data/lib/karafka/web/pro/ui/views/consumers/{pending_jobs.erb → jobs/pending.erb} +7 -8
- data/lib/karafka/web/pro/ui/views/consumers/{running_jobs.erb → jobs/running.erb} +7 -8
- data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_basics.erb +77 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_breadcrumbs.erb +58 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_form.erb +109 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_not_running_error.erb +16 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/_running_warning.erb +15 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/offsets/edit.erb +16 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_active_not_editable.erb +22 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_adjusting_warning.erb +27 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_breadcrumbs.erb +60 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_edit_form.erb +59 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_lrj_not_manageable.erb +24 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_new_form.erb +78 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/_not_running.erb +16 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/edit.erb +24 -0
- data/lib/karafka/web/pro/ui/views/consumers/partitions/pauses/new.erb +20 -0
- data/lib/karafka/web/pro/ui/views/dashboard/index.erb +4 -1
- data/lib/karafka/web/pro/ui/views/dlq/_breadcrumbs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/dlq/_no_topics.erb +3 -0
- data/lib/karafka/web/pro/ui/views/dlq/_topic.erb +4 -1
- data/lib/karafka/web/pro/ui/views/dlq/index.erb +3 -0
- data/lib/karafka/web/pro/ui/views/errors/_breadcrumbs.erb +4 -6
- data/lib/karafka/web/pro/ui/views/errors/_error.erb +9 -1
- data/lib/karafka/web/pro/ui/views/errors/_partition_option.erb +3 -0
- data/lib/karafka/web/pro/ui/views/errors/_selector.erb +3 -0
- data/lib/karafka/web/pro/ui/views/errors/_table.erb +4 -1
- data/lib/karafka/web/pro/ui/views/errors/index.erb +6 -3
- data/lib/karafka/web/pro/ui/views/errors/partition.erb +5 -2
- data/lib/karafka/web/pro/ui/views/errors/show.erb +42 -33
- data/lib/karafka/web/pro/ui/views/explorer/{_breadcrumbs.erb → explorer/_breadcrumbs.erb} +7 -4
- data/lib/karafka/web/pro/ui/views/explorer/{_failed_deserialization.erb → explorer/_failed_deserialization.erb} +3 -0
- data/lib/karafka/web/pro/ui/views/explorer/{_filtered.erb → explorer/_filtered.erb} +3 -0
- data/lib/karafka/web/pro/ui/views/explorer/{_message.erb → explorer/_message.erb} +4 -1
- data/lib/karafka/web/pro/ui/views/explorer/explorer/_no_topics.erb +4 -0
- data/lib/karafka/web/pro/ui/views/explorer/{_partition_option.erb → explorer/_partition_option.erb} +4 -1
- data/lib/karafka/web/pro/ui/views/explorer/{_selector.erb → explorer/_selector.erb} +4 -1
- data/lib/karafka/web/pro/ui/views/explorer/explorer/_topic.erb +13 -0
- data/lib/karafka/web/pro/ui/views/explorer/explorer/index.erb +17 -0
- data/lib/karafka/web/pro/ui/views/explorer/{message → explorer/message}/_metadata.erb +10 -7
- data/lib/karafka/web/pro/ui/views/explorer/{message → explorer/message}/_payload.erb +6 -3
- data/lib/karafka/web/pro/ui/views/explorer/{message → explorer/message}/_resources_utilization.erb +7 -4
- data/lib/karafka/web/pro/ui/views/explorer/{message → explorer/message}/_too_big_to_be_displayed.erb +3 -0
- data/lib/karafka/web/pro/ui/views/explorer/{messages → explorer/messages}/_detail.erb +3 -0
- data/lib/karafka/web/pro/ui/views/explorer/explorer/messages/_headers.erb +51 -0
- data/lib/karafka/web/pro/ui/views/explorer/{messages → explorer/messages}/_key.erb +3 -0
- data/lib/karafka/web/pro/ui/views/explorer/explorer/partition/_cleaned.erb +6 -0
- data/lib/karafka/web/pro/ui/views/explorer/explorer/partition/_empty.erb +6 -0
- data/lib/karafka/web/pro/ui/views/explorer/{partition → explorer/partition}/_messages.erb +4 -1
- data/lib/karafka/web/pro/ui/views/explorer/explorer/partition/_time_selector.erb +16 -0
- data/lib/karafka/web/pro/ui/views/explorer/explorer/partition/_timestamp_selector.erb +33 -0
- data/lib/karafka/web/pro/ui/views/explorer/{partition.erb → explorer/partition.erb} +24 -17
- data/lib/karafka/web/pro/ui/views/explorer/explorer/show.erb +100 -0
- data/lib/karafka/web/pro/ui/views/explorer/{topic → explorer/topic}/_actions.erb +5 -2
- data/lib/karafka/web/pro/ui/views/explorer/explorer/topic/_empty.erb +6 -0
- data/lib/karafka/web/pro/ui/views/explorer/{topic → explorer/topic}/_limited.erb +3 -0
- data/lib/karafka/web/pro/ui/views/explorer/{topic.erb → explorer/topic.erb} +7 -4
- data/lib/karafka/web/pro/ui/views/explorer/messages/_breadcrumbs.erb +32 -0
- data/lib/karafka/web/pro/ui/views/explorer/messages/forward.erb +143 -0
- data/lib/karafka/web/pro/ui/views/explorer/search/_breadcrumbs.erb +4 -0
- data/lib/karafka/web/pro/ui/views/explorer/search/_fix_errors.erb +6 -0
- data/lib/karafka/web/pro/ui/views/{search → explorer/search}/_metadata.erb +3 -0
- data/lib/karafka/web/pro/ui/views/explorer/search/_no_results.erb +6 -0
- data/lib/karafka/web/pro/ui/views/{search → explorer/search}/_no_search_criteria.erb +3 -0
- data/lib/karafka/web/pro/ui/views/{search → explorer/search}/_search_criteria.erb +3 -0
- data/lib/karafka/web/pro/ui/views/{search → explorer/search}/_search_modal.erb +5 -2
- data/lib/karafka/web/pro/ui/views/explorer/search/_timeout.erb +6 -0
- data/lib/karafka/web/pro/ui/views/explorer/search/index.erb +32 -0
- data/lib/karafka/web/pro/ui/views/health/_breadcrumbs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/health/_no_data.erb +3 -0
- data/lib/karafka/web/pro/ui/views/health/_partition.erb +16 -1
- data/lib/karafka/web/pro/ui/views/health/_partition_lags.erb +3 -0
- data/lib/karafka/web/pro/ui/views/health/_partition_offset.erb +3 -0
- data/lib/karafka/web/pro/ui/views/health/_partition_times.erb +3 -0
- data/lib/karafka/web/pro/ui/views/health/_table_metadata.erb +4 -1
- data/lib/karafka/web/pro/ui/views/health/_tabs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/health/changes.erb +4 -1
- data/lib/karafka/web/pro/ui/views/health/cluster_lags.erb +3 -0
- data/lib/karafka/web/pro/ui/views/health/lags.erb +5 -2
- data/lib/karafka/web/pro/ui/views/health/offsets.erb +4 -1
- data/lib/karafka/web/pro/ui/views/health/overview.erb +8 -3
- data/lib/karafka/web/pro/ui/views/jobs/_job.erb +5 -3
- data/lib/karafka/web/pro/ui/views/jobs/_no_jobs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/jobs/pending.erb +4 -1
- data/lib/karafka/web/pro/ui/views/jobs/running.erb +4 -1
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_actions.erb +3 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_batch_actions.erb +3 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_breadcrumbs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_log.erb +3 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_not_active.erb +3 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_tabs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/_task.erb +3 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/logs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/recurring_tasks/schedule.erb +3 -0
- data/lib/karafka/web/pro/ui/views/routing/_consumer_group.erb +3 -0
- data/lib/karafka/web/pro/ui/views/routing/_detail.erb +3 -0
- data/lib/karafka/web/pro/ui/views/routing/_topic.erb +3 -0
- data/lib/karafka/web/pro/ui/views/routing/index.erb +3 -0
- data/lib/karafka/web/pro/ui/views/routing/show.erb +3 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_breadcrumbs.erb +6 -3
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_key.erb +3 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_message.erb +28 -115
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/_messages.erb +3 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_cancel.erb +49 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_compacted.erb +16 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_schedule.erb +83 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_tombstone.erb +69 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/message/_unknown.erb +26 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/partition.erb +23 -16
- data/lib/karafka/web/pro/ui/views/scheduled_messages/explorer/topic.erb +6 -3
- data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/_breadcrumbs.erb +3 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/_no_groups.erb +3 -0
- data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/index.erb +4 -1
- data/lib/karafka/web/pro/ui/views/scheduled_messages/schedules/show.erb +17 -1
- data/lib/karafka/web/pro/ui/views/shared/_navigation.erb +25 -17
- data/lib/karafka/web/pro/ui/views/shared/_rdkafka_form_error_alert_box.erb +16 -0
- data/lib/karafka/web/pro/ui/views/shared/branding/_label.erb +3 -0
- data/lib/karafka/web/pro/ui/views/shared/branding/_notice.erb +3 -0
- data/lib/karafka/web/pro/ui/views/topics/configs/_breadcrumbs.erb +34 -0
- data/lib/karafka/web/pro/ui/views/topics/configs/_config.erb +26 -0
- data/lib/karafka/web/pro/ui/views/topics/configs/_delete_button.erb +13 -0
- data/lib/karafka/web/pro/ui/views/topics/configs/_edit_form.erb +50 -0
- data/lib/karafka/web/pro/ui/views/topics/configs/_edit_plan.erb +16 -0
- data/lib/karafka/web/pro/ui/views/topics/configs/_edit_warning.erb +12 -0
- data/lib/karafka/web/pro/ui/views/topics/configs/edit.erb +16 -0
- data/lib/karafka/web/pro/ui/views/topics/{config.erb → configs/index.erb} +9 -3
- data/lib/karafka/web/pro/ui/views/topics/distributions/_add_partitions_button.erb +13 -0
- data/lib/karafka/web/pro/ui/views/topics/{distribution → distributions}/_badges.erb +3 -0
- data/lib/karafka/web/pro/ui/views/topics/distributions/_breadcrumbs.erb +28 -0
- data/lib/karafka/web/pro/ui/views/topics/{distribution → distributions}/_chart.erb +3 -0
- data/lib/karafka/web/pro/ui/views/topics/distributions/_edit_form.erb +47 -0
- data/lib/karafka/web/pro/ui/views/topics/distributions/_edit_hints.erb +15 -0
- data/lib/karafka/web/pro/ui/views/topics/distributions/_edit_warnings.erb +14 -0
- data/lib/karafka/web/pro/ui/views/topics/distributions/_empty_partitions.erb +4 -0
- data/lib/karafka/web/pro/ui/views/topics/{distribution → distributions}/_limited.erb +3 -0
- data/lib/karafka/web/pro/ui/views/topics/distributions/_partition.erb +13 -0
- data/lib/karafka/web/pro/ui/views/topics/distributions/edit.erb +16 -0
- data/lib/karafka/web/pro/ui/views/topics/{distribution.erb → distributions/show.erb} +11 -7
- data/lib/karafka/web/pro/ui/views/topics/offsets/_breadcrumbs.erb +20 -0
- data/lib/karafka/web/pro/ui/views/topics/offsets/_partition.erb +13 -0
- data/lib/karafka/web/pro/ui/views/topics/{offsets.erb → offsets/show.erb} +6 -3
- data/lib/karafka/web/pro/ui/views/topics/replications/_breadcrumbs.erb +20 -0
- data/lib/karafka/web/pro/ui/views/topics/{_partition.erb → replications/_partition.erb} +4 -1
- data/lib/karafka/web/pro/ui/views/topics/{replication.erb → replications/show.erb} +6 -3
- data/lib/karafka/web/pro/ui/views/topics/topics/_breadcrumbs.erb +32 -0
- data/lib/karafka/web/pro/ui/views/topics/topics/_create_button.erb +13 -0
- data/lib/karafka/web/pro/ui/views/topics/topics/_create_hints.erb +15 -0
- data/lib/karafka/web/pro/ui/views/topics/topics/_delete_form.erb +36 -0
- data/lib/karafka/web/pro/ui/views/topics/topics/_delete_hints.erb +15 -0
- data/lib/karafka/web/pro/ui/views/topics/topics/_delete_warning.erb +13 -0
- data/lib/karafka/web/pro/ui/views/topics/topics/_new_form.erb +80 -0
- data/lib/karafka/web/pro/ui/views/topics/{_tabs.erb → topics/_tabs.erb} +7 -4
- data/lib/karafka/web/pro/ui/views/topics/topics/_topic.erb +12 -0
- data/lib/karafka/web/pro/ui/views/topics/topics/edit.erb +10 -0
- data/lib/karafka/web/pro/ui/views/topics/topics/index.erb +19 -0
- data/lib/karafka/web/pro/ui/views/topics/topics/new.erb +12 -0
- data/lib/karafka/web/processing/consumer.rb +7 -7
- data/lib/karafka/web/processing/consumers/aggregators/state.rb +14 -14
- data/lib/karafka/web/processing/consumers/metrics.rb +1 -1
- data/lib/karafka/web/processing/consumers/state.rb +1 -1
- data/lib/karafka/web/processing/publisher.rb +4 -4
- data/lib/karafka/web/tracking/consumers/contracts/partition.rb +1 -0
- data/lib/karafka/web/tracking/consumers/listeners/errors.rb +1 -0
- data/lib/karafka/web/tracking/consumers/listeners/pausing.rb +2 -2
- data/lib/karafka/web/tracking/consumers/listeners/transactions.rb +44 -0
- data/lib/karafka/web/tracking/consumers/reporter.rb +2 -2
- data/lib/karafka/web/tracking/consumers/sampler.rb +81 -14
- data/lib/karafka/web/tracking/helpers/sysconf.rb +33 -0
- data/lib/karafka/web/tracking/producers/reporter.rb +1 -1
- data/lib/karafka/web/ui/app.rb +19 -112
- data/lib/karafka/web/ui/base.rb +60 -3
- data/lib/karafka/web/ui/controllers/base_controller.rb +43 -1
- data/lib/karafka/web/ui/controllers/cluster_controller.rb +5 -2
- data/lib/karafka/web/ui/controllers/errors_controller.rb +13 -4
- data/lib/karafka/web/ui/controllers/requests/execution_wrapper.rb +52 -0
- data/lib/karafka/web/ui/controllers/requests/hookable.rb +99 -0
- data/lib/karafka/web/ui/controllers/requests/params.rb +39 -1
- data/lib/karafka/web/ui/controllers/responses/redirect.rb +0 -5
- data/lib/karafka/web/ui/controllers/status_controller.rb +3 -0
- data/lib/karafka/web/ui/helpers/application_helper.rb +10 -71
- data/lib/karafka/web/ui/helpers/paths_helper.rb +54 -10
- data/lib/karafka/web/ui/helpers/time_helper.rb +82 -0
- data/lib/karafka/web/ui/helpers/topics_helper.rb +156 -0
- data/lib/karafka/web/ui/lib/admin.rb +1 -1
- data/lib/karafka/web/ui/lib/cache.rb +135 -0
- data/lib/karafka/web/ui/models/broker.rb +1 -2
- data/lib/karafka/web/ui/models/cluster_info.rb +15 -21
- data/lib/karafka/web/ui/models/consumers_metrics.rb +1 -1
- data/lib/karafka/web/ui/models/consumers_state.rb +1 -1
- data/lib/karafka/web/ui/models/counters.rb +1 -1
- data/lib/karafka/web/ui/models/health.rb +9 -7
- data/lib/karafka/web/ui/models/message.rb +20 -2
- data/lib/karafka/web/ui/models/process.rb +16 -0
- data/lib/karafka/web/ui/models/processes.rb +29 -8
- data/lib/karafka/web/ui/models/recurring_tasks/schedule.rb +1 -1
- data/lib/karafka/web/ui/models/status.rb +28 -9
- data/lib/karafka/web/ui/models/topic.rb +1 -2
- data/lib/karafka/web/ui/public/javascripts/application.js +8 -98
- data/lib/karafka/web/ui/public/javascripts/application.min.js +12 -4
- data/lib/karafka/web/ui/public/javascripts/application.min.js.br +0 -0
- data/lib/karafka/web/ui/public/javascripts/application.min.js.gz +0 -0
- data/lib/karafka/web/ui/public/javascripts/components/action_confirmation_manager.js +30 -0
- data/lib/karafka/web/ui/public/javascripts/components/alerts.js +39 -0
- data/lib/karafka/web/ui/public/javascripts/components/button_lock_manager.js +50 -0
- data/lib/karafka/web/ui/public/javascripts/components/live_poll.js +71 -19
- data/lib/karafka/web/ui/public/javascripts/components/message_republish_manager.js +50 -0
- data/lib/karafka/web/ui/public/javascripts/components/page_title_tracker.js +21 -0
- data/lib/karafka/web/ui/public/javascripts/components/partition_redirect_manager.js +21 -0
- data/lib/karafka/web/ui/public/javascripts/components/time_ago_manager.js +25 -0
- data/lib/karafka/web/ui/public/javascripts/components/timestamp_selector.js +30 -0
- data/lib/karafka/web/ui/public/javascripts/libs/datepicker.js +2 -2
- data/lib/karafka/web/ui/public/stylesheets/application.css +30 -0
- data/lib/karafka/web/ui/public/stylesheets/application.min.css +5110 -13
- data/lib/karafka/web/ui/public/stylesheets/application.min.css.br +0 -0
- data/lib/karafka/web/ui/public/stylesheets/application.min.css.gz +0 -0
- data/lib/karafka/web/ui/public/stylesheets/libs/highlight_dark.min.css.gz +0 -0
- data/lib/karafka/web/ui/public/stylesheets/libs/highlight_light.min.css.gz +0 -0
- data/lib/karafka/web/ui/public/stylesheets/libs/tailwind.css +507 -214
- data/lib/karafka/web/ui/routes/assets.rb +53 -0
- data/lib/karafka/web/ui/routes/base.rb +36 -0
- data/lib/karafka/web/ui/routes/cluster.rb +28 -0
- data/lib/karafka/web/ui/routes/consumers.rb +35 -0
- data/lib/karafka/web/ui/routes/dashboard.rb +20 -0
- data/lib/karafka/web/ui/routes/errors.rb +30 -0
- data/lib/karafka/web/ui/routes/jobs.rb +28 -0
- data/lib/karafka/web/ui/routes/pro_only.rb +27 -0
- data/lib/karafka/web/ui/routes/routing.rb +26 -0
- data/lib/karafka/web/ui/routes/status.rb +19 -0
- data/lib/karafka/web/ui/routes/support.rb +19 -0
- data/lib/karafka/web/ui/routes/ux.rb +19 -0
- data/lib/karafka/web/ui/views/cluster/_partition.erb +2 -2
- data/lib/karafka/web/ui/views/cluster/brokers.erb +1 -1
- data/lib/karafka/web/ui/views/consumers/_assignments_badges.erb +2 -7
- data/lib/karafka/web/ui/views/consumers/_breadcrumbs.erb +7 -1
- data/lib/karafka/web/ui/views/consumers/_consumer.erb +1 -1
- data/lib/karafka/web/ui/views/consumers/_no_consumers.erb +2 -2
- data/lib/karafka/web/ui/views/consumers/_tabs.erb +4 -4
- data/lib/karafka/web/ui/views/consumers/index.erb +1 -1
- data/lib/karafka/web/ui/views/dashboard/_feature_pro.erb +1 -1
- data/lib/karafka/web/ui/views/dashboard/_not_enough_data.erb +2 -2
- data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +1 -1
- data/lib/karafka/web/ui/views/dashboard/index.erb +6 -49
- data/lib/karafka/web/ui/views/errors/_breadcrumbs.erb +3 -8
- data/lib/karafka/web/ui/views/errors/_detail.erb +3 -3
- data/lib/karafka/web/ui/views/errors/_error.erb +6 -1
- data/lib/karafka/web/ui/views/errors/index.erb +1 -1
- data/lib/karafka/web/ui/views/errors/show.erb +39 -33
- data/lib/karafka/web/ui/views/jobs/_job.erb +2 -3
- data/lib/karafka/web/ui/views/jobs/pending.erb +1 -1
- data/lib/karafka/web/ui/views/jobs/running.erb +1 -1
- data/lib/karafka/web/ui/views/layout.erb +7 -5
- data/lib/karafka/web/ui/views/shared/_become_pro.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_brand.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_breadcrumbs.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_compacted_message_info.erb +16 -0
- data/lib/karafka/web/ui/views/shared/_content.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_controls.erb +10 -3
- data/lib/karafka/web/ui/views/shared/_custom_nav.erb +9 -0
- data/lib/karafka/web/ui/views/shared/_flashes.erb +3 -5
- data/lib/karafka/web/ui/views/shared/_header.erb +25 -2
- data/lib/karafka/web/ui/views/shared/_navigation.erb +17 -15
- data/lib/karafka/web/ui/views/shared/alerts/_error.erb +8 -0
- data/lib/karafka/web/ui/views/shared/alerts/_info.erb +8 -0
- data/lib/karafka/web/ui/views/shared/alerts/_primary.erb +8 -0
- data/lib/karafka/web/ui/views/shared/alerts/_secondary.erb +8 -0
- data/lib/karafka/web/ui/views/shared/alerts/_success.erb +8 -0
- data/lib/karafka/web/ui/views/shared/alerts/_warning.erb +8 -0
- data/lib/karafka/web/ui/views/shared/exceptions/not_allowed.erb +4 -0
- data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +5 -1
- data/lib/karafka/web/ui/views/shared/exceptions/pro_only.erb +4 -0
- data/lib/karafka/web/ui/views/shared/icons/_arrow_left.erb +3 -0
- data/lib/karafka/web/ui/views/shared/icons/_arrow_up_tray.erb +3 -0
- data/lib/karafka/web/ui/views/shared/icons/_clock.erb +3 -0
- data/lib/karafka/web/ui/views/shared/icons/_pencil.erb +3 -0
- data/lib/karafka/web/ui/views/shared/icons/_pencil_square.erb +3 -0
- data/lib/karafka/web/ui/views/shared/icons/_play_pause.erb +3 -0
- data/lib/karafka/web/ui/views/shared/icons/_plus.erb +3 -0
- data/lib/karafka/web/ui/views/shared/icons/_trash.erb +3 -0
- data/lib/karafka/web/ui/views/status/failures/_live_reporting.erb +1 -1
- data/lib/karafka/web/ui/views/status/failures/_partitions.erb +3 -3
- data/lib/karafka/web/ui/views/status/failures/_state_calculation.erb +2 -2
- data/lib/karafka/web/ui/views/status/info/_components.erb +6 -6
- data/lib/karafka/web/ui/views/status/show.erb +15 -0
- data/lib/karafka/web/ui/views/status/warnings/_consumers_schemas.erb +31 -0
- data/lib/karafka/web/ui/views/ux/_icons.erb +1 -1
- data/lib/karafka/web/version.rb +1 -1
- data/lib/karafka/web.rb +3 -0
- data/package-lock.json +868 -1255
- data/package.json +6 -7
- data/postcss.config.js +1 -2
- data/renovate.json +20 -1
- data/tailwind.config.js +0 -4
- metadata +235 -135
- checksums.yaml.gz.sig +0 -0
- data/certs/cert.pem +0 -26
- data/lib/karafka/web/pro/commanding/commands/quiet.rb +0 -34
- data/lib/karafka/web/pro/commanding/commands/stop.rb +0 -34
- data/lib/karafka/web/pro/commanding/commands/trace.rb +0 -41
- data/lib/karafka/web/pro/ui/controllers/commanding_controller.rb +0 -118
- data/lib/karafka/web/pro/ui/controllers/commands_controller.rb +0 -96
- data/lib/karafka/web/pro/ui/controllers/consumers_controller.rb +0 -138
- data/lib/karafka/web/pro/ui/controllers/explorer_controller.rb +0 -220
- data/lib/karafka/web/pro/ui/controllers/messages_controller.rb +0 -107
- data/lib/karafka/web/pro/ui/controllers/search_controller.rb +0 -73
- data/lib/karafka/web/pro/ui/controllers/topics_controller.rb +0 -130
- data/lib/karafka/web/pro/ui/views/commands/_breadcrumbs.erb +0 -21
- data/lib/karafka/web/pro/ui/views/commands/_command_details.erb +0 -1
- data/lib/karafka/web/pro/ui/views/commands/_empty.erb +0 -3
- data/lib/karafka/web/pro/ui/views/commands/show.erb +0 -33
- data/lib/karafka/web/pro/ui/views/consumers/_breadcrumbs.erb +0 -55
- data/lib/karafka/web/pro/ui/views/consumers/_tabs.erb +0 -33
- data/lib/karafka/web/pro/ui/views/consumers/consumer/_commands.erb +0 -72
- data/lib/karafka/web/pro/ui/views/consumers/consumer/_consumer_group.erb +0 -8
- data/lib/karafka/web/pro/ui/views/consumers/consumer/_no_jobs.erb +0 -7
- data/lib/karafka/web/pro/ui/views/consumers/consumer/_no_subscriptions.erb +0 -7
- data/lib/karafka/web/pro/ui/views/consumers/details.erb +0 -13
- data/lib/karafka/web/pro/ui/views/consumers/subscriptions.erb +0 -25
- data/lib/karafka/web/pro/ui/views/explorer/_no_topics.erb +0 -1
- data/lib/karafka/web/pro/ui/views/explorer/_topic.erb +0 -10
- data/lib/karafka/web/pro/ui/views/explorer/index.erb +0 -14
- data/lib/karafka/web/pro/ui/views/explorer/messages/_headers.erb +0 -33
- data/lib/karafka/web/pro/ui/views/explorer/partition/_cleaned.erb +0 -3
- data/lib/karafka/web/pro/ui/views/explorer/partition/_empty.erb +0 -3
- data/lib/karafka/web/pro/ui/views/explorer/show.erb +0 -97
- data/lib/karafka/web/pro/ui/views/explorer/topic/_empty.erb +0 -3
- data/lib/karafka/web/pro/ui/views/search/_breadcrumbs.erb +0 -1
- data/lib/karafka/web/pro/ui/views/search/_fix_errors.erb +0 -3
- data/lib/karafka/web/pro/ui/views/search/_no_results.erb +0 -3
- data/lib/karafka/web/pro/ui/views/search/_timeout.erb +0 -3
- data/lib/karafka/web/pro/ui/views/search/index.erb +0 -29
- data/lib/karafka/web/pro/ui/views/topics/_breadcrumbs.erb +0 -45
- data/lib/karafka/web/pro/ui/views/topics/_partition_offsets.erb +0 -10
- data/lib/karafka/web/pro/ui/views/topics/_topic.erb +0 -9
- data/lib/karafka/web/pro/ui/views/topics/distribution/_empty_partitions.erb +0 -1
- data/lib/karafka/web/pro/ui/views/topics/distribution/_partition.erb +0 -10
- data/lib/karafka/web/pro/ui/views/topics/index.erb +0 -14
- data/lib/karafka/web/ui/lib/ttl_cache.rb +0 -82
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -0
@@ -1,220 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# This Karafka component is a Pro component under a commercial license.
|
4
|
-
# This Karafka component is NOT licensed under LGPL.
|
5
|
-
#
|
6
|
-
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
-
# repository and their usage requires commercial license agreement.
|
8
|
-
#
|
9
|
-
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
-
#
|
11
|
-
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
-
# your code to Maciej Mensfeld.
|
13
|
-
|
14
|
-
module Karafka
|
15
|
-
module Web
|
16
|
-
module Pro
|
17
|
-
module Ui
|
18
|
-
module Controllers
|
19
|
-
# Data explorer controller
|
20
|
-
class ExplorerController < BaseController
|
21
|
-
# Lists all the topics we can explore
|
22
|
-
def index
|
23
|
-
@topics = Models::ClusterInfo
|
24
|
-
.topics
|
25
|
-
.sort_by { |topic| topic[:topic_name] }
|
26
|
-
|
27
|
-
unless ::Karafka::Web.config.ui.visibility.internal_topics
|
28
|
-
@topics.reject! { |topic| topic[:topic_name].start_with?('__') }
|
29
|
-
end
|
30
|
-
|
31
|
-
render
|
32
|
-
end
|
33
|
-
|
34
|
-
# Displays aggregated messages from (potentially) all partitions of a topic
|
35
|
-
#
|
36
|
-
# @param topic_id [String]
|
37
|
-
#
|
38
|
-
# @note This view may not be 100% accurate because we merge multiple partitions data
|
39
|
-
# into a single view and this is never accurate. It can be used however to quickly
|
40
|
-
# look at most recent data flowing, etc, hence it is still useful for aggregated
|
41
|
-
# metrics information
|
42
|
-
#
|
43
|
-
# @note We cannot use offset references here because each of the partitions may have
|
44
|
-
# completely different values
|
45
|
-
def topic(topic_id)
|
46
|
-
@visibility_filter = ::Karafka::Web.config.ui.policies.messages
|
47
|
-
|
48
|
-
@topic_id = topic_id
|
49
|
-
@partitions_count = Models::ClusterInfo.partitions_count(topic_id)
|
50
|
-
|
51
|
-
@active_partitions, materialized_page, @limited = Paginators::Partitions.call(
|
52
|
-
@partitions_count, @params.current_page
|
53
|
-
)
|
54
|
-
|
55
|
-
@messages, next_page = Models::Message.topic_page(
|
56
|
-
topic_id, @active_partitions, materialized_page
|
57
|
-
)
|
58
|
-
|
59
|
-
paginate(@params.current_page, next_page)
|
60
|
-
|
61
|
-
render
|
62
|
-
end
|
63
|
-
|
64
|
-
# Shows messages available in a given partition
|
65
|
-
#
|
66
|
-
# @param topic_id [String]
|
67
|
-
# @param partition_id [Integer]
|
68
|
-
def partition(topic_id, partition_id)
|
69
|
-
@visibility_filter = ::Karafka::Web.config.ui.policies.messages
|
70
|
-
@topic_id = topic_id
|
71
|
-
@partition_id = partition_id
|
72
|
-
@watermark_offsets = Models::WatermarkOffsets.find(topic_id, partition_id)
|
73
|
-
@partitions_count = Models::ClusterInfo.partitions_count(topic_id)
|
74
|
-
|
75
|
-
previous_offset, @messages, next_offset = current_partition_data
|
76
|
-
|
77
|
-
paginate(
|
78
|
-
previous_offset,
|
79
|
-
@params.current_offset,
|
80
|
-
next_offset,
|
81
|
-
# If message is an array, it means it's a compacted dummy offset representation
|
82
|
-
@messages.map { |message| message.is_a?(Array) ? message.last : message.offset }
|
83
|
-
)
|
84
|
-
|
85
|
-
render
|
86
|
-
end
|
87
|
-
|
88
|
-
# Displays given message
|
89
|
-
#
|
90
|
-
# @param topic_id [String]
|
91
|
-
# @param partition_id [Integer]
|
92
|
-
# @param offset [Integer] offset of the message we want to display
|
93
|
-
# @param paginate [Boolean] do we want to have pagination
|
94
|
-
def show(topic_id, partition_id, offset, paginate: true)
|
95
|
-
Lib::PatternsDetector.new.call
|
96
|
-
|
97
|
-
@visibility_filter = ::Karafka::Web.config.ui.policies.messages
|
98
|
-
@topic_id = topic_id
|
99
|
-
@partition_id = partition_id
|
100
|
-
@offset = offset
|
101
|
-
@message = Models::Message.find(@topic_id, @partition_id, @offset)
|
102
|
-
|
103
|
-
@safe_key = Web::Pro::Ui::Lib::SafeRunner.new { @message.key }.tap(&:call)
|
104
|
-
@safe_headers = Web::Pro::Ui::Lib::SafeRunner.new { @message.headers }.tap(&:call)
|
105
|
-
@safe_payload = Web::Pro::Ui::Lib::SafeRunner.new { @message.payload }.tap(&:call)
|
106
|
-
|
107
|
-
# This may be off for certain views like recent view where we are interested only
|
108
|
-
# in the most recent all the time. It does not make any sense to display pagination
|
109
|
-
# there
|
110
|
-
if paginate
|
111
|
-
# We need watermark offsets to decide if we can paginate left and right
|
112
|
-
watermark_offsets = Models::WatermarkOffsets.find(topic_id, partition_id)
|
113
|
-
paginate(offset, watermark_offsets.low, watermark_offsets.high)
|
114
|
-
end
|
115
|
-
|
116
|
-
render
|
117
|
-
end
|
118
|
-
|
119
|
-
# Displays the most recent message on a topic/partition
|
120
|
-
#
|
121
|
-
# @param topic_id [String]
|
122
|
-
# @param partition_id [Integer, nil] partition we're interested in or nil if we are
|
123
|
-
# interested in the most recent message from all the partitions
|
124
|
-
def recent(topic_id, partition_id)
|
125
|
-
if partition_id
|
126
|
-
active_partitions = [partition_id]
|
127
|
-
else
|
128
|
-
partitions_count = Models::ClusterInfo.partitions_count(topic_id)
|
129
|
-
active_partitions, = Paginators::Partitions.call(partitions_count, 1)
|
130
|
-
end
|
131
|
-
|
132
|
-
recent = nil
|
133
|
-
|
134
|
-
# This selects first pages with most recent messages and moves to next if first
|
135
|
-
# contains only compacted data, etc.
|
136
|
-
#
|
137
|
-
# We do it until we find a message we could refer to (if doable) within first
|
138
|
-
# ten pages
|
139
|
-
10.times do |page|
|
140
|
-
messages, = Models::Message.topic_page(topic_id, active_partitions, page + 1)
|
141
|
-
|
142
|
-
# Selects newest out of all partitions
|
143
|
-
# Reject compacted messages and transaction-related once
|
144
|
-
recent = messages.reject { |message| message.is_a?(Array) }.max_by(&:timestamp)
|
145
|
-
|
146
|
-
break if recent
|
147
|
-
end
|
148
|
-
|
149
|
-
recent || not_found!
|
150
|
-
|
151
|
-
show(topic_id, recent.partition, recent.offset, paginate: false)
|
152
|
-
end
|
153
|
-
|
154
|
-
# Computes a page on which the given offset is in the middle of the page (if possible)
|
155
|
-
# Useful often when debugging to be able to quickly jump to the historical location
|
156
|
-
# of message and its surrounding to understand failure
|
157
|
-
#
|
158
|
-
# @param topic_id [String]
|
159
|
-
# @param partition_id [Integer]
|
160
|
-
# @param offset [Integer] offset of the message we want to display
|
161
|
-
def surrounding(topic_id, partition_id, offset)
|
162
|
-
watermark_offsets = Models::WatermarkOffsets.find(topic_id, partition_id)
|
163
|
-
|
164
|
-
not_found! if offset < watermark_offsets.low
|
165
|
-
not_found! if offset >= watermark_offsets.high
|
166
|
-
|
167
|
-
# Assume we start from this offset
|
168
|
-
shift = 0
|
169
|
-
elements = 0
|
170
|
-
|
171
|
-
# Position the offset as close to the middle of offset based page as possible
|
172
|
-
::Karafka::Web.config.ui.per_page.times do
|
173
|
-
break if elements >= ::Karafka::Web.config.ui.per_page
|
174
|
-
|
175
|
-
elements += 1 if offset + shift < watermark_offsets.high
|
176
|
-
|
177
|
-
if offset - shift > watermark_offsets.low
|
178
|
-
shift += 1
|
179
|
-
elements += 1
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
target = offset - shift
|
184
|
-
|
185
|
-
redirect("explorer/#{topic_id}/#{partition_id}?offset=#{target}")
|
186
|
-
end
|
187
|
-
|
188
|
-
# Finds the closest offset matching the requested time and redirects to this location
|
189
|
-
# Note, that it redirects to closest but always younger.
|
190
|
-
#
|
191
|
-
# @param topic_id [String]
|
192
|
-
# @param partition_id [Integer]
|
193
|
-
# @param time [Time] time of the message
|
194
|
-
def closest(topic_id, partition_id, time)
|
195
|
-
target = Web::Ui::Lib::Admin.read_topic(topic_id, partition_id, 1, time).first
|
196
|
-
|
197
|
-
partition_path = "explorer/#{topic_id}/#{partition_id}"
|
198
|
-
partition_path += "?offset=#{target.offset}" if target
|
199
|
-
|
200
|
-
redirect(partition_path)
|
201
|
-
end
|
202
|
-
|
203
|
-
private
|
204
|
-
|
205
|
-
# Fetches current page data
|
206
|
-
# @return [Array] fetched data with pagination information for the requested partition
|
207
|
-
def current_partition_data
|
208
|
-
Models::Message.offset_page(
|
209
|
-
@topic_id,
|
210
|
-
@partition_id,
|
211
|
-
@params.current_offset,
|
212
|
-
@watermark_offsets
|
213
|
-
)
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
@@ -1,107 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# This Karafka component is a Pro component under a commercial license.
|
4
|
-
# This Karafka component is NOT licensed under LGPL.
|
5
|
-
#
|
6
|
-
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
-
# repository and their usage requires commercial license agreement.
|
8
|
-
#
|
9
|
-
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
-
#
|
11
|
-
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
-
# your code to Maciej Mensfeld.
|
13
|
-
|
14
|
-
module Karafka
|
15
|
-
module Web
|
16
|
-
module Pro
|
17
|
-
module Ui
|
18
|
-
module Controllers
|
19
|
-
# Controller for working with messages
|
20
|
-
# While part of messages operations is done via explorer (exploring), this controller
|
21
|
-
# handles other cases not related to viewing data
|
22
|
-
class MessagesController < BaseController
|
23
|
-
# Takes a requested message content and republishes it again
|
24
|
-
#
|
25
|
-
# @param topic_id [String]
|
26
|
-
# @param partition_id [Integer]
|
27
|
-
# @param offset [Integer] offset of the message we want to republish
|
28
|
-
def republish(topic_id, partition_id, offset)
|
29
|
-
message = Models::Message.find(topic_id, partition_id, offset)
|
30
|
-
|
31
|
-
deny! unless visibility_filter.republish?(message)
|
32
|
-
|
33
|
-
delivery = ::Karafka::Web.producer.produce_sync(
|
34
|
-
topic: topic_id,
|
35
|
-
partition: partition_id,
|
36
|
-
payload: message.raw_payload,
|
37
|
-
headers: message.headers,
|
38
|
-
key: message.key
|
39
|
-
)
|
40
|
-
|
41
|
-
redirect(
|
42
|
-
:back,
|
43
|
-
success: reproduced(message, delivery)
|
44
|
-
)
|
45
|
-
end
|
46
|
-
|
47
|
-
# Dispatches the message raw payload to the browser as a file
|
48
|
-
#
|
49
|
-
# @param topic_id [String]
|
50
|
-
# @param partition_id [Integer]
|
51
|
-
# @param offset [Integer] offset of the message we want to download
|
52
|
-
def download(topic_id, partition_id, offset)
|
53
|
-
message = Models::Message.find(topic_id, partition_id, offset)
|
54
|
-
|
55
|
-
deny! unless visibility_filter.download?(message)
|
56
|
-
|
57
|
-
file(
|
58
|
-
message.raw_payload,
|
59
|
-
"#{topic_id}_#{partition_id}_#{offset}_payload.msg"
|
60
|
-
)
|
61
|
-
end
|
62
|
-
|
63
|
-
# Dispatches the message payload first deserialized and then serialized to JSON
|
64
|
-
# It differs from the raw payload in cases where raw payload is compressed or binary
|
65
|
-
# or contains data that the Web UI user should not see that was altered on the Web UI
|
66
|
-
# with the visibility filter.
|
67
|
-
#
|
68
|
-
# @param topic_id [String]
|
69
|
-
# @param partition_id [Integer]
|
70
|
-
# @param offset [Integer] offset of the message we want to export
|
71
|
-
def export(topic_id, partition_id, offset)
|
72
|
-
Lib::PatternsDetector.new.call
|
73
|
-
|
74
|
-
message = Models::Message.find(topic_id, partition_id, offset)
|
75
|
-
|
76
|
-
# Check if exports are allowed
|
77
|
-
deny! unless visibility_filter.export?(message)
|
78
|
-
|
79
|
-
file(
|
80
|
-
message.payload.to_json,
|
81
|
-
"#{topic_id}_#{partition_id}_#{offset}_payload.json"
|
82
|
-
)
|
83
|
-
end
|
84
|
-
|
85
|
-
private
|
86
|
-
|
87
|
-
# @param message [Karafka::Messages::Message]
|
88
|
-
# @param delivery [Rdkafka::Producer::DeliveryReport]
|
89
|
-
# @return [String] flash message about message reproducing
|
90
|
-
def reproduced(message, delivery)
|
91
|
-
<<~MSG
|
92
|
-
Message with offset #{message.offset}
|
93
|
-
has been sent again to #{message.topic}##{message.partition}
|
94
|
-
and received offset #{delivery.offset}.
|
95
|
-
MSG
|
96
|
-
end
|
97
|
-
|
98
|
-
# @return [Object] visibility filter. Either default or user-based
|
99
|
-
def visibility_filter
|
100
|
-
::Karafka::Web.config.ui.policies.messages
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# This Karafka component is a Pro component under a commercial license.
|
4
|
-
# This Karafka component is NOT licensed under LGPL.
|
5
|
-
#
|
6
|
-
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
-
# repository and their usage requires commercial license agreement.
|
8
|
-
#
|
9
|
-
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
-
#
|
11
|
-
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
-
# your code to Maciej Mensfeld.
|
13
|
-
|
14
|
-
module Karafka
|
15
|
-
module Web
|
16
|
-
module Pro
|
17
|
-
module Ui
|
18
|
-
module Controllers
|
19
|
-
# Handles the search requests
|
20
|
-
# We present this as a part of explorer scope but we use a separate controller not to
|
21
|
-
# mix data exploring with searching.
|
22
|
-
class SearchController < Web::Ui::Controllers::ClusterController
|
23
|
-
# Runs the search if search parameters are provided
|
24
|
-
# If no parameters provided, displays the search modal and info to provide search data
|
25
|
-
# If invalid search parameters provided, modal contains errors
|
26
|
-
#
|
27
|
-
# @param topic_id [String] topic we're interested in
|
28
|
-
# @note In theory search can be used to detect pieces of information within messages.
|
29
|
-
# Since we allow for custom search strategies, this is not an issue because users
|
30
|
-
# that need to provide only granular search can do so.
|
31
|
-
def index(topic_id)
|
32
|
-
@topic_id = topic_id
|
33
|
-
@partitions_count = Models::ClusterInfo.partitions_count(topic_id)
|
34
|
-
# Select only matchers that should be available in the context of the current topic
|
35
|
-
@matchers = Web.config.ui.search.matchers.select { |match| match.active?(@topic_id) }
|
36
|
-
@search_criteria = !@params.current_search.empty?
|
37
|
-
@current_search = Lib::Search::Normalizer.call(@params.current_search)
|
38
|
-
# Needed when rendering found messages rows. We should always filter the messages
|
39
|
-
# details with the visibility filter
|
40
|
-
@visibility_filter = ::Karafka::Web.config.ui.policies.messages
|
41
|
-
@limits = ::Karafka::Web.config.ui.search.limits.sort
|
42
|
-
|
43
|
-
# If there is search form filled, we validate it to make sure there are no errors
|
44
|
-
@errors = if @search_criteria
|
45
|
-
Lib::Search::Contracts::Form.new.call(@current_search).errors
|
46
|
-
else
|
47
|
-
{}
|
48
|
-
end
|
49
|
-
|
50
|
-
# If all good we run the search
|
51
|
-
if @search_criteria && @errors.empty?
|
52
|
-
found, @search_details = Lib::Search::Runner.new(
|
53
|
-
@topic_id,
|
54
|
-
@partitions_count,
|
55
|
-
@current_search
|
56
|
-
).call
|
57
|
-
|
58
|
-
@messages, last_page = Paginators::Arrays.call(
|
59
|
-
found,
|
60
|
-
@params.current_page
|
61
|
-
)
|
62
|
-
|
63
|
-
paginate(@params.current_page, !last_page)
|
64
|
-
end
|
65
|
-
|
66
|
-
render
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
@@ -1,130 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# This Karafka component is a Pro component under a commercial license.
|
4
|
-
# This Karafka component is NOT licensed under LGPL.
|
5
|
-
#
|
6
|
-
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
-
# repository and their usage requires commercial license agreement.
|
8
|
-
#
|
9
|
-
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
-
#
|
11
|
-
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
-
# your code to Maciej Mensfeld.
|
13
|
-
|
14
|
-
module Karafka
|
15
|
-
module Web
|
16
|
-
module Pro
|
17
|
-
module Ui
|
18
|
-
module Controllers
|
19
|
-
# Topics management controller
|
20
|
-
# Allows for exploration of settings and replication details
|
21
|
-
class TopicsController < BaseController
|
22
|
-
self.sortable_attributes = %w[
|
23
|
-
name
|
24
|
-
value
|
25
|
-
default?
|
26
|
-
read_only?
|
27
|
-
synonym?
|
28
|
-
sensitive?
|
29
|
-
partition_id
|
30
|
-
leader
|
31
|
-
replica_count
|
32
|
-
in_sync_replica_brokers
|
33
|
-
count
|
34
|
-
share
|
35
|
-
diff
|
36
|
-
low
|
37
|
-
high
|
38
|
-
].freeze
|
39
|
-
|
40
|
-
# Lists available topics in the cluster
|
41
|
-
def index
|
42
|
-
@topics = Models::Topic.all.sort_by(&:topic_name)
|
43
|
-
|
44
|
-
unless ::Karafka::Web.config.ui.visibility.internal_topics
|
45
|
-
@topics.delete_if { |topic| topic[:topic_name].start_with?('__') }
|
46
|
-
end
|
47
|
-
|
48
|
-
render
|
49
|
-
end
|
50
|
-
|
51
|
-
# Displays requested topic config details
|
52
|
-
#
|
53
|
-
# @param topic_name [String] topic we're interested in
|
54
|
-
def config(topic_name)
|
55
|
-
@topic = Models::Topic.find(topic_name)
|
56
|
-
|
57
|
-
@configs = refine(@topic.configs)
|
58
|
-
|
59
|
-
render
|
60
|
-
end
|
61
|
-
|
62
|
-
# Displays requested topic replication details
|
63
|
-
#
|
64
|
-
# @param topic_name [String] topic we're interested in
|
65
|
-
def replication(topic_name)
|
66
|
-
@topic = Models::Topic.find(topic_name)
|
67
|
-
|
68
|
-
@partitions = refine(@topic[:partitions])
|
69
|
-
|
70
|
-
render
|
71
|
-
end
|
72
|
-
|
73
|
-
# Displays the messages distribution across various partitions
|
74
|
-
#
|
75
|
-
# @param topic_name [String] topic we're interested in
|
76
|
-
#
|
77
|
-
# @note Because computing distribution is fairly expensive, we paginate this. While
|
78
|
-
# because of that results may not be exact, this allows us to support topics with
|
79
|
-
# many partitions.
|
80
|
-
def distribution(topic_name)
|
81
|
-
@topic = Models::Topic.find(topic_name)
|
82
|
-
|
83
|
-
@active_partitions, _materialized_page, @limited = Paginators::Partitions.call(
|
84
|
-
@topic.partition_count, @params.current_page
|
85
|
-
)
|
86
|
-
|
87
|
-
@aggregated, distribution = @topic.distribution(@active_partitions)
|
88
|
-
|
89
|
-
@distribution = refine(distribution)
|
90
|
-
|
91
|
-
next_page = @active_partitions.last < @topic.partition_count - 1
|
92
|
-
paginate(@params.current_page, next_page)
|
93
|
-
|
94
|
-
render
|
95
|
-
end
|
96
|
-
|
97
|
-
# Displays high and low offsets for given topic
|
98
|
-
#
|
99
|
-
# @param topic_name [String] topic we're interested in
|
100
|
-
def offsets(topic_name)
|
101
|
-
@topic = Models::Topic.find(topic_name)
|
102
|
-
|
103
|
-
@active_partitions, _materialized_page, @limited = Paginators::Partitions.call(
|
104
|
-
@topic.partition_count, @params.current_page
|
105
|
-
)
|
106
|
-
|
107
|
-
offsets = @active_partitions.map do |partition_id|
|
108
|
-
part_offsets = Admin.read_watermark_offsets(topic_name, partition_id)
|
109
|
-
|
110
|
-
{
|
111
|
-
partition_id: partition_id,
|
112
|
-
low: part_offsets.first,
|
113
|
-
high: part_offsets.last,
|
114
|
-
diff: part_offsets.last - part_offsets.first
|
115
|
-
}
|
116
|
-
end
|
117
|
-
|
118
|
-
@offsets = refine(offsets)
|
119
|
-
|
120
|
-
next_page = @active_partitions.last < @topic.partition_count - 1
|
121
|
-
paginate(@params.current_page, next_page)
|
122
|
-
|
123
|
-
render
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
<li>
|
2
|
-
<a href="<%= root_path('consumers/overview') %>">
|
3
|
-
Consumers
|
4
|
-
</a>
|
5
|
-
</li>
|
6
|
-
|
7
|
-
<% if current_path.include?('/commands') %>
|
8
|
-
<li>
|
9
|
-
<a href="<%= root_path('commands') %>">
|
10
|
-
Commands
|
11
|
-
</a>
|
12
|
-
</li>
|
13
|
-
<% end %>
|
14
|
-
|
15
|
-
<% if @command_message %>
|
16
|
-
<li>
|
17
|
-
<a href="<%= root_path('commands', @command_message.offset) %>">
|
18
|
-
<%= @command_message.offset %>
|
19
|
-
</a>
|
20
|
-
</li>
|
21
|
-
<% end %>
|
@@ -1 +0,0 @@
|
|
1
|
-
<pre class="code"><code class="json"><%= JSON.pretty_generate command %></code></pre>
|
@@ -1,33 +0,0 @@
|
|
1
|
-
<% command = @command_message.payload %>
|
2
|
-
|
3
|
-
<div class="col-span-12">
|
4
|
-
<% if command[:schema_version] == @schema_version %>
|
5
|
-
<% view_title "#{command[:command][:name]} #{command[:type]} #{@command_message.key}" %>
|
6
|
-
|
7
|
-
<%== partial 'consumers/tabs' %>
|
8
|
-
|
9
|
-
<%== partial 'commands/metadata', locals: { command: command } %>
|
10
|
-
|
11
|
-
<h2 class="h2">
|
12
|
-
<% if command[:type] == 'command' %>
|
13
|
-
Details
|
14
|
-
<% else %>
|
15
|
-
Backtraces
|
16
|
-
<% end %>
|
17
|
-
</h2>
|
18
|
-
|
19
|
-
<% if command[:type] == 'command' %>
|
20
|
-
<%== partial 'commands/command_details', locals: { command: command } %>
|
21
|
-
<% else %>
|
22
|
-
<% command[:result].each_value do |backtrace| %>
|
23
|
-
<%== partial 'commands/backtrace', locals: { backtrace: backtrace } %>
|
24
|
-
<% end %>
|
25
|
-
<% end %>
|
26
|
-
<% else %>
|
27
|
-
<% view_title 'Incompatible Command Schema' %>
|
28
|
-
|
29
|
-
<%== partial 'consumers/tabs' %>
|
30
|
-
|
31
|
-
<%== partial 'commands/incompatible_schema' %>
|
32
|
-
<% end %>
|
33
|
-
</div>
|
@@ -1,55 +0,0 @@
|
|
1
|
-
<li>
|
2
|
-
<a href="<%= root_path('consumers/overview') %>">
|
3
|
-
Consumers
|
4
|
-
</a>
|
5
|
-
</li>
|
6
|
-
|
7
|
-
<% if current_path.include?('/performance') %>
|
8
|
-
<li>
|
9
|
-
<a href="<%= root_path('consumers', 'performance') %>">
|
10
|
-
Performance
|
11
|
-
</a>
|
12
|
-
</li>
|
13
|
-
<% end %>
|
14
|
-
|
15
|
-
<% if current_path.include?('/controls') %>
|
16
|
-
<li>
|
17
|
-
<a href="<%= root_path('consumers', 'controls') %>">
|
18
|
-
Controls
|
19
|
-
</a>
|
20
|
-
</li>
|
21
|
-
<% end %>
|
22
|
-
|
23
|
-
<% if @process %>
|
24
|
-
<li>
|
25
|
-
<a href="<%= root_path('consumers', @process.id, 'subscriptions') %>">
|
26
|
-
<%== truncate(@process.id, strategy: :middle) %>
|
27
|
-
</a>
|
28
|
-
</li>
|
29
|
-
|
30
|
-
<% if current_path.include?('/jobs') %>
|
31
|
-
<li>
|
32
|
-
<a href="<%= root_path('consumers', @process.id, 'jobs') %>">
|
33
|
-
Jobs
|
34
|
-
</a>
|
35
|
-
</li>
|
36
|
-
|
37
|
-
<li>
|
38
|
-
<a href="<%= root_path('consumers', @process.id, 'jobs') %>">
|
39
|
-
Running
|
40
|
-
</a>
|
41
|
-
</li>
|
42
|
-
<% elsif current_path.include?('/subscriptions') %>
|
43
|
-
<li>
|
44
|
-
<a href="<%= root_path('consumers', @process.id, 'subscriptions') %>">
|
45
|
-
Subscriptions
|
46
|
-
</a>
|
47
|
-
</li>
|
48
|
-
<% else %>
|
49
|
-
<li>
|
50
|
-
<a href="<%= root_path('consumers', @process.id, 'details') %>">
|
51
|
-
Details
|
52
|
-
</a>
|
53
|
-
</li>
|
54
|
-
<% end %>
|
55
|
-
<% end %>
|
@@ -1,33 +0,0 @@
|
|
1
|
-
<div class="tab-container-wrapper">
|
2
|
-
<div class="tab-container">
|
3
|
-
<a
|
4
|
-
href="<%= root_path('consumers', 'overview') %>"
|
5
|
-
class="custom-tab <%= nav_class(include: 'overview') %>"
|
6
|
-
>
|
7
|
-
Overview
|
8
|
-
</a>
|
9
|
-
|
10
|
-
<a
|
11
|
-
href="<%= root_path('consumers', 'performance') %>"
|
12
|
-
class="custom-tab <%= nav_class(include: 'performance') %>"
|
13
|
-
>
|
14
|
-
Performance
|
15
|
-
</a>
|
16
|
-
|
17
|
-
<% if Karafka::Web.config.commanding.active %>
|
18
|
-
<a
|
19
|
-
href="<%= root_path('consumers', 'controls') %>"
|
20
|
-
class="custom-tab <%= nav_class(include: 'controls') %>"
|
21
|
-
>
|
22
|
-
Controls
|
23
|
-
</a>
|
24
|
-
|
25
|
-
<a
|
26
|
-
href="<%= root_path('commands') %>"
|
27
|
-
class="custom-tab <%= nav_class(include: 'commands') %>"
|
28
|
-
>
|
29
|
-
Commands
|
30
|
-
</a>
|
31
|
-
<% end %>
|
32
|
-
</div>
|
33
|
-
</div>
|