summaryrefslogtreecommitdiff
path: root/deps/rabbitmq_management
diff options
context:
space:
mode:
Diffstat (limited to 'deps/rabbitmq_management')
-rw-r--r--deps/rabbitmq_management/.gitignore36
-rw-r--r--deps/rabbitmq_management/.travis.yml61
-rw-r--r--deps/rabbitmq_management/CODE_OF_CONDUCT.md44
-rw-r--r--deps/rabbitmq_management/CONTRIBUTING.md99
-rw-r--r--deps/rabbitmq_management/Caddyfile5
-rw-r--r--deps/rabbitmq_management/LICENSE16
-rw-r--r--deps/rabbitmq_management/LICENSE-APACHE2-excanvas202
-rw-r--r--deps/rabbitmq_management/LICENSE-BSD-base64js25
-rw-r--r--deps/rabbitmq_management/LICENSE-ISC-cowboy13
-rw-r--r--deps/rabbitmq_management/LICENSE-MIT-EJS21
-rw-r--r--deps/rabbitmq_management/LICENSE-MIT-Flot22
-rw-r--r--deps/rabbitmq_management/LICENSE-MIT-Sammy22
-rw-r--r--deps/rabbitmq_management/LICENSE-MIT-jQuery20
-rw-r--r--deps/rabbitmq_management/LICENSE-MPL-RabbitMQ373
-rw-r--r--deps/rabbitmq_management/Makefile52
-rw-r--r--deps/rabbitmq_management/README.md21
-rwxr-xr-xdeps/rabbitmq_management/bin/rabbitmqadmin1184
-rw-r--r--deps/rabbitmq_management/erlang.mk7808
-rw-r--r--deps/rabbitmq_management/include/rabbit_mgmt.hrl19
-rw-r--r--deps/rabbitmq_management/priv/schema/rabbitmq_management.schema447
-rw-r--r--deps/rabbitmq_management/priv/www/api/index.html2093
-rw-r--r--deps/rabbitmq_management/priv/www/cli/index.html45
-rw-r--r--deps/rabbitmq_management/priv/www/css/evil.css1
-rw-r--r--deps/rabbitmq_management/priv/www/css/main.css361
-rw-r--r--deps/rabbitmq_management/priv/www/favicon.icobin0 -> 102856 bytes
-rw-r--r--deps/rabbitmq_management/priv/www/img/bg-binary.pngbin0 -> 433 bytes
-rw-r--r--deps/rabbitmq_management/priv/www/img/bg-green-dark.pngbin0 -> 190 bytes
-rw-r--r--deps/rabbitmq_management/priv/www/img/bg-red-dark.pngbin0 -> 190 bytes
-rw-r--r--deps/rabbitmq_management/priv/www/img/bg-red.pngbin0 -> 220 bytes
-rw-r--r--deps/rabbitmq_management/priv/www/img/bg-yellow-dark.pngbin0 -> 190 bytes
-rw-r--r--deps/rabbitmq_management/priv/www/img/collapse.pngbin0 -> 226 bytes
-rw-r--r--deps/rabbitmq_management/priv/www/img/expand.pngbin0 -> 221 bytes
-rw-r--r--deps/rabbitmq_management/priv/www/img/rabbitmqlogo-master-copy.svg122
-rw-r--r--deps/rabbitmq_management/priv/www/img/rabbitmqlogo.svg124
-rw-r--r--deps/rabbitmq_management/priv/www/index.html70
-rw-r--r--deps/rabbitmq_management/priv/www/js/base64.js154
-rw-r--r--deps/rabbitmq_management/priv/www/js/charts.js329
-rw-r--r--deps/rabbitmq_management/priv/www/js/dispatcher.js345
-rw-r--r--deps/rabbitmq_management/priv/www/js/ejs-1.0.js505
-rw-r--r--deps/rabbitmq_management/priv/www/js/ejs-1.0.min.js1
-rw-r--r--deps/rabbitmq_management/priv/www/js/excanvas.js1428
-rw-r--r--deps/rabbitmq_management/priv/www/js/excanvas.min.js1
-rw-r--r--deps/rabbitmq_management/priv/www/js/formatters.js1067
-rw-r--r--deps/rabbitmq_management/priv/www/js/global.js798
-rw-r--r--deps/rabbitmq_management/priv/www/js/jquery-3.5.1.js10872
-rw-r--r--deps/rabbitmq_management/priv/www/js/jquery-3.5.1.min.js2
-rw-r--r--deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.js3061
-rw-r--r--deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.min.js29
-rw-r--r--deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.time.js431
-rw-r--r--deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.time.min.js9
-rw-r--r--deps/rabbitmq_management/priv/www/js/json2-2016.10.28.js506
-rw-r--r--deps/rabbitmq_management/priv/www/js/main.js1717
-rw-r--r--deps/rabbitmq_management/priv/www/js/prefs.js164
-rw-r--r--deps/rabbitmq_management/priv/www/js/sammy-0.7.6.js2156
-rw-r--r--deps/rabbitmq_management/priv/www/js/sammy-0.7.6.min.js5
-rw-r--r--deps/rabbitmq_management/priv/www/js/singular/client_frame.html21
-rw-r--r--deps/rabbitmq_management/priv/www/js/singular/postaccess.html10
-rw-r--r--deps/rabbitmq_management/priv/www/js/singular/postauth.html10
-rw-r--r--deps/rabbitmq_management/priv/www/js/singular/rpFrame.js1
-rw-r--r--deps/rabbitmq_management/priv/www/js/singular/singular.js1
-rw-r--r--deps/rabbitmq_management/priv/www/js/singular/singular.umd.js1
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/404.ejs3
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/add-binding.ejs49
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/binary.ejs61
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/bindings.ejs66
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/channel.ejs144
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/channels-list.ejs203
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/channels.ejs7
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/cluster-name.ejs31
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/columns-options.ejs25
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/connection.ejs194
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/connections.ejs155
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/consumers.ejs43
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/exchange.ejs97
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/exchanges.ejs169
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs60
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/layout.ejs58
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/limits.ejs177
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/list-exchanges.ejs5
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/login.ejs21
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/login_uaa.ejs5
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/memory-bar.ejs24
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/memory-table.ejs28
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/memory.ejs97
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/messages.ejs39
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/msg-detail-deliveries.ejs30
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/msg-detail-publishes.ejs46
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/node.ejs416
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/overview.ejs399
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/partition.ejs105
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/permissions.ejs91
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/policies.ejs305
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/policy.ejs37
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/popup.ejs6
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/publish.ejs67
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/queue.ejs431
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/queues.ejs353
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/rate-options.ejs56
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/registry.ejs25
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/status.ejs1
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/topic-permissions.ejs96
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/user.ejs100
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/users.ejs98
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/vhost.ejs62
-rw-r--r--deps/rabbitmq_management/priv/www/js/tmpl/vhosts.ejs163
-rw-r--r--deps/rabbitmq_management/rabbitmq-components.mk359
-rwxr-xr-xdeps/rabbitmq_management/scripts/seed.sh29
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_app.erl206
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_cors.erl84
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_csp.erl28
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_db.erl799
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_db_cache.erl143
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_db_cache_sup.erl29
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl185
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_extension.erl16
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_headers.erl34
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_hsts.erl28
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_load_definitions.erl27
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_reset_handler.erl79
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_stats.erl739
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_sup.erl42
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_sup_sup.erl28
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_util.erl1220
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_aliveness_test.erl71
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl51
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_auth_attempts.erl79
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_binding.erl141
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_bindings.erl149
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_channel.erl68
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_channels.erl50
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_channels_vhost.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_cluster_name.erl59
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_connection.erl98
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_channels.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_connections.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_connections_vhost.erl49
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_consumers.erl59
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl298
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange.erl90
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange_publish.erl105
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_exchanges.erl75
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_extensions.erl33
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl55
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flags.erl45
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameter.erl71
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameters.erl36
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_alarms.erl56
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_certificate_expiration.erl178
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_local_alarms.erl56
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_quorum_critical.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_port_listener.erl66
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_protocol_listener.erl127
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_virtual_hosts.erl48
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_healthchecks.erl76
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_limit.erl64
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_limits.erl59
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_login.erl53
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_node.erl78
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory.erl75
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory_ets.erl85
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_nodes.erl56
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policies.erl49
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policy.erl83
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_overview.erl182
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_parameter.erl88
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_parameters.erl78
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_permission.erl102
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions.erl38
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_user.erl47
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_vhost.erl44
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_policies.erl49
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_policy.erl82
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queue.erl113
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_actions.erl68
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_get.erl166
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_purge.erl42
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queues.erl113
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_rebalance_queues.erl58
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_redirect.erl13
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_reset.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl65
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permission.erl103
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions.erl38
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_user.erl47
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_vhost.erl44
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_user.erl73
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limit.erl62
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limits.erl63
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_users.erl59
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_users_bulk_delete.erl47
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost.erl97
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl56
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_vhosts.erl69
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_whoami.erl36
-rw-r--r--deps/rabbitmq_management/test/cache_SUITE.erl109
-rw-r--r--deps/rabbitmq_management/test/clustering_SUITE.erl874
-rw-r--r--deps/rabbitmq_management/test/clustering_prop_SUITE.erl280
-rw-r--r--deps/rabbitmq_management/test/config_schema_SUITE.erl55
-rw-r--r--deps/rabbitmq_management/test/config_schema_SUITE_data/certs/cacert.pem1
-rw-r--r--deps/rabbitmq_management/test/config_schema_SUITE_data/certs/cert.pem1
-rw-r--r--deps/rabbitmq_management/test/config_schema_SUITE_data/certs/key.pem1
-rw-r--r--deps/rabbitmq_management/test/config_schema_SUITE_data/rabbit-mgmt/access.log0
-rw-r--r--deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets525
-rw-r--r--deps/rabbitmq_management/test/listener_config_SUITE.erl135
-rw-r--r--deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl3545
-rw-r--r--deps/rabbitmq_management/test/rabbit_mgmt_http_health_checks_SUITE.erl399
-rw-r--r--deps/rabbitmq_management/test/rabbit_mgmt_only_http_SUITE.erl1716
-rw-r--r--deps/rabbitmq_management/test/rabbit_mgmt_rabbitmqadmin_SUITE.erl512
-rw-r--r--deps/rabbitmq_management/test/rabbit_mgmt_runtime_parameters_util.erl63
-rw-r--r--deps/rabbitmq_management/test/rabbit_mgmt_stats_SUITE.erl458
-rw-r--r--deps/rabbitmq_management/test/rabbit_mgmt_test_db_SUITE.erl469
-rw-r--r--deps/rabbitmq_management/test/rabbit_mgmt_test_unit_SUITE.erl88
-rw-r--r--deps/rabbitmq_management/test/stats_SUITE.erl178
214 files changed, 60419 insertions, 0 deletions
diff --git a/deps/rabbitmq_management/.gitignore b/deps/rabbitmq_management/.gitignore
new file mode 100644
index 0000000000..ab433eaff0
--- /dev/null
+++ b/deps/rabbitmq_management/.gitignore
@@ -0,0 +1,36 @@
+.sw?
+.*.sw?
+*.beam
+*.pem
+erl_crash.dump
+MnesiaCore.*
+/.erlang.mk/
+/cover/
+/debug/
+/deps/
+/doc/
+/ebin/
+/escript/
+/escript.lock
+/logs/
+/plugins/
+/plugins.lock
+/sbin/
+/sbin.lock
+/xrefr
+
+rabbitmq_management.d
+.rabbitmq_management.plt
+
+# Common Test
+ct_run*
+all_runs.html
+index.html
+ct_default.css
+ct_log_cache
+variables-ct*
+
+*.coverdata
+
+test/config_schema_SUITE_data/schema/
+.vscode/* \ No newline at end of file
diff --git a/deps/rabbitmq_management/.travis.yml b/deps/rabbitmq_management/.travis.yml
new file mode 100644
index 0000000000..a9b75d084d
--- /dev/null
+++ b/deps/rabbitmq_management/.travis.yml
@@ -0,0 +1,61 @@
+# vim:sw=2:et:
+
+os: linux
+dist: xenial
+language: elixir
+notifications:
+ email:
+ recipients:
+ - alerts@rabbitmq.com
+ on_success: never
+ on_failure: always
+addons:
+ apt:
+ packages:
+ - awscli
+cache:
+ apt: true
+env:
+ global:
+ - secure: Tu26VJ9BsXxL20xxwWk4cbCkZyqyxYmNpSSqco5r3FLeU5hk5Vkk+s2BareRvqKhKHFlvyxu8GwsKtajMvsieP6y5J99gSeub6fDOIskPz61bo0aKA9nbDuBFSG1Z5wgXx1XRo0yDatLxXCXe3FbThRsylG7XNjtRaru1/lwuVxfxPtBGQ1opvQX71sST3GYSPoBYR+JlcVpU+uDHMAzsP8J0m5rEpxcl821aTMk3iz90hBQMsoLTBmSQePPcNqOA/1OH75VfjuXR8JBXHvA9njrUBrsyxgHf2uOh3jAXdIrHZwZg/17+y7gNVqByfx/UpGb8XEpVkncg/cRyVIHMk7/gFCZkeVC1QkIN5+EPiGLF7u32x9QaT7Zqz57iLh3IJzED2dj12qWaeX8QypF1K1r5qq4pRrN6iEZx76stpZbyFT4XnExHRdzPuouy7yz1gDHF0HOxbNLowzc/jk7tuTp+qmDSR5tRvegAIH3TONegxXyB7smdbvdI6MCN5/GP2bGK7HiqYWCmTGHtJwgxBKc5XoV8ZjpXfKxG98WbK5RsSP1miRnmxSbxaV0Gai1hfFlanJFFxTA9584O+NVRXNNFMfnnt20Ts6OwoXTcJ/boIPjF5Mcm0eJ4nz4R18TArXE4B5S4pTk3eQkG1ACDigkYZ3fc6ws4cWrt8BZASI=
+ - secure: fNEx9OXi2UisiYu0FiHJpV9+vWLB9DIUAIKG24GfUHVgZqFQOInBf5fEYrjlVgm5zNezSBS3hFNHXd/EXJF8KNgbf6mI0z4h4RyyQY98N+78tWvINoIawEeYpgC6NTI52MdaCfV+fTVWhiL0uP7mqWhLmll2bKXIy6HA6I9PnmiQSloNe64vUPF+UsVZHzzeabK4DR2VdI3h+BGXzOY9FG8Kt2voiXOLd2RFpVeN86FDTp+uVZY/K9e/MsktoK+XaZZ4qMAgm6lB32LVkzl3KA9ki6y6BY7le1m2c90hxAtBJGWZptkMb+VL0Fem39nEBnLjE0a0vIddp32PLJQmv6eopMfLay5BIkwtkRwv3P0uCwYd0bgYQSHF/gdTCcK1nr7fMhkQveBh6vmnbhrca7OeQRHz08+jo6EquUgNQZKmTZPWXQn9lS9mU/0EDLJJhn4KhJezGw6DcAAqB0KqmQedxtHMUT87by7LzhINwKZnm4y5WKA/W/zLI6dNqvIgc5C6UJh0EVgxa13GRmrnGmttV1dtLRQhiMJCbJykaekjPMULUmli0RbFz7bSFqFqEUsF+wwovyD+Y6D8KGOJdvvEYPdPIFpRPnhGUvH86JzsFdVKNJBicGI9LpCtlXlWNRbQIQ8uV5ze2HhxSJhtM6e6dB4d9yzpp6a81uR77bk=
+
+ # $base_rmq_ref is used by rabbitmq-components.mk to select the
+ # appropriate branch for dependencies.
+ - base_rmq_ref=master
+
+elixir:
+ - '1.10'
+otp_release:
+ - '22.3'
+ - '23.0'
+
+install:
+ # This project being an Erlang one (we just set language to Elixir
+ # to ensure it is installed), we don't want Travis to run mix(1)
+ # automatically as it will break.
+ skip
+
+script:
+ # $current_rmq_ref is also used by rabbitmq-components.mk to select
+ # the appropriate branch for dependencies.
+ - make check-rabbitmq-components.mk
+ current_rmq_ref="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}"
+ - make xref
+ current_rmq_ref="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}"
+ - make tests
+ current_rmq_ref="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}"
+
+after_failure:
+ - |
+ cd "$TRAVIS_BUILD_DIR"
+ if test -d logs && test "$AWS_ACCESS_KEY_ID" && test "$AWS_SECRET_ACCESS_KEY"; then
+ archive_name="$(basename "$TRAVIS_REPO_SLUG")-$TRAVIS_JOB_NUMBER"
+
+ tar -c --transform "s/^logs/${archive_name}/" -f - logs | \
+ xz > "${archive_name}.tar.xz"
+
+ aws s3 cp "${archive_name}.tar.xz" s3://server-release-pipeline/travis-ci-logs/ \
+ --region eu-west-1 \
+ --acl public-read
+ fi
diff --git a/deps/rabbitmq_management/CODE_OF_CONDUCT.md b/deps/rabbitmq_management/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000..08697906fd
--- /dev/null
+++ b/deps/rabbitmq_management/CODE_OF_CONDUCT.md
@@ -0,0 +1,44 @@
+# Contributor Code of Conduct
+
+As contributors and maintainers of this project, and in the interest of fostering an open
+and welcoming community, we pledge to respect all people who contribute through reporting
+issues, posting feature requests, updating documentation, submitting pull requests or
+patches, and other activities.
+
+We are committed to making participation in this project a harassment-free experience for
+everyone, regardless of level of experience, gender, gender identity and expression,
+sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
+religion, or nationality.
+
+Examples of unacceptable behavior by participants include:
+
+ * The use of sexualized language or imagery
+ * Personal attacks
+ * Trolling or insulting/derogatory comments
+ * Public or private harassment
+ * Publishing other's private information, such as physical or electronic addresses,
+ without explicit permission
+ * Other unethical or unprofessional conduct
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments,
+commits, code, wiki edits, issues, and other contributions that are not aligned to this
+Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
+that they deem inappropriate, threatening, offensive, or harmful.
+
+By adopting this Code of Conduct, project maintainers commit themselves to fairly and
+consistently applying these principles to every aspect of managing this project. Project
+maintainers who do not follow or enforce the Code of Conduct may be permanently removed
+from the project team.
+
+This Code of Conduct applies both within project spaces and in public spaces when an
+individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
+contacting a project maintainer at [info@rabbitmq.com](mailto:info@rabbitmq.com). All complaints will
+be reviewed and investigated and will result in a response that is deemed necessary and
+appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
+with regard to the reporter of an incident.
+
+This Code of Conduct is adapted from the
+[Contributor Covenant](https://contributor-covenant.org), version 1.3.0, available at
+[contributor-covenant.org/version/1/3/0/](https://contributor-covenant.org/version/1/3/0/)
diff --git a/deps/rabbitmq_management/CONTRIBUTING.md b/deps/rabbitmq_management/CONTRIBUTING.md
new file mode 100644
index 0000000000..592e7ced57
--- /dev/null
+++ b/deps/rabbitmq_management/CONTRIBUTING.md
@@ -0,0 +1,99 @@
+Thank you for using RabbitMQ and for taking the time to contribute to the project.
+This document has two main parts:
+
+ * when and how to file GitHub issues for RabbitMQ projects
+ * how to submit pull requests
+
+They intend to save you and RabbitMQ maintainers some time, so please
+take a moment to read through them.
+
+## Overview
+
+### GitHub issues
+
+The RabbitMQ team uses GitHub issues for _specific actionable items_ that
+engineers can work on. This assumes the following:
+
+* GitHub issues are not used for questions, investigations, root cause
+ analysis, discussions of potential issues, etc (as defined by this team)
+* Enough information is provided by the reporter for maintainers to work with
+
+The team receives many questions through various venues every single
+day. Frequently, these questions do not include the necessary details
+the team needs to begin useful work. GitHub issues can very quickly
+turn into a something impossible to navigate and make sense
+of. Because of this, questions, investigations, root cause analysis,
+and discussions of potential features are all considered to be
+[mailing list][rmq-users] material. If you are unsure where to begin,
+the [RabbitMQ users mailing list][rmq-users] is the right place.
+
+Getting all the details necessary to reproduce an issue, make a
+conclusion or even form a hypothesis about what's happening can take a
+fair amount of time. Please help others help you by providing a way to
+reproduce the behavior you're observing, or at least sharing as much
+relevant information as possible on the [RabbitMQ users mailing
+list][rmq-users].
+
+Please provide versions of the software used:
+
+ * RabbitMQ server
+ * Erlang
+ * Operating system version (and distribution, if applicable)
+ * All client libraries used
+ * RabbitMQ plugins (if applicable)
+
+The following information greatly helps in investigating and reproducing issues:
+
+ * RabbitMQ server logs
+ * A code example or terminal transcript that can be used to reproduce
+ * Full exception stack traces (a single line message is not enough!)
+ * `rabbitmqctl report` and `rabbitmqctl environment` output
+ * Other relevant details about the environment and workload, e.g. a traffic capture
+ * Feel free to edit out hostnames and other potentially sensitive information.
+
+To make collecting much of this and other environment information, use
+the [`rabbitmq-collect-env`][rmq-collect-env] script. It will produce an archive with
+server logs, operating system logs, output of certain diagnostics commands and so on.
+Please note that **no effort is made to scrub any information that may be sensitive**.
+
+### Pull Requests
+
+RabbitMQ projects use pull requests to discuss, collaborate on and accept code contributions.
+Pull requests is the primary place of discussing code changes.
+
+Here's the recommended workflow:
+
+ * [Fork the repository][github-fork] or repositories you plan on contributing to. If multiple
+ repositories are involved in addressing the same issue, please use the same branch name
+ in each repository
+ * Create a branch with a descriptive name in the relevant repositories
+ * Make your changes, run tests (usually with `make tests`), commit with a
+ [descriptive message][git-commit-msgs], push to your fork
+ * Submit pull requests with an explanation what has been changed and **why**
+ * Submit a filled out and signed [Contributor Agreement][ca-agreement] if needed (see below)
+ * Be patient. We will get to your pull request eventually
+
+If what you are going to work on is a substantial change, please first
+ask the core team for their opinion on the [RabbitMQ users mailing list][rmq-users].
+
+## Code of Conduct
+
+See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
+
+## Contributor Agreement
+
+If you want to contribute a non-trivial change, please submit a signed
+copy of our [Contributor Agreement][ca-agreement] around the time you
+submit your pull request. This will make it much easier (in some
+cases, possible) for the RabbitMQ team at Pivotal to merge your
+contribution.
+
+## Where to Ask Questions
+
+If something isn't clear, feel free to ask on our [mailing list][rmq-users].
+
+[rmq-collect-env]: https://github.com/rabbitmq/support-tools/blob/master/scripts/rabbitmq-collect-env
+[git-commit-msgs]: https://chris.beams.io/posts/git-commit/
+[rmq-users]: https://groups.google.com/forum/#!forum/rabbitmq-users
+[ca-agreement]: https://cla.pivotal.io/sign/rabbitmq
+[github-fork]: https://help.github.com/articles/fork-a-repo/
diff --git a/deps/rabbitmq_management/Caddyfile b/deps/rabbitmq_management/Caddyfile
new file mode 100644
index 0000000000..04a803d14b
--- /dev/null
+++ b/deps/rabbitmq_management/Caddyfile
@@ -0,0 +1,5 @@
+:2015
+log stdout
+errors stderr
+root priv/www
+proxy /api localhost:15672
diff --git a/deps/rabbitmq_management/LICENSE b/deps/rabbitmq_management/LICENSE
new file mode 100644
index 0000000000..261faf450b
--- /dev/null
+++ b/deps/rabbitmq_management/LICENSE
@@ -0,0 +1,16 @@
+This package, the RabbitMQ Management Plugin is licensed under the MPL 2.0. For the
+MPL 2.0, please see LICENSE-MPL-RabbitMQ.
+
+This package makes use of the following third party libraries:
+
+| Name | Website | License type | License file |
+| -------------- | ------------------------------------------------------ | ------------- | ------------------------ |
+| ExplorerCanvas | https://github.com/arv/explorercanvas | APACHE 2 | LICENSE-APACHE2-excanvas |
+| base64.js | https://code.google.com/p/stringencoders/ | BSD | LICENSE-BSD-base64js |
+| Cowboy | https://ninenines.eu/ | ISC | LICENSE-ISC-cowboy |
+| EJS | https://code.google.com/archive/p/embeddedjavascript/ | MIT | LICENSE-MIT-EJS |
+| Flot | https://github.com/flot/flot | MIT | LICENSE-MIT-Flot |
+| Sammy | https://code.quirkey.com/sammy/ | MIT | LICENSE-MIT-Sammy |
+| jQuery | https://jquery.com/ | MIT | LICENSE-MIT-jQuery |
+
+If you have any questions regarding licensing, please contact us at info@rabbitmq.com.
diff --git a/deps/rabbitmq_management/LICENSE-APACHE2-excanvas b/deps/rabbitmq_management/LICENSE-APACHE2-excanvas
new file mode 100644
index 0000000000..62589edd12
--- /dev/null
+++ b/deps/rabbitmq_management/LICENSE-APACHE2-excanvas
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ https://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/deps/rabbitmq_management/LICENSE-BSD-base64js b/deps/rabbitmq_management/LICENSE-BSD-base64js
new file mode 100644
index 0000000000..bc116b0231
--- /dev/null
+++ b/deps/rabbitmq_management/LICENSE-BSD-base64js
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010 Nick Galbreath
+ * https://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+*/
diff --git a/deps/rabbitmq_management/LICENSE-ISC-cowboy b/deps/rabbitmq_management/LICENSE-ISC-cowboy
new file mode 100644
index 0000000000..9d28158fb7
--- /dev/null
+++ b/deps/rabbitmq_management/LICENSE-ISC-cowboy
@@ -0,0 +1,13 @@
+Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/deps/rabbitmq_management/LICENSE-MIT-EJS b/deps/rabbitmq_management/LICENSE-MIT-EJS
new file mode 100644
index 0000000000..5990a35a7e
--- /dev/null
+++ b/deps/rabbitmq_management/LICENSE-MIT-EJS
@@ -0,0 +1,21 @@
+EJS - Embedded JavaScript
+
+Copyright (c) 2007 Edward Benson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/deps/rabbitmq_management/LICENSE-MIT-Flot b/deps/rabbitmq_management/LICENSE-MIT-Flot
new file mode 100644
index 0000000000..719da064fe
--- /dev/null
+++ b/deps/rabbitmq_management/LICENSE-MIT-Flot
@@ -0,0 +1,22 @@
+Copyright (c) 2007-2014 IOLA and Ole Laursen
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/deps/rabbitmq_management/LICENSE-MIT-Sammy b/deps/rabbitmq_management/LICENSE-MIT-Sammy
new file mode 100644
index 0000000000..ad77bb8e6b
--- /dev/null
+++ b/deps/rabbitmq_management/LICENSE-MIT-Sammy
@@ -0,0 +1,22 @@
+Copyright (c) 2008 Aaron Quint, Quirkey NYC, LLC
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/deps/rabbitmq_management/LICENSE-MIT-jQuery b/deps/rabbitmq_management/LICENSE-MIT-jQuery
new file mode 100644
index 0000000000..68b4f25813
--- /dev/null
+++ b/deps/rabbitmq_management/LICENSE-MIT-jQuery
@@ -0,0 +1,20 @@
+Copyright (c) 2011 John Resig, https://jquery.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/deps/rabbitmq_management/LICENSE-MPL-RabbitMQ b/deps/rabbitmq_management/LICENSE-MPL-RabbitMQ
new file mode 100644
index 0000000000..14e2f777f6
--- /dev/null
+++ b/deps/rabbitmq_management/LICENSE-MPL-RabbitMQ
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/deps/rabbitmq_management/Makefile b/deps/rabbitmq_management/Makefile
new file mode 100644
index 0000000000..2704f1f5d9
--- /dev/null
+++ b/deps/rabbitmq_management/Makefile
@@ -0,0 +1,52 @@
+PROJECT = rabbitmq_management
+PROJECT_DESCRIPTION = RabbitMQ Management Console
+PROJECT_MOD = rabbit_mgmt_app
+
+define PROJECT_ENV
+[
+ {http_log_dir, none},
+ {load_definitions, none},
+ {management_db_cache_multiplier, 5},
+ {process_stats_gc_timeout, 300000},
+ {stats_event_max_backlog, 250},
+
+ {cors_allow_origins, []},
+ {cors_max_age, 1800},
+ {content_security_policy, "script-src 'self' 'unsafe-eval' 'unsafe-inline'; object-src 'self'"}
+ ]
+endef
+
+define PROJECT_APP_EXTRA_KEYS
+ {broker_version_requirements, []}
+endef
+
+DEPS = rabbit_common rabbit amqp_client cowboy cowlib rabbitmq_web_dispatch rabbitmq_management_agent
+TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers proper
+LOCAL_DEPS += mnesia ranch ssl crypto public_key
+
+# FIXME: Add Ranch as a BUILD_DEPS to be sure the correct version is picked.
+# See rabbitmq-components.mk.
+BUILD_DEPS += ranch
+
+DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk
+DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk
+
+# FIXME: Use erlang.mk patched for RabbitMQ, while waiting for PRs to be
+# reviewed and merged.
+
+ERLANG_MK_REPO = https://github.com/rabbitmq/erlang.mk.git
+ERLANG_MK_COMMIT = rabbitmq-tmp
+
+include rabbitmq-components.mk
+include erlang.mk
+
+# --------------------------------------------------------------------
+# Distribution.
+# --------------------------------------------------------------------
+
+list-dist-deps::
+ @echo bin/rabbitmqadmin
+
+prepare-dist::
+ $(verbose) sed 's/%%VSN%%/$(PROJECT_VERSION)/' bin/rabbitmqadmin \
+ > $(EZ_DIR)/priv/www/cli/rabbitmqadmin
diff --git a/deps/rabbitmq_management/README.md b/deps/rabbitmq_management/README.md
new file mode 100644
index 0000000000..8073b31f95
--- /dev/null
+++ b/deps/rabbitmq_management/README.md
@@ -0,0 +1,21 @@
+[![Test Erlang 22.3](https://img.shields.io/github/workflow/status/rabbitmq/rabbitmq-management/Test%20-%20Erlang%2022.3/master?label=Erlang%2022.3)](https://github.com/rabbitmq/rabbitmq-management/actions?query=workflow%3A%22Test+-+Erlang+22.3%22+branch%3A%22master%22)
+[![Test Erlang 23](https://img.shields.io/github/workflow/status/rabbitmq/rabbitmq-management/Test%20-%20Erlang%2021.3/master?label=Erlang%2023.0)](https://github.com/rabbitmq/rabbitmq-management/actions?query=workflow%3A%22Test+-+Erlang+23.0%22+branch%3A%22master%22)
+
+# RabbitMQ Management Plugin
+
+This plugin provides a management UI and HTTP API for RabbitMQ.
+
+## Installation
+
+This plugin is included in the RabbitMQ distribution. Like all [plugins](https://www.rabbitmq.com/plugins.html),
+it has to be [enabled](https://www.rabbitmq.com/plugins.html#basics) before it can be used.
+
+
+## Documentation
+
+ * [RabbitMQ management UI documentation](https://www.rabbitmq.com/management.html).
+ * [HTTP API documentation](https://www.rabbitmq.com/management.html#http-api) and [reference](https://raw.githack.com/rabbitmq/rabbitmq-management/rabbitmq_v3_6_9/priv/www/api/index.html)
+
+## Copyright
+
+(c) 2007-2020 VMware, Inc. or its affiliates.
diff --git a/deps/rabbitmq_management/bin/rabbitmqadmin b/deps/rabbitmq_management/bin/rabbitmqadmin
new file mode 100755
index 0000000000..90509627a1
--- /dev/null
+++ b/deps/rabbitmq_management/bin/rabbitmqadmin
@@ -0,0 +1,1184 @@
+#!/usr/bin/env python3
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+from __future__ import print_function
+
+from optparse import OptionParser, TitledHelpFormatter
+
+import base64
+import copy
+import json
+import os
+import socket
+import ssl
+import traceback
+
+try:
+ from signal import signal, SIGPIPE, SIG_DFL
+ signal(SIGPIPE, SIG_DFL)
+except ImportError:
+ pass
+
+import sys
+
+
+def eprint(*args, **kwargs):
+ print(*args, file=sys.stderr, **kwargs)
+
+
+if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] < 6):
+ eprint("Sorry, rabbitmqadmin requires at least Python 2.6 (2.7.9 when HTTPS is enabled).")
+ sys.exit(1)
+
+if sys.version_info[0] == 2:
+ from ConfigParser import ConfigParser, NoSectionError
+ import httplib
+ import urlparse
+ from urllib import quote_plus
+ from urllib import quote
+
+ def b64(s):
+ return base64.b64encode(s)
+else:
+ from configparser import ConfigParser, NoSectionError
+ import http.client as httplib
+ import urllib.parse as urlparse
+ from urllib.parse import quote_plus
+ from urllib.parse import quote
+
+ def b64(s):
+ return base64.b64encode(s.encode('utf-8')).decode('utf-8')
+
+if sys.version_info[0] == 2:
+ class ConnectionError(OSError):
+ pass
+
+ class ConnectionRefusedError(ConnectionError):
+ pass
+
+VERSION = '%%VSN%%'
+
+LISTABLE = {'connections': {'vhost': False, 'cols': ['name', 'user', 'channels']},
+ 'channels': {'vhost': False, 'cols': ['name', 'user']},
+ 'consumers': {'vhost': True},
+ 'exchanges': {'vhost': True, 'cols': ['name', 'type']},
+ 'queues': {'vhost': True, 'cols': ['name', 'messages']},
+ 'bindings': {'vhost': True, 'cols': ['source', 'destination',
+ 'routing_key']},
+ 'users': {'vhost': False},
+ 'vhosts': {'vhost': False, 'cols': ['name', 'messages']},
+ 'permissions': {'vhost': False},
+ 'nodes': {'vhost': False, 'cols': ['name', 'type', 'mem_used']},
+ 'parameters': {'vhost': False, 'json': ['value']},
+ 'policies': {'vhost': False, 'json': ['definition']},
+ 'operator_policies': {'vhost': False, 'json': ['definition']},
+ 'vhost_limits': {'vhost': False, 'json': ['value']}}
+
+SHOWABLE = {'overview': {'vhost': False, 'cols': ['rabbitmq_version',
+ 'cluster_name',
+ 'queue_totals.messages',
+ 'object_totals.queues']}}
+
+PROMOTE_COLUMNS = ['vhost', 'name', 'type',
+ 'source', 'destination', 'destination_type', 'routing_key']
+
+URIS = {
+ 'exchange': '/exchanges/{vhost}/{name}',
+ 'queue': '/queues/{vhost}/{name}',
+ 'binding': '/bindings/{vhost}/e/{source}/{destination_char}/{destination}',
+ 'binding_del': '/bindings/{vhost}/e/{source}/{destination_char}/{destination}/{properties_key}',
+ 'vhost': '/vhosts/{name}',
+ 'user': '/users/{name}',
+ 'permission': '/permissions/{vhost}/{user}',
+ 'parameter': '/parameters/{component}/{vhost}/{name}',
+ 'policy': '/policies/{vhost}/{name}',
+ 'operator_policy': '/operator-policies/{vhost}/{name}',
+ 'vhost_limit': '/vhost-limits/{vhost}/{name}'
+ }
+
+
+def queue_upload_fixup(upload):
+ # rabbitmq/rabbitmq-management#761
+ #
+ # In general, the fixup_upload argument can be used to fixup/change the
+ # upload dict after all argument parsing is complete.
+ #
+ # This simplifies setting the queue type for a new queue by allowing the
+ # user to use a queue_type=quorum argument rather than the somewhat confusing
+ # arguments='{"x-queue-type":"quorum"}' parameter
+ #
+ if 'queue_type' in upload:
+ queue_type = upload.get('queue_type')
+ arguments = upload.get('arguments', {})
+ arguments['x-queue-type'] = queue_type
+ upload['arguments'] = arguments
+
+
+DECLARABLE = {
+ 'exchange': {'mandatory': ['name', 'type'],
+ 'json': ['arguments'],
+ 'optional': {'auto_delete': 'false', 'durable': 'true',
+ 'internal': 'false', 'arguments': {}}},
+ 'queue': {'mandatory': ['name'],
+ 'json': ['arguments'],
+ 'optional': {'auto_delete': 'false', 'durable': 'true',
+ 'arguments': {}, 'node': None, 'queue_type': None},
+ 'fixup_upload': queue_upload_fixup},
+ 'binding': {'mandatory': ['source', 'destination'],
+ 'json': ['arguments'],
+ 'optional': {'destination_type': 'queue',
+ 'routing_key': '', 'arguments': {}}},
+ 'vhost': {'mandatory': ['name'],
+ 'optional': {'tracing': None}},
+ 'user': {'mandatory': ['name', ['password', 'password_hash'], 'tags'],
+ 'optional': {'hashing_algorithm': None}},
+ 'permission': {'mandatory': ['vhost', 'user', 'configure', 'write', 'read'],
+ 'optional': {}},
+ 'parameter': {'mandatory': ['component', 'name', 'value'],
+ 'json': ['value'],
+ 'optional': {}},
+ # priority has to be converted to an integer
+ 'policy': {'mandatory': ['name', 'pattern', 'definition'],
+ 'json': ['definition', 'priority'],
+ 'optional': {'priority': 0, 'apply-to': None}},
+ 'operator_policy': {'mandatory': ['name', 'pattern', 'definition'],
+ 'json': ['definition', 'priority'],
+ 'optional': {'priority': 0, 'apply-to': None}},
+ 'vhost_limit': {'mandatory': ['vhost', 'name', 'value'],
+ 'json': ['value'],
+ 'optional': {}},
+ }
+
+DELETABLE = {
+ 'exchange': {'mandatory': ['name']},
+ 'queue': {'mandatory': ['name']},
+ 'binding': {'mandatory': ['source', 'destination_type', 'destination'],
+ 'optional': {'properties_key': '~'}},
+ 'vhost': {'mandatory': ['name']},
+ 'user': {'mandatory': ['name']},
+ 'permission': {'mandatory': ['vhost', 'user']},
+ 'parameter': {'mandatory': ['component', 'name']},
+ 'policy': {'mandatory': ['name']},
+ 'operator_policy': {'mandatory': ['name']},
+ 'vhost_limit': {'mandatory': ['vhost', 'name']}
+ }
+
+CLOSABLE = {
+ 'connection': {'mandatory': ['name'],
+ 'optional': {},
+ 'uri': '/connections/{name}'}
+ }
+
+PURGABLE = {
+ 'queue': {'mandatory': ['name'],
+ 'optional': {},
+ 'uri': '/queues/{vhost}/{name}/contents'}
+ }
+
+EXTRA_VERBS = {
+ 'publish': {'mandatory': ['routing_key'],
+ 'optional': {'payload': None,
+ 'properties': {},
+ 'exchange': 'amq.default',
+ 'payload_encoding': 'string'},
+ 'json': ['properties'],
+ 'uri': '/exchanges/{vhost}/{exchange}/publish'},
+ 'get': {'mandatory': ['queue'],
+ 'optional': {'count': '1', 'ackmode': 'ack_requeue_true',
+ 'payload_file': None, 'encoding': 'auto'},
+ 'uri': '/queues/{vhost}/{queue}/get'}
+}
+
+for k in DECLARABLE:
+ DECLARABLE[k]['uri'] = URIS[k]
+
+for k in DELETABLE:
+ DELETABLE[k]['uri'] = URIS[k]
+ DELETABLE[k]['optional'] = DELETABLE[k].get('optional', {})
+DELETABLE['binding']['uri'] = URIS['binding_del']
+
+
+def short_usage():
+ return "rabbitmqadmin [options] subcommand"
+
+
+def title(name):
+ return "\n%s\n%s\n\n" % (name, '=' * len(name))
+
+
+def subcommands_usage():
+ usage = """Usage
+=====
+ """ + short_usage() + """
+
+ where subcommand is one of:
+""" + title("Display")
+
+ for l in LISTABLE:
+ usage += " list {0} [<column>...]\n".format(l)
+ for s in SHOWABLE:
+ usage += " show {0} [<column>...]\n".format(s)
+ usage += title("Object Manipulation")
+ usage += fmt_usage_stanza(DECLARABLE, 'declare')
+ usage += fmt_usage_stanza(DELETABLE, 'delete')
+ usage += fmt_usage_stanza(CLOSABLE, 'close')
+ usage += fmt_usage_stanza(PURGABLE, 'purge')
+ usage += title("Broker Definitions")
+ usage += """ export <file>
+ import <file>
+"""
+ usage += title("Publishing and Consuming")
+ usage += fmt_usage_stanza(EXTRA_VERBS, '')
+ usage += """
+ * If payload is not specified on publish, standard input is used
+
+ * If payload_file is not specified on get, the payload will be shown on
+ standard output along with the message metadata
+
+ * If payload_file is specified on get, count must not be set
+"""
+ return usage
+
+
+def config_usage():
+ usage = "Usage\n=====\n" + short_usage()
+ usage += "\n" + title("Configuration File")
+ usage += """ It is possible to specify a configuration file from the command line.
+ Hosts can be configured easily in a configuration file and called
+ from the command line.
+"""
+ usage += title("Example")
+ usage += """ # rabbitmqadmin.conf.example START
+
+ [host_normal]
+ hostname = localhost
+ port = 15672
+ username = guest
+ password = guest
+ declare_vhost = / # Used as default for declare / delete only
+ vhost = / # Used as default for declare / delete / list
+
+ [host_ssl]
+ hostname = otherhost
+ port = 15672
+ username = guest
+ password = guest
+ ssl = True
+ ssl_key_file = /path/to/key.pem
+ ssl_cert_file = /path/to/cert.pem
+
+ # rabbitmqadmin.conf.example END
+"""
+ usage += title("Use")
+ usage += """ rabbitmqadmin -c rabbitmqadmin.conf.example -N host_normal ..."""
+ return usage
+
+
+def more_help():
+ return """
+More Help
+=========
+
+For more help use the help subcommand:
+
+ rabbitmqadmin help subcommands # For a list of available subcommands
+ rabbitmqadmin help config # For help with the configuration file
+"""
+
+
+def fmt_required_flag(val):
+ # when one of the options is required, e.g.
+ # password vs. password_hash
+ if type(val) is list:
+ # flag1=... OR flag2=... OR flag3=...
+ return "=... OR ".join(val)
+ else:
+ return val
+
+
+def fmt_optional_flag(val):
+ return val
+
+
+def fmt_usage_stanza(root, verb):
+ def fmt_args(args):
+ res = " ".join(["{0}=...".format(fmt_required_flag(a)) for a in args['mandatory']])
+ opts = " ".join("{0}=...".format(fmt_optional_flag(o)) for o in args['optional'].keys())
+ if opts != "":
+ res += " [{0}]".format(opts)
+ return res
+
+ text = ""
+ if verb != "":
+ verb = " " + verb
+ for k in root.keys():
+ text += " {0} {1} {2}\n".format(verb, k, fmt_args(root[k]))
+ return text
+
+
+default_options = {"hostname": "localhost",
+ "port": "15672",
+ # default config file section name
+ "node": "default",
+ "path_prefix": "",
+ "declare_vhost": "/",
+ "username": "guest",
+ "password": "guest",
+ "ssl": False,
+ "request_timeout": 120,
+ "verbose": True,
+ "format": "table",
+ "depth": 1,
+ "bash_completion": False}
+
+
+class MyFormatter(TitledHelpFormatter):
+ def format_epilog(self, epilog):
+ return epilog
+
+
+parser = OptionParser(usage=short_usage(),
+ formatter=MyFormatter(),
+ epilog=more_help())
+
+
+def make_parser():
+ def add(*args, **kwargs):
+ key = kwargs['dest']
+ if key in default_options:
+ default = " [default: %s]" % default_options[key]
+ kwargs['help'] = kwargs['help'] + default
+ parser.add_option(*args, **kwargs)
+
+ add("-c", "--config", dest="config",
+ help="configuration file [default: ~/.rabbitmqadmin.conf]",
+ metavar="CONFIG")
+ add("-N", "--node", dest="node",
+ help="node described in the configuration file [default: 'default' only if configuration file is specified]",
+ metavar="NODE")
+ add("-H", "--host", dest="hostname",
+ help="connect to host HOST",
+ metavar="HOST")
+ add("-P", "--port", dest="port",
+ help="connect to port PORT",
+ metavar="PORT")
+ add("--path-prefix", dest="path_prefix",
+ help="use specific URI path prefix for the RabbitMQ HTTP API. /api and operation path will be appended to it. (default: blank string)")
+ add("-V", "--vhost", dest="vhost",
+ help="connect to vhost VHOST [default: all vhosts for list, '/' for declare]",
+ metavar="VHOST")
+ add("-u", "--username", dest="username",
+ help="connect using username USERNAME",
+ metavar="USERNAME")
+ add("-p", "--password", dest="password",
+ help="connect using password PASSWORD",
+ metavar="PASSWORD")
+ add("-U", "--base-uri", dest="base_uri",
+ help="connect using a base HTTP API URI. /api and operation path will be appended to it. Path will be ignored. --vhost has to be provided separately.",
+ metavar="URI")
+ add("-q", "--quiet", action="store_false", dest="verbose",
+ help="suppress status messages")
+ add("-s", "--ssl", action="store_true", dest="ssl",
+ help="connect with ssl")
+ add("--ssl-key-file", dest="ssl_key_file",
+ help="PEM format key file for SSL")
+ add("--ssl-cert-file", dest="ssl_cert_file",
+ help="PEM format certificate file for SSL")
+ add("--ssl-ca-cert-file", dest="ssl_ca_cert_file",
+ help="PEM format CA certificate file for SSL")
+ add("--ssl-disable-hostname-verification", dest="ssl_disable_hostname_verification",
+ help="Disables peer hostname verification", default=False, action="store_true")
+ add("-k", "--ssl-insecure", dest="ssl_insecure",
+ help="Disables all SSL validations like curl's '-k' argument", default=False, action="store_true")
+ add("-t", "--request-timeout", dest="request_timeout",
+ help="HTTP request timeout in seconds", type="int")
+ add("-f", "--format", dest="format",
+ help="format for listing commands - one of [" + ", ".join(FORMATS.keys()) + "]")
+ add("-S", "--sort", dest="sort", help="sort key for listing queries")
+ add("-R", "--sort-reverse", action="store_true", dest="sort_reverse",
+ help="reverse the sort order")
+ add("-d", "--depth", dest="depth",
+ help="maximum depth to recurse for listing tables")
+ add("--bash-completion", action="store_true",
+ dest="bash_completion",
+ help="Print bash completion script")
+ add("--version", action="store_true",
+ dest="version",
+ help="Display version and exit")
+
+
+def default_config():
+ home = os.getenv('USERPROFILE') or os.getenv('HOME')
+ if home is not None:
+ config_file = home + os.sep + ".rabbitmqadmin.conf"
+ if os.path.isfile(config_file):
+ return config_file
+ return None
+
+
+def make_configuration():
+ make_parser()
+ (cli_options, args) = parser.parse_args()
+
+ if cli_options.version:
+ print_version()
+
+ setattr(cli_options, "declare_vhost", None)
+ final_options = copy.copy(cli_options)
+
+ # Resolve config file path
+ if cli_options.config is None:
+ config_file = default_config()
+ if config_file is not None:
+ setattr(final_options, "config", config_file)
+ else:
+ if not os.path.isfile(cli_options.config):
+ assert_usage(False, "Could not read config file '%s'" % cli_options.config)
+
+ final_options = merge_default_options(cli_options, final_options)
+ final_options = merge_config_file_options(cli_options, final_options)
+ final_options = expand_base_uri_options(cli_options, final_options)
+
+ return (final_options, args)
+
+def merge_default_options(cli_options, final_options):
+ for (key, default_val) in default_options.items():
+ if getattr(cli_options, key) is None:
+ setattr(final_options, key, default_val)
+ return final_options
+
+def merge_config_file_options(cli_options, final_options):
+ # Parse config file and load it, making sure that CLI flags
+ # take precedence
+ if final_options.config is not None:
+ config_parser = ConfigParser()
+ try:
+ config_parser.read(final_options.config)
+ section_settings = dict(config_parser.items(final_options.node))
+ except NoSectionError as error:
+ # Report if an explicitly provided section (node) does not exist in the file
+ if final_options.node == "default":
+ pass
+ else:
+ msg = "Could not read section '%s' in config file '%s':\n %s" % (final_options.node, final_options.config, error)
+ assert_usage(False, msg)
+ else:
+ for key, section_val in section_settings.items():
+ # special case --ssl
+ if key == 'ssl':
+ setattr(final_options, key, section_val == "True")
+ else:
+ # if CLI options do not contain this key, set it from the config file
+ if getattr(cli_options, key) is None:
+ setattr(final_options, key, section_val)
+ return final_options
+
+def expand_base_uri_options(cli_options, final_options):
+ # if --base-uri is passed, set connection parameters from it
+ if final_options.base_uri is not None:
+ u = urlparse.urlparse(final_options.base_uri)
+ for key in ["hostname", "port", "username", "password"]:
+ if getattr(u, key) is not None:
+ setattr(final_options, key, getattr(u, key))
+
+ if u.path is not None and (u.path != "") and (u.path != "/"):
+ eprint("WARNING: path in --base-uri is ignored. Please specify --vhost and/or --path-prefix separately.\n")
+ return final_options
+
+def assert_usage(expr, error):
+ if not expr:
+ eprint("\nERROR: {0}\n".format(error))
+ eprint("{0} --help for help\n".format(os.path.basename(sys.argv[0])))
+ sys.exit(1)
+
+
+def print_version():
+ print("rabbitmqadmin {0}".format(VERSION))
+ sys.exit(0)
+
+
+def column_sort_key(col):
+ if col in PROMOTE_COLUMNS:
+ return (1, PROMOTE_COLUMNS.index(col))
+ else:
+ return (2, col)
+
+
+def main():
+ (options, args) = make_configuration()
+ if options.bash_completion:
+ print_bash_completion()
+ sys.exit(0)
+ assert_usage(len(args) > 0, 'Action not specified')
+ mgmt = Management(options, args[1:])
+ mode = "invoke_" + args[0]
+ assert_usage(hasattr(mgmt, mode),
+ 'Action {0} not understood'.format(args[0]))
+ method = getattr(mgmt, "invoke_%s" % args[0])
+ method()
+
+
+def die(s):
+ eprint("*** {0}\n".format(s))
+ sys.exit(1)
+
+
+def maybe_utf8(s):
+ if isinstance(s, int):
+ # s can be also an int for ex messages count
+ return str(s)
+ if isinstance(s, float):
+ # s can be also a float for message rate
+ return str(s)
+ if sys.version_info[0] == 3:
+ # It will have an encoding, which Python will respect
+ return s
+ else:
+ # It won't have an encoding, and Python will pick ASCII by default
+ return s.encode('utf-8')
+
+
+class Management:
+ def __init__(self, options, args):
+ self.options = options
+ self.args = args
+
+ def get(self, path):
+ return self.http("GET", "%s/api%s" % (self.options.path_prefix, path), "")
+
+ def put(self, path, body):
+ return self.http("PUT", "%s/api%s" % (self.options.path_prefix, path), body)
+
+ def post(self, path, body):
+ return self.http("POST", "%s/api%s" % (self.options.path_prefix, path), body)
+
+ def delete(self, path):
+ return self.http("DELETE", "%s/api%s" % (self.options.path_prefix, path), "")
+
+ def __initialize_connection(self, hostname, port):
+ if self.options.ssl:
+ return self.__initialize_https_connection(hostname, port)
+ else:
+ return httplib.HTTPConnection(hostname, port, timeout=self.options.request_timeout)
+
+ def __initialize_https_connection(self, hostname, port):
+ # Python 2.7.9+
+ if hasattr(ssl, 'create_default_context'):
+ return httplib.HTTPSConnection(hostname, port, context=self.__initialize_tls_context())
+ # Python < 2.7.8, note: those versions still have SSLv3 enabled
+ # and other limitations. See rabbitmq/rabbitmq-management#225
+ else:
+ eprint("WARNING: rabbitmqadmin requires Python 2.7.9+ when HTTPS is used.")
+ return httplib.HTTPSConnection(hostname, port,
+ cert_file=self.options.ssl_cert_file,
+ key_file=self.options.ssl_key_file)
+
+ def __initialize_tls_context(self):
+ # Python 2.7.9+ only
+ ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
+ ssl_ctx.options &= ~ssl.OP_NO_SSLv3
+
+ ssl_insecure = self.options.ssl_insecure
+ ssl_disable_hostname_verification = ssl_insecure or self.options.ssl_disable_hostname_verification
+ # Note: you must set check_hostname prior to verify_mode
+ if ssl_disable_hostname_verification:
+ ssl_ctx.check_hostname = False
+ if ssl_insecure:
+ ssl_ctx.verify_mode = ssl.CERT_NONE
+
+ if self.options.ssl_key_file:
+ ssl_ctx.load_cert_chain(self.options.ssl_cert_file,
+ self.options.ssl_key_file)
+ if self.options.ssl_ca_cert_file:
+ ssl_ctx.load_verify_locations(self.options.ssl_ca_cert_file)
+ return ssl_ctx
+
+ def http(self, method, path, body):
+ conn = self.__initialize_connection(self.options.hostname, self.options.port)
+ auth = (self.options.username + ":" + self.options.password)
+
+ headers = {"Authorization": "Basic " + b64(auth)}
+ if body != "":
+ headers["Content-Type"] = "application/json"
+ try:
+ conn.request(method, path, body, headers)
+ except ConnectionRefusedError as e:
+ die("Could not connect: {0}".format(e))
+ except socket.error as e:
+ traceback.print_exc()
+ die("Could not connect: {0}".format(e))
+ try:
+ resp = conn.getresponse()
+ except socket.timeout:
+ die("Timed out getting HTTP response (request timeout: {0} seconds)".format(
+ self.options.request_timeout))
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except (Exception):
+ e_fmt = traceback.format_exc()
+ die("Error getting HTTP response:\n\n{0}".format(e_fmt))
+ if resp.status == 400:
+ die(json.loads(resp.read())['reason'])
+ if resp.status == 401:
+ die("Access refused: {0}".format(path))
+ if resp.status == 404:
+ die("Not found: {0}".format(path))
+ if resp.status == 301:
+ url = urlparse.urlparse(resp.getheader('location'))
+ [host, port] = url.netloc.split(':')
+ self.options.hostname = host
+ self.options.port = int(port)
+ return self.http(method, url.path + '?' + url.query, body)
+ if resp.status > 400:
+ raise Exception("Received response %d %s for path %s\n%s"
+ % (resp.status, resp.reason, path, resp.read()))
+ return resp.read().decode('utf-8')
+
+ def verbose(self, string):
+ if self.options.verbose:
+ print(string)
+
+ def get_arg(self):
+ assert_usage(len(self.args) == 1, 'Exactly one argument required')
+ return self.args[0]
+
+ def use_cols(self):
+ # Deliberately do not cast to int here; we only care about the
+ # default, not explicit setting.
+ return self.options.depth == 1 and ('json' not in self.options.format)
+
+ def invoke_help(self):
+ if len(self.args) == 0:
+ parser.print_help()
+ else:
+ help_cmd = self.get_arg()
+ if help_cmd == 'subcommands':
+ usage = subcommands_usage()
+ elif help_cmd == 'config':
+ usage = config_usage()
+ else:
+ assert_usage(False, """help topic must be one of:
+ subcommands
+ config""")
+ print(usage)
+ sys.exit(0)
+
+ def invoke_publish(self):
+ (uri, upload) = self.parse_args(self.args, EXTRA_VERBS['publish'])
+ if 'payload' not in upload:
+ data = sys.stdin.read()
+ upload['payload'] = b64(data)
+ upload['payload_encoding'] = 'base64'
+ resp = json.loads(self.post(uri, json.dumps(upload)))
+ if resp['routed']:
+ self.verbose("Message published")
+ else:
+ self.verbose("Message published but NOT routed")
+
+ def invoke_get(self):
+ (uri, upload) = self.parse_args(self.args, EXTRA_VERBS['get'])
+ payload_file = 'payload_file' in upload and upload['payload_file'] or None
+ assert_usage(not payload_file or upload['count'] == '1',
+ 'Cannot get multiple messages using payload_file')
+ result = self.post(uri, json.dumps(upload))
+ if payload_file:
+ write_payload_file(payload_file, result)
+ columns = ['routing_key', 'exchange', 'message_count',
+ 'payload_bytes', 'redelivered']
+ format_list(result, columns, {}, self.options)
+ else:
+ format_list(result, [], {}, self.options)
+
+ def invoke_export(self):
+ path = self.get_arg()
+ uri = "/definitions"
+ if self.options.vhost:
+ uri += "/%s" % quote_plus(self.options.vhost)
+ definitions = self.get(uri)
+ with open(path, 'wb') as f:
+ f.write(definitions.encode())
+ self.verbose("Exported definitions for %s to \"%s\""
+ % (self.options.hostname, path))
+
+ def invoke_import(self):
+ path = self.get_arg()
+ with open(path, 'rb') as f:
+ definitions = f.read()
+ uri = "/definitions"
+ if self.options.vhost:
+ uri += "/%s" % quote_plus(self.options.vhost)
+ self.post(uri, definitions)
+ self.verbose("Uploaded definitions from \"%s\" to %s. The import process may take some time. Consult server logs to track progress."
+ % (self.options.hostname, path))
+
+ def invoke_list(self):
+ (uri, obj_info, cols) = self.list_show_uri(LISTABLE, 'list')
+ format_list(self.get(uri), cols, obj_info, self.options)
+
+ def invoke_show(self):
+ (uri, obj_info, cols) = self.list_show_uri(SHOWABLE, 'show')
+ format_list('[{0}]'.format(self.get(uri)), cols, obj_info, self.options)
+
+ def _list_path_for_obj_type(self, obj_type):
+ # This returns a URL path for given object type, e.g.
+ # replaces underscores in command names with
+ # dashes that HTTP API endpoints use
+ return obj_type.replace("_", "-")
+
+ def list_show_uri(self, obj_types, verb):
+ obj_type = self.args[0]
+ assert_usage(obj_type in obj_types,
+ "Don't know how to {0} {1}".format(verb, obj_type))
+ obj_info = obj_types[obj_type]
+ uri = "/%s" % self._list_path_for_obj_type(obj_type)
+ query = []
+ if obj_info['vhost'] and self.options.vhost:
+ uri += "/%s" % quote_plus(self.options.vhost)
+ cols = self.args[1:]
+ if cols == [] and 'cols' in obj_info and self.use_cols():
+ cols = obj_info['cols']
+ if cols != []:
+ query.append("columns=" + ",".join(cols))
+ sort = self.options.sort
+ if sort:
+ query.append("sort=" + sort)
+ if self.options.sort_reverse:
+ query.append("sort_reverse=true")
+ query = "&".join(query)
+ if query != "":
+ uri += "?" + query
+ return (uri, obj_info, cols)
+
+ def invoke_declare(self):
+ (obj_type, uri, upload) = self.declare_delete_parse(DECLARABLE)
+ if obj_type == 'binding':
+ self.post(uri, json.dumps(upload))
+ else:
+ self.put(uri, json.dumps(upload))
+ self.verbose("{0} declared".format(obj_type))
+
+ def invoke_delete(self):
+ (obj_type, uri, upload) = self.declare_delete_parse(DELETABLE)
+ self.delete(uri)
+ self.verbose("{0} deleted".format(obj_type))
+
+ def invoke_close(self):
+ (obj_type, uri, upload) = self.declare_delete_parse(CLOSABLE)
+ self.delete(uri)
+ self.verbose("{0} closed".format(obj_type))
+
+ def invoke_purge(self):
+ (obj_type, uri, upload) = self.declare_delete_parse(PURGABLE)
+ self.delete(uri)
+ self.verbose("{0} purged".format(obj_type))
+
+ def declare_delete_parse(self, root):
+ assert_usage(len(self.args) > 0, 'Type not specified')
+ obj_type = self.args[0]
+ assert_usage(obj_type in root,
+ 'Type {0} not recognised'.format(obj_type))
+ obj = root[obj_type]
+ (uri, upload) = self.parse_args(self.args[1:], obj)
+ return (obj_type, uri, upload)
+
+ def assert_mandatory_keys(self, mandatory, upload):
+ for m in mandatory:
+ if type(m) is list:
+ a_set = set(m)
+ b_set = set(upload.keys())
+ assert_usage((a_set & b_set),
+ 'one of mandatory arguments "{0}" is required'.format(m))
+ else:
+ assert_usage(m in upload.keys(),
+ 'mandatory argument "{0}" is required'.format(m))
+
+ def parse_args(self, args, obj):
+ mandatory = obj['mandatory']
+ optional = obj['optional']
+ uri_template = obj['uri']
+ upload = {}
+ for k in optional.keys():
+ if optional[k] is not None:
+ upload[k] = optional[k]
+ for arg in args:
+ assert_usage("=" in arg,
+ 'Argument "{0}" not in the name=value format'.format(arg))
+ (name, value) = arg.split("=", 1)
+ # flatten the list of mandatory keys
+ mandatory_keys = []
+ for key in mandatory:
+ if type(key) is list:
+ for subkey in key:
+ mandatory_keys.append(subkey)
+ else:
+ mandatory_keys.append(key)
+
+ assert_usage(name in mandatory_keys or name in optional.keys(),
+ 'Argument "{0}" is not recognised'.format(name))
+
+ if 'json' in obj and name in obj['json']:
+ upload[name] = self.parse_json(value)
+ else:
+ upload[name] = value
+ self.assert_mandatory_keys(mandatory, upload)
+ if 'vhost' not in mandatory:
+ upload['vhost'] = self.options.vhost or self.options.declare_vhost
+ uri_args = {}
+ for k in upload:
+ v = upload[k]
+ if v and isinstance(v, (str, bytes)):
+ uri_args[k] = quote(v, '')
+ if k == 'destination_type':
+ uri_args['destination_char'] = v[0]
+ uri = uri_template.format(**uri_args)
+ if 'fixup_upload' in obj:
+ fixup = obj['fixup_upload']
+ fixup(upload)
+ return (uri, upload)
+
+ def parse_json(self, text):
+ try:
+ return json.loads(text)
+ except ValueError:
+ eprint("ERROR: Could not parse JSON:\n {0}".format(text))
+ sys.exit(1)
+
+
+def format_list(json_list, columns, args, options):
+ format = options.format
+ formatter = None
+ if format == "raw_json":
+ print(json_list)
+ return
+ elif format == "pretty_json":
+ json_list_parsed = json.loads(json_list)
+ print(json.dumps(json_list_parsed,
+ skipkeys=False, ensure_ascii=False, check_circular=True,
+ allow_nan=True, sort_keys=True, indent=2))
+ return
+ else:
+ formatter = FORMATS[format]
+ assert_usage(formatter is not None,
+ "Format {0} not recognised".format(format))
+ formatter_instance = formatter(columns, args, options)
+ formatter_instance.display(json_list)
+
+
+class Lister:
+ def verbose(self, string):
+ if self.options.verbose:
+ print(string)
+
+ def display(self, json_list):
+ depth = sys.maxsize
+ if len(self.columns) == 0:
+ depth = int(self.options.depth)
+ (columns, table) = self.list_to_table(json.loads(json_list), depth)
+ if len(table) > 0:
+ self.display_list(columns, table)
+ else:
+ self.verbose("No items")
+
+ def list_to_table(self, items, max_depth):
+ columns = {}
+ column_ix = {}
+ row = None
+ table = []
+
+ def add(prefix, depth, item, fun):
+ for key in item:
+ column = prefix == '' and key or (prefix + '.' + key)
+ subitem = item[key]
+ if type(subitem) == dict:
+ if 'json' in self.obj_info and key in self.obj_info['json']:
+ fun(column, json.dumps(subitem))
+ else:
+ if depth < max_depth:
+ add(column, depth + 1, subitem, fun)
+ elif type(subitem) == list:
+ # The first branch has mirrors in queues in
+ # mind (which come out looking decent); the second
+ # one has applications in nodes (which look less
+ # so, but what would look good?).
+ if [x for x in subitem if type(x) != str] == []:
+ serialised = " ".join(subitem)
+ else:
+ serialised = json.dumps(subitem)
+ fun(column, serialised)
+ else:
+ fun(column, subitem)
+
+ def add_to_columns(col, val):
+ columns[col] = True
+
+ def add_to_row(col, val):
+ if col in column_ix:
+ if val is not None:
+ row[column_ix[col]] = maybe_utf8(val)
+ else:
+ row[column_ix[col]] = None
+
+ if len(self.columns) == 0:
+ for item in items:
+ add('', 1, item, add_to_columns)
+ columns = list(columns.keys())
+ columns.sort(key=column_sort_key)
+ else:
+ columns = self.columns
+
+ for i in range(0, len(columns)):
+ column_ix[columns[i]] = i
+ for item in items:
+ row = len(columns) * ['']
+ add('', 1, item, add_to_row)
+ table.append(row)
+
+ return (columns, table)
+
+
+class TSVList(Lister):
+ def __init__(self, columns, obj_info, options):
+ self.columns = columns
+ self.obj_info = obj_info
+ self.options = options
+
+ def display_list(self, columns, table):
+ head = "\t".join(columns)
+ self.verbose(head)
+
+ for row in table:
+ line = "\t".join(row)
+ print(line)
+
+
+class LongList(Lister):
+ def __init__(self, columns, obj_info, options):
+ self.columns = columns
+ self.obj_info = obj_info
+ self.options = options
+
+ def display_list(self, columns, table):
+ sep = "\n" + "-" * 80 + "\n"
+ max_width = 0
+ for col in columns:
+ max_width = max(max_width, len(col))
+ fmt = "{0:>" + str(max_width) + "}: {1}"
+ print(sep)
+ for i in range(0, len(table)):
+ for j in range(0, len(columns)):
+ print(fmt.format(columns[j], table[i][j]))
+ print(sep)
+
+
+class TableList(Lister):
+ def __init__(self, columns, obj_info, options):
+ self.columns = columns
+ self.obj_info = obj_info
+ self.options = options
+
+ def display_list(self, columns, table):
+ total = [columns]
+ total.extend(table)
+ self.ascii_table(total)
+
+ def ascii_table(self, rows):
+ col_widths = [0] * len(rows[0])
+ for i in range(0, len(rows[0])):
+ for j in range(0, len(rows)):
+ col_widths[i] = max(col_widths[i], len(rows[j][i]))
+ self.ascii_bar(col_widths)
+ self.ascii_row(col_widths, rows[0], "^")
+ self.ascii_bar(col_widths)
+ for row in rows[1:]:
+ self.ascii_row(col_widths, row, "<")
+ self.ascii_bar(col_widths)
+
+ def ascii_row(self, col_widths, row, align):
+ txt = "|"
+ for i in range(0, len(col_widths)):
+ fmt = " {0:" + align + str(col_widths[i]) + "} "
+ txt += fmt.format(row[i]) + "|"
+ print(txt)
+
+ def ascii_bar(self, col_widths):
+ txt = "+"
+ for w in col_widths:
+ txt += ("-" * (w + 2)) + "+"
+ print(txt)
+
+
+class KeyValueList(Lister):
+ def __init__(self, columns, obj_info, options):
+ self.columns = columns
+ self.obj_info = obj_info
+ self.options = options
+
+ def display_list(self, columns, table):
+ for i in range(0, len(table)):
+ row = []
+ for j in range(0, len(columns)):
+ row.append("{0}=\"{1}\"".format(columns[j], table[i][j]))
+ print(" ".join(row))
+
+
+# TODO handle spaces etc in completable names
+class BashList(Lister):
+ def __init__(self, columns, obj_info, options):
+ self.columns = columns
+ self.obj_info = obj_info
+ self.options = options
+
+ def display_list(self, columns, table):
+ ix = None
+ for i in range(0, len(columns)):
+ if columns[i] == 'name':
+ ix = i
+ if ix is not None:
+ res = []
+ for row in table:
+ res.append(row[ix])
+ print(" ".join(res))
+
+
+FORMATS = {
+ # Special cased
+ 'raw_json': None,
+ # Ditto
+ 'pretty_json': None,
+ 'tsv': TSVList,
+ 'long': LongList,
+ 'table': TableList,
+ 'kvp': KeyValueList,
+ 'bash': BashList
+}
+
+
+def write_payload_file(payload_file, json_list):
+ result = json.loads(json_list)[0]
+ payload = result['payload']
+ payload_encoding = result['payload_encoding']
+ with open(payload_file, 'wb') as f:
+ if payload_encoding == 'base64':
+ data = base64.b64decode(payload)
+ else:
+ data = payload
+ f.write(data.encode("utf-8"))
+
+
+def print_bash_completion():
+ script = """# This is a bash completion script for rabbitmqadmin.
+# Redirect it to a file, then source it or copy it to /etc/bash_completion.d
+# to get tab completion. rabbitmqadmin must be on your PATH for this to work.
+_rabbitmqadmin()
+{
+ local cur prev opts base
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+
+ opts="list show declare delete close purge import export get publish help"
+ fargs="--help --host --port --vhost --username --password --format --depth --sort --sort-reverse"
+
+ case "${prev}" in
+ list)
+ COMPREPLY=( $(compgen -W '""" + " ".join(LISTABLE) + """' -- ${cur}) )
+ return 0
+ ;;
+ show)
+ COMPREPLY=( $(compgen -W '""" + " ".join(SHOWABLE) + """' -- ${cur}) )
+ return 0
+ ;;
+ declare)
+ COMPREPLY=( $(compgen -W '""" + " ".join(DECLARABLE.keys()) + """' -- ${cur}) )
+ return 0
+ ;;
+ delete)
+ COMPREPLY=( $(compgen -W '""" + " ".join(DELETABLE.keys()) + """' -- ${cur}) )
+ return 0
+ ;;
+ close)
+ COMPREPLY=( $(compgen -W '""" + " ".join(CLOSABLE.keys()) + """' -- ${cur}) )
+ return 0
+ ;;
+ purge)
+ COMPREPLY=( $(compgen -W '""" + " ".join(PURGABLE.keys()) + """' -- ${cur}) )
+ return 0
+ ;;
+ export)
+ COMPREPLY=( $(compgen -f ${cur}) )
+ return 0
+ ;;
+ import)
+ COMPREPLY=( $(compgen -f ${cur}) )
+ return 0
+ ;;
+ help)
+ opts="subcommands config"
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
+ -H)
+ COMPREPLY=( $(compgen -A hostname ${cur}) )
+ return 0
+ ;;
+ --host)
+ COMPREPLY=( $(compgen -A hostname ${cur}) )
+ return 0
+ ;;
+ -V)
+ opts="$(rabbitmqadmin -q -f bash list vhosts)"
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
+ --vhost)
+ opts="$(rabbitmqadmin -q -f bash list vhosts)"
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
+ -u)
+ opts="$(rabbitmqadmin -q -f bash list users)"
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
+ --username)
+ opts="$(rabbitmqadmin -q -f bash list users)"
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
+ -f)
+ COMPREPLY=( $(compgen -W \"""" + " ".join(FORMATS.keys()) + """\" -- ${cur}) )
+ return 0
+ ;;
+ --format)
+ COMPREPLY=( $(compgen -W \"""" + " ".join(FORMATS.keys()) + """\" -- ${cur}) )
+ return 0
+ ;;
+
+"""
+ for l in LISTABLE:
+ key = l[0:len(l) - 1]
+ script += " " + key + """)
+ opts="$(rabbitmqadmin -q -f bash list """ + l + """)"
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
+"""
+ script += """ *)
+ ;;
+ esac
+
+ COMPREPLY=($(compgen -W "${opts} ${fargs}" -- ${cur}))
+ return 0
+}
+complete -F _rabbitmqadmin rabbitmqadmin
+"""
+ print(script)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/deps/rabbitmq_management/erlang.mk b/deps/rabbitmq_management/erlang.mk
new file mode 100644
index 0000000000..fce4be0b0a
--- /dev/null
+++ b/deps/rabbitmq_management/erlang.mk
@@ -0,0 +1,7808 @@
+# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+.PHONY: all app apps deps search rel relup docs install-docs check tests clean distclean help erlang-mk
+
+ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST)))
+export ERLANG_MK_FILENAME
+
+ERLANG_MK_VERSION = 2019.07.01-40-geb3e4b0
+ERLANG_MK_WITHOUT =
+
+# Make 3.81 and 3.82 are deprecated.
+
+ifeq ($(MAKELEVEL)$(MAKE_VERSION),03.81)
+$(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html)
+endif
+
+ifeq ($(MAKELEVEL)$(MAKE_VERSION),03.82)
+$(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html)
+endif
+
+# Core configuration.
+
+PROJECT ?= $(notdir $(CURDIR))
+PROJECT := $(strip $(PROJECT))
+
+PROJECT_VERSION ?= rolling
+PROJECT_MOD ?= $(PROJECT)_app
+PROJECT_ENV ?= []
+
+# Verbosity.
+
+V ?= 0
+
+verbose_0 = @
+verbose_2 = set -x;
+verbose = $(verbose_$(V))
+
+ifeq ($(V),3)
+SHELL := $(SHELL) -x
+endif
+
+gen_verbose_0 = @echo " GEN " $@;
+gen_verbose_2 = set -x;
+gen_verbose = $(gen_verbose_$(V))
+
+gen_verbose_esc_0 = @echo " GEN " $$@;
+gen_verbose_esc_2 = set -x;
+gen_verbose_esc = $(gen_verbose_esc_$(V))
+
+# Temporary files directory.
+
+ERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk
+export ERLANG_MK_TMP
+
+# "erl" command.
+
+ERL = erl +A1 -noinput -boot no_dot_erlang
+
+# Platform detection.
+
+ifeq ($(PLATFORM),)
+UNAME_S := $(shell uname -s)
+
+ifeq ($(UNAME_S),Linux)
+PLATFORM = linux
+else ifeq ($(UNAME_S),Darwin)
+PLATFORM = darwin
+else ifeq ($(UNAME_S),SunOS)
+PLATFORM = solaris
+else ifeq ($(UNAME_S),GNU)
+PLATFORM = gnu
+else ifeq ($(UNAME_S),FreeBSD)
+PLATFORM = freebsd
+else ifeq ($(UNAME_S),NetBSD)
+PLATFORM = netbsd
+else ifeq ($(UNAME_S),OpenBSD)
+PLATFORM = openbsd
+else ifeq ($(UNAME_S),DragonFly)
+PLATFORM = dragonfly
+else ifeq ($(shell uname -o),Msys)
+PLATFORM = msys2
+else
+$(error Unable to detect platform. Please open a ticket with the output of uname -a.)
+endif
+
+export PLATFORM
+endif
+
+# Core targets.
+
+all:: deps app rel
+
+# Noop to avoid a Make warning when there's nothing to do.
+rel::
+ $(verbose) :
+
+relup:: deps app
+
+check:: tests
+
+clean:: clean-crashdump
+
+clean-crashdump:
+ifneq ($(wildcard erl_crash.dump),)
+ $(gen_verbose) rm -f erl_crash.dump
+endif
+
+distclean:: clean distclean-tmp
+
+$(ERLANG_MK_TMP):
+ $(verbose) mkdir -p $(ERLANG_MK_TMP)
+
+distclean-tmp:
+ $(gen_verbose) rm -rf $(ERLANG_MK_TMP)
+
+help::
+ $(verbose) printf "%s\n" \
+ "erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \
+ "Copyright (c) 2013-2016 Loïc Hoguin <essen@ninenines.eu>" \
+ "" \
+ "Usage: [V=1] $(MAKE) [target]..." \
+ "" \
+ "Core targets:" \
+ " all Run deps, app and rel targets in that order" \
+ " app Compile the project" \
+ " deps Fetch dependencies (if needed) and compile them" \
+ " fetch-deps Fetch dependencies recursively (if needed) without compiling them" \
+ " list-deps List dependencies recursively on stdout" \
+ " search q=... Search for a package in the built-in index" \
+ " rel Build a release for this project, if applicable" \
+ " docs Build the documentation for this project" \
+ " install-docs Install the man pages for this project" \
+ " check Compile and run all tests and analysis for this project" \
+ " tests Run the tests for this project" \
+ " clean Delete temporary and output files from most targets" \
+ " distclean Delete all temporary and output files" \
+ " help Display this help and exit" \
+ " erlang-mk Update erlang.mk to the latest version"
+
+# Core functions.
+
+empty :=
+space := $(empty) $(empty)
+tab := $(empty) $(empty)
+comma := ,
+
+define newline
+
+
+endef
+
+define comma_list
+$(subst $(space),$(comma),$(strip $(1)))
+endef
+
+define escape_dquotes
+$(subst ",\",$1)
+endef
+
+# Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy.
+define erlang
+$(ERL) $2 -pz $(ERLANG_MK_TMP)/rebar/ebin -eval "$(subst $(newline),,$(call escape_dquotes,$1))" -- erlang.mk
+endef
+
+ifeq ($(PLATFORM),msys2)
+core_native_path = $(shell cygpath -m $1)
+else
+core_native_path = $1
+endif
+
+core_http_get = curl -Lf$(if $(filter-out 0,$(V)),,s)o $(call core_native_path,$1) $2
+
+core_eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))
+
+# We skip files that contain spaces because they end up causing issues.
+core_find = $(if $(wildcard $1),$(shell find $(1:%/=%) \( -type l -o -type f \) -name $(subst *,\*,$2) | grep -v " "))
+
+core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1)))))))))))))))))))))))))))
+
+core_ls = $(filter-out $(1),$(shell echo $(1)))
+
+# @todo Use a solution that does not require using perl.
+core_relpath = $(shell perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $1 $2)
+
+define core_render
+ printf -- '$(subst $(newline),\n,$(subst %,%%,$(subst ','\'',$(subst $(tab),$(WS),$(call $(1))))))\n' > $(2)
+endef
+
+# Automated update.
+
+ERLANG_MK_REPO ?= https://github.com/ninenines/erlang.mk
+ERLANG_MK_COMMIT ?=
+ERLANG_MK_BUILD_CONFIG ?= build.config
+ERLANG_MK_BUILD_DIR ?= .erlang.mk.build
+
+erlang-mk: WITHOUT ?= $(ERLANG_MK_WITHOUT)
+erlang-mk:
+ifdef ERLANG_MK_COMMIT
+ $(verbose) git clone $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR)
+ $(verbose) cd $(ERLANG_MK_BUILD_DIR) && git checkout $(ERLANG_MK_COMMIT)
+else
+ $(verbose) git clone --depth 1 $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR)
+endif
+ $(verbose) if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR)/build.config; fi
+ $(gen_verbose) $(MAKE) --no-print-directory -C $(ERLANG_MK_BUILD_DIR) WITHOUT='$(strip $(WITHOUT))' UPGRADE=1
+ $(verbose) cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk
+ $(verbose) rm -rf $(ERLANG_MK_BUILD_DIR)
+ $(verbose) rm -rf $(ERLANG_MK_TMP)
+
+# The erlang.mk package index is bundled in the default erlang.mk build.
+# Search for the string "copyright" to skip to the rest of the code.
+
+# Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: distclean-kerl
+
+KERL_INSTALL_DIR ?= $(HOME)/erlang
+
+ifeq ($(strip $(KERL)),)
+KERL := $(ERLANG_MK_TMP)/kerl/kerl
+endif
+
+KERL_DIR = $(ERLANG_MK_TMP)/kerl
+
+export KERL
+
+KERL_GIT ?= https://github.com/kerl/kerl
+KERL_COMMIT ?= master
+
+KERL_MAKEFLAGS ?=
+
+OTP_GIT ?= https://github.com/erlang/otp
+
+define kerl_otp_target
+$(KERL_INSTALL_DIR)/$(1): $(KERL)
+ $(verbose) if [ ! -d $$@ ]; then \
+ MAKEFLAGS="$(KERL_MAKEFLAGS)" $(KERL) build git $(OTP_GIT) $(1) $(1); \
+ $(KERL) install $(1) $(KERL_INSTALL_DIR)/$(1); \
+ fi
+endef
+
+define kerl_hipe_target
+$(KERL_INSTALL_DIR)/$1-native: $(KERL)
+ $(verbose) if [ ! -d $$@ ]; then \
+ KERL_CONFIGURE_OPTIONS=--enable-native-libs \
+ MAKEFLAGS="$(KERL_MAKEFLAGS)" $(KERL) build git $(OTP_GIT) $1 $1-native; \
+ $(KERL) install $1-native $(KERL_INSTALL_DIR)/$1-native; \
+ fi
+endef
+
+$(KERL): $(KERL_DIR)
+
+$(KERL_DIR): | $(ERLANG_MK_TMP)
+ $(gen_verbose) git clone --depth 1 $(KERL_GIT) $(ERLANG_MK_TMP)/kerl
+ $(verbose) cd $(ERLANG_MK_TMP)/kerl && git checkout $(KERL_COMMIT)
+ $(verbose) chmod +x $(KERL)
+
+distclean:: distclean-kerl
+
+distclean-kerl:
+ $(gen_verbose) rm -rf $(KERL_DIR)
+
+# Allow users to select which version of Erlang/OTP to use for a project.
+
+ifneq ($(strip $(LATEST_ERLANG_OTP)),)
+# In some environments it is necessary to filter out master.
+ERLANG_OTP := $(notdir $(lastword $(sort\
+ $(filter-out $(KERL_INSTALL_DIR)/master $(KERL_INSTALL_DIR)/OTP_R%,\
+ $(filter-out %-rc1 %-rc2 %-rc3,$(wildcard $(KERL_INSTALL_DIR)/*[^-native]))))))
+endif
+
+ERLANG_OTP ?=
+ERLANG_HIPE ?=
+
+# Use kerl to enforce a specific Erlang/OTP version for a project.
+ifneq ($(strip $(ERLANG_OTP)),)
+export PATH := $(KERL_INSTALL_DIR)/$(ERLANG_OTP)/bin:$(PATH)
+SHELL := env PATH=$(PATH) $(SHELL)
+$(eval $(call kerl_otp_target,$(ERLANG_OTP)))
+
+# Build Erlang/OTP only if it doesn't already exist.
+ifeq ($(wildcard $(KERL_INSTALL_DIR)/$(ERLANG_OTP))$(BUILD_ERLANG_OTP),)
+$(info Building Erlang/OTP $(ERLANG_OTP)... Please wait...)
+$(shell $(MAKE) $(KERL_INSTALL_DIR)/$(ERLANG_OTP) ERLANG_OTP=$(ERLANG_OTP) BUILD_ERLANG_OTP=1 >&2)
+endif
+
+else
+# Same for a HiPE enabled VM.
+ifneq ($(strip $(ERLANG_HIPE)),)
+export PATH := $(KERL_INSTALL_DIR)/$(ERLANG_HIPE)-native/bin:$(PATH)
+SHELL := env PATH=$(PATH) $(SHELL)
+$(eval $(call kerl_hipe_target,$(ERLANG_HIPE)))
+
+# Build Erlang/OTP only if it doesn't already exist.
+ifeq ($(wildcard $(KERL_INSTALL_DIR)/$(ERLANG_HIPE)-native)$(BUILD_ERLANG_OTP),)
+$(info Building HiPE-enabled Erlang/OTP $(ERLANG_OTP)... Please wait...)
+$(shell $(MAKE) $(KERL_INSTALL_DIR)/$(ERLANG_HIPE)-native ERLANG_HIPE=$(ERLANG_HIPE) BUILD_ERLANG_OTP=1 >&2)
+endif
+
+endif
+endif
+
+PACKAGES += aberth
+pkg_aberth_name = aberth
+pkg_aberth_description = Generic BERT-RPC server in Erlang
+pkg_aberth_homepage = https://github.com/a13x/aberth
+pkg_aberth_fetch = git
+pkg_aberth_repo = https://github.com/a13x/aberth
+pkg_aberth_commit = master
+
+PACKAGES += active
+pkg_active_name = active
+pkg_active_description = Active development for Erlang: rebuild and reload source/binary files while the VM is running
+pkg_active_homepage = https://github.com/proger/active
+pkg_active_fetch = git
+pkg_active_repo = https://github.com/proger/active
+pkg_active_commit = master
+
+PACKAGES += actordb_core
+pkg_actordb_core_name = actordb_core
+pkg_actordb_core_description = ActorDB main source
+pkg_actordb_core_homepage = http://www.actordb.com/
+pkg_actordb_core_fetch = git
+pkg_actordb_core_repo = https://github.com/biokoda/actordb_core
+pkg_actordb_core_commit = master
+
+PACKAGES += actordb_thrift
+pkg_actordb_thrift_name = actordb_thrift
+pkg_actordb_thrift_description = Thrift API for ActorDB
+pkg_actordb_thrift_homepage = http://www.actordb.com/
+pkg_actordb_thrift_fetch = git
+pkg_actordb_thrift_repo = https://github.com/biokoda/actordb_thrift
+pkg_actordb_thrift_commit = master
+
+PACKAGES += aleppo
+pkg_aleppo_name = aleppo
+pkg_aleppo_description = Alternative Erlang Pre-Processor
+pkg_aleppo_homepage = https://github.com/ErlyORM/aleppo
+pkg_aleppo_fetch = git
+pkg_aleppo_repo = https://github.com/ErlyORM/aleppo
+pkg_aleppo_commit = master
+
+PACKAGES += alog
+pkg_alog_name = alog
+pkg_alog_description = Simply the best logging framework for Erlang
+pkg_alog_homepage = https://github.com/siberian-fast-food/alogger
+pkg_alog_fetch = git
+pkg_alog_repo = https://github.com/siberian-fast-food/alogger
+pkg_alog_commit = master
+
+PACKAGES += amqp_client
+pkg_amqp_client_name = amqp_client
+pkg_amqp_client_description = RabbitMQ Erlang AMQP client
+pkg_amqp_client_homepage = https://www.rabbitmq.com/erlang-client-user-guide.html
+pkg_amqp_client_fetch = git
+pkg_amqp_client_repo = https://github.com/rabbitmq/rabbitmq-erlang-client.git
+pkg_amqp_client_commit = master
+
+PACKAGES += annotations
+pkg_annotations_name = annotations
+pkg_annotations_description = Simple code instrumentation utilities
+pkg_annotations_homepage = https://github.com/hyperthunk/annotations
+pkg_annotations_fetch = git
+pkg_annotations_repo = https://github.com/hyperthunk/annotations
+pkg_annotations_commit = master
+
+PACKAGES += antidote
+pkg_antidote_name = antidote
+pkg_antidote_description = Large-scale computation without synchronisation
+pkg_antidote_homepage = https://syncfree.lip6.fr/
+pkg_antidote_fetch = git
+pkg_antidote_repo = https://github.com/SyncFree/antidote
+pkg_antidote_commit = master
+
+PACKAGES += apns
+pkg_apns_name = apns
+pkg_apns_description = Apple Push Notification Server for Erlang
+pkg_apns_homepage = http://inaka.github.com/apns4erl
+pkg_apns_fetch = git
+pkg_apns_repo = https://github.com/inaka/apns4erl
+pkg_apns_commit = master
+
+PACKAGES += asciideck
+pkg_asciideck_name = asciideck
+pkg_asciideck_description = Asciidoc for Erlang.
+pkg_asciideck_homepage = https://ninenines.eu
+pkg_asciideck_fetch = git
+pkg_asciideck_repo = https://github.com/ninenines/asciideck
+pkg_asciideck_commit = master
+
+PACKAGES += azdht
+pkg_azdht_name = azdht
+pkg_azdht_description = Azureus Distributed Hash Table (DHT) in Erlang
+pkg_azdht_homepage = https://github.com/arcusfelis/azdht
+pkg_azdht_fetch = git
+pkg_azdht_repo = https://github.com/arcusfelis/azdht
+pkg_azdht_commit = master
+
+PACKAGES += backoff
+pkg_backoff_name = backoff
+pkg_backoff_description = Simple exponential backoffs in Erlang
+pkg_backoff_homepage = https://github.com/ferd/backoff
+pkg_backoff_fetch = git
+pkg_backoff_repo = https://github.com/ferd/backoff
+pkg_backoff_commit = master
+
+PACKAGES += barrel_tcp
+pkg_barrel_tcp_name = barrel_tcp
+pkg_barrel_tcp_description = barrel is a generic TCP acceptor pool with low latency in Erlang.
+pkg_barrel_tcp_homepage = https://github.com/benoitc-attic/barrel_tcp
+pkg_barrel_tcp_fetch = git
+pkg_barrel_tcp_repo = https://github.com/benoitc-attic/barrel_tcp
+pkg_barrel_tcp_commit = master
+
+PACKAGES += basho_bench
+pkg_basho_bench_name = basho_bench
+pkg_basho_bench_description = A load-generation and testing tool for basically whatever you can write a returning Erlang function for.
+pkg_basho_bench_homepage = https://github.com/basho/basho_bench
+pkg_basho_bench_fetch = git
+pkg_basho_bench_repo = https://github.com/basho/basho_bench
+pkg_basho_bench_commit = master
+
+PACKAGES += bcrypt
+pkg_bcrypt_name = bcrypt
+pkg_bcrypt_description = Bcrypt Erlang / C library
+pkg_bcrypt_homepage = https://github.com/erlangpack/bcrypt
+pkg_bcrypt_fetch = git
+pkg_bcrypt_repo = https://github.com/erlangpack/bcrypt.git
+pkg_bcrypt_commit = master
+
+PACKAGES += beam
+pkg_beam_name = beam
+pkg_beam_description = BEAM emulator written in Erlang
+pkg_beam_homepage = https://github.com/tonyrog/beam
+pkg_beam_fetch = git
+pkg_beam_repo = https://github.com/tonyrog/beam
+pkg_beam_commit = master
+
+PACKAGES += beanstalk
+pkg_beanstalk_name = beanstalk
+pkg_beanstalk_description = An Erlang client for beanstalkd
+pkg_beanstalk_homepage = https://github.com/tim/erlang-beanstalk
+pkg_beanstalk_fetch = git
+pkg_beanstalk_repo = https://github.com/tim/erlang-beanstalk
+pkg_beanstalk_commit = master
+
+PACKAGES += bear
+pkg_bear_name = bear
+pkg_bear_description = a set of statistics functions for erlang
+pkg_bear_homepage = https://github.com/boundary/bear
+pkg_bear_fetch = git
+pkg_bear_repo = https://github.com/boundary/bear
+pkg_bear_commit = master
+
+PACKAGES += bertconf
+pkg_bertconf_name = bertconf
+pkg_bertconf_description = Make ETS tables out of statc BERT files that are auto-reloaded
+pkg_bertconf_homepage = https://github.com/ferd/bertconf
+pkg_bertconf_fetch = git
+pkg_bertconf_repo = https://github.com/ferd/bertconf
+pkg_bertconf_commit = master
+
+PACKAGES += bifrost
+pkg_bifrost_name = bifrost
+pkg_bifrost_description = Erlang FTP Server Framework
+pkg_bifrost_homepage = https://github.com/thorstadt/bifrost
+pkg_bifrost_fetch = git
+pkg_bifrost_repo = https://github.com/thorstadt/bifrost
+pkg_bifrost_commit = master
+
+PACKAGES += binpp
+pkg_binpp_name = binpp
+pkg_binpp_description = Erlang Binary Pretty Printer
+pkg_binpp_homepage = https://github.com/jtendo/binpp
+pkg_binpp_fetch = git
+pkg_binpp_repo = https://github.com/jtendo/binpp
+pkg_binpp_commit = master
+
+PACKAGES += bisect
+pkg_bisect_name = bisect
+pkg_bisect_description = Ordered fixed-size binary dictionary in Erlang
+pkg_bisect_homepage = https://github.com/knutin/bisect
+pkg_bisect_fetch = git
+pkg_bisect_repo = https://github.com/knutin/bisect
+pkg_bisect_commit = master
+
+PACKAGES += bitcask
+pkg_bitcask_name = bitcask
+pkg_bitcask_description = because you need another a key/value storage engine
+pkg_bitcask_homepage = https://github.com/basho/bitcask
+pkg_bitcask_fetch = git
+pkg_bitcask_repo = https://github.com/basho/bitcask
+pkg_bitcask_commit = develop
+
+PACKAGES += bitstore
+pkg_bitstore_name = bitstore
+pkg_bitstore_description = A document based ontology development environment
+pkg_bitstore_homepage = https://github.com/bdionne/bitstore
+pkg_bitstore_fetch = git
+pkg_bitstore_repo = https://github.com/bdionne/bitstore
+pkg_bitstore_commit = master
+
+PACKAGES += bootstrap
+pkg_bootstrap_name = bootstrap
+pkg_bootstrap_description = A simple, yet powerful Erlang cluster bootstrapping application.
+pkg_bootstrap_homepage = https://github.com/schlagert/bootstrap
+pkg_bootstrap_fetch = git
+pkg_bootstrap_repo = https://github.com/schlagert/bootstrap
+pkg_bootstrap_commit = master
+
+PACKAGES += boss
+pkg_boss_name = boss
+pkg_boss_description = Erlang web MVC, now featuring Comet
+pkg_boss_homepage = https://github.com/ChicagoBoss/ChicagoBoss
+pkg_boss_fetch = git
+pkg_boss_repo = https://github.com/ChicagoBoss/ChicagoBoss
+pkg_boss_commit = master
+
+PACKAGES += boss_db
+pkg_boss_db_name = boss_db
+pkg_boss_db_description = BossDB: a sharded, caching, pooling, evented ORM for Erlang
+pkg_boss_db_homepage = https://github.com/ErlyORM/boss_db
+pkg_boss_db_fetch = git
+pkg_boss_db_repo = https://github.com/ErlyORM/boss_db
+pkg_boss_db_commit = master
+
+PACKAGES += brod
+pkg_brod_name = brod
+pkg_brod_description = Kafka client in Erlang
+pkg_brod_homepage = https://github.com/klarna/brod
+pkg_brod_fetch = git
+pkg_brod_repo = https://github.com/klarna/brod.git
+pkg_brod_commit = master
+
+PACKAGES += bson
+pkg_bson_name = bson
+pkg_bson_description = BSON documents in Erlang, see bsonspec.org
+pkg_bson_homepage = https://github.com/comtihon/bson-erlang
+pkg_bson_fetch = git
+pkg_bson_repo = https://github.com/comtihon/bson-erlang
+pkg_bson_commit = master
+
+PACKAGES += bullet
+pkg_bullet_name = bullet
+pkg_bullet_description = Simple, reliable, efficient streaming for Cowboy.
+pkg_bullet_homepage = http://ninenines.eu
+pkg_bullet_fetch = git
+pkg_bullet_repo = https://github.com/ninenines/bullet
+pkg_bullet_commit = master
+
+PACKAGES += cache
+pkg_cache_name = cache
+pkg_cache_description = Erlang in-memory cache
+pkg_cache_homepage = https://github.com/fogfish/cache
+pkg_cache_fetch = git
+pkg_cache_repo = https://github.com/fogfish/cache
+pkg_cache_commit = master
+
+PACKAGES += cake
+pkg_cake_name = cake
+pkg_cake_description = Really simple terminal colorization
+pkg_cake_homepage = https://github.com/darach/cake-erl
+pkg_cake_fetch = git
+pkg_cake_repo = https://github.com/darach/cake-erl
+pkg_cake_commit = master
+
+PACKAGES += carotene
+pkg_carotene_name = carotene
+pkg_carotene_description = Real-time server
+pkg_carotene_homepage = https://github.com/carotene/carotene
+pkg_carotene_fetch = git
+pkg_carotene_repo = https://github.com/carotene/carotene
+pkg_carotene_commit = master
+
+PACKAGES += cberl
+pkg_cberl_name = cberl
+pkg_cberl_description = NIF based Erlang bindings for Couchbase
+pkg_cberl_homepage = https://github.com/chitika/cberl
+pkg_cberl_fetch = git
+pkg_cberl_repo = https://github.com/chitika/cberl
+pkg_cberl_commit = master
+
+PACKAGES += cecho
+pkg_cecho_name = cecho
+pkg_cecho_description = An ncurses library for Erlang
+pkg_cecho_homepage = https://github.com/mazenharake/cecho
+pkg_cecho_fetch = git
+pkg_cecho_repo = https://github.com/mazenharake/cecho
+pkg_cecho_commit = master
+
+PACKAGES += cferl
+pkg_cferl_name = cferl
+pkg_cferl_description = Rackspace / Open Stack Cloud Files Erlang Client
+pkg_cferl_homepage = https://github.com/ddossot/cferl
+pkg_cferl_fetch = git
+pkg_cferl_repo = https://github.com/ddossot/cferl
+pkg_cferl_commit = master
+
+PACKAGES += chaos_monkey
+pkg_chaos_monkey_name = chaos_monkey
+pkg_chaos_monkey_description = This is The CHAOS MONKEY. It will kill your processes.
+pkg_chaos_monkey_homepage = https://github.com/dLuna/chaos_monkey
+pkg_chaos_monkey_fetch = git
+pkg_chaos_monkey_repo = https://github.com/dLuna/chaos_monkey
+pkg_chaos_monkey_commit = master
+
+PACKAGES += check_node
+pkg_check_node_name = check_node
+pkg_check_node_description = Nagios Scripts for monitoring Riak
+pkg_check_node_homepage = https://github.com/basho-labs/riak_nagios
+pkg_check_node_fetch = git
+pkg_check_node_repo = https://github.com/basho-labs/riak_nagios
+pkg_check_node_commit = master
+
+PACKAGES += chronos
+pkg_chronos_name = chronos
+pkg_chronos_description = Timer module for Erlang that makes it easy to abstact time out of the tests.
+pkg_chronos_homepage = https://github.com/lehoff/chronos
+pkg_chronos_fetch = git
+pkg_chronos_repo = https://github.com/lehoff/chronos
+pkg_chronos_commit = master
+
+PACKAGES += chumak
+pkg_chumak_name = chumak
+pkg_chumak_description = Pure Erlang implementation of ZeroMQ Message Transport Protocol.
+pkg_chumak_homepage = http://choven.ca
+pkg_chumak_fetch = git
+pkg_chumak_repo = https://github.com/chovencorp/chumak
+pkg_chumak_commit = master
+
+PACKAGES += cl
+pkg_cl_name = cl
+pkg_cl_description = OpenCL binding for Erlang
+pkg_cl_homepage = https://github.com/tonyrog/cl
+pkg_cl_fetch = git
+pkg_cl_repo = https://github.com/tonyrog/cl
+pkg_cl_commit = master
+
+PACKAGES += clique
+pkg_clique_name = clique
+pkg_clique_description = CLI Framework for Erlang
+pkg_clique_homepage = https://github.com/basho/clique
+pkg_clique_fetch = git
+pkg_clique_repo = https://github.com/basho/clique
+pkg_clique_commit = develop
+
+PACKAGES += cloudi_core
+pkg_cloudi_core_name = cloudi_core
+pkg_cloudi_core_description = CloudI internal service runtime
+pkg_cloudi_core_homepage = http://cloudi.org/
+pkg_cloudi_core_fetch = git
+pkg_cloudi_core_repo = https://github.com/CloudI/cloudi_core
+pkg_cloudi_core_commit = master
+
+PACKAGES += cloudi_service_api_requests
+pkg_cloudi_service_api_requests_name = cloudi_service_api_requests
+pkg_cloudi_service_api_requests_description = CloudI Service API requests (JSON-RPC/Erlang-term support)
+pkg_cloudi_service_api_requests_homepage = http://cloudi.org/
+pkg_cloudi_service_api_requests_fetch = git
+pkg_cloudi_service_api_requests_repo = https://github.com/CloudI/cloudi_service_api_requests
+pkg_cloudi_service_api_requests_commit = master
+
+PACKAGES += cloudi_service_db
+pkg_cloudi_service_db_name = cloudi_service_db
+pkg_cloudi_service_db_description = CloudI Database (in-memory/testing/generic)
+pkg_cloudi_service_db_homepage = http://cloudi.org/
+pkg_cloudi_service_db_fetch = git
+pkg_cloudi_service_db_repo = https://github.com/CloudI/cloudi_service_db
+pkg_cloudi_service_db_commit = master
+
+PACKAGES += cloudi_service_db_cassandra
+pkg_cloudi_service_db_cassandra_name = cloudi_service_db_cassandra
+pkg_cloudi_service_db_cassandra_description = Cassandra CloudI Service
+pkg_cloudi_service_db_cassandra_homepage = http://cloudi.org/
+pkg_cloudi_service_db_cassandra_fetch = git
+pkg_cloudi_service_db_cassandra_repo = https://github.com/CloudI/cloudi_service_db_cassandra
+pkg_cloudi_service_db_cassandra_commit = master
+
+PACKAGES += cloudi_service_db_cassandra_cql
+pkg_cloudi_service_db_cassandra_cql_name = cloudi_service_db_cassandra_cql
+pkg_cloudi_service_db_cassandra_cql_description = Cassandra CQL CloudI Service
+pkg_cloudi_service_db_cassandra_cql_homepage = http://cloudi.org/
+pkg_cloudi_service_db_cassandra_cql_fetch = git
+pkg_cloudi_service_db_cassandra_cql_repo = https://github.com/CloudI/cloudi_service_db_cassandra_cql
+pkg_cloudi_service_db_cassandra_cql_commit = master
+
+PACKAGES += cloudi_service_db_couchdb
+pkg_cloudi_service_db_couchdb_name = cloudi_service_db_couchdb
+pkg_cloudi_service_db_couchdb_description = CouchDB CloudI Service
+pkg_cloudi_service_db_couchdb_homepage = http://cloudi.org/
+pkg_cloudi_service_db_couchdb_fetch = git
+pkg_cloudi_service_db_couchdb_repo = https://github.com/CloudI/cloudi_service_db_couchdb
+pkg_cloudi_service_db_couchdb_commit = master
+
+PACKAGES += cloudi_service_db_elasticsearch
+pkg_cloudi_service_db_elasticsearch_name = cloudi_service_db_elasticsearch
+pkg_cloudi_service_db_elasticsearch_description = elasticsearch CloudI Service
+pkg_cloudi_service_db_elasticsearch_homepage = http://cloudi.org/
+pkg_cloudi_service_db_elasticsearch_fetch = git
+pkg_cloudi_service_db_elasticsearch_repo = https://github.com/CloudI/cloudi_service_db_elasticsearch
+pkg_cloudi_service_db_elasticsearch_commit = master
+
+PACKAGES += cloudi_service_db_memcached
+pkg_cloudi_service_db_memcached_name = cloudi_service_db_memcached
+pkg_cloudi_service_db_memcached_description = memcached CloudI Service
+pkg_cloudi_service_db_memcached_homepage = http://cloudi.org/
+pkg_cloudi_service_db_memcached_fetch = git
+pkg_cloudi_service_db_memcached_repo = https://github.com/CloudI/cloudi_service_db_memcached
+pkg_cloudi_service_db_memcached_commit = master
+
+PACKAGES += cloudi_service_db_mysql
+pkg_cloudi_service_db_mysql_name = cloudi_service_db_mysql
+pkg_cloudi_service_db_mysql_description = MySQL CloudI Service
+pkg_cloudi_service_db_mysql_homepage = http://cloudi.org/
+pkg_cloudi_service_db_mysql_fetch = git
+pkg_cloudi_service_db_mysql_repo = https://github.com/CloudI/cloudi_service_db_mysql
+pkg_cloudi_service_db_mysql_commit = master
+
+PACKAGES += cloudi_service_db_pgsql
+pkg_cloudi_service_db_pgsql_name = cloudi_service_db_pgsql
+pkg_cloudi_service_db_pgsql_description = PostgreSQL CloudI Service
+pkg_cloudi_service_db_pgsql_homepage = http://cloudi.org/
+pkg_cloudi_service_db_pgsql_fetch = git
+pkg_cloudi_service_db_pgsql_repo = https://github.com/CloudI/cloudi_service_db_pgsql
+pkg_cloudi_service_db_pgsql_commit = master
+
+PACKAGES += cloudi_service_db_riak
+pkg_cloudi_service_db_riak_name = cloudi_service_db_riak
+pkg_cloudi_service_db_riak_description = Riak CloudI Service
+pkg_cloudi_service_db_riak_homepage = http://cloudi.org/
+pkg_cloudi_service_db_riak_fetch = git
+pkg_cloudi_service_db_riak_repo = https://github.com/CloudI/cloudi_service_db_riak
+pkg_cloudi_service_db_riak_commit = master
+
+PACKAGES += cloudi_service_db_tokyotyrant
+pkg_cloudi_service_db_tokyotyrant_name = cloudi_service_db_tokyotyrant
+pkg_cloudi_service_db_tokyotyrant_description = Tokyo Tyrant CloudI Service
+pkg_cloudi_service_db_tokyotyrant_homepage = http://cloudi.org/
+pkg_cloudi_service_db_tokyotyrant_fetch = git
+pkg_cloudi_service_db_tokyotyrant_repo = https://github.com/CloudI/cloudi_service_db_tokyotyrant
+pkg_cloudi_service_db_tokyotyrant_commit = master
+
+PACKAGES += cloudi_service_filesystem
+pkg_cloudi_service_filesystem_name = cloudi_service_filesystem
+pkg_cloudi_service_filesystem_description = Filesystem CloudI Service
+pkg_cloudi_service_filesystem_homepage = http://cloudi.org/
+pkg_cloudi_service_filesystem_fetch = git
+pkg_cloudi_service_filesystem_repo = https://github.com/CloudI/cloudi_service_filesystem
+pkg_cloudi_service_filesystem_commit = master
+
+PACKAGES += cloudi_service_http_client
+pkg_cloudi_service_http_client_name = cloudi_service_http_client
+pkg_cloudi_service_http_client_description = HTTP client CloudI Service
+pkg_cloudi_service_http_client_homepage = http://cloudi.org/
+pkg_cloudi_service_http_client_fetch = git
+pkg_cloudi_service_http_client_repo = https://github.com/CloudI/cloudi_service_http_client
+pkg_cloudi_service_http_client_commit = master
+
+PACKAGES += cloudi_service_http_cowboy
+pkg_cloudi_service_http_cowboy_name = cloudi_service_http_cowboy
+pkg_cloudi_service_http_cowboy_description = cowboy HTTP/HTTPS CloudI Service
+pkg_cloudi_service_http_cowboy_homepage = http://cloudi.org/
+pkg_cloudi_service_http_cowboy_fetch = git
+pkg_cloudi_service_http_cowboy_repo = https://github.com/CloudI/cloudi_service_http_cowboy
+pkg_cloudi_service_http_cowboy_commit = master
+
+PACKAGES += cloudi_service_http_elli
+pkg_cloudi_service_http_elli_name = cloudi_service_http_elli
+pkg_cloudi_service_http_elli_description = elli HTTP CloudI Service
+pkg_cloudi_service_http_elli_homepage = http://cloudi.org/
+pkg_cloudi_service_http_elli_fetch = git
+pkg_cloudi_service_http_elli_repo = https://github.com/CloudI/cloudi_service_http_elli
+pkg_cloudi_service_http_elli_commit = master
+
+PACKAGES += cloudi_service_map_reduce
+pkg_cloudi_service_map_reduce_name = cloudi_service_map_reduce
+pkg_cloudi_service_map_reduce_description = Map/Reduce CloudI Service
+pkg_cloudi_service_map_reduce_homepage = http://cloudi.org/
+pkg_cloudi_service_map_reduce_fetch = git
+pkg_cloudi_service_map_reduce_repo = https://github.com/CloudI/cloudi_service_map_reduce
+pkg_cloudi_service_map_reduce_commit = master
+
+PACKAGES += cloudi_service_oauth1
+pkg_cloudi_service_oauth1_name = cloudi_service_oauth1
+pkg_cloudi_service_oauth1_description = OAuth v1.0 CloudI Service
+pkg_cloudi_service_oauth1_homepage = http://cloudi.org/
+pkg_cloudi_service_oauth1_fetch = git
+pkg_cloudi_service_oauth1_repo = https://github.com/CloudI/cloudi_service_oauth1
+pkg_cloudi_service_oauth1_commit = master
+
+PACKAGES += cloudi_service_queue
+pkg_cloudi_service_queue_name = cloudi_service_queue
+pkg_cloudi_service_queue_description = Persistent Queue Service
+pkg_cloudi_service_queue_homepage = http://cloudi.org/
+pkg_cloudi_service_queue_fetch = git
+pkg_cloudi_service_queue_repo = https://github.com/CloudI/cloudi_service_queue
+pkg_cloudi_service_queue_commit = master
+
+PACKAGES += cloudi_service_quorum
+pkg_cloudi_service_quorum_name = cloudi_service_quorum
+pkg_cloudi_service_quorum_description = CloudI Quorum Service
+pkg_cloudi_service_quorum_homepage = http://cloudi.org/
+pkg_cloudi_service_quorum_fetch = git
+pkg_cloudi_service_quorum_repo = https://github.com/CloudI/cloudi_service_quorum
+pkg_cloudi_service_quorum_commit = master
+
+PACKAGES += cloudi_service_router
+pkg_cloudi_service_router_name = cloudi_service_router
+pkg_cloudi_service_router_description = CloudI Router Service
+pkg_cloudi_service_router_homepage = http://cloudi.org/
+pkg_cloudi_service_router_fetch = git
+pkg_cloudi_service_router_repo = https://github.com/CloudI/cloudi_service_router
+pkg_cloudi_service_router_commit = master
+
+PACKAGES += cloudi_service_tcp
+pkg_cloudi_service_tcp_name = cloudi_service_tcp
+pkg_cloudi_service_tcp_description = TCP CloudI Service
+pkg_cloudi_service_tcp_homepage = http://cloudi.org/
+pkg_cloudi_service_tcp_fetch = git
+pkg_cloudi_service_tcp_repo = https://github.com/CloudI/cloudi_service_tcp
+pkg_cloudi_service_tcp_commit = master
+
+PACKAGES += cloudi_service_timers
+pkg_cloudi_service_timers_name = cloudi_service_timers
+pkg_cloudi_service_timers_description = Timers CloudI Service
+pkg_cloudi_service_timers_homepage = http://cloudi.org/
+pkg_cloudi_service_timers_fetch = git
+pkg_cloudi_service_timers_repo = https://github.com/CloudI/cloudi_service_timers
+pkg_cloudi_service_timers_commit = master
+
+PACKAGES += cloudi_service_udp
+pkg_cloudi_service_udp_name = cloudi_service_udp
+pkg_cloudi_service_udp_description = UDP CloudI Service
+pkg_cloudi_service_udp_homepage = http://cloudi.org/
+pkg_cloudi_service_udp_fetch = git
+pkg_cloudi_service_udp_repo = https://github.com/CloudI/cloudi_service_udp
+pkg_cloudi_service_udp_commit = master
+
+PACKAGES += cloudi_service_validate
+pkg_cloudi_service_validate_name = cloudi_service_validate
+pkg_cloudi_service_validate_description = CloudI Validate Service
+pkg_cloudi_service_validate_homepage = http://cloudi.org/
+pkg_cloudi_service_validate_fetch = git
+pkg_cloudi_service_validate_repo = https://github.com/CloudI/cloudi_service_validate
+pkg_cloudi_service_validate_commit = master
+
+PACKAGES += cloudi_service_zeromq
+pkg_cloudi_service_zeromq_name = cloudi_service_zeromq
+pkg_cloudi_service_zeromq_description = ZeroMQ CloudI Service
+pkg_cloudi_service_zeromq_homepage = http://cloudi.org/
+pkg_cloudi_service_zeromq_fetch = git
+pkg_cloudi_service_zeromq_repo = https://github.com/CloudI/cloudi_service_zeromq
+pkg_cloudi_service_zeromq_commit = master
+
+PACKAGES += cluster_info
+pkg_cluster_info_name = cluster_info
+pkg_cluster_info_description = Fork of Hibari's nifty cluster_info OTP app
+pkg_cluster_info_homepage = https://github.com/basho/cluster_info
+pkg_cluster_info_fetch = git
+pkg_cluster_info_repo = https://github.com/basho/cluster_info
+pkg_cluster_info_commit = master
+
+PACKAGES += color
+pkg_color_name = color
+pkg_color_description = ANSI colors for your Erlang
+pkg_color_homepage = https://github.com/julianduque/erlang-color
+pkg_color_fetch = git
+pkg_color_repo = https://github.com/julianduque/erlang-color
+pkg_color_commit = master
+
+PACKAGES += confetti
+pkg_confetti_name = confetti
+pkg_confetti_description = Erlang configuration provider / application:get_env/2 on steroids
+pkg_confetti_homepage = https://github.com/jtendo/confetti
+pkg_confetti_fetch = git
+pkg_confetti_repo = https://github.com/jtendo/confetti
+pkg_confetti_commit = master
+
+PACKAGES += couchbeam
+pkg_couchbeam_name = couchbeam
+pkg_couchbeam_description = Apache CouchDB client in Erlang
+pkg_couchbeam_homepage = https://github.com/benoitc/couchbeam
+pkg_couchbeam_fetch = git
+pkg_couchbeam_repo = https://github.com/benoitc/couchbeam
+pkg_couchbeam_commit = master
+
+PACKAGES += covertool
+pkg_covertool_name = covertool
+pkg_covertool_description = Tool to convert Erlang cover data files into Cobertura XML reports
+pkg_covertool_homepage = https://github.com/idubrov/covertool
+pkg_covertool_fetch = git
+pkg_covertool_repo = https://github.com/idubrov/covertool
+pkg_covertool_commit = master
+
+PACKAGES += cowboy
+pkg_cowboy_name = cowboy
+pkg_cowboy_description = Small, fast and modular HTTP server.
+pkg_cowboy_homepage = http://ninenines.eu
+pkg_cowboy_fetch = git
+pkg_cowboy_repo = https://github.com/ninenines/cowboy
+pkg_cowboy_commit = 1.0.4
+
+PACKAGES += cowdb
+pkg_cowdb_name = cowdb
+pkg_cowdb_description = Pure Key/Value database library for Erlang Applications
+pkg_cowdb_homepage = https://github.com/refuge/cowdb
+pkg_cowdb_fetch = git
+pkg_cowdb_repo = https://github.com/refuge/cowdb
+pkg_cowdb_commit = master
+
+PACKAGES += cowlib
+pkg_cowlib_name = cowlib
+pkg_cowlib_description = Support library for manipulating Web protocols.
+pkg_cowlib_homepage = http://ninenines.eu
+pkg_cowlib_fetch = git
+pkg_cowlib_repo = https://github.com/ninenines/cowlib
+pkg_cowlib_commit = 1.0.2
+
+PACKAGES += cpg
+pkg_cpg_name = cpg
+pkg_cpg_description = CloudI Process Groups
+pkg_cpg_homepage = https://github.com/okeuday/cpg
+pkg_cpg_fetch = git
+pkg_cpg_repo = https://github.com/okeuday/cpg
+pkg_cpg_commit = master
+
+PACKAGES += cqerl
+pkg_cqerl_name = cqerl
+pkg_cqerl_description = Native Erlang CQL client for Cassandra
+pkg_cqerl_homepage = https://matehat.github.io/cqerl/
+pkg_cqerl_fetch = git
+pkg_cqerl_repo = https://github.com/matehat/cqerl
+pkg_cqerl_commit = master
+
+PACKAGES += cr
+pkg_cr_name = cr
+pkg_cr_description = Chain Replication
+pkg_cr_homepage = https://synrc.com/apps/cr/doc/cr.htm
+pkg_cr_fetch = git
+pkg_cr_repo = https://github.com/spawnproc/cr
+pkg_cr_commit = master
+
+PACKAGES += cuttlefish
+pkg_cuttlefish_name = cuttlefish
+pkg_cuttlefish_description = never lose your childlike sense of wonder baby cuttlefish, promise me?
+pkg_cuttlefish_homepage = https://github.com/basho/cuttlefish
+pkg_cuttlefish_fetch = git
+pkg_cuttlefish_repo = https://github.com/basho/cuttlefish
+pkg_cuttlefish_commit = master
+
+PACKAGES += damocles
+pkg_damocles_name = damocles
+pkg_damocles_description = Erlang library for generating adversarial network conditions for QAing distributed applications/systems on a single Linux box.
+pkg_damocles_homepage = https://github.com/lostcolony/damocles
+pkg_damocles_fetch = git
+pkg_damocles_repo = https://github.com/lostcolony/damocles
+pkg_damocles_commit = master
+
+PACKAGES += debbie
+pkg_debbie_name = debbie
+pkg_debbie_description = .DEB Built In Erlang
+pkg_debbie_homepage = https://github.com/crownedgrouse/debbie
+pkg_debbie_fetch = git
+pkg_debbie_repo = https://github.com/crownedgrouse/debbie
+pkg_debbie_commit = master
+
+PACKAGES += decimal
+pkg_decimal_name = decimal
+pkg_decimal_description = An Erlang decimal arithmetic library
+pkg_decimal_homepage = https://github.com/tim/erlang-decimal
+pkg_decimal_fetch = git
+pkg_decimal_repo = https://github.com/tim/erlang-decimal
+pkg_decimal_commit = master
+
+PACKAGES += detergent
+pkg_detergent_name = detergent
+pkg_detergent_description = An emulsifying Erlang SOAP library
+pkg_detergent_homepage = https://github.com/devinus/detergent
+pkg_detergent_fetch = git
+pkg_detergent_repo = https://github.com/devinus/detergent
+pkg_detergent_commit = master
+
+PACKAGES += detest
+pkg_detest_name = detest
+pkg_detest_description = Tool for running tests on a cluster of erlang nodes
+pkg_detest_homepage = https://github.com/biokoda/detest
+pkg_detest_fetch = git
+pkg_detest_repo = https://github.com/biokoda/detest
+pkg_detest_commit = master
+
+PACKAGES += dh_date
+pkg_dh_date_name = dh_date
+pkg_dh_date_description = Date formatting / parsing library for erlang
+pkg_dh_date_homepage = https://github.com/daleharvey/dh_date
+pkg_dh_date_fetch = git
+pkg_dh_date_repo = https://github.com/daleharvey/dh_date
+pkg_dh_date_commit = master
+
+PACKAGES += dirbusterl
+pkg_dirbusterl_name = dirbusterl
+pkg_dirbusterl_description = DirBuster successor in Erlang
+pkg_dirbusterl_homepage = https://github.com/silentsignal/DirBustErl
+pkg_dirbusterl_fetch = git
+pkg_dirbusterl_repo = https://github.com/silentsignal/DirBustErl
+pkg_dirbusterl_commit = master
+
+PACKAGES += dispcount
+pkg_dispcount_name = dispcount
+pkg_dispcount_description = Erlang task dispatcher based on ETS counters.
+pkg_dispcount_homepage = https://github.com/ferd/dispcount
+pkg_dispcount_fetch = git
+pkg_dispcount_repo = https://github.com/ferd/dispcount
+pkg_dispcount_commit = master
+
+PACKAGES += dlhttpc
+pkg_dlhttpc_name = dlhttpc
+pkg_dlhttpc_description = dispcount-based lhttpc fork for massive amounts of requests to limited endpoints
+pkg_dlhttpc_homepage = https://github.com/ferd/dlhttpc
+pkg_dlhttpc_fetch = git
+pkg_dlhttpc_repo = https://github.com/ferd/dlhttpc
+pkg_dlhttpc_commit = master
+
+PACKAGES += dns
+pkg_dns_name = dns
+pkg_dns_description = Erlang DNS library
+pkg_dns_homepage = https://github.com/aetrion/dns_erlang
+pkg_dns_fetch = git
+pkg_dns_repo = https://github.com/aetrion/dns_erlang
+pkg_dns_commit = master
+
+PACKAGES += dnssd
+pkg_dnssd_name = dnssd
+pkg_dnssd_description = Erlang interface to Apple's Bonjour D NS Service Discovery implementation
+pkg_dnssd_homepage = https://github.com/benoitc/dnssd_erlang
+pkg_dnssd_fetch = git
+pkg_dnssd_repo = https://github.com/benoitc/dnssd_erlang
+pkg_dnssd_commit = master
+
+PACKAGES += dynamic_compile
+pkg_dynamic_compile_name = dynamic_compile
+pkg_dynamic_compile_description = compile and load erlang modules from string input
+pkg_dynamic_compile_homepage = https://github.com/jkvor/dynamic_compile
+pkg_dynamic_compile_fetch = git
+pkg_dynamic_compile_repo = https://github.com/jkvor/dynamic_compile
+pkg_dynamic_compile_commit = master
+
+PACKAGES += e2
+pkg_e2_name = e2
+pkg_e2_description = Library to simply writing correct OTP applications.
+pkg_e2_homepage = http://e2project.org
+pkg_e2_fetch = git
+pkg_e2_repo = https://github.com/gar1t/e2
+pkg_e2_commit = master
+
+PACKAGES += eamf
+pkg_eamf_name = eamf
+pkg_eamf_description = eAMF provides Action Message Format (AMF) support for Erlang
+pkg_eamf_homepage = https://github.com/mrinalwadhwa/eamf
+pkg_eamf_fetch = git
+pkg_eamf_repo = https://github.com/mrinalwadhwa/eamf
+pkg_eamf_commit = master
+
+PACKAGES += eavro
+pkg_eavro_name = eavro
+pkg_eavro_description = Apache Avro encoder/decoder
+pkg_eavro_homepage = https://github.com/SIfoxDevTeam/eavro
+pkg_eavro_fetch = git
+pkg_eavro_repo = https://github.com/SIfoxDevTeam/eavro
+pkg_eavro_commit = master
+
+PACKAGES += ecapnp
+pkg_ecapnp_name = ecapnp
+pkg_ecapnp_description = Cap'n Proto library for Erlang
+pkg_ecapnp_homepage = https://github.com/kaos/ecapnp
+pkg_ecapnp_fetch = git
+pkg_ecapnp_repo = https://github.com/kaos/ecapnp
+pkg_ecapnp_commit = master
+
+PACKAGES += econfig
+pkg_econfig_name = econfig
+pkg_econfig_description = simple Erlang config handler using INI files
+pkg_econfig_homepage = https://github.com/benoitc/econfig
+pkg_econfig_fetch = git
+pkg_econfig_repo = https://github.com/benoitc/econfig
+pkg_econfig_commit = master
+
+PACKAGES += edate
+pkg_edate_name = edate
+pkg_edate_description = date manipulation library for erlang
+pkg_edate_homepage = https://github.com/dweldon/edate
+pkg_edate_fetch = git
+pkg_edate_repo = https://github.com/dweldon/edate
+pkg_edate_commit = master
+
+PACKAGES += edgar
+pkg_edgar_name = edgar
+pkg_edgar_description = Erlang Does GNU AR
+pkg_edgar_homepage = https://github.com/crownedgrouse/edgar
+pkg_edgar_fetch = git
+pkg_edgar_repo = https://github.com/crownedgrouse/edgar
+pkg_edgar_commit = master
+
+PACKAGES += edis
+pkg_edis_name = edis
+pkg_edis_description = An Erlang implementation of Redis KV Store
+pkg_edis_homepage = http://inaka.github.com/edis/
+pkg_edis_fetch = git
+pkg_edis_repo = https://github.com/inaka/edis
+pkg_edis_commit = master
+
+PACKAGES += edns
+pkg_edns_name = edns
+pkg_edns_description = Erlang/OTP DNS server
+pkg_edns_homepage = https://github.com/hcvst/erlang-dns
+pkg_edns_fetch = git
+pkg_edns_repo = https://github.com/hcvst/erlang-dns
+pkg_edns_commit = master
+
+PACKAGES += edown
+pkg_edown_name = edown
+pkg_edown_description = EDoc extension for generating Github-flavored Markdown
+pkg_edown_homepage = https://github.com/uwiger/edown
+pkg_edown_fetch = git
+pkg_edown_repo = https://github.com/uwiger/edown
+pkg_edown_commit = master
+
+PACKAGES += eep
+pkg_eep_name = eep
+pkg_eep_description = Erlang Easy Profiling (eep) application provides a way to analyze application performance and call hierarchy
+pkg_eep_homepage = https://github.com/virtan/eep
+pkg_eep_fetch = git
+pkg_eep_repo = https://github.com/virtan/eep
+pkg_eep_commit = master
+
+PACKAGES += eep_app
+pkg_eep_app_name = eep_app
+pkg_eep_app_description = Embedded Event Processing
+pkg_eep_app_homepage = https://github.com/darach/eep-erl
+pkg_eep_app_fetch = git
+pkg_eep_app_repo = https://github.com/darach/eep-erl
+pkg_eep_app_commit = master
+
+PACKAGES += efene
+pkg_efene_name = efene
+pkg_efene_description = Alternative syntax for the Erlang Programming Language focusing on simplicity, ease of use and programmer UX
+pkg_efene_homepage = https://github.com/efene/efene
+pkg_efene_fetch = git
+pkg_efene_repo = https://github.com/efene/efene
+pkg_efene_commit = master
+
+PACKAGES += egeoip
+pkg_egeoip_name = egeoip
+pkg_egeoip_description = Erlang IP Geolocation module, currently supporting the MaxMind GeoLite City Database.
+pkg_egeoip_homepage = https://github.com/mochi/egeoip
+pkg_egeoip_fetch = git
+pkg_egeoip_repo = https://github.com/mochi/egeoip
+pkg_egeoip_commit = master
+
+PACKAGES += ehsa
+pkg_ehsa_name = ehsa
+pkg_ehsa_description = Erlang HTTP server basic and digest authentication modules
+pkg_ehsa_homepage = https://bitbucket.org/a12n/ehsa
+pkg_ehsa_fetch = hg
+pkg_ehsa_repo = https://bitbucket.org/a12n/ehsa
+pkg_ehsa_commit = default
+
+PACKAGES += ej
+pkg_ej_name = ej
+pkg_ej_description = Helper module for working with Erlang terms representing JSON
+pkg_ej_homepage = https://github.com/seth/ej
+pkg_ej_fetch = git
+pkg_ej_repo = https://github.com/seth/ej
+pkg_ej_commit = master
+
+PACKAGES += ejabberd
+pkg_ejabberd_name = ejabberd
+pkg_ejabberd_description = Robust, ubiquitous and massively scalable Jabber / XMPP Instant Messaging platform
+pkg_ejabberd_homepage = https://github.com/processone/ejabberd
+pkg_ejabberd_fetch = git
+pkg_ejabberd_repo = https://github.com/processone/ejabberd
+pkg_ejabberd_commit = master
+
+PACKAGES += ejwt
+pkg_ejwt_name = ejwt
+pkg_ejwt_description = erlang library for JSON Web Token
+pkg_ejwt_homepage = https://github.com/artefactop/ejwt
+pkg_ejwt_fetch = git
+pkg_ejwt_repo = https://github.com/artefactop/ejwt
+pkg_ejwt_commit = master
+
+PACKAGES += ekaf
+pkg_ekaf_name = ekaf
+pkg_ekaf_description = A minimal, high-performance Kafka client in Erlang.
+pkg_ekaf_homepage = https://github.com/helpshift/ekaf
+pkg_ekaf_fetch = git
+pkg_ekaf_repo = https://github.com/helpshift/ekaf
+pkg_ekaf_commit = master
+
+PACKAGES += elarm
+pkg_elarm_name = elarm
+pkg_elarm_description = Alarm Manager for Erlang.
+pkg_elarm_homepage = https://github.com/esl/elarm
+pkg_elarm_fetch = git
+pkg_elarm_repo = https://github.com/esl/elarm
+pkg_elarm_commit = master
+
+PACKAGES += eleveldb
+pkg_eleveldb_name = eleveldb
+pkg_eleveldb_description = Erlang LevelDB API
+pkg_eleveldb_homepage = https://github.com/basho/eleveldb
+pkg_eleveldb_fetch = git
+pkg_eleveldb_repo = https://github.com/basho/eleveldb
+pkg_eleveldb_commit = master
+
+PACKAGES += elixir
+pkg_elixir_name = elixir
+pkg_elixir_description = Elixir is a dynamic, functional language designed for building scalable and maintainable applications
+pkg_elixir_homepage = https://elixir-lang.org/
+pkg_elixir_fetch = git
+pkg_elixir_repo = https://github.com/elixir-lang/elixir
+pkg_elixir_commit = master
+
+PACKAGES += elli
+pkg_elli_name = elli
+pkg_elli_description = Simple, robust and performant Erlang web server
+pkg_elli_homepage = https://github.com/elli-lib/elli
+pkg_elli_fetch = git
+pkg_elli_repo = https://github.com/elli-lib/elli
+pkg_elli_commit = master
+
+PACKAGES += elvis
+pkg_elvis_name = elvis
+pkg_elvis_description = Erlang Style Reviewer
+pkg_elvis_homepage = https://github.com/inaka/elvis
+pkg_elvis_fetch = git
+pkg_elvis_repo = https://github.com/inaka/elvis
+pkg_elvis_commit = master
+
+PACKAGES += emagick
+pkg_emagick_name = emagick
+pkg_emagick_description = Wrapper for Graphics/ImageMagick command line tool.
+pkg_emagick_homepage = https://github.com/kivra/emagick
+pkg_emagick_fetch = git
+pkg_emagick_repo = https://github.com/kivra/emagick
+pkg_emagick_commit = master
+
+PACKAGES += emysql
+pkg_emysql_name = emysql
+pkg_emysql_description = Stable, pure Erlang MySQL driver.
+pkg_emysql_homepage = https://github.com/Eonblast/Emysql
+pkg_emysql_fetch = git
+pkg_emysql_repo = https://github.com/Eonblast/Emysql
+pkg_emysql_commit = master
+
+PACKAGES += enm
+pkg_enm_name = enm
+pkg_enm_description = Erlang driver for nanomsg
+pkg_enm_homepage = https://github.com/basho/enm
+pkg_enm_fetch = git
+pkg_enm_repo = https://github.com/basho/enm
+pkg_enm_commit = master
+
+PACKAGES += entop
+pkg_entop_name = entop
+pkg_entop_description = A top-like tool for monitoring an Erlang node
+pkg_entop_homepage = https://github.com/mazenharake/entop
+pkg_entop_fetch = git
+pkg_entop_repo = https://github.com/mazenharake/entop
+pkg_entop_commit = master
+
+PACKAGES += epcap
+pkg_epcap_name = epcap
+pkg_epcap_description = Erlang packet capture interface using pcap
+pkg_epcap_homepage = https://github.com/msantos/epcap
+pkg_epcap_fetch = git
+pkg_epcap_repo = https://github.com/msantos/epcap
+pkg_epcap_commit = master
+
+PACKAGES += eper
+pkg_eper_name = eper
+pkg_eper_description = Erlang performance and debugging tools.
+pkg_eper_homepage = https://github.com/massemanet/eper
+pkg_eper_fetch = git
+pkg_eper_repo = https://github.com/massemanet/eper
+pkg_eper_commit = master
+
+PACKAGES += epgsql
+pkg_epgsql_name = epgsql
+pkg_epgsql_description = Erlang PostgreSQL client library.
+pkg_epgsql_homepage = https://github.com/epgsql/epgsql
+pkg_epgsql_fetch = git
+pkg_epgsql_repo = https://github.com/epgsql/epgsql
+pkg_epgsql_commit = master
+
+PACKAGES += episcina
+pkg_episcina_name = episcina
+pkg_episcina_description = A simple non intrusive resource pool for connections
+pkg_episcina_homepage = https://github.com/erlware/episcina
+pkg_episcina_fetch = git
+pkg_episcina_repo = https://github.com/erlware/episcina
+pkg_episcina_commit = master
+
+PACKAGES += eplot
+pkg_eplot_name = eplot
+pkg_eplot_description = A plot engine written in erlang.
+pkg_eplot_homepage = https://github.com/psyeugenic/eplot
+pkg_eplot_fetch = git
+pkg_eplot_repo = https://github.com/psyeugenic/eplot
+pkg_eplot_commit = master
+
+PACKAGES += epocxy
+pkg_epocxy_name = epocxy
+pkg_epocxy_description = Erlang Patterns of Concurrency
+pkg_epocxy_homepage = https://github.com/duomark/epocxy
+pkg_epocxy_fetch = git
+pkg_epocxy_repo = https://github.com/duomark/epocxy
+pkg_epocxy_commit = master
+
+PACKAGES += epubnub
+pkg_epubnub_name = epubnub
+pkg_epubnub_description = Erlang PubNub API
+pkg_epubnub_homepage = https://github.com/tsloughter/epubnub
+pkg_epubnub_fetch = git
+pkg_epubnub_repo = https://github.com/tsloughter/epubnub
+pkg_epubnub_commit = master
+
+PACKAGES += eqm
+pkg_eqm_name = eqm
+pkg_eqm_description = Erlang pub sub with supply-demand channels
+pkg_eqm_homepage = https://github.com/loucash/eqm
+pkg_eqm_fetch = git
+pkg_eqm_repo = https://github.com/loucash/eqm
+pkg_eqm_commit = master
+
+PACKAGES += eredis
+pkg_eredis_name = eredis
+pkg_eredis_description = Erlang Redis client
+pkg_eredis_homepage = https://github.com/wooga/eredis
+pkg_eredis_fetch = git
+pkg_eredis_repo = https://github.com/wooga/eredis
+pkg_eredis_commit = master
+
+PACKAGES += eredis_pool
+pkg_eredis_pool_name = eredis_pool
+pkg_eredis_pool_description = eredis_pool is Pool of Redis clients, using eredis and poolboy.
+pkg_eredis_pool_homepage = https://github.com/hiroeorz/eredis_pool
+pkg_eredis_pool_fetch = git
+pkg_eredis_pool_repo = https://github.com/hiroeorz/eredis_pool
+pkg_eredis_pool_commit = master
+
+PACKAGES += erl_streams
+pkg_erl_streams_name = erl_streams
+pkg_erl_streams_description = Streams in Erlang
+pkg_erl_streams_homepage = https://github.com/epappas/erl_streams
+pkg_erl_streams_fetch = git
+pkg_erl_streams_repo = https://github.com/epappas/erl_streams
+pkg_erl_streams_commit = master
+
+PACKAGES += erlang_cep
+pkg_erlang_cep_name = erlang_cep
+pkg_erlang_cep_description = A basic CEP package written in erlang
+pkg_erlang_cep_homepage = https://github.com/danmacklin/erlang_cep
+pkg_erlang_cep_fetch = git
+pkg_erlang_cep_repo = https://github.com/danmacklin/erlang_cep
+pkg_erlang_cep_commit = master
+
+PACKAGES += erlang_js
+pkg_erlang_js_name = erlang_js
+pkg_erlang_js_description = A linked-in driver for Erlang to Mozilla's Spidermonkey Javascript runtime.
+pkg_erlang_js_homepage = https://github.com/basho/erlang_js
+pkg_erlang_js_fetch = git
+pkg_erlang_js_repo = https://github.com/basho/erlang_js
+pkg_erlang_js_commit = master
+
+PACKAGES += erlang_localtime
+pkg_erlang_localtime_name = erlang_localtime
+pkg_erlang_localtime_description = Erlang library for conversion from one local time to another
+pkg_erlang_localtime_homepage = https://github.com/dmitryme/erlang_localtime
+pkg_erlang_localtime_fetch = git
+pkg_erlang_localtime_repo = https://github.com/dmitryme/erlang_localtime
+pkg_erlang_localtime_commit = master
+
+PACKAGES += erlang_smtp
+pkg_erlang_smtp_name = erlang_smtp
+pkg_erlang_smtp_description = Erlang SMTP and POP3 server code.
+pkg_erlang_smtp_homepage = https://github.com/tonyg/erlang-smtp
+pkg_erlang_smtp_fetch = git
+pkg_erlang_smtp_repo = https://github.com/tonyg/erlang-smtp
+pkg_erlang_smtp_commit = master
+
+PACKAGES += erlang_term
+pkg_erlang_term_name = erlang_term
+pkg_erlang_term_description = Erlang Term Info
+pkg_erlang_term_homepage = https://github.com/okeuday/erlang_term
+pkg_erlang_term_fetch = git
+pkg_erlang_term_repo = https://github.com/okeuday/erlang_term
+pkg_erlang_term_commit = master
+
+PACKAGES += erlastic_search
+pkg_erlastic_search_name = erlastic_search
+pkg_erlastic_search_description = An Erlang app for communicating with Elastic Search's rest interface.
+pkg_erlastic_search_homepage = https://github.com/tsloughter/erlastic_search
+pkg_erlastic_search_fetch = git
+pkg_erlastic_search_repo = https://github.com/tsloughter/erlastic_search
+pkg_erlastic_search_commit = master
+
+PACKAGES += erlasticsearch
+pkg_erlasticsearch_name = erlasticsearch
+pkg_erlasticsearch_description = Erlang thrift interface to elastic_search
+pkg_erlasticsearch_homepage = https://github.com/dieswaytoofast/erlasticsearch
+pkg_erlasticsearch_fetch = git
+pkg_erlasticsearch_repo = https://github.com/dieswaytoofast/erlasticsearch
+pkg_erlasticsearch_commit = master
+
+PACKAGES += erlbrake
+pkg_erlbrake_name = erlbrake
+pkg_erlbrake_description = Erlang Airbrake notification client
+pkg_erlbrake_homepage = https://github.com/kenpratt/erlbrake
+pkg_erlbrake_fetch = git
+pkg_erlbrake_repo = https://github.com/kenpratt/erlbrake
+pkg_erlbrake_commit = master
+
+PACKAGES += erlcloud
+pkg_erlcloud_name = erlcloud
+pkg_erlcloud_description = Cloud Computing library for erlang (Amazon EC2, S3, SQS, SimpleDB, Mechanical Turk, ELB)
+pkg_erlcloud_homepage = https://github.com/gleber/erlcloud
+pkg_erlcloud_fetch = git
+pkg_erlcloud_repo = https://github.com/gleber/erlcloud
+pkg_erlcloud_commit = master
+
+PACKAGES += erlcron
+pkg_erlcron_name = erlcron
+pkg_erlcron_description = Erlang cronish system
+pkg_erlcron_homepage = https://github.com/erlware/erlcron
+pkg_erlcron_fetch = git
+pkg_erlcron_repo = https://github.com/erlware/erlcron
+pkg_erlcron_commit = master
+
+PACKAGES += erldb
+pkg_erldb_name = erldb
+pkg_erldb_description = ORM (Object-relational mapping) application implemented in Erlang
+pkg_erldb_homepage = http://erldb.org
+pkg_erldb_fetch = git
+pkg_erldb_repo = https://github.com/erldb/erldb
+pkg_erldb_commit = master
+
+PACKAGES += erldis
+pkg_erldis_name = erldis
+pkg_erldis_description = redis erlang client library
+pkg_erldis_homepage = https://github.com/cstar/erldis
+pkg_erldis_fetch = git
+pkg_erldis_repo = https://github.com/cstar/erldis
+pkg_erldis_commit = master
+
+PACKAGES += erldns
+pkg_erldns_name = erldns
+pkg_erldns_description = DNS server, in erlang.
+pkg_erldns_homepage = https://github.com/aetrion/erl-dns
+pkg_erldns_fetch = git
+pkg_erldns_repo = https://github.com/aetrion/erl-dns
+pkg_erldns_commit = master
+
+PACKAGES += erldocker
+pkg_erldocker_name = erldocker
+pkg_erldocker_description = Docker Remote API client for Erlang
+pkg_erldocker_homepage = https://github.com/proger/erldocker
+pkg_erldocker_fetch = git
+pkg_erldocker_repo = https://github.com/proger/erldocker
+pkg_erldocker_commit = master
+
+PACKAGES += erlfsmon
+pkg_erlfsmon_name = erlfsmon
+pkg_erlfsmon_description = Erlang filesystem event watcher for Linux and OSX
+pkg_erlfsmon_homepage = https://github.com/proger/erlfsmon
+pkg_erlfsmon_fetch = git
+pkg_erlfsmon_repo = https://github.com/proger/erlfsmon
+pkg_erlfsmon_commit = master
+
+PACKAGES += erlgit
+pkg_erlgit_name = erlgit
+pkg_erlgit_description = Erlang convenience wrapper around git executable
+pkg_erlgit_homepage = https://github.com/gleber/erlgit
+pkg_erlgit_fetch = git
+pkg_erlgit_repo = https://github.com/gleber/erlgit
+pkg_erlgit_commit = master
+
+PACKAGES += erlguten
+pkg_erlguten_name = erlguten
+pkg_erlguten_description = ErlGuten is a system for high-quality typesetting, written purely in Erlang.
+pkg_erlguten_homepage = https://github.com/richcarl/erlguten
+pkg_erlguten_fetch = git
+pkg_erlguten_repo = https://github.com/richcarl/erlguten
+pkg_erlguten_commit = master
+
+PACKAGES += erlmc
+pkg_erlmc_name = erlmc
+pkg_erlmc_description = Erlang memcached binary protocol client
+pkg_erlmc_homepage = https://github.com/jkvor/erlmc
+pkg_erlmc_fetch = git
+pkg_erlmc_repo = https://github.com/jkvor/erlmc
+pkg_erlmc_commit = master
+
+PACKAGES += erlmongo
+pkg_erlmongo_name = erlmongo
+pkg_erlmongo_description = Record based Erlang driver for MongoDB with gridfs support
+pkg_erlmongo_homepage = https://github.com/SergejJurecko/erlmongo
+pkg_erlmongo_fetch = git
+pkg_erlmongo_repo = https://github.com/SergejJurecko/erlmongo
+pkg_erlmongo_commit = master
+
+PACKAGES += erlog
+pkg_erlog_name = erlog
+pkg_erlog_description = Prolog interpreter in and for Erlang
+pkg_erlog_homepage = https://github.com/rvirding/erlog
+pkg_erlog_fetch = git
+pkg_erlog_repo = https://github.com/rvirding/erlog
+pkg_erlog_commit = master
+
+PACKAGES += erlpass
+pkg_erlpass_name = erlpass
+pkg_erlpass_description = A library to handle password hashing and changing in a safe manner, independent from any kind of storage whatsoever.
+pkg_erlpass_homepage = https://github.com/ferd/erlpass
+pkg_erlpass_fetch = git
+pkg_erlpass_repo = https://github.com/ferd/erlpass
+pkg_erlpass_commit = master
+
+PACKAGES += erlport
+pkg_erlport_name = erlport
+pkg_erlport_description = ErlPort - connect Erlang to other languages
+pkg_erlport_homepage = https://github.com/hdima/erlport
+pkg_erlport_fetch = git
+pkg_erlport_repo = https://github.com/hdima/erlport
+pkg_erlport_commit = master
+
+PACKAGES += erlsh
+pkg_erlsh_name = erlsh
+pkg_erlsh_description = Erlang shell tools
+pkg_erlsh_homepage = https://github.com/proger/erlsh
+pkg_erlsh_fetch = git
+pkg_erlsh_repo = https://github.com/proger/erlsh
+pkg_erlsh_commit = master
+
+PACKAGES += erlsha2
+pkg_erlsha2_name = erlsha2
+pkg_erlsha2_description = SHA-224, SHA-256, SHA-384, SHA-512 implemented in Erlang NIFs.
+pkg_erlsha2_homepage = https://github.com/vinoski/erlsha2
+pkg_erlsha2_fetch = git
+pkg_erlsha2_repo = https://github.com/vinoski/erlsha2
+pkg_erlsha2_commit = master
+
+PACKAGES += erlsom
+pkg_erlsom_name = erlsom
+pkg_erlsom_description = XML parser for Erlang
+pkg_erlsom_homepage = https://github.com/willemdj/erlsom
+pkg_erlsom_fetch = git
+pkg_erlsom_repo = https://github.com/willemdj/erlsom
+pkg_erlsom_commit = master
+
+PACKAGES += erlubi
+pkg_erlubi_name = erlubi
+pkg_erlubi_description = Ubigraph Erlang Client (and Process Visualizer)
+pkg_erlubi_homepage = https://github.com/krestenkrab/erlubi
+pkg_erlubi_fetch = git
+pkg_erlubi_repo = https://github.com/krestenkrab/erlubi
+pkg_erlubi_commit = master
+
+PACKAGES += erlvolt
+pkg_erlvolt_name = erlvolt
+pkg_erlvolt_description = VoltDB Erlang Client Driver
+pkg_erlvolt_homepage = https://github.com/VoltDB/voltdb-client-erlang
+pkg_erlvolt_fetch = git
+pkg_erlvolt_repo = https://github.com/VoltDB/voltdb-client-erlang
+pkg_erlvolt_commit = master
+
+PACKAGES += erlware_commons
+pkg_erlware_commons_name = erlware_commons
+pkg_erlware_commons_description = Erlware Commons is an Erlware project focused on all aspects of reusable Erlang components.
+pkg_erlware_commons_homepage = https://github.com/erlware/erlware_commons
+pkg_erlware_commons_fetch = git
+pkg_erlware_commons_repo = https://github.com/erlware/erlware_commons
+pkg_erlware_commons_commit = master
+
+PACKAGES += erlydtl
+pkg_erlydtl_name = erlydtl
+pkg_erlydtl_description = Django Template Language for Erlang.
+pkg_erlydtl_homepage = https://github.com/erlydtl/erlydtl
+pkg_erlydtl_fetch = git
+pkg_erlydtl_repo = https://github.com/erlydtl/erlydtl
+pkg_erlydtl_commit = master
+
+PACKAGES += errd
+pkg_errd_name = errd
+pkg_errd_description = Erlang RRDTool library
+pkg_errd_homepage = https://github.com/archaelus/errd
+pkg_errd_fetch = git
+pkg_errd_repo = https://github.com/archaelus/errd
+pkg_errd_commit = master
+
+PACKAGES += erserve
+pkg_erserve_name = erserve
+pkg_erserve_description = Erlang/Rserve communication interface
+pkg_erserve_homepage = https://github.com/del/erserve
+pkg_erserve_fetch = git
+pkg_erserve_repo = https://github.com/del/erserve
+pkg_erserve_commit = master
+
+PACKAGES += erwa
+pkg_erwa_name = erwa
+pkg_erwa_description = A WAMP router and client written in Erlang.
+pkg_erwa_homepage = https://github.com/bwegh/erwa
+pkg_erwa_fetch = git
+pkg_erwa_repo = https://github.com/bwegh/erwa
+pkg_erwa_commit = master
+
+PACKAGES += escalus
+pkg_escalus_name = escalus
+pkg_escalus_description = An XMPP client library in Erlang for conveniently testing XMPP servers
+pkg_escalus_homepage = https://github.com/esl/escalus
+pkg_escalus_fetch = git
+pkg_escalus_repo = https://github.com/esl/escalus
+pkg_escalus_commit = master
+
+PACKAGES += esh_mk
+pkg_esh_mk_name = esh_mk
+pkg_esh_mk_description = esh template engine plugin for erlang.mk
+pkg_esh_mk_homepage = https://github.com/crownedgrouse/esh.mk
+pkg_esh_mk_fetch = git
+pkg_esh_mk_repo = https://github.com/crownedgrouse/esh.mk.git
+pkg_esh_mk_commit = master
+
+PACKAGES += espec
+pkg_espec_name = espec
+pkg_espec_description = ESpec: Behaviour driven development framework for Erlang
+pkg_espec_homepage = https://github.com/lucaspiller/espec
+pkg_espec_fetch = git
+pkg_espec_repo = https://github.com/lucaspiller/espec
+pkg_espec_commit = master
+
+PACKAGES += estatsd
+pkg_estatsd_name = estatsd
+pkg_estatsd_description = Erlang stats aggregation app that periodically flushes data to graphite
+pkg_estatsd_homepage = https://github.com/RJ/estatsd
+pkg_estatsd_fetch = git
+pkg_estatsd_repo = https://github.com/RJ/estatsd
+pkg_estatsd_commit = master
+
+PACKAGES += etap
+pkg_etap_name = etap
+pkg_etap_description = etap is a simple erlang testing library that provides TAP compliant output.
+pkg_etap_homepage = https://github.com/ngerakines/etap
+pkg_etap_fetch = git
+pkg_etap_repo = https://github.com/ngerakines/etap
+pkg_etap_commit = master
+
+PACKAGES += etest
+pkg_etest_name = etest
+pkg_etest_description = A lightweight, convention over configuration test framework for Erlang
+pkg_etest_homepage = https://github.com/wooga/etest
+pkg_etest_fetch = git
+pkg_etest_repo = https://github.com/wooga/etest
+pkg_etest_commit = master
+
+PACKAGES += etest_http
+pkg_etest_http_name = etest_http
+pkg_etest_http_description = etest Assertions around HTTP (client-side)
+pkg_etest_http_homepage = https://github.com/wooga/etest_http
+pkg_etest_http_fetch = git
+pkg_etest_http_repo = https://github.com/wooga/etest_http
+pkg_etest_http_commit = master
+
+PACKAGES += etoml
+pkg_etoml_name = etoml
+pkg_etoml_description = TOML language erlang parser
+pkg_etoml_homepage = https://github.com/kalta/etoml
+pkg_etoml_fetch = git
+pkg_etoml_repo = https://github.com/kalta/etoml
+pkg_etoml_commit = master
+
+PACKAGES += eunit
+pkg_eunit_name = eunit
+pkg_eunit_description = The EUnit lightweight unit testing framework for Erlang - this is the canonical development repository.
+pkg_eunit_homepage = https://github.com/richcarl/eunit
+pkg_eunit_fetch = git
+pkg_eunit_repo = https://github.com/richcarl/eunit
+pkg_eunit_commit = master
+
+PACKAGES += eunit_formatters
+pkg_eunit_formatters_name = eunit_formatters
+pkg_eunit_formatters_description = Because eunit's output sucks. Let's make it better.
+pkg_eunit_formatters_homepage = https://github.com/seancribbs/eunit_formatters
+pkg_eunit_formatters_fetch = git
+pkg_eunit_formatters_repo = https://github.com/seancribbs/eunit_formatters
+pkg_eunit_formatters_commit = master
+
+PACKAGES += euthanasia
+pkg_euthanasia_name = euthanasia
+pkg_euthanasia_description = Merciful killer for your Erlang processes
+pkg_euthanasia_homepage = https://github.com/doubleyou/euthanasia
+pkg_euthanasia_fetch = git
+pkg_euthanasia_repo = https://github.com/doubleyou/euthanasia
+pkg_euthanasia_commit = master
+
+PACKAGES += evum
+pkg_evum_name = evum
+pkg_evum_description = Spawn Linux VMs as Erlang processes in the Erlang VM
+pkg_evum_homepage = https://github.com/msantos/evum
+pkg_evum_fetch = git
+pkg_evum_repo = https://github.com/msantos/evum
+pkg_evum_commit = master
+
+PACKAGES += exec
+pkg_exec_name = erlexec
+pkg_exec_description = Execute and control OS processes from Erlang/OTP.
+pkg_exec_homepage = http://saleyn.github.com/erlexec
+pkg_exec_fetch = git
+pkg_exec_repo = https://github.com/saleyn/erlexec
+pkg_exec_commit = master
+
+PACKAGES += exml
+pkg_exml_name = exml
+pkg_exml_description = XML parsing library in Erlang
+pkg_exml_homepage = https://github.com/paulgray/exml
+pkg_exml_fetch = git
+pkg_exml_repo = https://github.com/paulgray/exml
+pkg_exml_commit = master
+
+PACKAGES += exometer
+pkg_exometer_name = exometer
+pkg_exometer_description = Basic measurement objects and probe behavior
+pkg_exometer_homepage = https://github.com/Feuerlabs/exometer
+pkg_exometer_fetch = git
+pkg_exometer_repo = https://github.com/Feuerlabs/exometer
+pkg_exometer_commit = master
+
+PACKAGES += exs1024
+pkg_exs1024_name = exs1024
+pkg_exs1024_description = Xorshift1024star pseudo random number generator for Erlang.
+pkg_exs1024_homepage = https://github.com/jj1bdx/exs1024
+pkg_exs1024_fetch = git
+pkg_exs1024_repo = https://github.com/jj1bdx/exs1024
+pkg_exs1024_commit = master
+
+PACKAGES += exs64
+pkg_exs64_name = exs64
+pkg_exs64_description = Xorshift64star pseudo random number generator for Erlang.
+pkg_exs64_homepage = https://github.com/jj1bdx/exs64
+pkg_exs64_fetch = git
+pkg_exs64_repo = https://github.com/jj1bdx/exs64
+pkg_exs64_commit = master
+
+PACKAGES += exsplus116
+pkg_exsplus116_name = exsplus116
+pkg_exsplus116_description = Xorshift116plus for Erlang
+pkg_exsplus116_homepage = https://github.com/jj1bdx/exsplus116
+pkg_exsplus116_fetch = git
+pkg_exsplus116_repo = https://github.com/jj1bdx/exsplus116
+pkg_exsplus116_commit = master
+
+PACKAGES += exsplus128
+pkg_exsplus128_name = exsplus128
+pkg_exsplus128_description = Xorshift128plus pseudo random number generator for Erlang.
+pkg_exsplus128_homepage = https://github.com/jj1bdx/exsplus128
+pkg_exsplus128_fetch = git
+pkg_exsplus128_repo = https://github.com/jj1bdx/exsplus128
+pkg_exsplus128_commit = master
+
+PACKAGES += ezmq
+pkg_ezmq_name = ezmq
+pkg_ezmq_description = zMQ implemented in Erlang
+pkg_ezmq_homepage = https://github.com/RoadRunnr/ezmq
+pkg_ezmq_fetch = git
+pkg_ezmq_repo = https://github.com/RoadRunnr/ezmq
+pkg_ezmq_commit = master
+
+PACKAGES += ezmtp
+pkg_ezmtp_name = ezmtp
+pkg_ezmtp_description = ZMTP protocol in pure Erlang.
+pkg_ezmtp_homepage = https://github.com/a13x/ezmtp
+pkg_ezmtp_fetch = git
+pkg_ezmtp_repo = https://github.com/a13x/ezmtp
+pkg_ezmtp_commit = master
+
+PACKAGES += fast_disk_log
+pkg_fast_disk_log_name = fast_disk_log
+pkg_fast_disk_log_description = Pool-based asynchronous Erlang disk logger
+pkg_fast_disk_log_homepage = https://github.com/lpgauth/fast_disk_log
+pkg_fast_disk_log_fetch = git
+pkg_fast_disk_log_repo = https://github.com/lpgauth/fast_disk_log
+pkg_fast_disk_log_commit = master
+
+PACKAGES += feeder
+pkg_feeder_name = feeder
+pkg_feeder_description = Stream parse RSS and Atom formatted XML feeds.
+pkg_feeder_homepage = https://github.com/michaelnisi/feeder
+pkg_feeder_fetch = git
+pkg_feeder_repo = https://github.com/michaelnisi/feeder
+pkg_feeder_commit = master
+
+PACKAGES += find_crate
+pkg_find_crate_name = find_crate
+pkg_find_crate_description = Find Rust libs and exes in Erlang application priv directory
+pkg_find_crate_homepage = https://github.com/goertzenator/find_crate
+pkg_find_crate_fetch = git
+pkg_find_crate_repo = https://github.com/goertzenator/find_crate
+pkg_find_crate_commit = master
+
+PACKAGES += fix
+pkg_fix_name = fix
+pkg_fix_description = http://fixprotocol.org/ implementation.
+pkg_fix_homepage = https://github.com/maxlapshin/fix
+pkg_fix_fetch = git
+pkg_fix_repo = https://github.com/maxlapshin/fix
+pkg_fix_commit = master
+
+PACKAGES += flower
+pkg_flower_name = flower
+pkg_flower_description = FlowER - a Erlang OpenFlow development platform
+pkg_flower_homepage = https://github.com/travelping/flower
+pkg_flower_fetch = git
+pkg_flower_repo = https://github.com/travelping/flower
+pkg_flower_commit = master
+
+PACKAGES += fn
+pkg_fn_name = fn
+pkg_fn_description = Function utilities for Erlang
+pkg_fn_homepage = https://github.com/reiddraper/fn
+pkg_fn_fetch = git
+pkg_fn_repo = https://github.com/reiddraper/fn
+pkg_fn_commit = master
+
+PACKAGES += folsom
+pkg_folsom_name = folsom
+pkg_folsom_description = Expose Erlang Events and Metrics
+pkg_folsom_homepage = https://github.com/boundary/folsom
+pkg_folsom_fetch = git
+pkg_folsom_repo = https://github.com/boundary/folsom
+pkg_folsom_commit = master
+
+PACKAGES += folsom_cowboy
+pkg_folsom_cowboy_name = folsom_cowboy
+pkg_folsom_cowboy_description = A Cowboy based Folsom HTTP Wrapper.
+pkg_folsom_cowboy_homepage = https://github.com/boundary/folsom_cowboy
+pkg_folsom_cowboy_fetch = git
+pkg_folsom_cowboy_repo = https://github.com/boundary/folsom_cowboy
+pkg_folsom_cowboy_commit = master
+
+PACKAGES += folsomite
+pkg_folsomite_name = folsomite
+pkg_folsomite_description = blow up your graphite / riemann server with folsom metrics
+pkg_folsomite_homepage = https://github.com/campanja/folsomite
+pkg_folsomite_fetch = git
+pkg_folsomite_repo = https://github.com/campanja/folsomite
+pkg_folsomite_commit = master
+
+PACKAGES += fs
+pkg_fs_name = fs
+pkg_fs_description = Erlang FileSystem Listener
+pkg_fs_homepage = https://github.com/synrc/fs
+pkg_fs_fetch = git
+pkg_fs_repo = https://github.com/synrc/fs
+pkg_fs_commit = master
+
+PACKAGES += fuse
+pkg_fuse_name = fuse
+pkg_fuse_description = A Circuit Breaker for Erlang
+pkg_fuse_homepage = https://github.com/jlouis/fuse
+pkg_fuse_fetch = git
+pkg_fuse_repo = https://github.com/jlouis/fuse
+pkg_fuse_commit = master
+
+PACKAGES += gcm
+pkg_gcm_name = gcm
+pkg_gcm_description = An Erlang application for Google Cloud Messaging
+pkg_gcm_homepage = https://github.com/pdincau/gcm-erlang
+pkg_gcm_fetch = git
+pkg_gcm_repo = https://github.com/pdincau/gcm-erlang
+pkg_gcm_commit = master
+
+PACKAGES += gcprof
+pkg_gcprof_name = gcprof
+pkg_gcprof_description = Garbage Collection profiler for Erlang
+pkg_gcprof_homepage = https://github.com/knutin/gcprof
+pkg_gcprof_fetch = git
+pkg_gcprof_repo = https://github.com/knutin/gcprof
+pkg_gcprof_commit = master
+
+PACKAGES += geas
+pkg_geas_name = geas
+pkg_geas_description = Guess Erlang Application Scattering
+pkg_geas_homepage = https://github.com/crownedgrouse/geas
+pkg_geas_fetch = git
+pkg_geas_repo = https://github.com/crownedgrouse/geas
+pkg_geas_commit = master
+
+PACKAGES += geef
+pkg_geef_name = geef
+pkg_geef_description = Git NEEEEF (Erlang NIF)
+pkg_geef_homepage = https://github.com/carlosmn/geef
+pkg_geef_fetch = git
+pkg_geef_repo = https://github.com/carlosmn/geef
+pkg_geef_commit = master
+
+PACKAGES += gen_coap
+pkg_gen_coap_name = gen_coap
+pkg_gen_coap_description = Generic Erlang CoAP Client/Server
+pkg_gen_coap_homepage = https://github.com/gotthardp/gen_coap
+pkg_gen_coap_fetch = git
+pkg_gen_coap_repo = https://github.com/gotthardp/gen_coap
+pkg_gen_coap_commit = master
+
+PACKAGES += gen_cycle
+pkg_gen_cycle_name = gen_cycle
+pkg_gen_cycle_description = Simple, generic OTP behaviour for recurring tasks
+pkg_gen_cycle_homepage = https://github.com/aerosol/gen_cycle
+pkg_gen_cycle_fetch = git
+pkg_gen_cycle_repo = https://github.com/aerosol/gen_cycle
+pkg_gen_cycle_commit = develop
+
+PACKAGES += gen_icmp
+pkg_gen_icmp_name = gen_icmp
+pkg_gen_icmp_description = Erlang interface to ICMP sockets
+pkg_gen_icmp_homepage = https://github.com/msantos/gen_icmp
+pkg_gen_icmp_fetch = git
+pkg_gen_icmp_repo = https://github.com/msantos/gen_icmp
+pkg_gen_icmp_commit = master
+
+PACKAGES += gen_leader
+pkg_gen_leader_name = gen_leader
+pkg_gen_leader_description = leader election behavior
+pkg_gen_leader_homepage = https://github.com/garret-smith/gen_leader_revival
+pkg_gen_leader_fetch = git
+pkg_gen_leader_repo = https://github.com/garret-smith/gen_leader_revival
+pkg_gen_leader_commit = master
+
+PACKAGES += gen_nb_server
+pkg_gen_nb_server_name = gen_nb_server
+pkg_gen_nb_server_description = OTP behavior for writing non-blocking servers
+pkg_gen_nb_server_homepage = https://github.com/kevsmith/gen_nb_server
+pkg_gen_nb_server_fetch = git
+pkg_gen_nb_server_repo = https://github.com/kevsmith/gen_nb_server
+pkg_gen_nb_server_commit = master
+
+PACKAGES += gen_paxos
+pkg_gen_paxos_name = gen_paxos
+pkg_gen_paxos_description = An Erlang/OTP-style implementation of the PAXOS distributed consensus protocol
+pkg_gen_paxos_homepage = https://github.com/gburd/gen_paxos
+pkg_gen_paxos_fetch = git
+pkg_gen_paxos_repo = https://github.com/gburd/gen_paxos
+pkg_gen_paxos_commit = master
+
+PACKAGES += gen_rpc
+pkg_gen_rpc_name = gen_rpc
+pkg_gen_rpc_description = A scalable RPC library for Erlang-VM based languages
+pkg_gen_rpc_homepage = https://github.com/priestjim/gen_rpc.git
+pkg_gen_rpc_fetch = git
+pkg_gen_rpc_repo = https://github.com/priestjim/gen_rpc.git
+pkg_gen_rpc_commit = master
+
+PACKAGES += gen_smtp
+pkg_gen_smtp_name = gen_smtp
+pkg_gen_smtp_description = A generic Erlang SMTP server and client that can be extended via callback modules
+pkg_gen_smtp_homepage = https://github.com/Vagabond/gen_smtp
+pkg_gen_smtp_fetch = git
+pkg_gen_smtp_repo = https://github.com/Vagabond/gen_smtp
+pkg_gen_smtp_commit = master
+
+PACKAGES += gen_tracker
+pkg_gen_tracker_name = gen_tracker
+pkg_gen_tracker_description = supervisor with ets handling of children and their metadata
+pkg_gen_tracker_homepage = https://github.com/erlyvideo/gen_tracker
+pkg_gen_tracker_fetch = git
+pkg_gen_tracker_repo = https://github.com/erlyvideo/gen_tracker
+pkg_gen_tracker_commit = master
+
+PACKAGES += gen_unix
+pkg_gen_unix_name = gen_unix
+pkg_gen_unix_description = Erlang Unix socket interface
+pkg_gen_unix_homepage = https://github.com/msantos/gen_unix
+pkg_gen_unix_fetch = git
+pkg_gen_unix_repo = https://github.com/msantos/gen_unix
+pkg_gen_unix_commit = master
+
+PACKAGES += geode
+pkg_geode_name = geode
+pkg_geode_description = geohash/proximity lookup in pure, uncut erlang.
+pkg_geode_homepage = https://github.com/bradfordw/geode
+pkg_geode_fetch = git
+pkg_geode_repo = https://github.com/bradfordw/geode
+pkg_geode_commit = master
+
+PACKAGES += getopt
+pkg_getopt_name = getopt
+pkg_getopt_description = Module to parse command line arguments using the GNU getopt syntax
+pkg_getopt_homepage = https://github.com/jcomellas/getopt
+pkg_getopt_fetch = git
+pkg_getopt_repo = https://github.com/jcomellas/getopt
+pkg_getopt_commit = master
+
+PACKAGES += gettext
+pkg_gettext_name = gettext
+pkg_gettext_description = Erlang internationalization library.
+pkg_gettext_homepage = https://github.com/etnt/gettext
+pkg_gettext_fetch = git
+pkg_gettext_repo = https://github.com/etnt/gettext
+pkg_gettext_commit = master
+
+PACKAGES += giallo
+pkg_giallo_name = giallo
+pkg_giallo_description = Small and flexible web framework on top of Cowboy
+pkg_giallo_homepage = https://github.com/kivra/giallo
+pkg_giallo_fetch = git
+pkg_giallo_repo = https://github.com/kivra/giallo
+pkg_giallo_commit = master
+
+PACKAGES += gin
+pkg_gin_name = gin
+pkg_gin_description = The guards and for Erlang parse_transform
+pkg_gin_homepage = https://github.com/mad-cocktail/gin
+pkg_gin_fetch = git
+pkg_gin_repo = https://github.com/mad-cocktail/gin
+pkg_gin_commit = master
+
+PACKAGES += gitty
+pkg_gitty_name = gitty
+pkg_gitty_description = Git access in erlang
+pkg_gitty_homepage = https://github.com/maxlapshin/gitty
+pkg_gitty_fetch = git
+pkg_gitty_repo = https://github.com/maxlapshin/gitty
+pkg_gitty_commit = master
+
+PACKAGES += gold_fever
+pkg_gold_fever_name = gold_fever
+pkg_gold_fever_description = A Treasure Hunt for Erlangers
+pkg_gold_fever_homepage = https://github.com/inaka/gold_fever
+pkg_gold_fever_fetch = git
+pkg_gold_fever_repo = https://github.com/inaka/gold_fever
+pkg_gold_fever_commit = master
+
+PACKAGES += gpb
+pkg_gpb_name = gpb
+pkg_gpb_description = A Google Protobuf implementation for Erlang
+pkg_gpb_homepage = https://github.com/tomas-abrahamsson/gpb
+pkg_gpb_fetch = git
+pkg_gpb_repo = https://github.com/tomas-abrahamsson/gpb
+pkg_gpb_commit = master
+
+PACKAGES += gproc
+pkg_gproc_name = gproc
+pkg_gproc_description = Extended process registry for Erlang
+pkg_gproc_homepage = https://github.com/uwiger/gproc
+pkg_gproc_fetch = git
+pkg_gproc_repo = https://github.com/uwiger/gproc
+pkg_gproc_commit = master
+
+PACKAGES += grapherl
+pkg_grapherl_name = grapherl
+pkg_grapherl_description = Create graphs of Erlang systems and programs
+pkg_grapherl_homepage = https://github.com/eproxus/grapherl
+pkg_grapherl_fetch = git
+pkg_grapherl_repo = https://github.com/eproxus/grapherl
+pkg_grapherl_commit = master
+
+PACKAGES += grpc
+pkg_grpc_name = grpc
+pkg_grpc_description = gRPC server in Erlang
+pkg_grpc_homepage = https://github.com/Bluehouse-Technology/grpc
+pkg_grpc_fetch = git
+pkg_grpc_repo = https://github.com/Bluehouse-Technology/grpc
+pkg_grpc_commit = master
+
+PACKAGES += grpc_client
+pkg_grpc_client_name = grpc_client
+pkg_grpc_client_description = gRPC client in Erlang
+pkg_grpc_client_homepage = https://github.com/Bluehouse-Technology/grpc_client
+pkg_grpc_client_fetch = git
+pkg_grpc_client_repo = https://github.com/Bluehouse-Technology/grpc_client
+pkg_grpc_client_commit = master
+
+PACKAGES += gun
+pkg_gun_name = gun
+pkg_gun_description = Asynchronous SPDY, HTTP and Websocket client written in Erlang.
+pkg_gun_homepage = http//ninenines.eu
+pkg_gun_fetch = git
+pkg_gun_repo = https://github.com/ninenines/gun
+pkg_gun_commit = master
+
+PACKAGES += gut
+pkg_gut_name = gut
+pkg_gut_description = gut is a template printing, aka scaffolding, tool for Erlang. Like rails generate or yeoman
+pkg_gut_homepage = https://github.com/unbalancedparentheses/gut
+pkg_gut_fetch = git
+pkg_gut_repo = https://github.com/unbalancedparentheses/gut
+pkg_gut_commit = master
+
+PACKAGES += hackney
+pkg_hackney_name = hackney
+pkg_hackney_description = simple HTTP client in Erlang
+pkg_hackney_homepage = https://github.com/benoitc/hackney
+pkg_hackney_fetch = git
+pkg_hackney_repo = https://github.com/benoitc/hackney
+pkg_hackney_commit = master
+
+PACKAGES += hamcrest
+pkg_hamcrest_name = hamcrest
+pkg_hamcrest_description = Erlang port of Hamcrest
+pkg_hamcrest_homepage = https://github.com/hyperthunk/hamcrest-erlang
+pkg_hamcrest_fetch = git
+pkg_hamcrest_repo = https://github.com/hyperthunk/hamcrest-erlang
+pkg_hamcrest_commit = master
+
+PACKAGES += hanoidb
+pkg_hanoidb_name = hanoidb
+pkg_hanoidb_description = Erlang LSM BTree Storage
+pkg_hanoidb_homepage = https://github.com/krestenkrab/hanoidb
+pkg_hanoidb_fetch = git
+pkg_hanoidb_repo = https://github.com/krestenkrab/hanoidb
+pkg_hanoidb_commit = master
+
+PACKAGES += hottub
+pkg_hottub_name = hottub
+pkg_hottub_description = Permanent Erlang Worker Pool
+pkg_hottub_homepage = https://github.com/bfrog/hottub
+pkg_hottub_fetch = git
+pkg_hottub_repo = https://github.com/bfrog/hottub
+pkg_hottub_commit = master
+
+PACKAGES += hpack
+pkg_hpack_name = hpack
+pkg_hpack_description = HPACK Implementation for Erlang
+pkg_hpack_homepage = https://github.com/joedevivo/hpack
+pkg_hpack_fetch = git
+pkg_hpack_repo = https://github.com/joedevivo/hpack
+pkg_hpack_commit = master
+
+PACKAGES += hyper
+pkg_hyper_name = hyper
+pkg_hyper_description = Erlang implementation of HyperLogLog
+pkg_hyper_homepage = https://github.com/GameAnalytics/hyper
+pkg_hyper_fetch = git
+pkg_hyper_repo = https://github.com/GameAnalytics/hyper
+pkg_hyper_commit = master
+
+PACKAGES += i18n
+pkg_i18n_name = i18n
+pkg_i18n_description = International components for unicode from Erlang (unicode, date, string, number, format, locale, localization, transliteration, icu4e)
+pkg_i18n_homepage = https://github.com/erlang-unicode/i18n
+pkg_i18n_fetch = git
+pkg_i18n_repo = https://github.com/erlang-unicode/i18n
+pkg_i18n_commit = master
+
+PACKAGES += ibrowse
+pkg_ibrowse_name = ibrowse
+pkg_ibrowse_description = Erlang HTTP client
+pkg_ibrowse_homepage = https://github.com/cmullaparthi/ibrowse
+pkg_ibrowse_fetch = git
+pkg_ibrowse_repo = https://github.com/cmullaparthi/ibrowse
+pkg_ibrowse_commit = master
+
+PACKAGES += idna
+pkg_idna_name = idna
+pkg_idna_description = Erlang IDNA lib
+pkg_idna_homepage = https://github.com/benoitc/erlang-idna
+pkg_idna_fetch = git
+pkg_idna_repo = https://github.com/benoitc/erlang-idna
+pkg_idna_commit = master
+
+PACKAGES += ierlang
+pkg_ierlang_name = ierlang
+pkg_ierlang_description = An Erlang language kernel for IPython.
+pkg_ierlang_homepage = https://github.com/robbielynch/ierlang
+pkg_ierlang_fetch = git
+pkg_ierlang_repo = https://github.com/robbielynch/ierlang
+pkg_ierlang_commit = master
+
+PACKAGES += iota
+pkg_iota_name = iota
+pkg_iota_description = iota (Inter-dependency Objective Testing Apparatus) - a tool to enforce clean separation of responsibilities in Erlang code
+pkg_iota_homepage = https://github.com/jpgneves/iota
+pkg_iota_fetch = git
+pkg_iota_repo = https://github.com/jpgneves/iota
+pkg_iota_commit = master
+
+PACKAGES += irc_lib
+pkg_irc_lib_name = irc_lib
+pkg_irc_lib_description = Erlang irc client library
+pkg_irc_lib_homepage = https://github.com/OtpChatBot/irc_lib
+pkg_irc_lib_fetch = git
+pkg_irc_lib_repo = https://github.com/OtpChatBot/irc_lib
+pkg_irc_lib_commit = master
+
+PACKAGES += ircd
+pkg_ircd_name = ircd
+pkg_ircd_description = A pluggable IRC daemon application/library for Erlang.
+pkg_ircd_homepage = https://github.com/tonyg/erlang-ircd
+pkg_ircd_fetch = git
+pkg_ircd_repo = https://github.com/tonyg/erlang-ircd
+pkg_ircd_commit = master
+
+PACKAGES += iris
+pkg_iris_name = iris
+pkg_iris_description = Iris Erlang binding
+pkg_iris_homepage = https://github.com/project-iris/iris-erl
+pkg_iris_fetch = git
+pkg_iris_repo = https://github.com/project-iris/iris-erl
+pkg_iris_commit = master
+
+PACKAGES += iso8601
+pkg_iso8601_name = iso8601
+pkg_iso8601_description = Erlang ISO 8601 date formatter/parser
+pkg_iso8601_homepage = https://github.com/seansawyer/erlang_iso8601
+pkg_iso8601_fetch = git
+pkg_iso8601_repo = https://github.com/seansawyer/erlang_iso8601
+pkg_iso8601_commit = master
+
+PACKAGES += jamdb_sybase
+pkg_jamdb_sybase_name = jamdb_sybase
+pkg_jamdb_sybase_description = Erlang driver for SAP Sybase ASE
+pkg_jamdb_sybase_homepage = https://github.com/erlangbureau/jamdb_sybase
+pkg_jamdb_sybase_fetch = git
+pkg_jamdb_sybase_repo = https://github.com/erlangbureau/jamdb_sybase
+pkg_jamdb_sybase_commit = master
+
+PACKAGES += jerg
+pkg_jerg_name = jerg
+pkg_jerg_description = JSON Schema to Erlang Records Generator
+pkg_jerg_homepage = https://github.com/ddossot/jerg
+pkg_jerg_fetch = git
+pkg_jerg_repo = https://github.com/ddossot/jerg
+pkg_jerg_commit = master
+
+PACKAGES += jesse
+pkg_jesse_name = jesse
+pkg_jesse_description = jesse (JSon Schema Erlang) is an implementation of a json schema validator for Erlang.
+pkg_jesse_homepage = https://github.com/for-GET/jesse
+pkg_jesse_fetch = git
+pkg_jesse_repo = https://github.com/for-GET/jesse
+pkg_jesse_commit = master
+
+PACKAGES += jiffy
+pkg_jiffy_name = jiffy
+pkg_jiffy_description = JSON NIFs for Erlang.
+pkg_jiffy_homepage = https://github.com/davisp/jiffy
+pkg_jiffy_fetch = git
+pkg_jiffy_repo = https://github.com/davisp/jiffy
+pkg_jiffy_commit = master
+
+PACKAGES += jiffy_v
+pkg_jiffy_v_name = jiffy_v
+pkg_jiffy_v_description = JSON validation utility
+pkg_jiffy_v_homepage = https://github.com/shizzard/jiffy-v
+pkg_jiffy_v_fetch = git
+pkg_jiffy_v_repo = https://github.com/shizzard/jiffy-v
+pkg_jiffy_v_commit = master
+
+PACKAGES += jobs
+pkg_jobs_name = jobs
+pkg_jobs_description = a Job scheduler for load regulation
+pkg_jobs_homepage = https://github.com/esl/jobs
+pkg_jobs_fetch = git
+pkg_jobs_repo = https://github.com/esl/jobs
+pkg_jobs_commit = master
+
+PACKAGES += joxa
+pkg_joxa_name = joxa
+pkg_joxa_description = A Modern Lisp for the Erlang VM
+pkg_joxa_homepage = https://github.com/joxa/joxa
+pkg_joxa_fetch = git
+pkg_joxa_repo = https://github.com/joxa/joxa
+pkg_joxa_commit = master
+
+PACKAGES += json
+pkg_json_name = json
+pkg_json_description = a high level json library for erlang (17.0+)
+pkg_json_homepage = https://github.com/talentdeficit/json
+pkg_json_fetch = git
+pkg_json_repo = https://github.com/talentdeficit/json
+pkg_json_commit = master
+
+PACKAGES += json_rec
+pkg_json_rec_name = json_rec
+pkg_json_rec_description = JSON to erlang record
+pkg_json_rec_homepage = https://github.com/justinkirby/json_rec
+pkg_json_rec_fetch = git
+pkg_json_rec_repo = https://github.com/justinkirby/json_rec
+pkg_json_rec_commit = master
+
+PACKAGES += jsone
+pkg_jsone_name = jsone
+pkg_jsone_description = An Erlang library for encoding, decoding JSON data.
+pkg_jsone_homepage = https://github.com/sile/jsone.git
+pkg_jsone_fetch = git
+pkg_jsone_repo = https://github.com/sile/jsone.git
+pkg_jsone_commit = master
+
+PACKAGES += jsonerl
+pkg_jsonerl_name = jsonerl
+pkg_jsonerl_description = yet another but slightly different erlang <-> json encoder/decoder
+pkg_jsonerl_homepage = https://github.com/lambder/jsonerl
+pkg_jsonerl_fetch = git
+pkg_jsonerl_repo = https://github.com/lambder/jsonerl
+pkg_jsonerl_commit = master
+
+PACKAGES += jsonpath
+pkg_jsonpath_name = jsonpath
+pkg_jsonpath_description = Fast Erlang JSON data retrieval and updates via javascript-like notation
+pkg_jsonpath_homepage = https://github.com/GeneStevens/jsonpath
+pkg_jsonpath_fetch = git
+pkg_jsonpath_repo = https://github.com/GeneStevens/jsonpath
+pkg_jsonpath_commit = master
+
+PACKAGES += jsonx
+pkg_jsonx_name = jsonx
+pkg_jsonx_description = JSONX is an Erlang library for efficient decode and encode JSON, written in C.
+pkg_jsonx_homepage = https://github.com/iskra/jsonx
+pkg_jsonx_fetch = git
+pkg_jsonx_repo = https://github.com/iskra/jsonx
+pkg_jsonx_commit = master
+
+PACKAGES += jsx
+pkg_jsx_name = jsx
+pkg_jsx_description = An Erlang application for consuming, producing and manipulating JSON.
+pkg_jsx_homepage = https://github.com/talentdeficit/jsx
+pkg_jsx_fetch = git
+pkg_jsx_repo = https://github.com/talentdeficit/jsx
+pkg_jsx_commit = master
+
+PACKAGES += kafka
+pkg_kafka_name = kafka
+pkg_kafka_description = Kafka consumer and producer in Erlang
+pkg_kafka_homepage = https://github.com/wooga/kafka-erlang
+pkg_kafka_fetch = git
+pkg_kafka_repo = https://github.com/wooga/kafka-erlang
+pkg_kafka_commit = master
+
+PACKAGES += kafka_protocol
+pkg_kafka_protocol_name = kafka_protocol
+pkg_kafka_protocol_description = Kafka protocol Erlang library
+pkg_kafka_protocol_homepage = https://github.com/klarna/kafka_protocol
+pkg_kafka_protocol_fetch = git
+pkg_kafka_protocol_repo = https://github.com/klarna/kafka_protocol.git
+pkg_kafka_protocol_commit = master
+
+PACKAGES += kai
+pkg_kai_name = kai
+pkg_kai_description = DHT storage by Takeshi Inoue
+pkg_kai_homepage = https://github.com/synrc/kai
+pkg_kai_fetch = git
+pkg_kai_repo = https://github.com/synrc/kai
+pkg_kai_commit = master
+
+PACKAGES += katja
+pkg_katja_name = katja
+pkg_katja_description = A simple Riemann client written in Erlang.
+pkg_katja_homepage = https://github.com/nifoc/katja
+pkg_katja_fetch = git
+pkg_katja_repo = https://github.com/nifoc/katja
+pkg_katja_commit = master
+
+PACKAGES += kdht
+pkg_kdht_name = kdht
+pkg_kdht_description = kdht is an erlang DHT implementation
+pkg_kdht_homepage = https://github.com/kevinlynx/kdht
+pkg_kdht_fetch = git
+pkg_kdht_repo = https://github.com/kevinlynx/kdht
+pkg_kdht_commit = master
+
+PACKAGES += key2value
+pkg_key2value_name = key2value
+pkg_key2value_description = Erlang 2-way map
+pkg_key2value_homepage = https://github.com/okeuday/key2value
+pkg_key2value_fetch = git
+pkg_key2value_repo = https://github.com/okeuday/key2value
+pkg_key2value_commit = master
+
+PACKAGES += keys1value
+pkg_keys1value_name = keys1value
+pkg_keys1value_description = Erlang set associative map for key lists
+pkg_keys1value_homepage = https://github.com/okeuday/keys1value
+pkg_keys1value_fetch = git
+pkg_keys1value_repo = https://github.com/okeuday/keys1value
+pkg_keys1value_commit = master
+
+PACKAGES += kinetic
+pkg_kinetic_name = kinetic
+pkg_kinetic_description = Erlang Kinesis Client
+pkg_kinetic_homepage = https://github.com/AdRoll/kinetic
+pkg_kinetic_fetch = git
+pkg_kinetic_repo = https://github.com/AdRoll/kinetic
+pkg_kinetic_commit = master
+
+PACKAGES += kjell
+pkg_kjell_name = kjell
+pkg_kjell_description = Erlang Shell
+pkg_kjell_homepage = https://github.com/karlll/kjell
+pkg_kjell_fetch = git
+pkg_kjell_repo = https://github.com/karlll/kjell
+pkg_kjell_commit = master
+
+PACKAGES += kraken
+pkg_kraken_name = kraken
+pkg_kraken_description = Distributed Pubsub Server for Realtime Apps
+pkg_kraken_homepage = https://github.com/Asana/kraken
+pkg_kraken_fetch = git
+pkg_kraken_repo = https://github.com/Asana/kraken
+pkg_kraken_commit = master
+
+PACKAGES += kucumberl
+pkg_kucumberl_name = kucumberl
+pkg_kucumberl_description = A pure-erlang, open-source, implementation of Cucumber
+pkg_kucumberl_homepage = https://github.com/openshine/kucumberl
+pkg_kucumberl_fetch = git
+pkg_kucumberl_repo = https://github.com/openshine/kucumberl
+pkg_kucumberl_commit = master
+
+PACKAGES += kvc
+pkg_kvc_name = kvc
+pkg_kvc_description = KVC - Key Value Coding for Erlang data structures
+pkg_kvc_homepage = https://github.com/etrepum/kvc
+pkg_kvc_fetch = git
+pkg_kvc_repo = https://github.com/etrepum/kvc
+pkg_kvc_commit = master
+
+PACKAGES += kvlists
+pkg_kvlists_name = kvlists
+pkg_kvlists_description = Lists of key-value pairs (decoded JSON) in Erlang
+pkg_kvlists_homepage = https://github.com/jcomellas/kvlists
+pkg_kvlists_fetch = git
+pkg_kvlists_repo = https://github.com/jcomellas/kvlists
+pkg_kvlists_commit = master
+
+PACKAGES += kvs
+pkg_kvs_name = kvs
+pkg_kvs_description = Container and Iterator
+pkg_kvs_homepage = https://github.com/synrc/kvs
+pkg_kvs_fetch = git
+pkg_kvs_repo = https://github.com/synrc/kvs
+pkg_kvs_commit = master
+
+PACKAGES += lager
+pkg_lager_name = lager
+pkg_lager_description = A logging framework for Erlang/OTP.
+pkg_lager_homepage = https://github.com/erlang-lager/lager
+pkg_lager_fetch = git
+pkg_lager_repo = https://github.com/erlang-lager/lager
+pkg_lager_commit = master
+
+PACKAGES += lager_amqp_backend
+pkg_lager_amqp_backend_name = lager_amqp_backend
+pkg_lager_amqp_backend_description = AMQP RabbitMQ Lager backend
+pkg_lager_amqp_backend_homepage = https://github.com/jbrisbin/lager_amqp_backend
+pkg_lager_amqp_backend_fetch = git
+pkg_lager_amqp_backend_repo = https://github.com/jbrisbin/lager_amqp_backend
+pkg_lager_amqp_backend_commit = master
+
+PACKAGES += lager_syslog
+pkg_lager_syslog_name = lager_syslog
+pkg_lager_syslog_description = Syslog backend for lager
+pkg_lager_syslog_homepage = https://github.com/erlang-lager/lager_syslog
+pkg_lager_syslog_fetch = git
+pkg_lager_syslog_repo = https://github.com/erlang-lager/lager_syslog
+pkg_lager_syslog_commit = master
+
+PACKAGES += lambdapad
+pkg_lambdapad_name = lambdapad
+pkg_lambdapad_description = Static site generator using Erlang. Yes, Erlang.
+pkg_lambdapad_homepage = https://github.com/gar1t/lambdapad
+pkg_lambdapad_fetch = git
+pkg_lambdapad_repo = https://github.com/gar1t/lambdapad
+pkg_lambdapad_commit = master
+
+PACKAGES += lasp
+pkg_lasp_name = lasp
+pkg_lasp_description = A Language for Distributed, Eventually Consistent Computations
+pkg_lasp_homepage = http://lasp-lang.org/
+pkg_lasp_fetch = git
+pkg_lasp_repo = https://github.com/lasp-lang/lasp
+pkg_lasp_commit = master
+
+PACKAGES += lasse
+pkg_lasse_name = lasse
+pkg_lasse_description = SSE handler for Cowboy
+pkg_lasse_homepage = https://github.com/inaka/lasse
+pkg_lasse_fetch = git
+pkg_lasse_repo = https://github.com/inaka/lasse
+pkg_lasse_commit = master
+
+PACKAGES += ldap
+pkg_ldap_name = ldap
+pkg_ldap_description = LDAP server written in Erlang
+pkg_ldap_homepage = https://github.com/spawnproc/ldap
+pkg_ldap_fetch = git
+pkg_ldap_repo = https://github.com/spawnproc/ldap
+pkg_ldap_commit = master
+
+PACKAGES += lethink
+pkg_lethink_name = lethink
+pkg_lethink_description = erlang driver for rethinkdb
+pkg_lethink_homepage = https://github.com/taybin/lethink
+pkg_lethink_fetch = git
+pkg_lethink_repo = https://github.com/taybin/lethink
+pkg_lethink_commit = master
+
+PACKAGES += lfe
+pkg_lfe_name = lfe
+pkg_lfe_description = Lisp Flavoured Erlang (LFE)
+pkg_lfe_homepage = https://github.com/rvirding/lfe
+pkg_lfe_fetch = git
+pkg_lfe_repo = https://github.com/rvirding/lfe
+pkg_lfe_commit = master
+
+PACKAGES += ling
+pkg_ling_name = ling
+pkg_ling_description = Erlang on Xen
+pkg_ling_homepage = https://github.com/cloudozer/ling
+pkg_ling_fetch = git
+pkg_ling_repo = https://github.com/cloudozer/ling
+pkg_ling_commit = master
+
+PACKAGES += live
+pkg_live_name = live
+pkg_live_description = Automated module and configuration reloader.
+pkg_live_homepage = http://ninenines.eu
+pkg_live_fetch = git
+pkg_live_repo = https://github.com/ninenines/live
+pkg_live_commit = master
+
+PACKAGES += lmq
+pkg_lmq_name = lmq
+pkg_lmq_description = Lightweight Message Queue
+pkg_lmq_homepage = https://github.com/iij/lmq
+pkg_lmq_fetch = git
+pkg_lmq_repo = https://github.com/iij/lmq
+pkg_lmq_commit = master
+
+PACKAGES += locker
+pkg_locker_name = locker
+pkg_locker_description = Atomic distributed 'check and set' for short-lived keys
+pkg_locker_homepage = https://github.com/wooga/locker
+pkg_locker_fetch = git
+pkg_locker_repo = https://github.com/wooga/locker
+pkg_locker_commit = master
+
+PACKAGES += locks
+pkg_locks_name = locks
+pkg_locks_description = A scalable, deadlock-resolving resource locker
+pkg_locks_homepage = https://github.com/uwiger/locks
+pkg_locks_fetch = git
+pkg_locks_repo = https://github.com/uwiger/locks
+pkg_locks_commit = master
+
+PACKAGES += log4erl
+pkg_log4erl_name = log4erl
+pkg_log4erl_description = A logger for erlang in the spirit of Log4J.
+pkg_log4erl_homepage = https://github.com/ahmednawras/log4erl
+pkg_log4erl_fetch = git
+pkg_log4erl_repo = https://github.com/ahmednawras/log4erl
+pkg_log4erl_commit = master
+
+PACKAGES += lol
+pkg_lol_name = lol
+pkg_lol_description = Lisp on erLang, and programming is fun again
+pkg_lol_homepage = https://github.com/b0oh/lol
+pkg_lol_fetch = git
+pkg_lol_repo = https://github.com/b0oh/lol
+pkg_lol_commit = master
+
+PACKAGES += lucid
+pkg_lucid_name = lucid
+pkg_lucid_description = HTTP/2 server written in Erlang
+pkg_lucid_homepage = https://github.com/tatsuhiro-t/lucid
+pkg_lucid_fetch = git
+pkg_lucid_repo = https://github.com/tatsuhiro-t/lucid
+pkg_lucid_commit = master
+
+PACKAGES += luerl
+pkg_luerl_name = luerl
+pkg_luerl_description = Lua in Erlang
+pkg_luerl_homepage = https://github.com/rvirding/luerl
+pkg_luerl_fetch = git
+pkg_luerl_repo = https://github.com/rvirding/luerl
+pkg_luerl_commit = develop
+
+PACKAGES += luwak
+pkg_luwak_name = luwak
+pkg_luwak_description = Large-object storage interface for Riak
+pkg_luwak_homepage = https://github.com/basho/luwak
+pkg_luwak_fetch = git
+pkg_luwak_repo = https://github.com/basho/luwak
+pkg_luwak_commit = master
+
+PACKAGES += lux
+pkg_lux_name = lux
+pkg_lux_description = Lux (LUcid eXpect scripting) simplifies test automation and provides an Expect-style execution of commands
+pkg_lux_homepage = https://github.com/hawk/lux
+pkg_lux_fetch = git
+pkg_lux_repo = https://github.com/hawk/lux
+pkg_lux_commit = master
+
+PACKAGES += machi
+pkg_machi_name = machi
+pkg_machi_description = Machi file store
+pkg_machi_homepage = https://github.com/basho/machi
+pkg_machi_fetch = git
+pkg_machi_repo = https://github.com/basho/machi
+pkg_machi_commit = master
+
+PACKAGES += mad
+pkg_mad_name = mad
+pkg_mad_description = Small and Fast Rebar Replacement
+pkg_mad_homepage = https://github.com/synrc/mad
+pkg_mad_fetch = git
+pkg_mad_repo = https://github.com/synrc/mad
+pkg_mad_commit = master
+
+PACKAGES += marina
+pkg_marina_name = marina
+pkg_marina_description = Non-blocking Erlang Cassandra CQL3 client
+pkg_marina_homepage = https://github.com/lpgauth/marina
+pkg_marina_fetch = git
+pkg_marina_repo = https://github.com/lpgauth/marina
+pkg_marina_commit = master
+
+PACKAGES += mavg
+pkg_mavg_name = mavg
+pkg_mavg_description = Erlang :: Exponential moving average library
+pkg_mavg_homepage = https://github.com/EchoTeam/mavg
+pkg_mavg_fetch = git
+pkg_mavg_repo = https://github.com/EchoTeam/mavg
+pkg_mavg_commit = master
+
+PACKAGES += mc_erl
+pkg_mc_erl_name = mc_erl
+pkg_mc_erl_description = mc-erl is a server for Minecraft 1.4.7 written in Erlang.
+pkg_mc_erl_homepage = https://github.com/clonejo/mc-erl
+pkg_mc_erl_fetch = git
+pkg_mc_erl_repo = https://github.com/clonejo/mc-erl
+pkg_mc_erl_commit = master
+
+PACKAGES += mcd
+pkg_mcd_name = mcd
+pkg_mcd_description = Fast memcached protocol client in pure Erlang
+pkg_mcd_homepage = https://github.com/EchoTeam/mcd
+pkg_mcd_fetch = git
+pkg_mcd_repo = https://github.com/EchoTeam/mcd
+pkg_mcd_commit = master
+
+PACKAGES += mcerlang
+pkg_mcerlang_name = mcerlang
+pkg_mcerlang_description = The McErlang model checker for Erlang
+pkg_mcerlang_homepage = https://github.com/fredlund/McErlang
+pkg_mcerlang_fetch = git
+pkg_mcerlang_repo = https://github.com/fredlund/McErlang
+pkg_mcerlang_commit = master
+
+PACKAGES += meck
+pkg_meck_name = meck
+pkg_meck_description = A mocking library for Erlang
+pkg_meck_homepage = https://github.com/eproxus/meck
+pkg_meck_fetch = git
+pkg_meck_repo = https://github.com/eproxus/meck
+pkg_meck_commit = master
+
+PACKAGES += mekao
+pkg_mekao_name = mekao
+pkg_mekao_description = SQL constructor
+pkg_mekao_homepage = https://github.com/ddosia/mekao
+pkg_mekao_fetch = git
+pkg_mekao_repo = https://github.com/ddosia/mekao
+pkg_mekao_commit = master
+
+PACKAGES += memo
+pkg_memo_name = memo
+pkg_memo_description = Erlang memoization server
+pkg_memo_homepage = https://github.com/tuncer/memo
+pkg_memo_fetch = git
+pkg_memo_repo = https://github.com/tuncer/memo
+pkg_memo_commit = master
+
+PACKAGES += merge_index
+pkg_merge_index_name = merge_index
+pkg_merge_index_description = MergeIndex is an Erlang library for storing ordered sets on disk. It is very similar to an SSTable (in Google's Bigtable) or an HFile (in Hadoop).
+pkg_merge_index_homepage = https://github.com/basho/merge_index
+pkg_merge_index_fetch = git
+pkg_merge_index_repo = https://github.com/basho/merge_index
+pkg_merge_index_commit = master
+
+PACKAGES += merl
+pkg_merl_name = merl
+pkg_merl_description = Metaprogramming in Erlang
+pkg_merl_homepage = https://github.com/richcarl/merl
+pkg_merl_fetch = git
+pkg_merl_repo = https://github.com/richcarl/merl
+pkg_merl_commit = master
+
+PACKAGES += mimerl
+pkg_mimerl_name = mimerl
+pkg_mimerl_description = library to handle mimetypes
+pkg_mimerl_homepage = https://github.com/benoitc/mimerl
+pkg_mimerl_fetch = git
+pkg_mimerl_repo = https://github.com/benoitc/mimerl
+pkg_mimerl_commit = master
+
+PACKAGES += mimetypes
+pkg_mimetypes_name = mimetypes
+pkg_mimetypes_description = Erlang MIME types library
+pkg_mimetypes_homepage = https://github.com/spawngrid/mimetypes
+pkg_mimetypes_fetch = git
+pkg_mimetypes_repo = https://github.com/spawngrid/mimetypes
+pkg_mimetypes_commit = master
+
+PACKAGES += mixer
+pkg_mixer_name = mixer
+pkg_mixer_description = Mix in functions from other modules
+pkg_mixer_homepage = https://github.com/chef/mixer
+pkg_mixer_fetch = git
+pkg_mixer_repo = https://github.com/chef/mixer
+pkg_mixer_commit = master
+
+PACKAGES += mochiweb
+pkg_mochiweb_name = mochiweb
+pkg_mochiweb_description = MochiWeb is an Erlang library for building lightweight HTTP servers.
+pkg_mochiweb_homepage = https://github.com/mochi/mochiweb
+pkg_mochiweb_fetch = git
+pkg_mochiweb_repo = https://github.com/mochi/mochiweb
+pkg_mochiweb_commit = master
+
+PACKAGES += mochiweb_xpath
+pkg_mochiweb_xpath_name = mochiweb_xpath
+pkg_mochiweb_xpath_description = XPath support for mochiweb's html parser
+pkg_mochiweb_xpath_homepage = https://github.com/retnuh/mochiweb_xpath
+pkg_mochiweb_xpath_fetch = git
+pkg_mochiweb_xpath_repo = https://github.com/retnuh/mochiweb_xpath
+pkg_mochiweb_xpath_commit = master
+
+PACKAGES += mockgyver
+pkg_mockgyver_name = mockgyver
+pkg_mockgyver_description = A mocking library for Erlang
+pkg_mockgyver_homepage = https://github.com/klajo/mockgyver
+pkg_mockgyver_fetch = git
+pkg_mockgyver_repo = https://github.com/klajo/mockgyver
+pkg_mockgyver_commit = master
+
+PACKAGES += modlib
+pkg_modlib_name = modlib
+pkg_modlib_description = Web framework based on Erlang's inets httpd
+pkg_modlib_homepage = https://github.com/gar1t/modlib
+pkg_modlib_fetch = git
+pkg_modlib_repo = https://github.com/gar1t/modlib
+pkg_modlib_commit = master
+
+PACKAGES += mongodb
+pkg_mongodb_name = mongodb
+pkg_mongodb_description = MongoDB driver for Erlang
+pkg_mongodb_homepage = https://github.com/comtihon/mongodb-erlang
+pkg_mongodb_fetch = git
+pkg_mongodb_repo = https://github.com/comtihon/mongodb-erlang
+pkg_mongodb_commit = master
+
+PACKAGES += mongooseim
+pkg_mongooseim_name = mongooseim
+pkg_mongooseim_description = Jabber / XMPP server with focus on performance and scalability, by Erlang Solutions
+pkg_mongooseim_homepage = https://www.erlang-solutions.com/products/mongooseim-massively-scalable-ejabberd-platform
+pkg_mongooseim_fetch = git
+pkg_mongooseim_repo = https://github.com/esl/MongooseIM
+pkg_mongooseim_commit = master
+
+PACKAGES += moyo
+pkg_moyo_name = moyo
+pkg_moyo_description = Erlang utility functions library
+pkg_moyo_homepage = https://github.com/dwango/moyo
+pkg_moyo_fetch = git
+pkg_moyo_repo = https://github.com/dwango/moyo
+pkg_moyo_commit = master
+
+PACKAGES += msgpack
+pkg_msgpack_name = msgpack
+pkg_msgpack_description = MessagePack (de)serializer implementation for Erlang
+pkg_msgpack_homepage = https://github.com/msgpack/msgpack-erlang
+pkg_msgpack_fetch = git
+pkg_msgpack_repo = https://github.com/msgpack/msgpack-erlang
+pkg_msgpack_commit = master
+
+PACKAGES += mu2
+pkg_mu2_name = mu2
+pkg_mu2_description = Erlang mutation testing tool
+pkg_mu2_homepage = https://github.com/ramsay-t/mu2
+pkg_mu2_fetch = git
+pkg_mu2_repo = https://github.com/ramsay-t/mu2
+pkg_mu2_commit = master
+
+PACKAGES += mustache
+pkg_mustache_name = mustache
+pkg_mustache_description = Mustache template engine for Erlang.
+pkg_mustache_homepage = https://github.com/mojombo/mustache.erl
+pkg_mustache_fetch = git
+pkg_mustache_repo = https://github.com/mojombo/mustache.erl
+pkg_mustache_commit = master
+
+PACKAGES += myproto
+pkg_myproto_name = myproto
+pkg_myproto_description = MySQL Server Protocol in Erlang
+pkg_myproto_homepage = https://github.com/altenwald/myproto
+pkg_myproto_fetch = git
+pkg_myproto_repo = https://github.com/altenwald/myproto
+pkg_myproto_commit = master
+
+PACKAGES += mysql
+pkg_mysql_name = mysql
+pkg_mysql_description = MySQL client library for Erlang/OTP
+pkg_mysql_homepage = https://github.com/mysql-otp/mysql-otp
+pkg_mysql_fetch = git
+pkg_mysql_repo = https://github.com/mysql-otp/mysql-otp
+pkg_mysql_commit = 1.5.1
+
+PACKAGES += n2o
+pkg_n2o_name = n2o
+pkg_n2o_description = WebSocket Application Server
+pkg_n2o_homepage = https://github.com/5HT/n2o
+pkg_n2o_fetch = git
+pkg_n2o_repo = https://github.com/5HT/n2o
+pkg_n2o_commit = master
+
+PACKAGES += nat_upnp
+pkg_nat_upnp_name = nat_upnp
+pkg_nat_upnp_description = Erlang library to map your internal port to an external using UNP IGD
+pkg_nat_upnp_homepage = https://github.com/benoitc/nat_upnp
+pkg_nat_upnp_fetch = git
+pkg_nat_upnp_repo = https://github.com/benoitc/nat_upnp
+pkg_nat_upnp_commit = master
+
+PACKAGES += neo4j
+pkg_neo4j_name = neo4j
+pkg_neo4j_description = Erlang client library for Neo4J.
+pkg_neo4j_homepage = https://github.com/dmitriid/neo4j-erlang
+pkg_neo4j_fetch = git
+pkg_neo4j_repo = https://github.com/dmitriid/neo4j-erlang
+pkg_neo4j_commit = master
+
+PACKAGES += neotoma
+pkg_neotoma_name = neotoma
+pkg_neotoma_description = Erlang library and packrat parser-generator for parsing expression grammars.
+pkg_neotoma_homepage = https://github.com/seancribbs/neotoma
+pkg_neotoma_fetch = git
+pkg_neotoma_repo = https://github.com/seancribbs/neotoma
+pkg_neotoma_commit = master
+
+PACKAGES += newrelic
+pkg_newrelic_name = newrelic
+pkg_newrelic_description = Erlang library for sending metrics to New Relic
+pkg_newrelic_homepage = https://github.com/wooga/newrelic-erlang
+pkg_newrelic_fetch = git
+pkg_newrelic_repo = https://github.com/wooga/newrelic-erlang
+pkg_newrelic_commit = master
+
+PACKAGES += nifty
+pkg_nifty_name = nifty
+pkg_nifty_description = Erlang NIF wrapper generator
+pkg_nifty_homepage = https://github.com/parapluu/nifty
+pkg_nifty_fetch = git
+pkg_nifty_repo = https://github.com/parapluu/nifty
+pkg_nifty_commit = master
+
+PACKAGES += nitrogen_core
+pkg_nitrogen_core_name = nitrogen_core
+pkg_nitrogen_core_description = The core Nitrogen library.
+pkg_nitrogen_core_homepage = http://nitrogenproject.com/
+pkg_nitrogen_core_fetch = git
+pkg_nitrogen_core_repo = https://github.com/nitrogen/nitrogen_core
+pkg_nitrogen_core_commit = master
+
+PACKAGES += nkbase
+pkg_nkbase_name = nkbase
+pkg_nkbase_description = NkBASE distributed database
+pkg_nkbase_homepage = https://github.com/Nekso/nkbase
+pkg_nkbase_fetch = git
+pkg_nkbase_repo = https://github.com/Nekso/nkbase
+pkg_nkbase_commit = develop
+
+PACKAGES += nkdocker
+pkg_nkdocker_name = nkdocker
+pkg_nkdocker_description = Erlang Docker client
+pkg_nkdocker_homepage = https://github.com/Nekso/nkdocker
+pkg_nkdocker_fetch = git
+pkg_nkdocker_repo = https://github.com/Nekso/nkdocker
+pkg_nkdocker_commit = master
+
+PACKAGES += nkpacket
+pkg_nkpacket_name = nkpacket
+pkg_nkpacket_description = Generic Erlang transport layer
+pkg_nkpacket_homepage = https://github.com/Nekso/nkpacket
+pkg_nkpacket_fetch = git
+pkg_nkpacket_repo = https://github.com/Nekso/nkpacket
+pkg_nkpacket_commit = master
+
+PACKAGES += nksip
+pkg_nksip_name = nksip
+pkg_nksip_description = Erlang SIP application server
+pkg_nksip_homepage = https://github.com/kalta/nksip
+pkg_nksip_fetch = git
+pkg_nksip_repo = https://github.com/kalta/nksip
+pkg_nksip_commit = master
+
+PACKAGES += nodefinder
+pkg_nodefinder_name = nodefinder
+pkg_nodefinder_description = automatic node discovery via UDP multicast
+pkg_nodefinder_homepage = https://github.com/erlanger/nodefinder
+pkg_nodefinder_fetch = git
+pkg_nodefinder_repo = https://github.com/okeuday/nodefinder
+pkg_nodefinder_commit = master
+
+PACKAGES += nprocreg
+pkg_nprocreg_name = nprocreg
+pkg_nprocreg_description = Minimal Distributed Erlang Process Registry
+pkg_nprocreg_homepage = http://nitrogenproject.com/
+pkg_nprocreg_fetch = git
+pkg_nprocreg_repo = https://github.com/nitrogen/nprocreg
+pkg_nprocreg_commit = master
+
+PACKAGES += oauth
+pkg_oauth_name = oauth
+pkg_oauth_description = An Erlang OAuth 1.0 implementation
+pkg_oauth_homepage = https://github.com/tim/erlang-oauth
+pkg_oauth_fetch = git
+pkg_oauth_repo = https://github.com/tim/erlang-oauth
+pkg_oauth_commit = master
+
+PACKAGES += oauth2
+pkg_oauth2_name = oauth2
+pkg_oauth2_description = Erlang Oauth2 implementation
+pkg_oauth2_homepage = https://github.com/kivra/oauth2
+pkg_oauth2_fetch = git
+pkg_oauth2_repo = https://github.com/kivra/oauth2
+pkg_oauth2_commit = master
+
+PACKAGES += observer_cli
+pkg_observer_cli_name = observer_cli
+pkg_observer_cli_description = Visualize Erlang/Elixir Nodes On The Command Line
+pkg_observer_cli_homepage = http://zhongwencool.github.io/observer_cli
+pkg_observer_cli_fetch = git
+pkg_observer_cli_repo = https://github.com/zhongwencool/observer_cli
+pkg_observer_cli_commit = master
+
+PACKAGES += octopus
+pkg_octopus_name = octopus
+pkg_octopus_description = Small and flexible pool manager written in Erlang
+pkg_octopus_homepage = https://github.com/erlangbureau/octopus
+pkg_octopus_fetch = git
+pkg_octopus_repo = https://github.com/erlangbureau/octopus
+pkg_octopus_commit = master
+
+PACKAGES += of_protocol
+pkg_of_protocol_name = of_protocol
+pkg_of_protocol_description = OpenFlow Protocol Library for Erlang
+pkg_of_protocol_homepage = https://github.com/FlowForwarding/of_protocol
+pkg_of_protocol_fetch = git
+pkg_of_protocol_repo = https://github.com/FlowForwarding/of_protocol
+pkg_of_protocol_commit = master
+
+PACKAGES += opencouch
+pkg_opencouch_name = couch
+pkg_opencouch_description = A embeddable document oriented database compatible with Apache CouchDB
+pkg_opencouch_homepage = https://github.com/benoitc/opencouch
+pkg_opencouch_fetch = git
+pkg_opencouch_repo = https://github.com/benoitc/opencouch
+pkg_opencouch_commit = master
+
+PACKAGES += openflow
+pkg_openflow_name = openflow
+pkg_openflow_description = An OpenFlow controller written in pure erlang
+pkg_openflow_homepage = https://github.com/renatoaguiar/erlang-openflow
+pkg_openflow_fetch = git
+pkg_openflow_repo = https://github.com/renatoaguiar/erlang-openflow
+pkg_openflow_commit = master
+
+PACKAGES += openid
+pkg_openid_name = openid
+pkg_openid_description = Erlang OpenID
+pkg_openid_homepage = https://github.com/brendonh/erl_openid
+pkg_openid_fetch = git
+pkg_openid_repo = https://github.com/brendonh/erl_openid
+pkg_openid_commit = master
+
+PACKAGES += openpoker
+pkg_openpoker_name = openpoker
+pkg_openpoker_description = Genesis Texas hold'em Game Server
+pkg_openpoker_homepage = https://github.com/hpyhacking/openpoker
+pkg_openpoker_fetch = git
+pkg_openpoker_repo = https://github.com/hpyhacking/openpoker
+pkg_openpoker_commit = master
+
+PACKAGES += otpbp
+pkg_otpbp_name = otpbp
+pkg_otpbp_description = Parse transformer for use new OTP functions in old Erlang/OTP releases (R15, R16, 17, 18, 19)
+pkg_otpbp_homepage = https://github.com/Ledest/otpbp
+pkg_otpbp_fetch = git
+pkg_otpbp_repo = https://github.com/Ledest/otpbp
+pkg_otpbp_commit = master
+
+PACKAGES += pal
+pkg_pal_name = pal
+pkg_pal_description = Pragmatic Authentication Library
+pkg_pal_homepage = https://github.com/manifest/pal
+pkg_pal_fetch = git
+pkg_pal_repo = https://github.com/manifest/pal
+pkg_pal_commit = master
+
+PACKAGES += parse_trans
+pkg_parse_trans_name = parse_trans
+pkg_parse_trans_description = Parse transform utilities for Erlang
+pkg_parse_trans_homepage = https://github.com/uwiger/parse_trans
+pkg_parse_trans_fetch = git
+pkg_parse_trans_repo = https://github.com/uwiger/parse_trans
+pkg_parse_trans_commit = master
+
+PACKAGES += parsexml
+pkg_parsexml_name = parsexml
+pkg_parsexml_description = Simple DOM XML parser with convenient and very simple API
+pkg_parsexml_homepage = https://github.com/maxlapshin/parsexml
+pkg_parsexml_fetch = git
+pkg_parsexml_repo = https://github.com/maxlapshin/parsexml
+pkg_parsexml_commit = master
+
+PACKAGES += partisan
+pkg_partisan_name = partisan
+pkg_partisan_description = High-performance, high-scalability distributed computing with Erlang and Elixir.
+pkg_partisan_homepage = http://partisan.cloud
+pkg_partisan_fetch = git
+pkg_partisan_repo = https://github.com/lasp-lang/partisan
+pkg_partisan_commit = master
+
+PACKAGES += pegjs
+pkg_pegjs_name = pegjs
+pkg_pegjs_description = An implementation of PEG.js grammar for Erlang.
+pkg_pegjs_homepage = https://github.com/dmitriid/pegjs
+pkg_pegjs_fetch = git
+pkg_pegjs_repo = https://github.com/dmitriid/pegjs
+pkg_pegjs_commit = master
+
+PACKAGES += percept2
+pkg_percept2_name = percept2
+pkg_percept2_description = Concurrent profiling tool for Erlang
+pkg_percept2_homepage = https://github.com/huiqing/percept2
+pkg_percept2_fetch = git
+pkg_percept2_repo = https://github.com/huiqing/percept2
+pkg_percept2_commit = master
+
+PACKAGES += pgo
+pkg_pgo_name = pgo
+pkg_pgo_description = Erlang Postgres client and connection pool
+pkg_pgo_homepage = https://github.com/erleans/pgo.git
+pkg_pgo_fetch = git
+pkg_pgo_repo = https://github.com/erleans/pgo.git
+pkg_pgo_commit = master
+
+PACKAGES += pgsql
+pkg_pgsql_name = pgsql
+pkg_pgsql_description = Erlang PostgreSQL driver
+pkg_pgsql_homepage = https://github.com/semiocast/pgsql
+pkg_pgsql_fetch = git
+pkg_pgsql_repo = https://github.com/semiocast/pgsql
+pkg_pgsql_commit = master
+
+PACKAGES += pkgx
+pkg_pkgx_name = pkgx
+pkg_pkgx_description = Build .deb packages from Erlang releases
+pkg_pkgx_homepage = https://github.com/arjan/pkgx
+pkg_pkgx_fetch = git
+pkg_pkgx_repo = https://github.com/arjan/pkgx
+pkg_pkgx_commit = master
+
+PACKAGES += pkt
+pkg_pkt_name = pkt
+pkg_pkt_description = Erlang network protocol library
+pkg_pkt_homepage = https://github.com/msantos/pkt
+pkg_pkt_fetch = git
+pkg_pkt_repo = https://github.com/msantos/pkt
+pkg_pkt_commit = master
+
+PACKAGES += plain_fsm
+pkg_plain_fsm_name = plain_fsm
+pkg_plain_fsm_description = A behaviour/support library for writing plain Erlang FSMs.
+pkg_plain_fsm_homepage = https://github.com/uwiger/plain_fsm
+pkg_plain_fsm_fetch = git
+pkg_plain_fsm_repo = https://github.com/uwiger/plain_fsm
+pkg_plain_fsm_commit = master
+
+PACKAGES += plumtree
+pkg_plumtree_name = plumtree
+pkg_plumtree_description = Epidemic Broadcast Trees
+pkg_plumtree_homepage = https://github.com/helium/plumtree
+pkg_plumtree_fetch = git
+pkg_plumtree_repo = https://github.com/helium/plumtree
+pkg_plumtree_commit = master
+
+PACKAGES += pmod_transform
+pkg_pmod_transform_name = pmod_transform
+pkg_pmod_transform_description = Parse transform for parameterized modules
+pkg_pmod_transform_homepage = https://github.com/erlang/pmod_transform
+pkg_pmod_transform_fetch = git
+pkg_pmod_transform_repo = https://github.com/erlang/pmod_transform
+pkg_pmod_transform_commit = master
+
+PACKAGES += pobox
+pkg_pobox_name = pobox
+pkg_pobox_description = External buffer processes to protect against mailbox overflow in Erlang
+pkg_pobox_homepage = https://github.com/ferd/pobox
+pkg_pobox_fetch = git
+pkg_pobox_repo = https://github.com/ferd/pobox
+pkg_pobox_commit = master
+
+PACKAGES += ponos
+pkg_ponos_name = ponos
+pkg_ponos_description = ponos is a simple yet powerful load generator written in erlang
+pkg_ponos_homepage = https://github.com/klarna/ponos
+pkg_ponos_fetch = git
+pkg_ponos_repo = https://github.com/klarna/ponos
+pkg_ponos_commit = master
+
+PACKAGES += poolboy
+pkg_poolboy_name = poolboy
+pkg_poolboy_description = A hunky Erlang worker pool factory
+pkg_poolboy_homepage = https://github.com/devinus/poolboy
+pkg_poolboy_fetch = git
+pkg_poolboy_repo = https://github.com/devinus/poolboy
+pkg_poolboy_commit = master
+
+PACKAGES += pooler
+pkg_pooler_name = pooler
+pkg_pooler_description = An OTP Process Pool Application
+pkg_pooler_homepage = https://github.com/seth/pooler
+pkg_pooler_fetch = git
+pkg_pooler_repo = https://github.com/seth/pooler
+pkg_pooler_commit = master
+
+PACKAGES += pqueue
+pkg_pqueue_name = pqueue
+pkg_pqueue_description = Erlang Priority Queues
+pkg_pqueue_homepage = https://github.com/okeuday/pqueue
+pkg_pqueue_fetch = git
+pkg_pqueue_repo = https://github.com/okeuday/pqueue
+pkg_pqueue_commit = master
+
+PACKAGES += procket
+pkg_procket_name = procket
+pkg_procket_description = Erlang interface to low level socket operations
+pkg_procket_homepage = http://blog.listincomprehension.com/search/label/procket
+pkg_procket_fetch = git
+pkg_procket_repo = https://github.com/msantos/procket
+pkg_procket_commit = master
+
+PACKAGES += prometheus
+pkg_prometheus_name = prometheus
+pkg_prometheus_description = Prometheus.io client in Erlang
+pkg_prometheus_homepage = https://github.com/deadtrickster/prometheus.erl
+pkg_prometheus_fetch = git
+pkg_prometheus_repo = https://github.com/deadtrickster/prometheus.erl
+pkg_prometheus_commit = master
+
+PACKAGES += prop
+pkg_prop_name = prop
+pkg_prop_description = An Erlang code scaffolding and generator system.
+pkg_prop_homepage = https://github.com/nuex/prop
+pkg_prop_fetch = git
+pkg_prop_repo = https://github.com/nuex/prop
+pkg_prop_commit = master
+
+PACKAGES += proper
+pkg_proper_name = proper
+pkg_proper_description = PropEr: a QuickCheck-inspired property-based testing tool for Erlang.
+pkg_proper_homepage = http://proper.softlab.ntua.gr
+pkg_proper_fetch = git
+pkg_proper_repo = https://github.com/manopapad/proper
+pkg_proper_commit = master
+
+PACKAGES += props
+pkg_props_name = props
+pkg_props_description = Property structure library
+pkg_props_homepage = https://github.com/greyarea/props
+pkg_props_fetch = git
+pkg_props_repo = https://github.com/greyarea/props
+pkg_props_commit = master
+
+PACKAGES += protobuffs
+pkg_protobuffs_name = protobuffs
+pkg_protobuffs_description = An implementation of Google's Protocol Buffers for Erlang, based on ngerakines/erlang_protobuffs.
+pkg_protobuffs_homepage = https://github.com/basho/erlang_protobuffs
+pkg_protobuffs_fetch = git
+pkg_protobuffs_repo = https://github.com/basho/erlang_protobuffs
+pkg_protobuffs_commit = master
+
+PACKAGES += psycho
+pkg_psycho_name = psycho
+pkg_psycho_description = HTTP server that provides a WSGI-like interface for applications and middleware.
+pkg_psycho_homepage = https://github.com/gar1t/psycho
+pkg_psycho_fetch = git
+pkg_psycho_repo = https://github.com/gar1t/psycho
+pkg_psycho_commit = master
+
+PACKAGES += purity
+pkg_purity_name = purity
+pkg_purity_description = A side-effect analyzer for Erlang
+pkg_purity_homepage = https://github.com/mpitid/purity
+pkg_purity_fetch = git
+pkg_purity_repo = https://github.com/mpitid/purity
+pkg_purity_commit = master
+
+PACKAGES += push_service
+pkg_push_service_name = push_service
+pkg_push_service_description = Push service
+pkg_push_service_homepage = https://github.com/hairyhum/push_service
+pkg_push_service_fetch = git
+pkg_push_service_repo = https://github.com/hairyhum/push_service
+pkg_push_service_commit = master
+
+PACKAGES += qdate
+pkg_qdate_name = qdate
+pkg_qdate_description = Date, time, and timezone parsing, formatting, and conversion for Erlang.
+pkg_qdate_homepage = https://github.com/choptastic/qdate
+pkg_qdate_fetch = git
+pkg_qdate_repo = https://github.com/choptastic/qdate
+pkg_qdate_commit = master
+
+PACKAGES += qrcode
+pkg_qrcode_name = qrcode
+pkg_qrcode_description = QR Code encoder in Erlang
+pkg_qrcode_homepage = https://github.com/komone/qrcode
+pkg_qrcode_fetch = git
+pkg_qrcode_repo = https://github.com/komone/qrcode
+pkg_qrcode_commit = master
+
+PACKAGES += quest
+pkg_quest_name = quest
+pkg_quest_description = Learn Erlang through this set of challenges. An interactive system for getting to know Erlang.
+pkg_quest_homepage = https://github.com/eriksoe/ErlangQuest
+pkg_quest_fetch = git
+pkg_quest_repo = https://github.com/eriksoe/ErlangQuest
+pkg_quest_commit = master
+
+PACKAGES += quickrand
+pkg_quickrand_name = quickrand
+pkg_quickrand_description = Quick Erlang Random Number Generation
+pkg_quickrand_homepage = https://github.com/okeuday/quickrand
+pkg_quickrand_fetch = git
+pkg_quickrand_repo = https://github.com/okeuday/quickrand
+pkg_quickrand_commit = master
+
+PACKAGES += rabbit
+pkg_rabbit_name = rabbit
+pkg_rabbit_description = RabbitMQ Server
+pkg_rabbit_homepage = https://www.rabbitmq.com/
+pkg_rabbit_fetch = git
+pkg_rabbit_repo = https://github.com/rabbitmq/rabbitmq-server.git
+pkg_rabbit_commit = master
+
+PACKAGES += rabbit_exchange_type_riak
+pkg_rabbit_exchange_type_riak_name = rabbit_exchange_type_riak
+pkg_rabbit_exchange_type_riak_description = Custom RabbitMQ exchange type for sticking messages in Riak
+pkg_rabbit_exchange_type_riak_homepage = https://github.com/jbrisbin/riak-exchange
+pkg_rabbit_exchange_type_riak_fetch = git
+pkg_rabbit_exchange_type_riak_repo = https://github.com/jbrisbin/riak-exchange
+pkg_rabbit_exchange_type_riak_commit = master
+
+PACKAGES += rack
+pkg_rack_name = rack
+pkg_rack_description = Rack handler for erlang
+pkg_rack_homepage = https://github.com/erlyvideo/rack
+pkg_rack_fetch = git
+pkg_rack_repo = https://github.com/erlyvideo/rack
+pkg_rack_commit = master
+
+PACKAGES += radierl
+pkg_radierl_name = radierl
+pkg_radierl_description = RADIUS protocol stack implemented in Erlang.
+pkg_radierl_homepage = https://github.com/vances/radierl
+pkg_radierl_fetch = git
+pkg_radierl_repo = https://github.com/vances/radierl
+pkg_radierl_commit = master
+
+PACKAGES += rafter
+pkg_rafter_name = rafter
+pkg_rafter_description = An Erlang library application which implements the Raft consensus protocol
+pkg_rafter_homepage = https://github.com/andrewjstone/rafter
+pkg_rafter_fetch = git
+pkg_rafter_repo = https://github.com/andrewjstone/rafter
+pkg_rafter_commit = master
+
+PACKAGES += ranch
+pkg_ranch_name = ranch
+pkg_ranch_description = Socket acceptor pool for TCP protocols.
+pkg_ranch_homepage = http://ninenines.eu
+pkg_ranch_fetch = git
+pkg_ranch_repo = https://github.com/ninenines/ranch
+pkg_ranch_commit = 1.2.1
+
+PACKAGES += rbeacon
+pkg_rbeacon_name = rbeacon
+pkg_rbeacon_description = LAN discovery and presence in Erlang.
+pkg_rbeacon_homepage = https://github.com/refuge/rbeacon
+pkg_rbeacon_fetch = git
+pkg_rbeacon_repo = https://github.com/refuge/rbeacon
+pkg_rbeacon_commit = master
+
+PACKAGES += rebar
+pkg_rebar_name = rebar
+pkg_rebar_description = Erlang build tool that makes it easy to compile and test Erlang applications, port drivers and releases.
+pkg_rebar_homepage = http://www.rebar3.org
+pkg_rebar_fetch = git
+pkg_rebar_repo = https://github.com/rebar/rebar3
+pkg_rebar_commit = master
+
+PACKAGES += rebus
+pkg_rebus_name = rebus
+pkg_rebus_description = A stupid simple, internal, pub/sub event bus written in- and for Erlang.
+pkg_rebus_homepage = https://github.com/olle/rebus
+pkg_rebus_fetch = git
+pkg_rebus_repo = https://github.com/olle/rebus
+pkg_rebus_commit = master
+
+PACKAGES += rec2json
+pkg_rec2json_name = rec2json
+pkg_rec2json_description = Compile erlang record definitions into modules to convert them to/from json easily.
+pkg_rec2json_homepage = https://github.com/lordnull/rec2json
+pkg_rec2json_fetch = git
+pkg_rec2json_repo = https://github.com/lordnull/rec2json
+pkg_rec2json_commit = master
+
+PACKAGES += recon
+pkg_recon_name = recon
+pkg_recon_description = Collection of functions and scripts to debug Erlang in production.
+pkg_recon_homepage = https://github.com/ferd/recon
+pkg_recon_fetch = git
+pkg_recon_repo = https://github.com/ferd/recon
+pkg_recon_commit = master
+
+PACKAGES += record_info
+pkg_record_info_name = record_info
+pkg_record_info_description = Convert between record and proplist
+pkg_record_info_homepage = https://github.com/bipthelin/erlang-record_info
+pkg_record_info_fetch = git
+pkg_record_info_repo = https://github.com/bipthelin/erlang-record_info
+pkg_record_info_commit = master
+
+PACKAGES += redgrid
+pkg_redgrid_name = redgrid
+pkg_redgrid_description = automatic Erlang node discovery via redis
+pkg_redgrid_homepage = https://github.com/jkvor/redgrid
+pkg_redgrid_fetch = git
+pkg_redgrid_repo = https://github.com/jkvor/redgrid
+pkg_redgrid_commit = master
+
+PACKAGES += redo
+pkg_redo_name = redo
+pkg_redo_description = pipelined erlang redis client
+pkg_redo_homepage = https://github.com/jkvor/redo
+pkg_redo_fetch = git
+pkg_redo_repo = https://github.com/jkvor/redo
+pkg_redo_commit = master
+
+PACKAGES += reload_mk
+pkg_reload_mk_name = reload_mk
+pkg_reload_mk_description = Live reload plugin for erlang.mk.
+pkg_reload_mk_homepage = https://github.com/bullno1/reload.mk
+pkg_reload_mk_fetch = git
+pkg_reload_mk_repo = https://github.com/bullno1/reload.mk
+pkg_reload_mk_commit = master
+
+PACKAGES += reltool_util
+pkg_reltool_util_name = reltool_util
+pkg_reltool_util_description = Erlang reltool utility functionality application
+pkg_reltool_util_homepage = https://github.com/okeuday/reltool_util
+pkg_reltool_util_fetch = git
+pkg_reltool_util_repo = https://github.com/okeuday/reltool_util
+pkg_reltool_util_commit = master
+
+PACKAGES += relx
+pkg_relx_name = relx
+pkg_relx_description = Sane, simple release creation for Erlang
+pkg_relx_homepage = https://github.com/erlware/relx
+pkg_relx_fetch = git
+pkg_relx_repo = https://github.com/erlware/relx
+pkg_relx_commit = master
+
+PACKAGES += resource_discovery
+pkg_resource_discovery_name = resource_discovery
+pkg_resource_discovery_description = An application used to dynamically discover resources present in an Erlang node cluster.
+pkg_resource_discovery_homepage = http://erlware.org/
+pkg_resource_discovery_fetch = git
+pkg_resource_discovery_repo = https://github.com/erlware/resource_discovery
+pkg_resource_discovery_commit = master
+
+PACKAGES += restc
+pkg_restc_name = restc
+pkg_restc_description = Erlang Rest Client
+pkg_restc_homepage = https://github.com/kivra/restclient
+pkg_restc_fetch = git
+pkg_restc_repo = https://github.com/kivra/restclient
+pkg_restc_commit = master
+
+PACKAGES += rfc4627_jsonrpc
+pkg_rfc4627_jsonrpc_name = rfc4627_jsonrpc
+pkg_rfc4627_jsonrpc_description = Erlang RFC4627 (JSON) codec and JSON-RPC server implementation.
+pkg_rfc4627_jsonrpc_homepage = https://github.com/tonyg/erlang-rfc4627
+pkg_rfc4627_jsonrpc_fetch = git
+pkg_rfc4627_jsonrpc_repo = https://github.com/tonyg/erlang-rfc4627
+pkg_rfc4627_jsonrpc_commit = master
+
+PACKAGES += riak_control
+pkg_riak_control_name = riak_control
+pkg_riak_control_description = Webmachine-based administration interface for Riak.
+pkg_riak_control_homepage = https://github.com/basho/riak_control
+pkg_riak_control_fetch = git
+pkg_riak_control_repo = https://github.com/basho/riak_control
+pkg_riak_control_commit = master
+
+PACKAGES += riak_core
+pkg_riak_core_name = riak_core
+pkg_riak_core_description = Distributed systems infrastructure used by Riak.
+pkg_riak_core_homepage = https://github.com/basho/riak_core
+pkg_riak_core_fetch = git
+pkg_riak_core_repo = https://github.com/basho/riak_core
+pkg_riak_core_commit = master
+
+PACKAGES += riak_dt
+pkg_riak_dt_name = riak_dt
+pkg_riak_dt_description = Convergent replicated datatypes in Erlang
+pkg_riak_dt_homepage = https://github.com/basho/riak_dt
+pkg_riak_dt_fetch = git
+pkg_riak_dt_repo = https://github.com/basho/riak_dt
+pkg_riak_dt_commit = master
+
+PACKAGES += riak_ensemble
+pkg_riak_ensemble_name = riak_ensemble
+pkg_riak_ensemble_description = Multi-Paxos framework in Erlang
+pkg_riak_ensemble_homepage = https://github.com/basho/riak_ensemble
+pkg_riak_ensemble_fetch = git
+pkg_riak_ensemble_repo = https://github.com/basho/riak_ensemble
+pkg_riak_ensemble_commit = master
+
+PACKAGES += riak_kv
+pkg_riak_kv_name = riak_kv
+pkg_riak_kv_description = Riak Key/Value Store
+pkg_riak_kv_homepage = https://github.com/basho/riak_kv
+pkg_riak_kv_fetch = git
+pkg_riak_kv_repo = https://github.com/basho/riak_kv
+pkg_riak_kv_commit = master
+
+PACKAGES += riak_pg
+pkg_riak_pg_name = riak_pg
+pkg_riak_pg_description = Distributed process groups with riak_core.
+pkg_riak_pg_homepage = https://github.com/cmeiklejohn/riak_pg
+pkg_riak_pg_fetch = git
+pkg_riak_pg_repo = https://github.com/cmeiklejohn/riak_pg
+pkg_riak_pg_commit = master
+
+PACKAGES += riak_pipe
+pkg_riak_pipe_name = riak_pipe
+pkg_riak_pipe_description = Riak Pipelines
+pkg_riak_pipe_homepage = https://github.com/basho/riak_pipe
+pkg_riak_pipe_fetch = git
+pkg_riak_pipe_repo = https://github.com/basho/riak_pipe
+pkg_riak_pipe_commit = master
+
+PACKAGES += riak_sysmon
+pkg_riak_sysmon_name = riak_sysmon
+pkg_riak_sysmon_description = Simple OTP app for managing Erlang VM system_monitor event messages
+pkg_riak_sysmon_homepage = https://github.com/basho/riak_sysmon
+pkg_riak_sysmon_fetch = git
+pkg_riak_sysmon_repo = https://github.com/basho/riak_sysmon
+pkg_riak_sysmon_commit = master
+
+PACKAGES += riak_test
+pkg_riak_test_name = riak_test
+pkg_riak_test_description = I'm in your cluster, testing your riaks
+pkg_riak_test_homepage = https://github.com/basho/riak_test
+pkg_riak_test_fetch = git
+pkg_riak_test_repo = https://github.com/basho/riak_test
+pkg_riak_test_commit = master
+
+PACKAGES += riakc
+pkg_riakc_name = riakc
+pkg_riakc_description = Erlang clients for Riak.
+pkg_riakc_homepage = https://github.com/basho/riak-erlang-client
+pkg_riakc_fetch = git
+pkg_riakc_repo = https://github.com/basho/riak-erlang-client
+pkg_riakc_commit = master
+
+PACKAGES += riakhttpc
+pkg_riakhttpc_name = riakhttpc
+pkg_riakhttpc_description = Riak Erlang client using the HTTP interface
+pkg_riakhttpc_homepage = https://github.com/basho/riak-erlang-http-client
+pkg_riakhttpc_fetch = git
+pkg_riakhttpc_repo = https://github.com/basho/riak-erlang-http-client
+pkg_riakhttpc_commit = master
+
+PACKAGES += riaknostic
+pkg_riaknostic_name = riaknostic
+pkg_riaknostic_description = A diagnostic tool for Riak installations, to find common errors asap
+pkg_riaknostic_homepage = https://github.com/basho/riaknostic
+pkg_riaknostic_fetch = git
+pkg_riaknostic_repo = https://github.com/basho/riaknostic
+pkg_riaknostic_commit = master
+
+PACKAGES += riakpool
+pkg_riakpool_name = riakpool
+pkg_riakpool_description = erlang riak client pool
+pkg_riakpool_homepage = https://github.com/dweldon/riakpool
+pkg_riakpool_fetch = git
+pkg_riakpool_repo = https://github.com/dweldon/riakpool
+pkg_riakpool_commit = master
+
+PACKAGES += rivus_cep
+pkg_rivus_cep_name = rivus_cep
+pkg_rivus_cep_description = Complex event processing in Erlang
+pkg_rivus_cep_homepage = https://github.com/vascokk/rivus_cep
+pkg_rivus_cep_fetch = git
+pkg_rivus_cep_repo = https://github.com/vascokk/rivus_cep
+pkg_rivus_cep_commit = master
+
+PACKAGES += rlimit
+pkg_rlimit_name = rlimit
+pkg_rlimit_description = Magnus Klaar's rate limiter code from etorrent
+pkg_rlimit_homepage = https://github.com/jlouis/rlimit
+pkg_rlimit_fetch = git
+pkg_rlimit_repo = https://github.com/jlouis/rlimit
+pkg_rlimit_commit = master
+
+PACKAGES += rust_mk
+pkg_rust_mk_name = rust_mk
+pkg_rust_mk_description = Build Rust crates in an Erlang application
+pkg_rust_mk_homepage = https://github.com/goertzenator/rust.mk
+pkg_rust_mk_fetch = git
+pkg_rust_mk_repo = https://github.com/goertzenator/rust.mk
+pkg_rust_mk_commit = master
+
+PACKAGES += safetyvalve
+pkg_safetyvalve_name = safetyvalve
+pkg_safetyvalve_description = A safety valve for your erlang node
+pkg_safetyvalve_homepage = https://github.com/jlouis/safetyvalve
+pkg_safetyvalve_fetch = git
+pkg_safetyvalve_repo = https://github.com/jlouis/safetyvalve
+pkg_safetyvalve_commit = master
+
+PACKAGES += seestar
+pkg_seestar_name = seestar
+pkg_seestar_description = The Erlang client for Cassandra 1.2+ binary protocol
+pkg_seestar_homepage = https://github.com/iamaleksey/seestar
+pkg_seestar_fetch = git
+pkg_seestar_repo = https://github.com/iamaleksey/seestar
+pkg_seestar_commit = master
+
+PACKAGES += service
+pkg_service_name = service
+pkg_service_description = A minimal Erlang behavior for creating CloudI internal services
+pkg_service_homepage = http://cloudi.org/
+pkg_service_fetch = git
+pkg_service_repo = https://github.com/CloudI/service
+pkg_service_commit = master
+
+PACKAGES += setup
+pkg_setup_name = setup
+pkg_setup_description = Generic setup utility for Erlang-based systems
+pkg_setup_homepage = https://github.com/uwiger/setup
+pkg_setup_fetch = git
+pkg_setup_repo = https://github.com/uwiger/setup
+pkg_setup_commit = master
+
+PACKAGES += sext
+pkg_sext_name = sext
+pkg_sext_description = Sortable Erlang Term Serialization
+pkg_sext_homepage = https://github.com/uwiger/sext
+pkg_sext_fetch = git
+pkg_sext_repo = https://github.com/uwiger/sext
+pkg_sext_commit = master
+
+PACKAGES += sfmt
+pkg_sfmt_name = sfmt
+pkg_sfmt_description = SFMT pseudo random number generator for Erlang.
+pkg_sfmt_homepage = https://github.com/jj1bdx/sfmt-erlang
+pkg_sfmt_fetch = git
+pkg_sfmt_repo = https://github.com/jj1bdx/sfmt-erlang
+pkg_sfmt_commit = master
+
+PACKAGES += sgte
+pkg_sgte_name = sgte
+pkg_sgte_description = A simple Erlang Template Engine
+pkg_sgte_homepage = https://github.com/filippo/sgte
+pkg_sgte_fetch = git
+pkg_sgte_repo = https://github.com/filippo/sgte
+pkg_sgte_commit = master
+
+PACKAGES += sheriff
+pkg_sheriff_name = sheriff
+pkg_sheriff_description = Parse transform for type based validation.
+pkg_sheriff_homepage = http://ninenines.eu
+pkg_sheriff_fetch = git
+pkg_sheriff_repo = https://github.com/extend/sheriff
+pkg_sheriff_commit = master
+
+PACKAGES += shotgun
+pkg_shotgun_name = shotgun
+pkg_shotgun_description = better than just a gun
+pkg_shotgun_homepage = https://github.com/inaka/shotgun
+pkg_shotgun_fetch = git
+pkg_shotgun_repo = https://github.com/inaka/shotgun
+pkg_shotgun_commit = master
+
+PACKAGES += sidejob
+pkg_sidejob_name = sidejob
+pkg_sidejob_description = Parallel worker and capacity limiting library for Erlang
+pkg_sidejob_homepage = https://github.com/basho/sidejob
+pkg_sidejob_fetch = git
+pkg_sidejob_repo = https://github.com/basho/sidejob
+pkg_sidejob_commit = master
+
+PACKAGES += sieve
+pkg_sieve_name = sieve
+pkg_sieve_description = sieve is a simple TCP routing proxy (layer 7) in erlang
+pkg_sieve_homepage = https://github.com/benoitc/sieve
+pkg_sieve_fetch = git
+pkg_sieve_repo = https://github.com/benoitc/sieve
+pkg_sieve_commit = master
+
+PACKAGES += sighandler
+pkg_sighandler_name = sighandler
+pkg_sighandler_description = Handle UNIX signals in Er lang
+pkg_sighandler_homepage = https://github.com/jkingsbery/sighandler
+pkg_sighandler_fetch = git
+pkg_sighandler_repo = https://github.com/jkingsbery/sighandler
+pkg_sighandler_commit = master
+
+PACKAGES += simhash
+pkg_simhash_name = simhash
+pkg_simhash_description = Simhashing for Erlang -- hashing algorithm to find near-duplicates in binary data.
+pkg_simhash_homepage = https://github.com/ferd/simhash
+pkg_simhash_fetch = git
+pkg_simhash_repo = https://github.com/ferd/simhash
+pkg_simhash_commit = master
+
+PACKAGES += simple_bridge
+pkg_simple_bridge_name = simple_bridge
+pkg_simple_bridge_description = A simple, standardized interface library to Erlang HTTP Servers.
+pkg_simple_bridge_homepage = https://github.com/nitrogen/simple_bridge
+pkg_simple_bridge_fetch = git
+pkg_simple_bridge_repo = https://github.com/nitrogen/simple_bridge
+pkg_simple_bridge_commit = master
+
+PACKAGES += simple_oauth2
+pkg_simple_oauth2_name = simple_oauth2
+pkg_simple_oauth2_description = Simple erlang OAuth2 client module for any http server framework (Google, Facebook, Yandex, Vkontakte are preconfigured)
+pkg_simple_oauth2_homepage = https://github.com/virtan/simple_oauth2
+pkg_simple_oauth2_fetch = git
+pkg_simple_oauth2_repo = https://github.com/virtan/simple_oauth2
+pkg_simple_oauth2_commit = master
+
+PACKAGES += skel
+pkg_skel_name = skel
+pkg_skel_description = A Streaming Process-based Skeleton Library for Erlang
+pkg_skel_homepage = https://github.com/ParaPhrase/skel
+pkg_skel_fetch = git
+pkg_skel_repo = https://github.com/ParaPhrase/skel
+pkg_skel_commit = master
+
+PACKAGES += slack
+pkg_slack_name = slack
+pkg_slack_description = Minimal slack notification OTP library.
+pkg_slack_homepage = https://github.com/DonBranson/slack
+pkg_slack_fetch = git
+pkg_slack_repo = https://github.com/DonBranson/slack.git
+pkg_slack_commit = master
+
+PACKAGES += smother
+pkg_smother_name = smother
+pkg_smother_description = Extended code coverage metrics for Erlang.
+pkg_smother_homepage = https://ramsay-t.github.io/Smother/
+pkg_smother_fetch = git
+pkg_smother_repo = https://github.com/ramsay-t/Smother
+pkg_smother_commit = master
+
+PACKAGES += snappyer
+pkg_snappyer_name = snappyer
+pkg_snappyer_description = Snappy as nif for Erlang
+pkg_snappyer_homepage = https://github.com/zmstone/snappyer
+pkg_snappyer_fetch = git
+pkg_snappyer_repo = https://github.com/zmstone/snappyer.git
+pkg_snappyer_commit = master
+
+PACKAGES += social
+pkg_social_name = social
+pkg_social_description = Cowboy handler for social login via OAuth2 providers
+pkg_social_homepage = https://github.com/dvv/social
+pkg_social_fetch = git
+pkg_social_repo = https://github.com/dvv/social
+pkg_social_commit = master
+
+PACKAGES += spapi_router
+pkg_spapi_router_name = spapi_router
+pkg_spapi_router_description = Partially-connected Erlang clustering
+pkg_spapi_router_homepage = https://github.com/spilgames/spapi-router
+pkg_spapi_router_fetch = git
+pkg_spapi_router_repo = https://github.com/spilgames/spapi-router
+pkg_spapi_router_commit = master
+
+PACKAGES += sqerl
+pkg_sqerl_name = sqerl
+pkg_sqerl_description = An Erlang-flavoured SQL DSL
+pkg_sqerl_homepage = https://github.com/hairyhum/sqerl
+pkg_sqerl_fetch = git
+pkg_sqerl_repo = https://github.com/hairyhum/sqerl
+pkg_sqerl_commit = master
+
+PACKAGES += srly
+pkg_srly_name = srly
+pkg_srly_description = Native Erlang Unix serial interface
+pkg_srly_homepage = https://github.com/msantos/srly
+pkg_srly_fetch = git
+pkg_srly_repo = https://github.com/msantos/srly
+pkg_srly_commit = master
+
+PACKAGES += sshrpc
+pkg_sshrpc_name = sshrpc
+pkg_sshrpc_description = Erlang SSH RPC module (experimental)
+pkg_sshrpc_homepage = https://github.com/jj1bdx/sshrpc
+pkg_sshrpc_fetch = git
+pkg_sshrpc_repo = https://github.com/jj1bdx/sshrpc
+pkg_sshrpc_commit = master
+
+PACKAGES += stable
+pkg_stable_name = stable
+pkg_stable_description = Library of assorted helpers for Cowboy web server.
+pkg_stable_homepage = https://github.com/dvv/stable
+pkg_stable_fetch = git
+pkg_stable_repo = https://github.com/dvv/stable
+pkg_stable_commit = master
+
+PACKAGES += statebox
+pkg_statebox_name = statebox
+pkg_statebox_description = Erlang state monad with merge/conflict-resolution capabilities. Useful for Riak.
+pkg_statebox_homepage = https://github.com/mochi/statebox
+pkg_statebox_fetch = git
+pkg_statebox_repo = https://github.com/mochi/statebox
+pkg_statebox_commit = master
+
+PACKAGES += statebox_riak
+pkg_statebox_riak_name = statebox_riak
+pkg_statebox_riak_description = Convenience library that makes it easier to use statebox with riak, extracted from best practices in our production code at Mochi Media.
+pkg_statebox_riak_homepage = https://github.com/mochi/statebox_riak
+pkg_statebox_riak_fetch = git
+pkg_statebox_riak_repo = https://github.com/mochi/statebox_riak
+pkg_statebox_riak_commit = master
+
+PACKAGES += statman
+pkg_statman_name = statman
+pkg_statman_description = Efficiently collect massive volumes of metrics inside the Erlang VM
+pkg_statman_homepage = https://github.com/knutin/statman
+pkg_statman_fetch = git
+pkg_statman_repo = https://github.com/knutin/statman
+pkg_statman_commit = master
+
+PACKAGES += statsderl
+pkg_statsderl_name = statsderl
+pkg_statsderl_description = StatsD client (erlang)
+pkg_statsderl_homepage = https://github.com/lpgauth/statsderl
+pkg_statsderl_fetch = git
+pkg_statsderl_repo = https://github.com/lpgauth/statsderl
+pkg_statsderl_commit = master
+
+PACKAGES += stdinout_pool
+pkg_stdinout_pool_name = stdinout_pool
+pkg_stdinout_pool_description = stdinout_pool : stuff goes in, stuff goes out. there's never any miscommunication.
+pkg_stdinout_pool_homepage = https://github.com/mattsta/erlang-stdinout-pool
+pkg_stdinout_pool_fetch = git
+pkg_stdinout_pool_repo = https://github.com/mattsta/erlang-stdinout-pool
+pkg_stdinout_pool_commit = master
+
+PACKAGES += stockdb
+pkg_stockdb_name = stockdb
+pkg_stockdb_description = Database for storing Stock Exchange quotes in erlang
+pkg_stockdb_homepage = https://github.com/maxlapshin/stockdb
+pkg_stockdb_fetch = git
+pkg_stockdb_repo = https://github.com/maxlapshin/stockdb
+pkg_stockdb_commit = master
+
+PACKAGES += stripe
+pkg_stripe_name = stripe
+pkg_stripe_description = Erlang interface to the stripe.com API
+pkg_stripe_homepage = https://github.com/mattsta/stripe-erlang
+pkg_stripe_fetch = git
+pkg_stripe_repo = https://github.com/mattsta/stripe-erlang
+pkg_stripe_commit = v1
+
+PACKAGES += subproc
+pkg_subproc_name = subproc
+pkg_subproc_description = unix subprocess manager with {active,once|false} modes
+pkg_subproc_homepage = http://dozzie.jarowit.net/trac/wiki/subproc
+pkg_subproc_fetch = git
+pkg_subproc_repo = https://github.com/dozzie/subproc
+pkg_subproc_commit = v0.1.0
+
+PACKAGES += supervisor3
+pkg_supervisor3_name = supervisor3
+pkg_supervisor3_description = OTP supervisor with additional strategies
+pkg_supervisor3_homepage = https://github.com/klarna/supervisor3
+pkg_supervisor3_fetch = git
+pkg_supervisor3_repo = https://github.com/klarna/supervisor3.git
+pkg_supervisor3_commit = master
+
+PACKAGES += surrogate
+pkg_surrogate_name = surrogate
+pkg_surrogate_description = Proxy server written in erlang. Supports reverse proxy load balancing and forward proxy with http (including CONNECT), socks4, socks5, and transparent proxy modes.
+pkg_surrogate_homepage = https://github.com/skruger/Surrogate
+pkg_surrogate_fetch = git
+pkg_surrogate_repo = https://github.com/skruger/Surrogate
+pkg_surrogate_commit = master
+
+PACKAGES += swab
+pkg_swab_name = swab
+pkg_swab_description = General purpose buffer handling module
+pkg_swab_homepage = https://github.com/crownedgrouse/swab
+pkg_swab_fetch = git
+pkg_swab_repo = https://github.com/crownedgrouse/swab
+pkg_swab_commit = master
+
+PACKAGES += swarm
+pkg_swarm_name = swarm
+pkg_swarm_description = Fast and simple acceptor pool for Erlang
+pkg_swarm_homepage = https://github.com/jeremey/swarm
+pkg_swarm_fetch = git
+pkg_swarm_repo = https://github.com/jeremey/swarm
+pkg_swarm_commit = master
+
+PACKAGES += switchboard
+pkg_switchboard_name = switchboard
+pkg_switchboard_description = A framework for processing email using worker plugins.
+pkg_switchboard_homepage = https://github.com/thusfresh/switchboard
+pkg_switchboard_fetch = git
+pkg_switchboard_repo = https://github.com/thusfresh/switchboard
+pkg_switchboard_commit = master
+
+PACKAGES += syn
+pkg_syn_name = syn
+pkg_syn_description = A global Process Registry and Process Group manager for Erlang.
+pkg_syn_homepage = https://github.com/ostinelli/syn
+pkg_syn_fetch = git
+pkg_syn_repo = https://github.com/ostinelli/syn
+pkg_syn_commit = master
+
+PACKAGES += sync
+pkg_sync_name = sync
+pkg_sync_description = On-the-fly recompiling and reloading in Erlang.
+pkg_sync_homepage = https://github.com/rustyio/sync
+pkg_sync_fetch = git
+pkg_sync_repo = https://github.com/rustyio/sync
+pkg_sync_commit = master
+
+PACKAGES += syntaxerl
+pkg_syntaxerl_name = syntaxerl
+pkg_syntaxerl_description = Syntax checker for Erlang
+pkg_syntaxerl_homepage = https://github.com/ten0s/syntaxerl
+pkg_syntaxerl_fetch = git
+pkg_syntaxerl_repo = https://github.com/ten0s/syntaxerl
+pkg_syntaxerl_commit = master
+
+PACKAGES += syslog
+pkg_syslog_name = syslog
+pkg_syslog_description = Erlang port driver for interacting with syslog via syslog(3)
+pkg_syslog_homepage = https://github.com/Vagabond/erlang-syslog
+pkg_syslog_fetch = git
+pkg_syslog_repo = https://github.com/Vagabond/erlang-syslog
+pkg_syslog_commit = master
+
+PACKAGES += taskforce
+pkg_taskforce_name = taskforce
+pkg_taskforce_description = Erlang worker pools for controlled parallelisation of arbitrary tasks.
+pkg_taskforce_homepage = https://github.com/g-andrade/taskforce
+pkg_taskforce_fetch = git
+pkg_taskforce_repo = https://github.com/g-andrade/taskforce
+pkg_taskforce_commit = master
+
+PACKAGES += tddreloader
+pkg_tddreloader_name = tddreloader
+pkg_tddreloader_description = Shell utility for recompiling, reloading, and testing code as it changes
+pkg_tddreloader_homepage = https://github.com/version2beta/tddreloader
+pkg_tddreloader_fetch = git
+pkg_tddreloader_repo = https://github.com/version2beta/tddreloader
+pkg_tddreloader_commit = master
+
+PACKAGES += tempo
+pkg_tempo_name = tempo
+pkg_tempo_description = NIF-based date and time parsing and formatting for Erlang.
+pkg_tempo_homepage = https://github.com/selectel/tempo
+pkg_tempo_fetch = git
+pkg_tempo_repo = https://github.com/selectel/tempo
+pkg_tempo_commit = master
+
+PACKAGES += ticktick
+pkg_ticktick_name = ticktick
+pkg_ticktick_description = Ticktick is an id generator for message service.
+pkg_ticktick_homepage = https://github.com/ericliang/ticktick
+pkg_ticktick_fetch = git
+pkg_ticktick_repo = https://github.com/ericliang/ticktick
+pkg_ticktick_commit = master
+
+PACKAGES += tinymq
+pkg_tinymq_name = tinymq
+pkg_tinymq_description = TinyMQ - a diminutive, in-memory message queue
+pkg_tinymq_homepage = https://github.com/ChicagoBoss/tinymq
+pkg_tinymq_fetch = git
+pkg_tinymq_repo = https://github.com/ChicagoBoss/tinymq
+pkg_tinymq_commit = master
+
+PACKAGES += tinymt
+pkg_tinymt_name = tinymt
+pkg_tinymt_description = TinyMT pseudo random number generator for Erlang.
+pkg_tinymt_homepage = https://github.com/jj1bdx/tinymt-erlang
+pkg_tinymt_fetch = git
+pkg_tinymt_repo = https://github.com/jj1bdx/tinymt-erlang
+pkg_tinymt_commit = master
+
+PACKAGES += tirerl
+pkg_tirerl_name = tirerl
+pkg_tirerl_description = Erlang interface to Elastic Search
+pkg_tirerl_homepage = https://github.com/inaka/tirerl
+pkg_tirerl_fetch = git
+pkg_tirerl_repo = https://github.com/inaka/tirerl
+pkg_tirerl_commit = master
+
+PACKAGES += toml
+pkg_toml_name = toml
+pkg_toml_description = TOML (0.4.0) config parser
+pkg_toml_homepage = http://dozzie.jarowit.net/trac/wiki/TOML
+pkg_toml_fetch = git
+pkg_toml_repo = https://github.com/dozzie/toml
+pkg_toml_commit = v0.2.0
+
+PACKAGES += traffic_tools
+pkg_traffic_tools_name = traffic_tools
+pkg_traffic_tools_description = Simple traffic limiting library
+pkg_traffic_tools_homepage = https://github.com/systra/traffic_tools
+pkg_traffic_tools_fetch = git
+pkg_traffic_tools_repo = https://github.com/systra/traffic_tools
+pkg_traffic_tools_commit = master
+
+PACKAGES += trails
+pkg_trails_name = trails
+pkg_trails_description = A couple of improvements over Cowboy Routes
+pkg_trails_homepage = http://inaka.github.io/cowboy-trails/
+pkg_trails_fetch = git
+pkg_trails_repo = https://github.com/inaka/cowboy-trails
+pkg_trails_commit = master
+
+PACKAGES += trane
+pkg_trane_name = trane
+pkg_trane_description = SAX style broken HTML parser in Erlang
+pkg_trane_homepage = https://github.com/massemanet/trane
+pkg_trane_fetch = git
+pkg_trane_repo = https://github.com/massemanet/trane
+pkg_trane_commit = master
+
+PACKAGES += transit
+pkg_transit_name = transit
+pkg_transit_description = transit format for erlang
+pkg_transit_homepage = https://github.com/isaiah/transit-erlang
+pkg_transit_fetch = git
+pkg_transit_repo = https://github.com/isaiah/transit-erlang
+pkg_transit_commit = master
+
+PACKAGES += trie
+pkg_trie_name = trie
+pkg_trie_description = Erlang Trie Implementation
+pkg_trie_homepage = https://github.com/okeuday/trie
+pkg_trie_fetch = git
+pkg_trie_repo = https://github.com/okeuday/trie
+pkg_trie_commit = master
+
+PACKAGES += triq
+pkg_triq_name = triq
+pkg_triq_description = Trifork QuickCheck
+pkg_triq_homepage = https://triq.gitlab.io
+pkg_triq_fetch = git
+pkg_triq_repo = https://gitlab.com/triq/triq.git
+pkg_triq_commit = master
+
+PACKAGES += tunctl
+pkg_tunctl_name = tunctl
+pkg_tunctl_description = Erlang TUN/TAP interface
+pkg_tunctl_homepage = https://github.com/msantos/tunctl
+pkg_tunctl_fetch = git
+pkg_tunctl_repo = https://github.com/msantos/tunctl
+pkg_tunctl_commit = master
+
+PACKAGES += twerl
+pkg_twerl_name = twerl
+pkg_twerl_description = Erlang client for the Twitter Streaming API
+pkg_twerl_homepage = https://github.com/lucaspiller/twerl
+pkg_twerl_fetch = git
+pkg_twerl_repo = https://github.com/lucaspiller/twerl
+pkg_twerl_commit = oauth
+
+PACKAGES += twitter_erlang
+pkg_twitter_erlang_name = twitter_erlang
+pkg_twitter_erlang_description = An Erlang twitter client
+pkg_twitter_erlang_homepage = https://github.com/ngerakines/erlang_twitter
+pkg_twitter_erlang_fetch = git
+pkg_twitter_erlang_repo = https://github.com/ngerakines/erlang_twitter
+pkg_twitter_erlang_commit = master
+
+PACKAGES += ucol_nif
+pkg_ucol_nif_name = ucol_nif
+pkg_ucol_nif_description = ICU based collation Erlang module
+pkg_ucol_nif_homepage = https://github.com/refuge/ucol_nif
+pkg_ucol_nif_fetch = git
+pkg_ucol_nif_repo = https://github.com/refuge/ucol_nif
+pkg_ucol_nif_commit = master
+
+PACKAGES += unicorn
+pkg_unicorn_name = unicorn
+pkg_unicorn_description = Generic configuration server
+pkg_unicorn_homepage = https://github.com/shizzard/unicorn
+pkg_unicorn_fetch = git
+pkg_unicorn_repo = https://github.com/shizzard/unicorn
+pkg_unicorn_commit = master
+
+PACKAGES += unsplit
+pkg_unsplit_name = unsplit
+pkg_unsplit_description = Resolves conflicts in Mnesia after network splits
+pkg_unsplit_homepage = https://github.com/uwiger/unsplit
+pkg_unsplit_fetch = git
+pkg_unsplit_repo = https://github.com/uwiger/unsplit
+pkg_unsplit_commit = master
+
+PACKAGES += uuid
+pkg_uuid_name = uuid
+pkg_uuid_description = Erlang UUID Implementation
+pkg_uuid_homepage = https://github.com/okeuday/uuid
+pkg_uuid_fetch = git
+pkg_uuid_repo = https://github.com/okeuday/uuid
+pkg_uuid_commit = master
+
+PACKAGES += ux
+pkg_ux_name = ux
+pkg_ux_description = Unicode eXtention for Erlang (Strings, Collation)
+pkg_ux_homepage = https://github.com/erlang-unicode/ux
+pkg_ux_fetch = git
+pkg_ux_repo = https://github.com/erlang-unicode/ux
+pkg_ux_commit = master
+
+PACKAGES += vert
+pkg_vert_name = vert
+pkg_vert_description = Erlang binding to libvirt virtualization API
+pkg_vert_homepage = https://github.com/msantos/erlang-libvirt
+pkg_vert_fetch = git
+pkg_vert_repo = https://github.com/msantos/erlang-libvirt
+pkg_vert_commit = master
+
+PACKAGES += verx
+pkg_verx_name = verx
+pkg_verx_description = Erlang implementation of the libvirtd remote protocol
+pkg_verx_homepage = https://github.com/msantos/verx
+pkg_verx_fetch = git
+pkg_verx_repo = https://github.com/msantos/verx
+pkg_verx_commit = master
+
+PACKAGES += vmq_acl
+pkg_vmq_acl_name = vmq_acl
+pkg_vmq_acl_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_acl_homepage = https://verne.mq/
+pkg_vmq_acl_fetch = git
+pkg_vmq_acl_repo = https://github.com/erlio/vmq_acl
+pkg_vmq_acl_commit = master
+
+PACKAGES += vmq_bridge
+pkg_vmq_bridge_name = vmq_bridge
+pkg_vmq_bridge_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_bridge_homepage = https://verne.mq/
+pkg_vmq_bridge_fetch = git
+pkg_vmq_bridge_repo = https://github.com/erlio/vmq_bridge
+pkg_vmq_bridge_commit = master
+
+PACKAGES += vmq_graphite
+pkg_vmq_graphite_name = vmq_graphite
+pkg_vmq_graphite_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_graphite_homepage = https://verne.mq/
+pkg_vmq_graphite_fetch = git
+pkg_vmq_graphite_repo = https://github.com/erlio/vmq_graphite
+pkg_vmq_graphite_commit = master
+
+PACKAGES += vmq_passwd
+pkg_vmq_passwd_name = vmq_passwd
+pkg_vmq_passwd_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_passwd_homepage = https://verne.mq/
+pkg_vmq_passwd_fetch = git
+pkg_vmq_passwd_repo = https://github.com/erlio/vmq_passwd
+pkg_vmq_passwd_commit = master
+
+PACKAGES += vmq_server
+pkg_vmq_server_name = vmq_server
+pkg_vmq_server_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_server_homepage = https://verne.mq/
+pkg_vmq_server_fetch = git
+pkg_vmq_server_repo = https://github.com/erlio/vmq_server
+pkg_vmq_server_commit = master
+
+PACKAGES += vmq_snmp
+pkg_vmq_snmp_name = vmq_snmp
+pkg_vmq_snmp_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_snmp_homepage = https://verne.mq/
+pkg_vmq_snmp_fetch = git
+pkg_vmq_snmp_repo = https://github.com/erlio/vmq_snmp
+pkg_vmq_snmp_commit = master
+
+PACKAGES += vmq_systree
+pkg_vmq_systree_name = vmq_systree
+pkg_vmq_systree_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_systree_homepage = https://verne.mq/
+pkg_vmq_systree_fetch = git
+pkg_vmq_systree_repo = https://github.com/erlio/vmq_systree
+pkg_vmq_systree_commit = master
+
+PACKAGES += vmstats
+pkg_vmstats_name = vmstats
+pkg_vmstats_description = tiny Erlang app that works in conjunction with statsderl in order to generate information on the Erlang VM for graphite logs.
+pkg_vmstats_homepage = https://github.com/ferd/vmstats
+pkg_vmstats_fetch = git
+pkg_vmstats_repo = https://github.com/ferd/vmstats
+pkg_vmstats_commit = master
+
+PACKAGES += walrus
+pkg_walrus_name = walrus
+pkg_walrus_description = Walrus - Mustache-like Templating
+pkg_walrus_homepage = https://github.com/devinus/walrus
+pkg_walrus_fetch = git
+pkg_walrus_repo = https://github.com/devinus/walrus
+pkg_walrus_commit = master
+
+PACKAGES += webmachine
+pkg_webmachine_name = webmachine
+pkg_webmachine_description = A REST-based system for building web applications.
+pkg_webmachine_homepage = https://github.com/basho/webmachine
+pkg_webmachine_fetch = git
+pkg_webmachine_repo = https://github.com/basho/webmachine
+pkg_webmachine_commit = master
+
+PACKAGES += websocket_client
+pkg_websocket_client_name = websocket_client
+pkg_websocket_client_description = Erlang websocket client (ws and wss supported)
+pkg_websocket_client_homepage = https://github.com/jeremyong/websocket_client
+pkg_websocket_client_fetch = git
+pkg_websocket_client_repo = https://github.com/jeremyong/websocket_client
+pkg_websocket_client_commit = master
+
+PACKAGES += worker_pool
+pkg_worker_pool_name = worker_pool
+pkg_worker_pool_description = a simple erlang worker pool
+pkg_worker_pool_homepage = https://github.com/inaka/worker_pool
+pkg_worker_pool_fetch = git
+pkg_worker_pool_repo = https://github.com/inaka/worker_pool
+pkg_worker_pool_commit = master
+
+PACKAGES += wrangler
+pkg_wrangler_name = wrangler
+pkg_wrangler_description = Import of the Wrangler svn repository.
+pkg_wrangler_homepage = http://www.cs.kent.ac.uk/projects/wrangler/Home.html
+pkg_wrangler_fetch = git
+pkg_wrangler_repo = https://github.com/RefactoringTools/wrangler
+pkg_wrangler_commit = master
+
+PACKAGES += wsock
+pkg_wsock_name = wsock
+pkg_wsock_description = Erlang library to build WebSocket clients and servers
+pkg_wsock_homepage = https://github.com/madtrick/wsock
+pkg_wsock_fetch = git
+pkg_wsock_repo = https://github.com/madtrick/wsock
+pkg_wsock_commit = master
+
+PACKAGES += xhttpc
+pkg_xhttpc_name = xhttpc
+pkg_xhttpc_description = Extensible HTTP Client for Erlang
+pkg_xhttpc_homepage = https://github.com/seriyps/xhttpc
+pkg_xhttpc_fetch = git
+pkg_xhttpc_repo = https://github.com/seriyps/xhttpc
+pkg_xhttpc_commit = master
+
+PACKAGES += xref_runner
+pkg_xref_runner_name = xref_runner
+pkg_xref_runner_description = Erlang Xref Runner (inspired in rebar xref)
+pkg_xref_runner_homepage = https://github.com/inaka/xref_runner
+pkg_xref_runner_fetch = git
+pkg_xref_runner_repo = https://github.com/inaka/xref_runner
+pkg_xref_runner_commit = master
+
+PACKAGES += yamerl
+pkg_yamerl_name = yamerl
+pkg_yamerl_description = YAML 1.2 parser in pure Erlang
+pkg_yamerl_homepage = https://github.com/yakaz/yamerl
+pkg_yamerl_fetch = git
+pkg_yamerl_repo = https://github.com/yakaz/yamerl
+pkg_yamerl_commit = master
+
+PACKAGES += yamler
+pkg_yamler_name = yamler
+pkg_yamler_description = libyaml-based yaml loader for Erlang
+pkg_yamler_homepage = https://github.com/goertzenator/yamler
+pkg_yamler_fetch = git
+pkg_yamler_repo = https://github.com/goertzenator/yamler
+pkg_yamler_commit = master
+
+PACKAGES += yaws
+pkg_yaws_name = yaws
+pkg_yaws_description = Yaws webserver
+pkg_yaws_homepage = http://yaws.hyber.org
+pkg_yaws_fetch = git
+pkg_yaws_repo = https://github.com/klacke/yaws
+pkg_yaws_commit = master
+
+PACKAGES += zab_engine
+pkg_zab_engine_name = zab_engine
+pkg_zab_engine_description = zab propotocol implement by erlang
+pkg_zab_engine_homepage = https://github.com/xinmingyao/zab_engine
+pkg_zab_engine_fetch = git
+pkg_zab_engine_repo = https://github.com/xinmingyao/zab_engine
+pkg_zab_engine_commit = master
+
+PACKAGES += zabbix_sender
+pkg_zabbix_sender_name = zabbix_sender
+pkg_zabbix_sender_description = Zabbix trapper for sending data to Zabbix in pure Erlang
+pkg_zabbix_sender_homepage = https://github.com/stalkermn/zabbix_sender
+pkg_zabbix_sender_fetch = git
+pkg_zabbix_sender_repo = https://github.com/stalkermn/zabbix_sender.git
+pkg_zabbix_sender_commit = master
+
+PACKAGES += zeta
+pkg_zeta_name = zeta
+pkg_zeta_description = HTTP access log parser in Erlang
+pkg_zeta_homepage = https://github.com/s1n4/zeta
+pkg_zeta_fetch = git
+pkg_zeta_repo = https://github.com/s1n4/zeta
+pkg_zeta_commit = master
+
+PACKAGES += zippers
+pkg_zippers_name = zippers
+pkg_zippers_description = A library for functional zipper data structures in Erlang. Read more on zippers
+pkg_zippers_homepage = https://github.com/ferd/zippers
+pkg_zippers_fetch = git
+pkg_zippers_repo = https://github.com/ferd/zippers
+pkg_zippers_commit = master
+
+PACKAGES += zlists
+pkg_zlists_name = zlists
+pkg_zlists_description = Erlang lazy lists library.
+pkg_zlists_homepage = https://github.com/vjache/erlang-zlists
+pkg_zlists_fetch = git
+pkg_zlists_repo = https://github.com/vjache/erlang-zlists
+pkg_zlists_commit = master
+
+PACKAGES += zraft_lib
+pkg_zraft_lib_name = zraft_lib
+pkg_zraft_lib_description = Erlang raft consensus protocol implementation
+pkg_zraft_lib_homepage = https://github.com/dreyk/zraft_lib
+pkg_zraft_lib_fetch = git
+pkg_zraft_lib_repo = https://github.com/dreyk/zraft_lib
+pkg_zraft_lib_commit = master
+
+PACKAGES += zucchini
+pkg_zucchini_name = zucchini
+pkg_zucchini_description = An Erlang INI parser
+pkg_zucchini_homepage = https://github.com/devinus/zucchini
+pkg_zucchini_fetch = git
+pkg_zucchini_repo = https://github.com/devinus/zucchini
+pkg_zucchini_commit = master
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: search
+
+define pkg_print
+ $(verbose) printf "%s\n" \
+ $(if $(call core_eq,$(1),$(pkg_$(1)_name)),,"Pkg name: $(1)") \
+ "App name: $(pkg_$(1)_name)" \
+ "Description: $(pkg_$(1)_description)" \
+ "Home page: $(pkg_$(1)_homepage)" \
+ "Fetch with: $(pkg_$(1)_fetch)" \
+ "Repository: $(pkg_$(1)_repo)" \
+ "Commit: $(pkg_$(1)_commit)" \
+ ""
+
+endef
+
+search:
+ifdef q
+ $(foreach p,$(PACKAGES), \
+ $(if $(findstring $(call core_lc,$(q)),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \
+ $(call pkg_print,$(p))))
+else
+ $(foreach p,$(PACKAGES),$(call pkg_print,$(p)))
+endif
+
+# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: distclean-deps clean-tmp-deps.log
+
+# Configuration.
+
+ifdef OTP_DEPS
+$(warning The variable OTP_DEPS is deprecated in favor of LOCAL_DEPS.)
+endif
+
+IGNORE_DEPS ?=
+export IGNORE_DEPS
+
+APPS_DIR ?= $(CURDIR)/apps
+export APPS_DIR
+
+DEPS_DIR ?= $(CURDIR)/deps
+export DEPS_DIR
+
+REBAR_DEPS_DIR = $(DEPS_DIR)
+export REBAR_DEPS_DIR
+
+REBAR_GIT ?= https://github.com/rebar/rebar
+REBAR_COMMIT ?= 576e12171ab8d69b048b827b92aa65d067deea01
+
+# External "early" plugins (see core/plugins.mk for regular plugins).
+# They both use the core_dep_plugin macro.
+
+define core_dep_plugin
+ifeq ($(2),$(PROJECT))
+-include $$(patsubst $(PROJECT)/%,%,$(1))
+else
+-include $(DEPS_DIR)/$(1)
+
+$(DEPS_DIR)/$(1): $(DEPS_DIR)/$(2) ;
+endif
+endef
+
+DEP_EARLY_PLUGINS ?=
+
+$(foreach p,$(DEP_EARLY_PLUGINS),\
+ $(eval $(if $(findstring /,$p),\
+ $(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\
+ $(call core_dep_plugin,$p/early-plugins.mk,$p))))
+
+# Query functions.
+
+query_fetch_method = $(if $(dep_$(1)),$(call _qfm_dep,$(word 1,$(dep_$(1)))),$(call _qfm_pkg,$(1)))
+_qfm_dep = $(if $(dep_fetch_$(1)),$(1),$(if $(IS_DEP),legacy,fail))
+_qfm_pkg = $(if $(pkg_$(1)_fetch),$(pkg_$(1)_fetch),fail)
+
+query_name = $(if $(dep_$(1)),$(1),$(if $(pkg_$(1)_name),$(pkg_$(1)_name),$(1)))
+
+query_repo = $(call _qr,$(1),$(call query_fetch_method,$(1)))
+_qr = $(if $(query_repo_$(2)),$(call query_repo_$(2),$(1)),$(call dep_repo,$(1)))
+
+query_repo_default = $(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_repo))
+query_repo_git = $(patsubst git://github.com/%,https://github.com/%,$(call query_repo_default,$(1)))
+query_repo_git-subfolder = $(call query_repo_git,$(1))
+query_repo_git-submodule = -
+query_repo_hg = $(call query_repo_default,$(1))
+query_repo_svn = $(call query_repo_default,$(1))
+query_repo_cp = $(call query_repo_default,$(1))
+query_repo_ln = $(call query_repo_default,$(1))
+query_repo_hex = https://hex.pm/packages/$(if $(word 3,$(dep_$(1))),$(word 3,$(dep_$(1))),$(1))
+query_repo_fail = -
+query_repo_legacy = -
+
+query_version = $(call _qv,$(1),$(call query_fetch_method,$(1)))
+_qv = $(if $(query_version_$(2)),$(call query_version_$(2),$(1)),$(call dep_commit,$(1)))
+
+query_version_default = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 3,$(dep_$(1))),$(pkg_$(1)_commit)))
+query_version_git = $(call query_version_default,$(1))
+query_version_git-subfolder = $(call query_version_git,$(1))
+query_version_git-submodule = -
+query_version_hg = $(call query_version_default,$(1))
+query_version_svn = -
+query_version_cp = -
+query_version_ln = -
+query_version_hex = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_commit)))
+query_version_fail = -
+query_version_legacy = -
+
+query_extra = $(call _qe,$(1),$(call query_fetch_method,$(1)))
+_qe = $(if $(query_extra_$(2)),$(call query_extra_$(2),$(1)),-)
+
+query_extra_git = -
+query_extra_git-subfolder = $(if $(dep_$(1)),subfolder=$(word 4,$(dep_$(1))),-)
+query_extra_git-submodule = -
+query_extra_hg = -
+query_extra_svn = -
+query_extra_cp = -
+query_extra_ln = -
+query_extra_hex = $(if $(dep_$(1)),package-name=$(word 3,$(dep_$(1))),-)
+query_extra_fail = -
+query_extra_legacy = -
+
+query_absolute_path = $(addprefix $(DEPS_DIR)/,$(call query_name,$(1)))
+
+# Deprecated legacy query functions.
+dep_fetch = $(call query_fetch_method,$(1))
+dep_name = $(call query_name,$(1))
+dep_repo = $(call query_repo_git,$(1))
+dep_commit = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(if $(filter hex,$(word 1,$(dep_$(1)))),$(word 2,$(dep_$(1))),$(word 3,$(dep_$(1)))),$(pkg_$(1)_commit)))
+
+LOCAL_DEPS_DIRS = $(foreach a,$(LOCAL_DEPS),$(if $(wildcard $(APPS_DIR)/$(a)),$(APPS_DIR)/$(a)))
+ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(BUILD_DEPS) $(DEPS)),$(call dep_name,$(dep))))
+
+# When we are calling an app directly we don't want to include it here
+# otherwise it'll be treated both as an apps and a top-level project.
+ALL_APPS_DIRS = $(if $(wildcard $(APPS_DIR)/),$(filter-out $(APPS_DIR),$(shell find $(APPS_DIR) -maxdepth 1 -type d)))
+ifdef ROOT_DIR
+ifndef IS_APP
+ALL_APPS_DIRS := $(filter-out $(APPS_DIR)/$(notdir $(CURDIR)),$(ALL_APPS_DIRS))
+endif
+endif
+
+ifeq ($(filter $(APPS_DIR) $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),)
+ifeq ($(ERL_LIBS),)
+ ERL_LIBS = $(APPS_DIR):$(DEPS_DIR)
+else
+ ERL_LIBS := $(ERL_LIBS):$(APPS_DIR):$(DEPS_DIR)
+endif
+endif
+export ERL_LIBS
+
+export NO_AUTOPATCH
+
+# Verbosity.
+
+dep_verbose_0 = @echo " DEP $1 ($(call dep_commit,$1))";
+dep_verbose_2 = set -x;
+dep_verbose = $(dep_verbose_$(V))
+
+# Optimization: don't recompile deps unless truly necessary.
+
+ifndef IS_DEP
+ifneq ($(MAKELEVEL),0)
+$(shell rm -f ebin/dep_built)
+endif
+endif
+
+# Core targets.
+
+ALL_APPS_DIRS_TO_BUILD = $(if $(LOCAL_DEPS_DIRS)$(IS_APP),$(LOCAL_DEPS_DIRS),$(ALL_APPS_DIRS))
+
+apps:: $(ALL_APPS_DIRS) clean-tmp-deps.log | $(ERLANG_MK_TMP)
+# Create ebin directory for all apps to make sure Erlang recognizes them
+# as proper OTP applications when using -include_lib. This is a temporary
+# fix, a proper fix would be to compile apps/* in the right order.
+ifndef IS_APP
+ifneq ($(ALL_APPS_DIRS),)
+ $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \
+ mkdir -p $$dep/ebin; \
+ done
+endif
+endif
+# At the toplevel: if LOCAL_DEPS is defined with at least one local app, only
+# compile that list of apps. Otherwise, compile everything.
+# Within an app: compile all LOCAL_DEPS that are (uncompiled) local apps.
+ifneq ($(ALL_APPS_DIRS_TO_BUILD),)
+ $(verbose) set -e; for dep in $(ALL_APPS_DIRS_TO_BUILD); do \
+ if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/apps.log; then \
+ :; \
+ else \
+ echo $$dep >> $(ERLANG_MK_TMP)/apps.log; \
+ $(MAKE) -C $$dep $(if $(IS_TEST),test-build-app) IS_APP=1; \
+ fi \
+ done
+endif
+
+clean-tmp-deps.log:
+ifeq ($(IS_APP)$(IS_DEP),)
+ $(verbose) rm -f $(ERLANG_MK_TMP)/apps.log $(ERLANG_MK_TMP)/deps.log
+endif
+
+# Erlang.mk does not rebuild dependencies after they were compiled
+# once. If a developer is working on the top-level project and some
+# dependencies at the same time, he may want to change this behavior.
+# There are two solutions:
+# 1. Set `FULL=1` so that all dependencies are visited and
+# recursively recompiled if necessary.
+# 2. Set `FORCE_REBUILD=` to the specific list of dependencies that
+# should be recompiled (instead of the whole set).
+
+FORCE_REBUILD ?=
+
+ifeq ($(origin FULL),undefined)
+ifneq ($(strip $(force_rebuild_dep)$(FORCE_REBUILD)),)
+define force_rebuild_dep
+echo "$(FORCE_REBUILD)" | grep -qw "$$(basename "$1")"
+endef
+endif
+endif
+
+ifneq ($(SKIP_DEPS),)
+deps::
+else
+deps:: $(ALL_DEPS_DIRS) apps clean-tmp-deps.log | $(ERLANG_MK_TMP)
+ifneq ($(ALL_DEPS_DIRS),)
+ $(verbose) set -e; for dep in $(ALL_DEPS_DIRS); do \
+ if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \
+ :; \
+ else \
+ echo $$dep >> $(ERLANG_MK_TMP)/deps.log; \
+ if [ -z "$(strip $(FULL))" ] $(if $(force_rebuild_dep),&& ! ($(call force_rebuild_dep,$$dep)),) && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \
+ :; \
+ elif [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ]; then \
+ $(MAKE) -C $$dep IS_DEP=1; \
+ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \
+ else \
+ echo "Error: No Makefile to build dependency $$dep." >&2; \
+ exit 2; \
+ fi \
+ fi \
+ done
+endif
+endif
+
+# Deps related targets.
+
+# @todo rename GNUmakefile and makefile into Makefile first, if they exist
+# While Makefile file could be GNUmakefile or makefile,
+# in practice only Makefile is needed so far.
+define dep_autopatch
+ if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \
+ rm -rf $(DEPS_DIR)/$1/ebin/; \
+ $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
+ $(call dep_autopatch_erlang_mk,$(1)); \
+ elif [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
+ if [ -f $(DEPS_DIR)/$1/rebar.lock ]; then \
+ $(call dep_autopatch2,$1); \
+ elif [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \
+ $(call dep_autopatch2,$(1)); \
+ elif [ 0 != `grep -ci "^[^#].*rebar" $(DEPS_DIR)/$(1)/Makefile` ]; then \
+ $(call dep_autopatch2,$(1)); \
+ elif [ -n "`find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk -exec grep -i "^[^#].*rebar" '{}' \;`" ]; then \
+ $(call dep_autopatch2,$(1)); \
+ fi \
+ else \
+ if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \
+ $(call dep_autopatch_noop,$(1)); \
+ else \
+ $(call dep_autopatch2,$(1)); \
+ fi \
+ fi
+endef
+
+define dep_autopatch2
+ ! test -f $(DEPS_DIR)/$1/ebin/$1.app || \
+ mv -n $(DEPS_DIR)/$1/ebin/$1.app $(DEPS_DIR)/$1/src/$1.app.src; \
+ rm -f $(DEPS_DIR)/$1/ebin/$1.app; \
+ if [ -f $(DEPS_DIR)/$1/src/$1.app.src.script ]; then \
+ $(call erlang,$(call dep_autopatch_appsrc_script.erl,$(1))); \
+ fi; \
+ $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
+ if [ -f $(DEPS_DIR)/$(1)/rebar -o -f $(DEPS_DIR)/$(1)/rebar.config -o -f $(DEPS_DIR)/$(1)/rebar.config.script -o -f $(DEPS_DIR)/$1/rebar.lock ]; then \
+ $(call dep_autopatch_fetch_rebar); \
+ $(call dep_autopatch_rebar,$(1)); \
+ else \
+ $(call dep_autopatch_gen,$(1)); \
+ fi
+endef
+
+define dep_autopatch_noop
+ printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile
+endef
+
+# Replace "include erlang.mk" with a line that will load the parent Erlang.mk
+# if given. Do it for all 3 possible Makefile file names.
+ifeq ($(NO_AUTOPATCH_ERLANG_MK),)
+define dep_autopatch_erlang_mk
+ for f in Makefile makefile GNUmakefile; do \
+ if [ -f $(DEPS_DIR)/$1/$$f ]; then \
+ sed -i.bak s/'include *erlang.mk'/'include $$(if $$(ERLANG_MK_FILENAME),$$(ERLANG_MK_FILENAME),erlang.mk)'/ $(DEPS_DIR)/$1/$$f; \
+ fi \
+ done
+endef
+else
+define dep_autopatch_erlang_mk
+ :
+endef
+endif
+
+define dep_autopatch_gen
+ printf "%s\n" \
+ "ERLC_OPTS = +debug_info" \
+ "include ../../erlang.mk" > $(DEPS_DIR)/$(1)/Makefile
+endef
+
+# We use flock/lockf when available to avoid concurrency issues.
+define dep_autopatch_fetch_rebar
+ if command -v flock >/dev/null; then \
+ flock $(ERLANG_MK_TMP)/rebar.lock sh -c "$(call dep_autopatch_fetch_rebar2)"; \
+ elif command -v lockf >/dev/null; then \
+ lockf $(ERLANG_MK_TMP)/rebar.lock sh -c "$(call dep_autopatch_fetch_rebar2)"; \
+ else \
+ $(call dep_autopatch_fetch_rebar2); \
+ fi
+endef
+
+define dep_autopatch_fetch_rebar2
+ if [ ! -d $(ERLANG_MK_TMP)/rebar ]; then \
+ git clone -q -n -- $(REBAR_GIT) $(ERLANG_MK_TMP)/rebar; \
+ cd $(ERLANG_MK_TMP)/rebar; \
+ git checkout -q $(REBAR_COMMIT); \
+ ./bootstrap; \
+ cd -; \
+ fi
+endef
+
+define dep_autopatch_rebar
+ if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
+ mv $(DEPS_DIR)/$(1)/Makefile $(DEPS_DIR)/$(1)/Makefile.orig.mk; \
+ fi; \
+ $(call erlang,$(call dep_autopatch_rebar.erl,$(1))); \
+ rm -f $(DEPS_DIR)/$(1)/ebin/$(1).app
+endef
+
+define dep_autopatch_rebar.erl
+ application:load(rebar),
+ application:set_env(rebar, log_level, debug),
+ rmemo:start(),
+ Conf1 = case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config)") of
+ {ok, Conf0} -> Conf0;
+ _ -> []
+ end,
+ {Conf, OsEnv} = fun() ->
+ case filelib:is_file("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)") of
+ false -> {Conf1, []};
+ true ->
+ Bindings0 = erl_eval:new_bindings(),
+ Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0),
+ Bindings = erl_eval:add_binding('SCRIPT', "$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)", Bindings1),
+ Before = os:getenv(),
+ {ok, Conf2} = file:script("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)", Bindings),
+ {Conf2, lists:foldl(fun(E, Acc) -> lists:delete(E, Acc) end, os:getenv(), Before)}
+ end
+ end(),
+ Write = fun (Text) ->
+ file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/Makefile)", Text, [append])
+ end,
+ Escape = fun (Text) ->
+ re:replace(Text, "\\\\$$", "\$$$$", [global, {return, list}])
+ end,
+ Write("IGNORE_DEPS += edown eper eunit_formatters meck node_package "
+ "rebar_lock_deps_plugin rebar_vsn_plugin reltool_util\n"),
+ Write("C_SRC_DIR = /path/do/not/exist\n"),
+ Write("C_SRC_TYPE = rebar\n"),
+ Write("DRV_CFLAGS = -fPIC\nexport DRV_CFLAGS\n"),
+ Write(["ERLANG_ARCH = ", rebar_utils:wordsize(), "\nexport ERLANG_ARCH\n"]),
+ ToList = fun
+ (V) when is_atom(V) -> atom_to_list(V);
+ (V) when is_list(V) -> "'\\"" ++ V ++ "\\"'"
+ end,
+ fun() ->
+ Write("ERLC_OPTS = +debug_info\nexport ERLC_OPTS\n"),
+ case lists:keyfind(erl_opts, 1, Conf) of
+ false -> ok;
+ {_, ErlOpts} ->
+ lists:foreach(fun
+ ({d, D}) ->
+ Write("ERLC_OPTS += -D" ++ ToList(D) ++ "=1\n");
+ ({d, DKey, DVal}) ->
+ Write("ERLC_OPTS += -D" ++ ToList(DKey) ++ "=" ++ ToList(DVal) ++ "\n");
+ ({i, I}) ->
+ Write(["ERLC_OPTS += -I ", I, "\n"]);
+ ({platform_define, Regex, D}) ->
+ case rebar_utils:is_arch(Regex) of
+ true -> Write("ERLC_OPTS += -D" ++ ToList(D) ++ "=1\n");
+ false -> ok
+ end;
+ ({parse_transform, PT}) ->
+ Write("ERLC_OPTS += +'{parse_transform, " ++ ToList(PT) ++ "}'\n");
+ (_) -> ok
+ end, ErlOpts)
+ end,
+ Write("\n")
+ end(),
+ GetHexVsn = fun(N, NP) ->
+ case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.lock)") of
+ {ok, Lock} ->
+ io:format("~p~n", [Lock]),
+ case lists:keyfind("1.1.0", 1, Lock) of
+ {_, LockPkgs} ->
+ io:format("~p~n", [LockPkgs]),
+ case lists:keyfind(atom_to_binary(N, latin1), 1, LockPkgs) of
+ {_, {pkg, _, Vsn}, _} ->
+ io:format("~p~n", [Vsn]),
+ {N, {hex, NP, binary_to_list(Vsn)}};
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end
+ end,
+ SemVsn = fun
+ ("~>" ++ S0) ->
+ S = case S0 of
+ " " ++ S1 -> S1;
+ _ -> S0
+ end,
+ case length([ok || $$. <- S]) of
+ 0 -> S ++ ".0.0";
+ 1 -> S ++ ".0";
+ _ -> S
+ end;
+ (S) -> S
+ end,
+ fun() ->
+ File = case lists:keyfind(deps, 1, Conf) of
+ false -> [];
+ {_, Deps} ->
+ [begin case case Dep of
+ N when is_atom(N) -> GetHexVsn(N, N);
+ {N, S} when is_atom(N), is_list(S) -> {N, {hex, N, SemVsn(S)}};
+ {N, {pkg, NP}} when is_atom(N) -> GetHexVsn(N, NP);
+ {N, S, {pkg, NP}} -> {N, {hex, NP, S}};
+ {N, S} when is_tuple(S) -> {N, S};
+ {N, _, S} -> {N, S};
+ {N, _, S, _} -> {N, S};
+ _ -> false
+ end of
+ false -> ok;
+ {Name, Source} ->
+ {Method, Repo, Commit} = case Source of
+ {hex, NPV, V} -> {hex, V, NPV};
+ {git, R} -> {git, R, master};
+ {M, R, {branch, C}} -> {M, R, C};
+ {M, R, {ref, C}} -> {M, R, C};
+ {M, R, {tag, C}} -> {M, R, C};
+ {M, R, C} -> {M, R, C}
+ end,
+ Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, Commit]))
+ end end || Dep <- Deps]
+ end
+ end(),
+ fun() ->
+ case lists:keyfind(erl_first_files, 1, Conf) of
+ false -> ok;
+ {_, Files} ->
+ Names = [[" ", case lists:reverse(F) of
+ "lre." ++ Elif -> lists:reverse(Elif);
+ "lrx." ++ Elif -> lists:reverse(Elif);
+ "lry." ++ Elif -> lists:reverse(Elif);
+ Elif -> lists:reverse(Elif)
+ end] || "src/" ++ F <- Files],
+ Write(io_lib:format("COMPILE_FIRST +=~s\n", [Names]))
+ end
+ end(),
+ Write("\n\nrebar_dep: preprocess pre-deps deps pre-app app\n"),
+ Write("\npreprocess::\n"),
+ Write("\npre-deps::\n"),
+ Write("\npre-app::\n"),
+ PatchHook = fun(Cmd) ->
+ Cmd2 = re:replace(Cmd, "^([g]?make)(.*)( -C.*)", "\\\\1\\\\3\\\\2", [{return, list}]),
+ case Cmd2 of
+ "make -C" ++ Cmd1 -> "$$\(MAKE) -C" ++ Escape(Cmd1);
+ "gmake -C" ++ Cmd1 -> "$$\(MAKE) -C" ++ Escape(Cmd1);
+ "make " ++ Cmd1 -> "$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1);
+ "gmake " ++ Cmd1 -> "$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1);
+ _ -> Escape(Cmd)
+ end
+ end,
+ fun() ->
+ case lists:keyfind(pre_hooks, 1, Conf) of
+ false -> ok;
+ {_, Hooks} ->
+ [case H of
+ {'get-deps', Cmd} ->
+ Write("\npre-deps::\n\t" ++ PatchHook(Cmd) ++ "\n");
+ {compile, Cmd} ->
+ Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
+ {Regex, compile, Cmd} ->
+ case rebar_utils:is_arch(Regex) of
+ true -> Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
+ false -> ok
+ end;
+ _ -> ok
+ end || H <- Hooks]
+ end
+ end(),
+ ShellToMk = fun(V0) ->
+ V1 = re:replace(V0, "[$$][(]", "$$\(shell ", [global]),
+ V = re:replace(V1, "([$$])(?![(])(\\\\w*)", "\\\\1(\\\\2)", [global]),
+ re:replace(V, "-Werror\\\\b", "", [{return, list}, global])
+ end,
+ PortSpecs = fun() ->
+ case lists:keyfind(port_specs, 1, Conf) of
+ false ->
+ case filelib:is_dir("$(call core_native_path,$(DEPS_DIR)/$1/c_src)") of
+ false -> [];
+ true ->
+ [{"priv/" ++ proplists:get_value(so_name, Conf, "$(1)_drv.so"),
+ proplists:get_value(port_sources, Conf, ["c_src/*.c"]), []}]
+ end;
+ {_, Specs} ->
+ lists:flatten([case S of
+ {Output, Input} -> {ShellToMk(Output), Input, []};
+ {Regex, Output, Input} ->
+ case rebar_utils:is_arch(Regex) of
+ true -> {ShellToMk(Output), Input, []};
+ false -> []
+ end;
+ {Regex, Output, Input, [{env, Env}]} ->
+ case rebar_utils:is_arch(Regex) of
+ true -> {ShellToMk(Output), Input, Env};
+ false -> []
+ end
+ end || S <- Specs])
+ end
+ end(),
+ PortSpecWrite = fun (Text) ->
+ file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/c_src/Makefile.erlang.mk)", Text, [append])
+ end,
+ case PortSpecs of
+ [] -> ok;
+ _ ->
+ Write("\npre-app::\n\t@$$\(MAKE) --no-print-directory -f c_src/Makefile.erlang.mk\n"),
+ PortSpecWrite(io_lib:format("ERL_CFLAGS ?= -finline-functions -Wall -fPIC -I \\"~s/erts-~s/include\\" -I \\"~s\\"\n",
+ [code:root_dir(), erlang:system_info(version), code:lib_dir(erl_interface, include)])),
+ PortSpecWrite(io_lib:format("ERL_LDFLAGS ?= -L \\"~s\\" -lei\n",
+ [code:lib_dir(erl_interface, lib)])),
+ [PortSpecWrite(["\n", E, "\n"]) || E <- OsEnv],
+ FilterEnv = fun(Env) ->
+ lists:flatten([case E of
+ {_, _} -> E;
+ {Regex, K, V} ->
+ case rebar_utils:is_arch(Regex) of
+ true -> {K, V};
+ false -> []
+ end
+ end || E <- Env])
+ end,
+ MergeEnv = fun(Env) ->
+ lists:foldl(fun ({K, V}, Acc) ->
+ case lists:keyfind(K, 1, Acc) of
+ false -> [{K, rebar_utils:expand_env_variable(V, K, "")}|Acc];
+ {_, V0} -> [{K, rebar_utils:expand_env_variable(V, K, V0)}|Acc]
+ end
+ end, [], Env)
+ end,
+ PortEnv = case lists:keyfind(port_env, 1, Conf) of
+ false -> [];
+ {_, PortEnv0} -> FilterEnv(PortEnv0)
+ end,
+ PortSpec = fun ({Output, Input0, Env}) ->
+ filelib:ensure_dir("$(call core_native_path,$(DEPS_DIR)/$1/)" ++ Output),
+ Input = [[" ", I] || I <- Input0],
+ PortSpecWrite([
+ [["\n", K, " = ", ShellToMk(V)] || {K, V} <- lists:reverse(MergeEnv(PortEnv))],
+ case $(PLATFORM) of
+ darwin -> "\n\nLDFLAGS += -flat_namespace -undefined suppress";
+ _ -> ""
+ end,
+ "\n\nall:: ", Output, "\n\t@:\n\n",
+ "%.o: %.c\n\t$$\(CC) -c -o $$\@ $$\< $$\(CFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n",
+ "%.o: %.C\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n",
+ "%.o: %.cc\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n",
+ "%.o: %.cpp\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n",
+ [[Output, ": ", K, " += ", ShellToMk(V), "\n"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))],
+ Output, ": $$\(foreach ext,.c .C .cc .cpp,",
+ "$$\(patsubst %$$\(ext),%.o,$$\(filter %$$\(ext),$$\(wildcard", Input, "))))\n",
+ "\t$$\(CC) -o $$\@ $$\? $$\(LDFLAGS) $$\(ERL_LDFLAGS) $$\(DRV_LDFLAGS) $$\(EXE_LDFLAGS)",
+ case {filename:extension(Output), $(PLATFORM)} of
+ {[], _} -> "\n";
+ {_, darwin} -> "\n";
+ _ -> " -shared\n"
+ end])
+ end,
+ [PortSpec(S) || S <- PortSpecs]
+ end,
+ fun() ->
+ case lists:keyfind(plugins, 1, Conf) of
+ false -> ok;
+ {_, Plugins0} ->
+ Plugins = [P || P <- Plugins0, is_tuple(P)],
+ case lists:keyfind('lfe-compile', 1, Plugins) of
+ false -> ok;
+ _ -> Write("\nBUILD_DEPS = lfe lfe.mk\ndep_lfe.mk = git https://github.com/ninenines/lfe.mk master\nDEP_PLUGINS = lfe.mk\n")
+ end
+ end
+ end(),
+ Write("\ninclude $$\(if $$\(ERLANG_MK_FILENAME),$$\(ERLANG_MK_FILENAME),erlang.mk)"),
+ RunPlugin = fun(Plugin, Step) ->
+ case erlang:function_exported(Plugin, Step, 2) of
+ false -> ok;
+ true ->
+ c:cd("$(call core_native_path,$(DEPS_DIR)/$1/)"),
+ Ret = Plugin:Step({config, "", Conf, dict:new(), dict:new(), dict:new(),
+ dict:store(base_dir, "", dict:new())}, undefined),
+ io:format("rebar plugin ~p step ~p ret ~p~n", [Plugin, Step, Ret])
+ end
+ end,
+ fun() ->
+ case lists:keyfind(plugins, 1, Conf) of
+ false -> ok;
+ {_, Plugins0} ->
+ Plugins = [P || P <- Plugins0, is_atom(P)],
+ [begin
+ case lists:keyfind(deps, 1, Conf) of
+ false -> ok;
+ {_, Deps} ->
+ case lists:keyfind(P, 1, Deps) of
+ false -> ok;
+ _ ->
+ Path = "$(call core_native_path,$(DEPS_DIR)/)" ++ atom_to_list(P),
+ io:format("~s", [os:cmd("$(MAKE) -C $(call core_native_path,$(DEPS_DIR)/$1) " ++ Path)]),
+ io:format("~s", [os:cmd("$(MAKE) -C " ++ Path ++ " IS_DEP=1")]),
+ code:add_patha(Path ++ "/ebin")
+ end
+ end
+ end || P <- Plugins],
+ [case code:load_file(P) of
+ {module, P} -> ok;
+ _ ->
+ case lists:keyfind(plugin_dir, 1, Conf) of
+ false -> ok;
+ {_, PluginsDir} ->
+ ErlFile = "$(call core_native_path,$(DEPS_DIR)/$1/)" ++ PluginsDir ++ "/" ++ atom_to_list(P) ++ ".erl",
+ {ok, P, Bin} = compile:file(ErlFile, [binary]),
+ {module, P} = code:load_binary(P, ErlFile, Bin)
+ end
+ end || P <- Plugins],
+ [RunPlugin(P, preprocess) || P <- Plugins],
+ [RunPlugin(P, pre_compile) || P <- Plugins],
+ [RunPlugin(P, compile) || P <- Plugins]
+ end
+ end(),
+ halt()
+endef
+
+define dep_autopatch_appsrc_script.erl
+ AppSrc = "$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)",
+ AppSrcScript = AppSrc ++ ".script",
+ {ok, Conf0} = file:consult(AppSrc),
+ Bindings0 = erl_eval:new_bindings(),
+ Bindings1 = erl_eval:add_binding('CONFIG', Conf0, Bindings0),
+ Bindings = erl_eval:add_binding('SCRIPT', AppSrcScript, Bindings1),
+ Conf = case file:script(AppSrcScript, Bindings) of
+ {ok, [C]} -> C;
+ {ok, C} -> C
+ end,
+ ok = file:write_file(AppSrc, io_lib:format("~p.~n", [Conf])),
+ halt()
+endef
+
+define dep_autopatch_appsrc.erl
+ AppSrcOut = "$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)",
+ AppSrcIn = case filelib:is_regular(AppSrcOut) of false -> "$(call core_native_path,$(DEPS_DIR)/$1/ebin/$1.app)"; true -> AppSrcOut end,
+ case filelib:is_regular(AppSrcIn) of
+ false -> ok;
+ true ->
+ {ok, [{application, $(1), L0}]} = file:consult(AppSrcIn),
+ L1 = lists:keystore(modules, 1, L0, {modules, []}),
+ L2 = case lists:keyfind(vsn, 1, L1) of
+ {_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, lists:droplast(os:cmd("git -C $(DEPS_DIR)/$1 describe --dirty --tags --always"))});
+ {_, {cmd, _}} -> lists:keyreplace(vsn, 1, L1, {vsn, "cmd"});
+ _ -> L1
+ end,
+ L3 = case lists:keyfind(registered, 1, L2) of false -> [{registered, []}|L2]; _ -> L2 end,
+ ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $(1), L3}])),
+ case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end
+ end,
+ halt()
+endef
+
+define dep_fetch_git
+ git clone -q -n -- $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \
+ cd $(DEPS_DIR)/$(call dep_name,$(1)) && git checkout -q $(call dep_commit,$(1));
+endef
+
+define dep_fetch_git-subfolder
+ mkdir -p $(ERLANG_MK_TMP)/git-subfolder; \
+ git clone -q -n -- $(call dep_repo,$1) \
+ $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1); \
+ cd $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1) \
+ && git checkout -q $(call dep_commit,$1); \
+ ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1)/$(word 4,$(dep_$(1))) \
+ $(DEPS_DIR)/$(call dep_name,$1);
+endef
+
+define dep_fetch_git-submodule
+ git submodule update --init -- $(DEPS_DIR)/$1;
+endef
+
+define dep_fetch_hg
+ hg clone -q -U $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \
+ cd $(DEPS_DIR)/$(call dep_name,$(1)) && hg update -q $(call dep_commit,$(1));
+endef
+
+define dep_fetch_svn
+ svn checkout -q $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1));
+endef
+
+define dep_fetch_cp
+ cp -R $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1));
+endef
+
+define dep_fetch_ln
+ ln -s $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1));
+endef
+
+# Hex only has a package version. No need to look in the Erlang.mk packages.
+define dep_fetch_hex
+ mkdir -p $(ERLANG_MK_TMP)/hex $(DEPS_DIR)/$1; \
+ $(call core_http_get,$(ERLANG_MK_TMP)/hex/$1.tar,\
+ https://repo.hex.pm/tarballs/$(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1)-$(strip $(word 2,$(dep_$1))).tar); \
+ tar -xOf $(ERLANG_MK_TMP)/hex/$1.tar contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -;
+endef
+
+define dep_fetch_fail
+ echo "Error: Unknown or invalid dependency: $(1)." >&2; \
+ exit 78;
+endef
+
+# Kept for compatibility purposes with older Erlang.mk configuration.
+define dep_fetch_legacy
+ $(warning WARNING: '$(1)' dependency configuration uses deprecated format.) \
+ git clone -q -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1); \
+ cd $(DEPS_DIR)/$(1) && git checkout -q $(if $(word 2,$(dep_$(1))),$(word 2,$(dep_$(1))),master);
+endef
+
+define dep_target
+$(DEPS_DIR)/$(call dep_name,$1): | $(ERLANG_MK_TMP)
+ $(eval DEP_NAME := $(call dep_name,$1))
+ $(eval DEP_STR := $(if $(filter $1,$(DEP_NAME)),$1,"$1 ($(DEP_NAME))"))
+ $(verbose) if test -d $(APPS_DIR)/$(DEP_NAME); then \
+ echo "Error: Dependency" $(DEP_STR) "conflicts with application found in $(APPS_DIR)/$(DEP_NAME)." >&2; \
+ exit 17; \
+ fi
+ $(verbose) mkdir -p $(DEPS_DIR)
+ $(dep_verbose) $(call dep_fetch_$(strip $(call dep_fetch,$(1))),$(1))
+ $(verbose) if [ -f $(DEPS_DIR)/$(1)/configure.ac -o -f $(DEPS_DIR)/$(1)/configure.in ] \
+ && [ ! -f $(DEPS_DIR)/$(1)/configure ]; then \
+ echo " AUTO " $(DEP_STR); \
+ cd $(DEPS_DIR)/$(1) && autoreconf -Wall -vif -I m4; \
+ fi
+ - $(verbose) if [ -f $(DEPS_DIR)/$(DEP_NAME)/configure ]; then \
+ echo " CONF " $(DEP_STR); \
+ cd $(DEPS_DIR)/$(DEP_NAME) && ./configure; \
+ fi
+ifeq ($(filter $(1),$(NO_AUTOPATCH)),)
+ $(verbose) $$(MAKE) --no-print-directory autopatch-$(DEP_NAME)
+endif
+
+.PHONY: autopatch-$(call dep_name,$1)
+
+autopatch-$(call dep_name,$1)::
+ $(verbose) if [ "$(1)" = "amqp_client" -a "$(RABBITMQ_CLIENT_PATCH)" ]; then \
+ if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
+ echo " PATCH Downloading rabbitmq-codegen"; \
+ git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
+ fi; \
+ if [ ! -d $(DEPS_DIR)/rabbitmq-server ]; then \
+ echo " PATCH Downloading rabbitmq-server"; \
+ git clone https://github.com/rabbitmq/rabbitmq-server.git $(DEPS_DIR)/rabbitmq-server; \
+ fi; \
+ ln -s $(DEPS_DIR)/amqp_client/deps/rabbit_common-0.0.0 $(DEPS_DIR)/rabbit_common; \
+ elif [ "$(1)" = "rabbit" -a "$(RABBITMQ_SERVER_PATCH)" ]; then \
+ if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
+ echo " PATCH Downloading rabbitmq-codegen"; \
+ git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
+ fi \
+ elif [ "$1" = "elixir" -a "$(ELIXIR_PATCH)" ]; then \
+ ln -s lib/elixir/ebin $(DEPS_DIR)/elixir/; \
+ else \
+ $$(call dep_autopatch,$(call dep_name,$1)) \
+ fi
+endef
+
+$(foreach dep,$(BUILD_DEPS) $(DEPS),$(eval $(call dep_target,$(dep))))
+
+ifndef IS_APP
+clean:: clean-apps
+
+clean-apps:
+ $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \
+ $(MAKE) -C $$dep clean IS_APP=1; \
+ done
+
+distclean:: distclean-apps
+
+distclean-apps:
+ $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \
+ $(MAKE) -C $$dep distclean IS_APP=1; \
+ done
+endif
+
+ifndef SKIP_DEPS
+distclean:: distclean-deps
+
+distclean-deps:
+ $(gen_verbose) rm -rf $(DEPS_DIR)
+endif
+
+# Forward-declare variables used in core/deps-tools.mk. This is required
+# in case plugins use them.
+
+ERLANG_MK_RECURSIVE_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-deps-list.log
+ERLANG_MK_RECURSIVE_DOC_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-doc-deps-list.log
+ERLANG_MK_RECURSIVE_REL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-rel-deps-list.log
+ERLANG_MK_RECURSIVE_TEST_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-test-deps-list.log
+ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-shell-deps-list.log
+
+ERLANG_MK_QUERY_DEPS_FILE = $(ERLANG_MK_TMP)/query-deps.log
+ERLANG_MK_QUERY_DOC_DEPS_FILE = $(ERLANG_MK_TMP)/query-doc-deps.log
+ERLANG_MK_QUERY_REL_DEPS_FILE = $(ERLANG_MK_TMP)/query-rel-deps.log
+ERLANG_MK_QUERY_TEST_DEPS_FILE = $(ERLANG_MK_TMP)/query-test-deps.log
+ERLANG_MK_QUERY_SHELL_DEPS_FILE = $(ERLANG_MK_TMP)/query-shell-deps.log
+
+# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: clean-app
+
+# Configuration.
+
+ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \
+ +warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec
+COMPILE_FIRST ?=
+COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
+ERLC_EXCLUDE ?=
+ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE)))
+
+ERLC_ASN1_OPTS ?=
+
+ERLC_MIB_OPTS ?=
+COMPILE_MIB_FIRST ?=
+COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST)))
+
+# Verbosity.
+
+app_verbose_0 = @echo " APP " $(PROJECT);
+app_verbose_2 = set -x;
+app_verbose = $(app_verbose_$(V))
+
+appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
+appsrc_verbose_2 = set -x;
+appsrc_verbose = $(appsrc_verbose_$(V))
+
+makedep_verbose_0 = @echo " DEPEND" $(PROJECT).d;
+makedep_verbose_2 = set -x;
+makedep_verbose = $(makedep_verbose_$(V))
+
+erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\
+ $(filter %.erl %.core,$(?F)));
+erlc_verbose_2 = set -x;
+erlc_verbose = $(erlc_verbose_$(V))
+
+xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F));
+xyrl_verbose_2 = set -x;
+xyrl_verbose = $(xyrl_verbose_$(V))
+
+asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F));
+asn1_verbose_2 = set -x;
+asn1_verbose = $(asn1_verbose_$(V))
+
+mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F));
+mib_verbose_2 = set -x;
+mib_verbose = $(mib_verbose_$(V))
+
+ifneq ($(wildcard src/),)
+
+# Targets.
+
+app:: $(if $(wildcard ebin/test),clean) deps
+ $(verbose) $(MAKE) --no-print-directory $(PROJECT).d
+ $(verbose) $(MAKE) --no-print-directory app-build
+
+ifeq ($(wildcard src/$(PROJECT_MOD).erl),)
+define app_file
+{application, '$(PROJECT)', [
+ {description, "$(PROJECT_DESCRIPTION)"},
+ {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP),
+ {id$(comma)$(space)"$(1)"}$(comma))
+ {modules, [$(call comma_list,$(2))]},
+ {registered, []},
+ {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]},
+ {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),)
+]}.
+endef
+else
+define app_file
+{application, '$(PROJECT)', [
+ {description, "$(PROJECT_DESCRIPTION)"},
+ {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP),
+ {id$(comma)$(space)"$(1)"}$(comma))
+ {modules, [$(call comma_list,$(2))]},
+ {registered, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]},
+ {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]},
+ {mod, {$(PROJECT_MOD), []}},
+ {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),)
+]}.
+endef
+endif
+
+app-build: ebin/$(PROJECT).app
+ $(verbose) :
+
+# Source files.
+
+ALL_SRC_FILES := $(sort $(call core_find,src/,*))
+
+ERL_FILES := $(filter %.erl,$(ALL_SRC_FILES))
+CORE_FILES := $(filter %.core,$(ALL_SRC_FILES))
+
+# ASN.1 files.
+
+ifneq ($(wildcard asn1/),)
+ASN1_FILES = $(sort $(call core_find,asn1/,*.asn1))
+ERL_FILES += $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES))))
+
+define compile_asn1
+ $(verbose) mkdir -p include/
+ $(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(ERLC_ASN1_OPTS) $(1)
+ $(verbose) mv asn1/*.erl src/
+ -$(verbose) mv asn1/*.hrl include/
+ $(verbose) mv asn1/*.asn1db include/
+endef
+
+$(PROJECT).d:: $(ASN1_FILES)
+ $(if $(strip $?),$(call compile_asn1,$?))
+endif
+
+# SNMP MIB files.
+
+ifneq ($(wildcard mibs/),)
+MIB_FILES = $(sort $(call core_find,mibs/,*.mib))
+
+$(PROJECT).d:: $(COMPILE_MIB_FIRST_PATHS) $(MIB_FILES)
+ $(verbose) mkdir -p include/ priv/mibs/
+ $(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ -I priv/mibs/ $?
+ $(mib_verbose) erlc -o include/ -- $(addprefix priv/mibs/,$(patsubst %.mib,%.bin,$(notdir $?)))
+endif
+
+# Leex and Yecc files.
+
+XRL_FILES := $(filter %.xrl,$(ALL_SRC_FILES))
+XRL_ERL_FILES = $(addprefix src/,$(patsubst %.xrl,%.erl,$(notdir $(XRL_FILES))))
+ERL_FILES += $(XRL_ERL_FILES)
+
+YRL_FILES := $(filter %.yrl,$(ALL_SRC_FILES))
+YRL_ERL_FILES = $(addprefix src/,$(patsubst %.yrl,%.erl,$(notdir $(YRL_FILES))))
+ERL_FILES += $(YRL_ERL_FILES)
+
+$(PROJECT).d:: $(XRL_FILES) $(YRL_FILES)
+ $(if $(strip $?),$(xyrl_verbose) erlc -v -o src/ $(YRL_ERLC_OPTS) $?)
+
+# Erlang and Core Erlang files.
+
+define makedep.erl
+ E = ets:new(makedep, [bag]),
+ G = digraph:new([acyclic]),
+ ErlFiles = lists:usort(string:tokens("$(ERL_FILES)", " ")),
+ DepsDir = "$(call core_native_path,$(DEPS_DIR))",
+ AppsDir = "$(call core_native_path,$(APPS_DIR))",
+ DepsDirsSrc = "$(if $(wildcard $(DEPS_DIR)/*/src), $(call core_native_path,$(wildcard $(DEPS_DIR)/*/src)))",
+ DepsDirsInc = "$(if $(wildcard $(DEPS_DIR)/*/include), $(call core_native_path,$(wildcard $(DEPS_DIR)/*/include)))",
+ AppsDirsSrc = "$(if $(wildcard $(APPS_DIR)/*/src), $(call core_native_path,$(wildcard $(APPS_DIR)/*/src)))",
+ AppsDirsInc = "$(if $(wildcard $(APPS_DIR)/*/include), $(call core_native_path,$(wildcard $(APPS_DIR)/*/include)))",
+ DepsDirs = lists:usort(string:tokens(DepsDirsSrc++DepsDirsInc, " ")),
+ AppsDirs = lists:usort(string:tokens(AppsDirsSrc++AppsDirsInc, " ")),
+ Modules = [{list_to_atom(filename:basename(F, ".erl")), F} || F <- ErlFiles],
+ Add = fun (Mod, Dep) ->
+ case lists:keyfind(Dep, 1, Modules) of
+ false -> ok;
+ {_, DepFile} ->
+ {_, ModFile} = lists:keyfind(Mod, 1, Modules),
+ ets:insert(E, {ModFile, DepFile}),
+ digraph:add_vertex(G, Mod),
+ digraph:add_vertex(G, Dep),
+ digraph:add_edge(G, Mod, Dep)
+ end
+ end,
+ AddHd = fun (F, Mod, DepFile) ->
+ case file:open(DepFile, [read]) of
+ {error, enoent} ->
+ ok;
+ {ok, Fd} ->
+ {_, ModFile} = lists:keyfind(Mod, 1, Modules),
+ case ets:match(E, {ModFile, DepFile}) of
+ [] ->
+ ets:insert(E, {ModFile, DepFile}),
+ F(F, Fd, Mod,0);
+ _ -> ok
+ end
+ end
+ end,
+ SearchHrl = fun
+ F(_Hrl, []) -> {error,enoent};
+ F(Hrl, [Dir|Dirs]) ->
+ HrlF = filename:join([Dir,Hrl]),
+ case filelib:is_file(HrlF) of
+ true ->
+ {ok, HrlF};
+ false -> F(Hrl,Dirs)
+ end
+ end,
+ Attr = fun
+ (_F, Mod, behavior, Dep) ->
+ Add(Mod, Dep);
+ (_F, Mod, behaviour, Dep) ->
+ Add(Mod, Dep);
+ (_F, Mod, compile, {parse_transform, Dep}) ->
+ Add(Mod, Dep);
+ (_F, Mod, compile, Opts) when is_list(Opts) ->
+ case proplists:get_value(parse_transform, Opts) of
+ undefined -> ok;
+ Dep -> Add(Mod, Dep)
+ end;
+ (F, Mod, include, Hrl) ->
+ case SearchHrl(Hrl, ["src", "include",AppsDir,DepsDir]++AppsDirs++DepsDirs) of
+ {ok, FoundHrl} -> AddHd(F, Mod, FoundHrl);
+ {error, _} -> false
+ end;
+ (F, Mod, include_lib, Hrl) ->
+ case SearchHrl(Hrl, ["src", "include",AppsDir,DepsDir]++AppsDirs++DepsDirs) of
+ {ok, FoundHrl} -> AddHd(F, Mod, FoundHrl);
+ {error, _} -> false
+ end;
+ (F, Mod, import, {Imp, _}) ->
+ IsFile =
+ case lists:keyfind(Imp, 1, Modules) of
+ false -> false;
+ {_, FilePath} -> filelib:is_file(FilePath)
+ end,
+ case IsFile of
+ false -> ok;
+ true -> Add(Mod, Imp)
+ end;
+ (_, _, _, _) -> ok
+ end,
+ MakeDepend = fun
+ (F, Fd, Mod, StartLocation) ->
+ {ok, Filename} = file:pid2name(Fd),
+ case io:parse_erl_form(Fd, undefined, StartLocation) of
+ {ok, AbsData, EndLocation} ->
+ case AbsData of
+ {attribute, _, Key, Value} ->
+ Attr(F, Mod, Key, Value),
+ F(F, Fd, Mod, EndLocation);
+ _ -> F(F, Fd, Mod, EndLocation)
+ end;
+ {eof, _ } -> file:close(Fd);
+ {error, ErrorDescription } ->
+ file:close(Fd);
+ {error, ErrorInfo, ErrorLocation} ->
+ F(F, Fd, Mod, ErrorLocation)
+ end,
+ ok
+ end,
+ [begin
+ Mod = list_to_atom(filename:basename(F, ".erl")),
+ case file:open(F, [read]) of
+ {ok, Fd} -> MakeDepend(MakeDepend, Fd, Mod,0);
+ {error, enoent} -> ok
+ end
+ end || F <- ErlFiles],
+ Depend = sofs:to_external(sofs:relation_to_family(sofs:relation(ets:tab2list(E)))),
+ CompileFirst = [X || X <- lists:reverse(digraph_utils:topsort(G)), [] =/= digraph:in_neighbours(G, X)],
+ TargetPath = fun(Target) ->
+ case lists:keyfind(Target, 1, Modules) of
+ false -> "";
+ {_, DepFile} ->
+ DirSubname = tl(string:tokens(filename:dirname(DepFile), "/")),
+ string:join(DirSubname ++ [atom_to_list(Target)], "/")
+ end
+ end,
+ Output0 = [
+ "# Generated by Erlang.mk. Edit at your own risk!\n\n",
+ [[F, "::", [[" ", D] || D <- Deps], "; @touch \$$@\n"] || {F, Deps} <- Depend],
+ "\nCOMPILE_FIRST +=", [[" ", TargetPath(CF)] || CF <- CompileFirst], "\n"
+ ],
+ Output = case "é" of
+ [233] -> unicode:characters_to_binary(Output0);
+ _ -> Output0
+ end,
+ ok = file:write_file("$(1)", Output),
+ halt()
+endef
+
+ifeq ($(if $(NO_MAKEDEP),$(wildcard $(PROJECT).d),),)
+$(PROJECT).d:: $(ERL_FILES) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST)
+ $(makedep_verbose) $(call erlang,$(call makedep.erl,$@))
+endif
+
+ifeq ($(IS_APP)$(IS_DEP),)
+ifneq ($(words $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES)),0)
+# Rebuild everything when the Makefile changes.
+$(ERLANG_MK_TMP)/last-makefile-change: $(MAKEFILE_LIST) | $(ERLANG_MK_TMP)
+ $(verbose) if test -f $@; then \
+ touch $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES); \
+ touch -c $(PROJECT).d; \
+ fi
+ $(verbose) touch $@
+
+$(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(ERLANG_MK_TMP)/last-makefile-change
+ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change
+endif
+endif
+
+$(PROJECT).d::
+ $(verbose) :
+
+include $(wildcard $(PROJECT).d)
+
+ebin/$(PROJECT).app:: ebin/
+
+ebin/:
+ $(verbose) mkdir -p ebin/
+
+define compile_erl
+ $(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \
+ -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),$(COMPILE_FIRST_PATHS) $(1))
+endef
+
+define validate_app_file
+ case file:consult("ebin/$(PROJECT).app") of
+ {ok, _} -> halt();
+ _ -> halt(1)
+ end
+endef
+
+ebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.src)
+ $(eval FILES_TO_COMPILE := $(filter-out src/$(PROJECT).app.src,$?))
+ $(if $(strip $(FILES_TO_COMPILE)),$(call compile_erl,$(FILES_TO_COMPILE)))
+# Older git versions do not have the --first-parent flag. Do without in that case.
+ $(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null \
+ || git describe --dirty --abbrev=7 --tags --always 2>/dev/null || true))
+ $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \
+ $(filter-out $(ERLC_EXCLUDE_PATHS),$(ERL_FILES) $(CORE_FILES) $(BEAM_FILES)))))))
+ifeq ($(wildcard src/$(PROJECT).app.src),)
+ $(app_verbose) printf '$(subst %,%%,$(subst $(newline),\n,$(subst ','\'',$(call app_file,$(GITDESCRIBE),$(MODULES)))))' \
+ > ebin/$(PROJECT).app
+ $(verbose) if ! $(call erlang,$(call validate_app_file)); then \
+ echo "The .app file produced is invalid. Please verify the value of PROJECT_ENV." >&2; \
+ exit 1; \
+ fi
+else
+ $(verbose) if [ -z "$$(grep -e '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \
+ echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk documentation for instructions." >&2; \
+ exit 1; \
+ fi
+ $(appsrc_verbose) cat src/$(PROJECT).app.src \
+ | sed "s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\[\]}/{modules, \[$(call comma_list,$(MODULES))\]}/" \
+ | sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(subst /,\/,$(GITDESCRIBE))\"}/" \
+ > ebin/$(PROJECT).app
+endif
+ifneq ($(wildcard src/$(PROJECT).appup),)
+ $(verbose) cp src/$(PROJECT).appup ebin/
+endif
+
+clean:: clean-app
+
+clean-app:
+ $(gen_verbose) rm -rf $(PROJECT).d ebin/ priv/mibs/ $(XRL_ERL_FILES) $(YRL_ERL_FILES) \
+ $(addprefix include/,$(patsubst %.mib,%.hrl,$(notdir $(MIB_FILES)))) \
+ $(addprefix include/,$(patsubst %.asn1,%.hrl,$(notdir $(ASN1_FILES)))) \
+ $(addprefix include/,$(patsubst %.asn1,%.asn1db,$(notdir $(ASN1_FILES)))) \
+ $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES))))
+
+endif
+
+# Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: docs-deps
+
+# Configuration.
+
+ALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS))
+
+# Targets.
+
+$(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep))))
+
+ifneq ($(SKIP_DEPS),)
+doc-deps:
+else
+doc-deps: $(ALL_DOC_DEPS_DIRS)
+ $(verbose) set -e; for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done
+endif
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: rel-deps
+
+# Configuration.
+
+ALL_REL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(REL_DEPS))
+
+# Targets.
+
+$(foreach dep,$(REL_DEPS),$(eval $(call dep_target,$(dep))))
+
+ifneq ($(SKIP_DEPS),)
+rel-deps:
+else
+rel-deps: $(ALL_REL_DEPS_DIRS)
+ $(verbose) set -e; for dep in $(ALL_REL_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
+endif
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: test-deps test-dir test-build clean-test-dir
+
+# Configuration.
+
+TEST_DIR ?= $(CURDIR)/test
+
+ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
+
+TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
+TEST_ERLC_OPTS += -DTEST=1
+
+# Targets.
+
+$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))
+
+ifneq ($(SKIP_DEPS),)
+test-deps:
+else
+test-deps: $(ALL_TEST_DEPS_DIRS)
+ $(verbose) set -e; for dep in $(ALL_TEST_DEPS_DIRS) ; do \
+ if [ -z "$(strip $(FULL))" ] && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \
+ :; \
+ else \
+ $(MAKE) -C $$dep IS_DEP=1; \
+ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \
+ fi \
+ done
+endif
+
+ifneq ($(wildcard $(TEST_DIR)),)
+test-dir: $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build
+ @:
+
+test_erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\
+ $(filter %.erl %.core,$(notdir $(FILES_TO_COMPILE))));
+test_erlc_verbose_2 = set -x;
+test_erlc_verbose = $(test_erlc_verbose_$(V))
+
+define compile_test_erl
+ $(test_erlc_verbose) erlc -v $(TEST_ERLC_OPTS) -o $(TEST_DIR) \
+ -pa ebin/ -I include/ $(1)
+endef
+
+ERL_TEST_FILES = $(call core_find,$(TEST_DIR)/,*.erl)
+$(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build: $(ERL_TEST_FILES) $(MAKEFILE_LIST)
+ $(eval FILES_TO_COMPILE := $(if $(filter $(MAKEFILE_LIST),$?),$(filter $(ERL_TEST_FILES),$^),$?))
+ $(if $(strip $(FILES_TO_COMPILE)),$(call compile_test_erl,$(FILES_TO_COMPILE)) && touch $@)
+endif
+
+test-build:: IS_TEST=1
+test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
+test-build:: $(if $(wildcard src),$(if $(wildcard ebin/test),,clean)) $(if $(IS_APP),,deps test-deps)
+# We already compiled everything when IS_APP=1.
+ifndef IS_APP
+ifneq ($(wildcard src),)
+ $(verbose) $(MAKE) --no-print-directory $(PROJECT).d ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))"
+ $(verbose) $(MAKE) --no-print-directory app-build ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))"
+ $(gen_verbose) touch ebin/test
+endif
+ifneq ($(wildcard $(TEST_DIR)),)
+ $(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))"
+endif
+endif
+
+# Roughly the same as test-build, but when IS_APP=1.
+# We only care about compiling the current application.
+ifdef IS_APP
+test-build-app:: ERLC_OPTS=$(TEST_ERLC_OPTS)
+test-build-app:: deps test-deps
+ifneq ($(wildcard src),)
+ $(verbose) $(MAKE) --no-print-directory $(PROJECT).d ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))"
+ $(verbose) $(MAKE) --no-print-directory app-build ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))"
+ $(gen_verbose) touch ebin/test
+endif
+ifneq ($(wildcard $(TEST_DIR)),)
+ $(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))"
+endif
+endif
+
+clean:: clean-test-dir
+
+clean-test-dir:
+ifneq ($(wildcard $(TEST_DIR)/*.beam),)
+ $(gen_verbose) rm -f $(TEST_DIR)/*.beam $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build
+endif
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: rebar.config
+
+# We strip out -Werror because we don't want to fail due to
+# warnings when used as a dependency.
+
+compat_prepare_erlc_opts = $(shell echo "$1" | sed 's/, */,/g')
+
+define compat_convert_erlc_opts
+$(if $(filter-out -Werror,$1),\
+ $(if $(findstring +,$1),\
+ $(shell echo $1 | cut -b 2-)))
+endef
+
+define compat_erlc_opts_to_list
+[$(call comma_list,$(foreach o,$(call compat_prepare_erlc_opts,$1),$(call compat_convert_erlc_opts,$o)))]
+endef
+
+define compat_rebar_config
+{deps, [
+$(call comma_list,$(foreach d,$(DEPS),\
+ $(if $(filter hex,$(call dep_fetch,$d)),\
+ {$(call dep_name,$d)$(comma)"$(call dep_repo,$d)"},\
+ {$(call dep_name,$d)$(comma)".*"$(comma){git,"$(call dep_repo,$d)"$(comma)"$(call dep_commit,$d)"}})))
+]}.
+{erl_opts, $(call compat_erlc_opts_to_list,$(ERLC_OPTS))}.
+endef
+
+rebar.config:
+ $(gen_verbose) $(call core_render,compat_rebar_config,rebar.config)
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+ifeq ($(filter asciideck,$(DEPS) $(DOC_DEPS)),asciideck)
+
+.PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc-guide distclean-asciidoc-manual
+
+# Core targets.
+
+docs:: asciidoc
+
+distclean:: distclean-asciidoc-guide distclean-asciidoc-manual
+
+# Plugin-specific targets.
+
+asciidoc: asciidoc-guide asciidoc-manual
+
+# User guide.
+
+ifeq ($(wildcard doc/src/guide/book.asciidoc),)
+asciidoc-guide:
+else
+asciidoc-guide: distclean-asciidoc-guide doc-deps
+ a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf
+ a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/
+
+distclean-asciidoc-guide:
+ $(gen_verbose) rm -rf doc/html/ doc/guide.pdf
+endif
+
+# Man pages.
+
+ASCIIDOC_MANUAL_FILES := $(wildcard doc/src/manual/*.asciidoc)
+
+ifeq ($(ASCIIDOC_MANUAL_FILES),)
+asciidoc-manual:
+else
+
+# Configuration.
+
+MAN_INSTALL_PATH ?= /usr/local/share/man
+MAN_SECTIONS ?= 3 7
+MAN_PROJECT ?= $(shell echo $(PROJECT) | sed 's/^./\U&\E/')
+MAN_VERSION ?= $(PROJECT_VERSION)
+
+# Plugin-specific targets.
+
+define asciidoc2man.erl
+try
+ [begin
+ io:format(" ADOC ~s~n", [F]),
+ ok = asciideck:to_manpage(asciideck:parse_file(F), #{
+ compress => gzip,
+ outdir => filename:dirname(F),
+ extra2 => "$(MAN_PROJECT) $(MAN_VERSION)",
+ extra3 => "$(MAN_PROJECT) Function Reference"
+ })
+ end || F <- [$(shell echo $(addprefix $(comma)\",$(addsuffix \",$1)) | sed 's/^.//')]],
+ halt(0)
+catch C:E ->
+ io:format("Exception ~p:~p~nStacktrace: ~p~n", [C, E, erlang:get_stacktrace()]),
+ halt(1)
+end.
+endef
+
+asciidoc-manual:: doc-deps
+
+asciidoc-manual:: $(ASCIIDOC_MANUAL_FILES)
+ $(gen_verbose) $(call erlang,$(call asciidoc2man.erl,$?))
+ $(verbose) $(foreach s,$(MAN_SECTIONS),mkdir -p doc/man$s/ && mv doc/src/manual/*.$s.gz doc/man$s/;)
+
+install-docs:: install-asciidoc
+
+install-asciidoc: asciidoc-manual
+ $(foreach s,$(MAN_SECTIONS),\
+ mkdir -p $(MAN_INSTALL_PATH)/man$s/ && \
+ install -g `id -g` -o `id -u` -m 0644 doc/man$s/*.gz $(MAN_INSTALL_PATH)/man$s/;)
+
+distclean-asciidoc-manual:
+ $(gen_verbose) rm -rf $(addprefix doc/man,$(MAN_SECTIONS))
+endif
+endif
+
+# Copyright (c) 2014-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates
+
+# Core targets.
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "Bootstrap targets:" \
+ " bootstrap Generate a skeleton of an OTP application" \
+ " bootstrap-lib Generate a skeleton of an OTP library" \
+ " bootstrap-rel Generate the files needed to build a release" \
+ " new-app in=NAME Create a new local OTP application NAME" \
+ " new-lib in=NAME Create a new local OTP library NAME" \
+ " new t=TPL n=NAME Generate a module NAME based on the template TPL" \
+ " new t=T n=N in=APP Generate a module NAME based on the template TPL in APP" \
+ " list-templates List available templates"
+
+# Bootstrap templates.
+
+define bs_appsrc
+{application, $p, [
+ {description, ""},
+ {vsn, "0.1.0"},
+ {id, "git"},
+ {modules, []},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, {$p_app, []}},
+ {env, []}
+]}.
+endef
+
+define bs_appsrc_lib
+{application, $p, [
+ {description, ""},
+ {vsn, "0.1.0"},
+ {id, "git"},
+ {modules, []},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]}
+]}.
+endef
+
+# To prevent autocompletion issues with ZSH, we add "include erlang.mk"
+# separately during the actual bootstrap.
+define bs_Makefile
+PROJECT = $p
+PROJECT_DESCRIPTION = New project
+PROJECT_VERSION = 0.1.0
+$(if $(SP),
+# Whitespace to be used when creating files from templates.
+SP = $(SP)
+)
+endef
+
+define bs_apps_Makefile
+PROJECT = $p
+PROJECT_DESCRIPTION = New project
+PROJECT_VERSION = 0.1.0
+$(if $(SP),
+# Whitespace to be used when creating files from templates.
+SP = $(SP)
+)
+# Make sure we know where the applications are located.
+ROOT_DIR ?= $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app)
+APPS_DIR ?= ..
+DEPS_DIR ?= $(call core_relpath,$(DEPS_DIR),$(APPS_DIR)/app)
+
+include $$(ROOT_DIR)/erlang.mk
+endef
+
+define bs_app
+-module($p_app).
+-behaviour(application).
+
+-export([start/2]).
+-export([stop/1]).
+
+start(_Type, _Args) ->
+ $p_sup:start_link().
+
+stop(_State) ->
+ ok.
+endef
+
+define bs_relx_config
+{release, {$p_release, "1"}, [$p, sasl, runtime_tools]}.
+{extended_start_script, true}.
+{sys_config, "config/sys.config"}.
+{vm_args, "config/vm.args"}.
+endef
+
+define bs_sys_config
+[
+].
+endef
+
+define bs_vm_args
+-name $p@127.0.0.1
+-setcookie $p
+-heart
+endef
+
+# Normal templates.
+
+define tpl_supervisor
+-module($(n)).
+-behaviour(supervisor).
+
+-export([start_link/0]).
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ Procs = [],
+ {ok, {{one_for_one, 1, 5}, Procs}}.
+endef
+
+define tpl_gen_server
+-module($(n)).
+-behaviour(gen_server).
+
+%% API.
+-export([start_link/0]).
+
+%% gen_server.
+-export([init/1]).
+-export([handle_call/3]).
+-export([handle_cast/2]).
+-export([handle_info/2]).
+-export([terminate/2]).
+-export([code_change/3]).
+
+-record(state, {
+}).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ gen_server:start_link(?MODULE, [], []).
+
+%% gen_server.
+
+init([]) ->
+ {ok, #state{}}.
+
+handle_call(_Request, _From, State) ->
+ {reply, ignored, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+endef
+
+define tpl_module
+-module($(n)).
+-export([]).
+endef
+
+define tpl_cowboy_http
+-module($(n)).
+-behaviour(cowboy_http_handler).
+
+-export([init/3]).
+-export([handle/2]).
+-export([terminate/3]).
+
+-record(state, {
+}).
+
+init(_, Req, _Opts) ->
+ {ok, Req, #state{}}.
+
+handle(Req, State=#state{}) ->
+ {ok, Req2} = cowboy_req:reply(200, Req),
+ {ok, Req2, State}.
+
+terminate(_Reason, _Req, _State) ->
+ ok.
+endef
+
+define tpl_gen_fsm
+-module($(n)).
+-behaviour(gen_fsm).
+
+%% API.
+-export([start_link/0]).
+
+%% gen_fsm.
+-export([init/1]).
+-export([state_name/2]).
+-export([handle_event/3]).
+-export([state_name/3]).
+-export([handle_sync_event/4]).
+-export([handle_info/3]).
+-export([terminate/3]).
+-export([code_change/4]).
+
+-record(state, {
+}).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ gen_fsm:start_link(?MODULE, [], []).
+
+%% gen_fsm.
+
+init([]) ->
+ {ok, state_name, #state{}}.
+
+state_name(_Event, StateData) ->
+ {next_state, state_name, StateData}.
+
+handle_event(_Event, StateName, StateData) ->
+ {next_state, StateName, StateData}.
+
+state_name(_Event, _From, StateData) ->
+ {reply, ignored, state_name, StateData}.
+
+handle_sync_event(_Event, _From, StateName, StateData) ->
+ {reply, ignored, StateName, StateData}.
+
+handle_info(_Info, StateName, StateData) ->
+ {next_state, StateName, StateData}.
+
+terminate(_Reason, _StateName, _StateData) ->
+ ok.
+
+code_change(_OldVsn, StateName, StateData, _Extra) ->
+ {ok, StateName, StateData}.
+endef
+
+define tpl_gen_statem
+-module($(n)).
+-behaviour(gen_statem).
+
+%% API.
+-export([start_link/0]).
+
+%% gen_statem.
+-export([callback_mode/0]).
+-export([init/1]).
+-export([state_name/3]).
+-export([handle_event/4]).
+-export([terminate/3]).
+-export([code_change/4]).
+
+-record(state, {
+}).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ gen_statem:start_link(?MODULE, [], []).
+
+%% gen_statem.
+
+callback_mode() ->
+ state_functions.
+
+init([]) ->
+ {ok, state_name, #state{}}.
+
+state_name(_EventType, _EventData, StateData) ->
+ {next_state, state_name, StateData}.
+
+handle_event(_EventType, _EventData, StateName, StateData) ->
+ {next_state, StateName, StateData}.
+
+terminate(_Reason, _StateName, _StateData) ->
+ ok.
+
+code_change(_OldVsn, StateName, StateData, _Extra) ->
+ {ok, StateName, StateData}.
+endef
+
+define tpl_cowboy_loop
+-module($(n)).
+-behaviour(cowboy_loop_handler).
+
+-export([init/3]).
+-export([info/3]).
+-export([terminate/3]).
+
+-record(state, {
+}).
+
+init(_, Req, _Opts) ->
+ {loop, Req, #state{}, 5000, hibernate}.
+
+info(_Info, Req, State) ->
+ {loop, Req, State, hibernate}.
+
+terminate(_Reason, _Req, _State) ->
+ ok.
+endef
+
+define tpl_cowboy_rest
+-module($(n)).
+
+-export([init/3]).
+-export([content_types_provided/2]).
+-export([get_html/2]).
+
+init(_, _Req, _Opts) ->
+ {upgrade, protocol, cowboy_rest}.
+
+content_types_provided(Req, State) ->
+ {[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}.
+
+get_html(Req, State) ->
+ {<<"<html><body>This is REST!</body></html>">>, Req, State}.
+endef
+
+define tpl_cowboy_ws
+-module($(n)).
+-behaviour(cowboy_websocket_handler).
+
+-export([init/3]).
+-export([websocket_init/3]).
+-export([websocket_handle/3]).
+-export([websocket_info/3]).
+-export([websocket_terminate/3]).
+
+-record(state, {
+}).
+
+init(_, _, _) ->
+ {upgrade, protocol, cowboy_websocket}.
+
+websocket_init(_, Req, _Opts) ->
+ Req2 = cowboy_req:compact(Req),
+ {ok, Req2, #state{}}.
+
+websocket_handle({text, Data}, Req, State) ->
+ {reply, {text, Data}, Req, State};
+websocket_handle({binary, Data}, Req, State) ->
+ {reply, {binary, Data}, Req, State};
+websocket_handle(_Frame, Req, State) ->
+ {ok, Req, State}.
+
+websocket_info(_Info, Req, State) ->
+ {ok, Req, State}.
+
+websocket_terminate(_Reason, _Req, _State) ->
+ ok.
+endef
+
+define tpl_ranch_protocol
+-module($(n)).
+-behaviour(ranch_protocol).
+
+-export([start_link/4]).
+-export([init/4]).
+
+-type opts() :: [].
+-export_type([opts/0]).
+
+-record(state, {
+ socket :: inet:socket(),
+ transport :: module()
+}).
+
+start_link(Ref, Socket, Transport, Opts) ->
+ Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
+ {ok, Pid}.
+
+-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
+init(Ref, Socket, Transport, _Opts) ->
+ ok = ranch:accept_ack(Ref),
+ loop(#state{socket=Socket, transport=Transport}).
+
+loop(State) ->
+ loop(State).
+endef
+
+# Plugin-specific targets.
+
+ifndef WS
+ifdef SP
+WS = $(subst a,,a $(wordlist 1,$(SP),a a a a a a a a a a a a a a a a a a a a))
+else
+WS = $(tab)
+endif
+endif
+
+bootstrap:
+ifneq ($(wildcard src/),)
+ $(error Error: src/ directory already exists)
+endif
+ $(eval p := $(PROJECT))
+ $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\
+ $(error Error: Invalid characters in the application name))
+ $(eval n := $(PROJECT)_sup)
+ $(verbose) $(call core_render,bs_Makefile,Makefile)
+ $(verbose) echo "include erlang.mk" >> Makefile
+ $(verbose) mkdir src/
+ifdef LEGACY
+ $(verbose) $(call core_render,bs_appsrc,src/$(PROJECT).app.src)
+endif
+ $(verbose) $(call core_render,bs_app,src/$(PROJECT)_app.erl)
+ $(verbose) $(call core_render,tpl_supervisor,src/$(PROJECT)_sup.erl)
+
+bootstrap-lib:
+ifneq ($(wildcard src/),)
+ $(error Error: src/ directory already exists)
+endif
+ $(eval p := $(PROJECT))
+ $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\
+ $(error Error: Invalid characters in the application name))
+ $(verbose) $(call core_render,bs_Makefile,Makefile)
+ $(verbose) echo "include erlang.mk" >> Makefile
+ $(verbose) mkdir src/
+ifdef LEGACY
+ $(verbose) $(call core_render,bs_appsrc_lib,src/$(PROJECT).app.src)
+endif
+
+bootstrap-rel:
+ifneq ($(wildcard relx.config),)
+ $(error Error: relx.config already exists)
+endif
+ifneq ($(wildcard config/),)
+ $(error Error: config/ directory already exists)
+endif
+ $(eval p := $(PROJECT))
+ $(verbose) $(call core_render,bs_relx_config,relx.config)
+ $(verbose) mkdir config/
+ $(verbose) $(call core_render,bs_sys_config,config/sys.config)
+ $(verbose) $(call core_render,bs_vm_args,config/vm.args)
+
+new-app:
+ifndef in
+ $(error Usage: $(MAKE) new-app in=APP)
+endif
+ifneq ($(wildcard $(APPS_DIR)/$in),)
+ $(error Error: Application $in already exists)
+endif
+ $(eval p := $(in))
+ $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\
+ $(error Error: Invalid characters in the application name))
+ $(eval n := $(in)_sup)
+ $(verbose) mkdir -p $(APPS_DIR)/$p/src/
+ $(verbose) $(call core_render,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile)
+ifdef LEGACY
+ $(verbose) $(call core_render,bs_appsrc,$(APPS_DIR)/$p/src/$p.app.src)
+endif
+ $(verbose) $(call core_render,bs_app,$(APPS_DIR)/$p/src/$p_app.erl)
+ $(verbose) $(call core_render,tpl_supervisor,$(APPS_DIR)/$p/src/$p_sup.erl)
+
+new-lib:
+ifndef in
+ $(error Usage: $(MAKE) new-lib in=APP)
+endif
+ifneq ($(wildcard $(APPS_DIR)/$in),)
+ $(error Error: Application $in already exists)
+endif
+ $(eval p := $(in))
+ $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\
+ $(error Error: Invalid characters in the application name))
+ $(verbose) mkdir -p $(APPS_DIR)/$p/src/
+ $(verbose) $(call core_render,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile)
+ifdef LEGACY
+ $(verbose) $(call core_render,bs_appsrc_lib,$(APPS_DIR)/$p/src/$p.app.src)
+endif
+
+new:
+ifeq ($(wildcard src/)$(in),)
+ $(error Error: src/ directory does not exist)
+endif
+ifndef t
+ $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])
+endif
+ifndef n
+ $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])
+endif
+ifdef in
+ $(verbose) $(call core_render,tpl_$(t),$(APPS_DIR)/$(in)/src/$(n).erl)
+else
+ $(verbose) $(call core_render,tpl_$(t),src/$(n).erl)
+endif
+
+list-templates:
+ $(verbose) @echo Available templates:
+ $(verbose) printf " %s\n" $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))
+
+# Copyright (c) 2014-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: clean-c_src distclean-c_src-env
+
+# Configuration.
+
+C_SRC_DIR ?= $(CURDIR)/c_src
+C_SRC_ENV ?= $(C_SRC_DIR)/env.mk
+C_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT)
+C_SRC_TYPE ?= shared
+
+# System type and C compiler/flags.
+
+ifeq ($(PLATFORM),msys2)
+ C_SRC_OUTPUT_EXECUTABLE_EXTENSION ?= .exe
+ C_SRC_OUTPUT_SHARED_EXTENSION ?= .dll
+else
+ C_SRC_OUTPUT_EXECUTABLE_EXTENSION ?=
+ C_SRC_OUTPUT_SHARED_EXTENSION ?= .so
+endif
+
+ifeq ($(C_SRC_TYPE),shared)
+ C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_SHARED_EXTENSION)
+else
+ C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_EXECUTABLE_EXTENSION)
+endif
+
+ifeq ($(PLATFORM),msys2)
+# We hardcode the compiler used on MSYS2. The default CC=cc does
+# not produce working code. The "gcc" MSYS2 package also doesn't.
+ CC = /mingw64/bin/gcc
+ export CC
+ CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
+ CXXFLAGS ?= -O3 -finline-functions -Wall
+else ifeq ($(PLATFORM),darwin)
+ CC ?= cc
+ CFLAGS ?= -O3 -std=c99 -arch x86_64 -Wall -Wmissing-prototypes
+ CXXFLAGS ?= -O3 -arch x86_64 -Wall
+ LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress
+else ifeq ($(PLATFORM),freebsd)
+ CC ?= cc
+ CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
+ CXXFLAGS ?= -O3 -finline-functions -Wall
+else ifeq ($(PLATFORM),linux)
+ CC ?= gcc
+ CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
+ CXXFLAGS ?= -O3 -finline-functions -Wall
+endif
+
+ifneq ($(PLATFORM),msys2)
+ CFLAGS += -fPIC
+ CXXFLAGS += -fPIC
+endif
+
+CFLAGS += -I"$(ERTS_INCLUDE_DIR)" -I"$(ERL_INTERFACE_INCLUDE_DIR)"
+CXXFLAGS += -I"$(ERTS_INCLUDE_DIR)" -I"$(ERL_INTERFACE_INCLUDE_DIR)"
+
+LDLIBS += -L"$(ERL_INTERFACE_LIB_DIR)" -lei
+
+# Verbosity.
+
+c_verbose_0 = @echo " C " $(filter-out $(notdir $(MAKEFILE_LIST) $(C_SRC_ENV)),$(^F));
+c_verbose = $(c_verbose_$(V))
+
+cpp_verbose_0 = @echo " CPP " $(filter-out $(notdir $(MAKEFILE_LIST) $(C_SRC_ENV)),$(^F));
+cpp_verbose = $(cpp_verbose_$(V))
+
+link_verbose_0 = @echo " LD " $(@F);
+link_verbose = $(link_verbose_$(V))
+
+# Targets.
+
+ifeq ($(wildcard $(C_SRC_DIR)),)
+else ifneq ($(wildcard $(C_SRC_DIR)/Makefile),)
+app:: app-c_src
+
+test-build:: app-c_src
+
+app-c_src:
+ $(MAKE) -C $(C_SRC_DIR)
+
+clean::
+ $(MAKE) -C $(C_SRC_DIR) clean
+
+else
+
+ifeq ($(SOURCES),)
+SOURCES := $(sort $(foreach pat,*.c *.C *.cc *.cpp,$(call core_find,$(C_SRC_DIR)/,$(pat))))
+endif
+OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))
+
+COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c
+COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c
+
+app:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE)
+
+test-build:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE)
+
+$(C_SRC_OUTPUT_FILE): $(OBJECTS)
+ $(verbose) mkdir -p $(dir $@)
+ $(link_verbose) $(CC) $(OBJECTS) \
+ $(LDFLAGS) $(if $(filter $(C_SRC_TYPE),shared),-shared) $(LDLIBS) \
+ -o $(C_SRC_OUTPUT_FILE)
+
+$(OBJECTS): $(MAKEFILE_LIST) $(C_SRC_ENV)
+
+%.o: %.c
+ $(COMPILE_C) $(OUTPUT_OPTION) $<
+
+%.o: %.cc
+ $(COMPILE_CPP) $(OUTPUT_OPTION) $<
+
+%.o: %.C
+ $(COMPILE_CPP) $(OUTPUT_OPTION) $<
+
+%.o: %.cpp
+ $(COMPILE_CPP) $(OUTPUT_OPTION) $<
+
+clean:: clean-c_src
+
+clean-c_src:
+ $(gen_verbose) rm -f $(C_SRC_OUTPUT_FILE) $(OBJECTS)
+
+endif
+
+ifneq ($(wildcard $(C_SRC_DIR)),)
+ERL_ERTS_DIR = $(shell $(ERL) -eval 'io:format("~s~n", [code:lib_dir(erts)]), halt().')
+
+$(C_SRC_ENV):
+ $(verbose) $(ERL) -eval "file:write_file(\"$(call core_native_path,$(C_SRC_ENV))\", \
+ io_lib:format( \
+ \"# Generated by Erlang.mk. Edit at your own risk!~n~n\" \
+ \"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\" \
+ \"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\" \
+ \"ERL_INTERFACE_LIB_DIR ?= ~s~n\" \
+ \"ERTS_DIR ?= $(ERL_ERTS_DIR)~n\", \
+ [code:root_dir(), erlang:system_info(version), \
+ code:lib_dir(erl_interface, include), \
+ code:lib_dir(erl_interface, lib)])), \
+ halt()."
+
+distclean:: distclean-c_src-env
+
+distclean-c_src-env:
+ $(gen_verbose) rm -f $(C_SRC_ENV)
+
+-include $(C_SRC_ENV)
+
+ifneq ($(ERL_ERTS_DIR),$(ERTS_DIR))
+$(shell rm -f $(C_SRC_ENV))
+endif
+endif
+
+# Templates.
+
+define bs_c_nif
+#include "erl_nif.h"
+
+static int loads = 0;
+
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ /* Initialize private data. */
+ *priv_data = NULL;
+
+ loads++;
+
+ return 0;
+}
+
+static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
+{
+ /* Convert the private data to the new version. */
+ *priv_data = *old_priv_data;
+
+ loads++;
+
+ return 0;
+}
+
+static void unload(ErlNifEnv* env, void* priv_data)
+{
+ if (loads == 1) {
+ /* Destroy the private data. */
+ }
+
+ loads--;
+}
+
+static ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ if (enif_is_atom(env, argv[0])) {
+ return enif_make_tuple2(env,
+ enif_make_atom(env, "hello"),
+ argv[0]);
+ }
+
+ return enif_make_tuple2(env,
+ enif_make_atom(env, "error"),
+ enif_make_atom(env, "badarg"));
+}
+
+static ErlNifFunc nif_funcs[] = {
+ {"hello", 1, hello}
+};
+
+ERL_NIF_INIT($n, nif_funcs, load, NULL, upgrade, unload)
+endef
+
+define bs_erl_nif
+-module($n).
+
+-export([hello/1]).
+
+-on_load(on_load/0).
+on_load() ->
+ PrivDir = case code:priv_dir(?MODULE) of
+ {error, _} ->
+ AppPath = filename:dirname(filename:dirname(code:which(?MODULE))),
+ filename:join(AppPath, "priv");
+ Path ->
+ Path
+ end,
+ erlang:load_nif(filename:join(PrivDir, atom_to_list(?MODULE)), 0).
+
+hello(_) ->
+ erlang:nif_error({not_loaded, ?MODULE}).
+endef
+
+new-nif:
+ifneq ($(wildcard $(C_SRC_DIR)/$n.c),)
+ $(error Error: $(C_SRC_DIR)/$n.c already exists)
+endif
+ifneq ($(wildcard src/$n.erl),)
+ $(error Error: src/$n.erl already exists)
+endif
+ifndef n
+ $(error Usage: $(MAKE) new-nif n=NAME [in=APP])
+endif
+ifdef in
+ $(verbose) $(MAKE) -C $(APPS_DIR)/$(in)/ new-nif n=$n in=
+else
+ $(verbose) mkdir -p $(C_SRC_DIR) src/
+ $(verbose) $(call core_render,bs_c_nif,$(C_SRC_DIR)/$n.c)
+ $(verbose) $(call core_render,bs_erl_nif,src/$n.erl)
+endif
+
+# Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: ci ci-prepare ci-setup
+
+CI_OTP ?=
+CI_HIPE ?=
+CI_ERLLVM ?=
+
+ifeq ($(CI_VM),native)
+ERLC_OPTS += +native
+TEST_ERLC_OPTS += +native
+else ifeq ($(CI_VM),erllvm)
+ERLC_OPTS += +native +'{hipe, [to_llvm]}'
+TEST_ERLC_OPTS += +native +'{hipe, [to_llvm]}'
+endif
+
+ifeq ($(strip $(CI_OTP) $(CI_HIPE) $(CI_ERLLVM)),)
+ci::
+else
+
+ci:: $(addprefix ci-,$(CI_OTP) $(addsuffix -native,$(CI_HIPE)) $(addsuffix -erllvm,$(CI_ERLLVM)))
+
+ci-prepare: $(addprefix $(KERL_INSTALL_DIR)/,$(CI_OTP) $(addsuffix -native,$(CI_HIPE)))
+
+ci-setup::
+ $(verbose) :
+
+ci-extra::
+ $(verbose) :
+
+ci_verbose_0 = @echo " CI " $(1);
+ci_verbose = $(ci_verbose_$(V))
+
+define ci_target
+ci-$1: $(KERL_INSTALL_DIR)/$2
+ $(verbose) $(MAKE) --no-print-directory clean
+ $(ci_verbose) \
+ PATH="$(KERL_INSTALL_DIR)/$2/bin:$(PATH)" \
+ CI_OTP_RELEASE="$1" \
+ CT_OPTS="-label $1" \
+ CI_VM="$3" \
+ $(MAKE) ci-setup tests
+ $(verbose) $(MAKE) --no-print-directory ci-extra
+endef
+
+$(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp),$(otp),otp)))
+$(foreach otp,$(CI_HIPE),$(eval $(call ci_target,$(otp)-native,$(otp)-native,native)))
+$(foreach otp,$(CI_ERLLVM),$(eval $(call ci_target,$(otp)-erllvm,$(otp)-native,erllvm)))
+
+$(foreach otp,$(filter-out $(ERLANG_OTP),$(CI_OTP)),$(eval $(call kerl_otp_target,$(otp))))
+$(foreach otp,$(filter-out $(ERLANG_HIPE),$(sort $(CI_HIPE) $(CI_ERLLLVM))),$(eval $(call kerl_hipe_target,$(otp))))
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "Continuous Integration targets:" \
+ " ci Run '$(MAKE) tests' on all configured Erlang versions." \
+ "" \
+ "The CI_OTP variable must be defined with the Erlang versions" \
+ "that must be tested. For example: CI_OTP = OTP-17.3.4 OTP-17.5.3"
+
+endif
+
+# Copyright (c) 2020, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+ifdef CONCUERROR_TESTS
+
+.PHONY: concuerror distclean-concuerror
+
+# Configuration
+
+CONCUERROR_LOGS_DIR ?= $(CURDIR)/logs
+CONCUERROR_OPTS ?=
+
+# Core targets.
+
+check:: concuerror
+
+ifndef KEEP_LOGS
+distclean:: distclean-concuerror
+endif
+
+# Plugin-specific targets.
+
+$(ERLANG_MK_TMP)/Concuerror/bin/concuerror: | $(ERLANG_MK_TMP)
+ $(verbose) git clone https://github.com/parapluu/Concuerror $(ERLANG_MK_TMP)/Concuerror
+ $(verbose) $(MAKE) -C $(ERLANG_MK_TMP)/Concuerror
+
+$(CONCUERROR_LOGS_DIR):
+ $(verbose) mkdir -p $(CONCUERROR_LOGS_DIR)
+
+define concuerror_html_report
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<title>Concuerror HTML report</title>
+</head>
+<body>
+<h1>Concuerror HTML report</h1>
+<p>Generated on $(concuerror_date)</p>
+<ul>
+$(foreach t,$(concuerror_targets),<li><a href="$(t).txt">$(t)</a></li>)
+</ul>
+</body>
+</html>
+endef
+
+concuerror: $(addprefix concuerror-,$(subst :,-,$(CONCUERROR_TESTS)))
+ $(eval concuerror_date := $(shell date))
+ $(eval concuerror_targets := $^)
+ $(verbose) $(call core_render,concuerror_html_report,$(CONCUERROR_LOGS_DIR)/concuerror.html)
+
+define concuerror_target
+.PHONY: concuerror-$1-$2
+
+concuerror-$1-$2: test-build | $(ERLANG_MK_TMP)/Concuerror/bin/concuerror $(CONCUERROR_LOGS_DIR)
+ $(ERLANG_MK_TMP)/Concuerror/bin/concuerror \
+ --pa $(CURDIR)/ebin --pa $(TEST_DIR) \
+ -o $(CONCUERROR_LOGS_DIR)/concuerror-$1-$2.txt \
+ $$(CONCUERROR_OPTS) -m $1 -t $2
+endef
+
+$(foreach test,$(CONCUERROR_TESTS),$(eval $(call concuerror_target,$(firstword $(subst :, ,$(test))),$(lastword $(subst :, ,$(test))))))
+
+distclean-concuerror:
+ $(gen_verbose) rm -rf $(CONCUERROR_LOGS_DIR)
+
+endif
+
+# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: ct apps-ct distclean-ct
+
+# Configuration.
+
+CT_OPTS ?=
+
+ifneq ($(wildcard $(TEST_DIR)),)
+ifndef CT_SUITES
+CT_SUITES := $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl))))
+endif
+endif
+CT_SUITES ?=
+CT_LOGS_DIR ?= $(CURDIR)/logs
+
+# Core targets.
+
+tests:: ct
+
+ifndef KEEP_LOGS
+distclean:: distclean-ct
+endif
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "Common_test targets:" \
+ " ct Run all the common_test suites for this project" \
+ "" \
+ "All your common_test suites have their associated targets." \
+ "A suite named http_SUITE can be ran using the ct-http target."
+
+# Plugin-specific targets.
+
+CT_RUN = ct_run \
+ -no_auto_compile \
+ -noinput \
+ -pa $(CURDIR)/ebin $(TEST_DIR) \
+ -dir $(TEST_DIR) \
+ -logdir $(CT_LOGS_DIR)
+
+ifeq ($(CT_SUITES),)
+ct: $(if $(IS_APP)$(ROOT_DIR),,apps-ct)
+else
+# We do not run tests if we are in an apps/* with no test directory.
+ifneq ($(IS_APP)$(wildcard $(TEST_DIR)),1)
+ct: test-build $(if $(IS_APP)$(ROOT_DIR),,apps-ct)
+ $(verbose) mkdir -p $(CT_LOGS_DIR)
+ $(gen_verbose) $(CT_RUN) -sname ct_$(PROJECT) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS)
+endif
+endif
+
+ifneq ($(ALL_APPS_DIRS),)
+define ct_app_target
+apps-ct-$1: test-build
+ $$(MAKE) -C $1 ct IS_APP=1
+endef
+
+$(foreach app,$(ALL_APPS_DIRS),$(eval $(call ct_app_target,$(app))))
+
+apps-ct: $(addprefix apps-ct-,$(ALL_APPS_DIRS))
+endif
+
+ifdef t
+ifeq (,$(findstring :,$t))
+CT_EXTRA = -group $t
+else
+t_words = $(subst :, ,$t)
+CT_EXTRA = -group $(firstword $(t_words)) -case $(lastword $(t_words))
+endif
+else
+ifdef c
+CT_EXTRA = -case $c
+else
+CT_EXTRA =
+endif
+endif
+
+define ct_suite_target
+ct-$(1): test-build
+ $(verbose) mkdir -p $(CT_LOGS_DIR)
+ $(gen_verbose_esc) $(CT_RUN) -sname ct_$(PROJECT) -suite $(addsuffix _SUITE,$(1)) $(CT_EXTRA) $(CT_OPTS)
+endef
+
+$(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test))))
+
+distclean-ct:
+ $(gen_verbose) rm -rf $(CT_LOGS_DIR)
+
+# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: plt distclean-plt dialyze
+
+# Configuration.
+
+DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt
+export DIALYZER_PLT
+
+PLT_APPS ?=
+DIALYZER_DIRS ?= --src -r $(wildcard src) $(ALL_APPS_DIRS)
+DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs
+DIALYZER_PLT_OPTS ?=
+
+# Core targets.
+
+check:: dialyze
+
+distclean:: distclean-plt
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "Dialyzer targets:" \
+ " plt Build a PLT file for this project" \
+ " dialyze Analyze the project using Dialyzer"
+
+# Plugin-specific targets.
+
+define filter_opts.erl
+ Opts = init:get_plain_arguments(),
+ {Filtered, _} = lists:foldl(fun
+ (O, {Os, true}) -> {[O|Os], false};
+ (O = "-D", {Os, _}) -> {[O|Os], true};
+ (O = [\\$$-, \\$$D, _ | _], {Os, _}) -> {[O|Os], false};
+ (O = "-I", {Os, _}) -> {[O|Os], true};
+ (O = [\\$$-, \\$$I, _ | _], {Os, _}) -> {[O|Os], false};
+ (O = "-pa", {Os, _}) -> {[O|Os], true};
+ (_, Acc) -> Acc
+ end, {[], false}, Opts),
+ io:format("~s~n", [string:join(lists:reverse(Filtered), " ")]),
+ halt().
+endef
+
+# DIALYZER_PLT is a variable understood directly by Dialyzer.
+#
+# We append the path to erts at the end of the PLT. This works
+# because the PLT file is in the external term format and the
+# function binary_to_term/1 ignores any trailing data.
+$(DIALYZER_PLT): deps app
+ $(eval DEPS_LOG := $(shell test -f $(ERLANG_MK_TMP)/deps.log && \
+ while read p; do test -d $$p/ebin && echo $$p/ebin; done <$(ERLANG_MK_TMP)/deps.log))
+ $(verbose) dialyzer --build_plt $(DIALYZER_PLT_OPTS) --apps \
+ erts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS_LOG) || test $$? -eq 2
+ $(verbose) $(ERL) -eval 'io:format("~n~s~n", [code:lib_dir(erts)]), halt().' >> $@
+
+plt: $(DIALYZER_PLT)
+
+distclean-plt:
+ $(gen_verbose) rm -f $(DIALYZER_PLT)
+
+ifneq ($(wildcard $(DIALYZER_PLT)),)
+dialyze: $(if $(filter --src,$(DIALYZER_DIRS)),,deps app)
+ $(verbose) if ! tail -n1 $(DIALYZER_PLT) | \
+ grep -q "^`$(ERL) -eval 'io:format("~s", [code:lib_dir(erts)]), halt().'`$$"; then \
+ rm $(DIALYZER_PLT); \
+ $(MAKE) plt; \
+ fi
+else
+dialyze: $(DIALYZER_PLT)
+endif
+ $(verbose) dialyzer --no_native `$(ERL) \
+ -eval "$(subst $(newline),,$(call escape_dquotes,$(call filter_opts.erl)))" \
+ -extra $(ERLC_OPTS)` $(DIALYZER_DIRS) $(DIALYZER_OPTS) $(if $(wildcard ebin/),-pa ebin/)
+
+# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: distclean-edoc edoc
+
+# Configuration.
+
+EDOC_OPTS ?=
+EDOC_SRC_DIRS ?=
+EDOC_OUTPUT ?= doc
+
+define edoc.erl
+ SrcPaths = lists:foldl(fun(P, Acc) ->
+ filelib:wildcard(atom_to_list(P) ++ "/{src,c_src}") ++ Acc
+ end, [], [$(call comma_list,$(patsubst %,'%',$(call core_native_path,$(EDOC_SRC_DIRS))))]),
+ DefaultOpts = [{dir, "$(EDOC_OUTPUT)"}, {source_path, SrcPaths}, {subpackages, false}],
+ edoc:application($(1), ".", [$(2)] ++ DefaultOpts),
+ halt(0).
+endef
+
+# Core targets.
+
+ifneq ($(strip $(EDOC_SRC_DIRS)$(wildcard doc/overview.edoc)),)
+docs:: edoc
+endif
+
+distclean:: distclean-edoc
+
+# Plugin-specific targets.
+
+edoc: distclean-edoc doc-deps
+ $(gen_verbose) $(call erlang,$(call edoc.erl,$(PROJECT),$(EDOC_OPTS)))
+
+distclean-edoc:
+ $(gen_verbose) rm -f $(EDOC_OUTPUT)/*.css $(EDOC_OUTPUT)/*.html $(EDOC_OUTPUT)/*.png $(EDOC_OUTPUT)/edoc-info
+
+# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+# Configuration.
+
+DTL_FULL_PATH ?=
+DTL_PATH ?= templates/
+DTL_PREFIX ?=
+DTL_SUFFIX ?= _dtl
+DTL_OPTS ?=
+
+# Verbosity.
+
+dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F));
+dtl_verbose = $(dtl_verbose_$(V))
+
+# Core targets.
+
+DTL_PATH := $(abspath $(DTL_PATH))
+DTL_FILES := $(sort $(call core_find,$(DTL_PATH),*.dtl))
+
+ifneq ($(DTL_FILES),)
+
+DTL_NAMES = $(addprefix $(DTL_PREFIX),$(addsuffix $(DTL_SUFFIX),$(DTL_FILES:$(DTL_PATH)/%.dtl=%)))
+DTL_MODULES = $(if $(DTL_FULL_PATH),$(subst /,_,$(DTL_NAMES)),$(notdir $(DTL_NAMES)))
+BEAM_FILES += $(addsuffix .beam,$(addprefix ebin/,$(DTL_MODULES)))
+
+ifneq ($(words $(DTL_FILES)),0)
+# Rebuild templates when the Makefile changes.
+$(ERLANG_MK_TMP)/last-makefile-change-erlydtl: $(MAKEFILE_LIST) | $(ERLANG_MK_TMP)
+ $(verbose) if test -f $@; then \
+ touch $(DTL_FILES); \
+ fi
+ $(verbose) touch $@
+
+ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change-erlydtl
+endif
+
+define erlydtl_compile.erl
+ [begin
+ Module0 = case "$(strip $(DTL_FULL_PATH))" of
+ "" ->
+ filename:basename(F, ".dtl");
+ _ ->
+ "$(call core_native_path,$(DTL_PATH))/" ++ F2 = filename:rootname(F, ".dtl"),
+ re:replace(F2, "/", "_", [{return, list}, global])
+ end,
+ Module = list_to_atom("$(DTL_PREFIX)" ++ string:to_lower(Module0) ++ "$(DTL_SUFFIX)"),
+ case erlydtl:compile(F, Module, [$(DTL_OPTS)] ++ [{out_dir, "ebin/"}, return_errors]) of
+ ok -> ok;
+ {ok, _} -> ok
+ end
+ end || F <- string:tokens("$(1)", " ")],
+ halt().
+endef
+
+ebin/$(PROJECT).app:: $(DTL_FILES) | ebin/
+ $(if $(strip $?),\
+ $(dtl_verbose) $(call erlang,$(call erlydtl_compile.erl,$(call core_native_path,$?)),\
+ -pa ebin/))
+
+endif
+
+# Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2014, Dave Cottlehuber <dch@skunkwerks.at>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: distclean-escript escript escript-zip
+
+# Configuration.
+
+ESCRIPT_NAME ?= $(PROJECT)
+ESCRIPT_FILE ?= $(ESCRIPT_NAME)
+
+ESCRIPT_SHEBANG ?= /usr/bin/env escript
+ESCRIPT_COMMENT ?= This is an -*- erlang -*- file
+ESCRIPT_EMU_ARGS ?= -escript main $(ESCRIPT_NAME)
+
+ESCRIPT_ZIP ?= 7z a -tzip -mx=9 -mtc=off $(if $(filter-out 0,$(V)),,> /dev/null)
+ESCRIPT_ZIP_FILE ?= $(ERLANG_MK_TMP)/escript.zip
+
+# Core targets.
+
+distclean:: distclean-escript
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "Escript targets:" \
+ " escript Build an executable escript archive" \
+
+# Plugin-specific targets.
+
+escript-zip:: FULL=1
+escript-zip:: deps app
+ $(verbose) mkdir -p $(dir $(ESCRIPT_ZIP))
+ $(verbose) rm -f $(ESCRIPT_ZIP_FILE)
+ $(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(ESCRIPT_ZIP_FILE) $(PROJECT)/ebin/*
+ifneq ($(DEPS),)
+ $(verbose) cd $(DEPS_DIR) && $(ESCRIPT_ZIP) $(ESCRIPT_ZIP_FILE) \
+ $(subst $(DEPS_DIR)/,,$(addsuffix /*,$(wildcard \
+ $(addsuffix /ebin,$(shell cat $(ERLANG_MK_TMP)/deps.log)))))
+endif
+
+escript:: escript-zip
+ $(gen_verbose) printf "%s\n" \
+ "#!$(ESCRIPT_SHEBANG)" \
+ "%% $(ESCRIPT_COMMENT)" \
+ "%%! $(ESCRIPT_EMU_ARGS)" > $(ESCRIPT_FILE)
+ $(verbose) cat $(ESCRIPT_ZIP_FILE) >> $(ESCRIPT_FILE)
+ $(verbose) chmod +x $(ESCRIPT_FILE)
+
+distclean-escript:
+ $(gen_verbose) rm -f $(ESCRIPT_FILE)
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2014, Enrique Fernandez <enrique.fernandez@erlang-solutions.com>
+# This file is contributed to erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: eunit apps-eunit
+
+# Configuration
+
+EUNIT_OPTS ?=
+EUNIT_ERL_OPTS ?=
+
+# Core targets.
+
+tests:: eunit
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "EUnit targets:" \
+ " eunit Run all the EUnit tests for this project"
+
+# Plugin-specific targets.
+
+define eunit.erl
+ $(call cover.erl)
+ CoverSetup(),
+ case eunit:test($1, [$(EUNIT_OPTS)]) of
+ ok -> ok;
+ error -> halt(2)
+ end,
+ CoverExport("$(call core_native_path,$(COVER_DATA_DIR))/eunit.coverdata"),
+ halt()
+endef
+
+EUNIT_ERL_OPTS += -pa $(TEST_DIR) $(CURDIR)/ebin
+
+ifdef t
+ifeq (,$(findstring :,$(t)))
+eunit: test-build cover-data-dir
+ $(gen_verbose) $(call erlang,$(call eunit.erl,['$(t)']),$(EUNIT_ERL_OPTS))
+else
+eunit: test-build cover-data-dir
+ $(gen_verbose) $(call erlang,$(call eunit.erl,fun $(t)/0),$(EUNIT_ERL_OPTS))
+endif
+else
+EUNIT_EBIN_MODS = $(notdir $(basename $(ERL_FILES) $(BEAM_FILES)))
+EUNIT_TEST_MODS = $(notdir $(basename $(call core_find,$(TEST_DIR)/,*.erl)))
+
+EUNIT_MODS = $(foreach mod,$(EUNIT_EBIN_MODS) $(filter-out \
+ $(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(EUNIT_TEST_MODS)),'$(mod)')
+
+eunit: test-build $(if $(IS_APP)$(ROOT_DIR),,apps-eunit) cover-data-dir
+ifneq ($(wildcard src/ $(TEST_DIR)),)
+ $(gen_verbose) $(call erlang,$(call eunit.erl,[$(call comma_list,$(EUNIT_MODS))]),$(EUNIT_ERL_OPTS))
+endif
+
+ifneq ($(ALL_APPS_DIRS),)
+apps-eunit: test-build
+ $(verbose) eunit_retcode=0 ; for app in $(ALL_APPS_DIRS); do $(MAKE) -C $$app eunit IS_APP=1; \
+ [ $$? -ne 0 ] && eunit_retcode=1 ; done ; \
+ exit $$eunit_retcode
+endif
+endif
+
+# Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+ifeq ($(filter proper,$(DEPS) $(TEST_DEPS)),proper)
+.PHONY: proper
+
+# Targets.
+
+tests:: proper
+
+define proper_check.erl
+ $(call cover.erl)
+ code:add_pathsa([
+ "$(call core_native_path,$(CURDIR)/ebin)",
+ "$(call core_native_path,$(DEPS_DIR)/*/ebin)",
+ "$(call core_native_path,$(TEST_DIR))"]),
+ Module = fun(M) ->
+ [true] =:= lists:usort([
+ case atom_to_list(F) of
+ "prop_" ++ _ ->
+ io:format("Testing ~p:~p/0~n", [M, F]),
+ proper:quickcheck(M:F(), nocolors);
+ _ ->
+ true
+ end
+ || {F, 0} <- M:module_info(exports)])
+ end,
+ try begin
+ CoverSetup(),
+ Res = case $(1) of
+ all -> [true] =:= lists:usort([Module(M) || M <- [$(call comma_list,$(3))]]);
+ module -> Module($(2));
+ function -> proper:quickcheck($(2), nocolors)
+ end,
+ CoverExport("$(COVER_DATA_DIR)/proper.coverdata"),
+ Res
+ end of
+ true -> halt(0);
+ _ -> halt(1)
+ catch error:undef ->
+ io:format("Undefined property or module?~n~p~n", [erlang:get_stacktrace()]),
+ halt(0)
+ end.
+endef
+
+ifdef t
+ifeq (,$(findstring :,$(t)))
+proper: test-build cover-data-dir
+ $(verbose) $(call erlang,$(call proper_check.erl,module,$(t)))
+else
+proper: test-build cover-data-dir
+ $(verbose) echo Testing $(t)/0
+ $(verbose) $(call erlang,$(call proper_check.erl,function,$(t)()))
+endif
+else
+proper: test-build cover-data-dir
+ $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \
+ $(wildcard ebin/*.beam) $(call core_find,$(TEST_DIR)/,*.beam))))))
+ $(gen_verbose) $(call erlang,$(call proper_check.erl,all,undefined,$(MODULES)))
+endif
+endif
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+# Verbosity.
+
+proto_verbose_0 = @echo " PROTO " $(filter %.proto,$(?F));
+proto_verbose = $(proto_verbose_$(V))
+
+# Core targets.
+
+ifneq ($(wildcard src/),)
+ifneq ($(filter gpb protobuffs,$(BUILD_DEPS) $(DEPS)),)
+PROTO_FILES := $(filter %.proto,$(ALL_SRC_FILES))
+ERL_FILES += $(addprefix src/,$(patsubst %.proto,%_pb.erl,$(notdir $(PROTO_FILES))))
+
+ifeq ($(PROTO_FILES),)
+$(ERLANG_MK_TMP)/last-makefile-change-protobuffs:
+ $(verbose) :
+else
+# Rebuild proto files when the Makefile changes.
+# We exclude $(PROJECT).d to avoid a circular dependency.
+$(ERLANG_MK_TMP)/last-makefile-change-protobuffs: $(filter-out $(PROJECT).d,$(MAKEFILE_LIST)) | $(ERLANG_MK_TMP)
+ $(verbose) if test -f $@; then \
+ touch $(PROTO_FILES); \
+ fi
+ $(verbose) touch $@
+
+$(PROJECT).d:: $(ERLANG_MK_TMP)/last-makefile-change-protobuffs
+endif
+
+ifeq ($(filter gpb,$(BUILD_DEPS) $(DEPS)),)
+define compile_proto.erl
+ [begin
+ protobuffs_compile:generate_source(F, [
+ {output_include_dir, "./include"},
+ {output_src_dir, "./src"}])
+ end || F <- string:tokens("$1", " ")],
+ halt().
+endef
+else
+define compile_proto.erl
+ [begin
+ gpb_compile:file(F, [
+ {include_as_lib, true},
+ {module_name_suffix, "_pb"},
+ {o_hrl, "./include"},
+ {o_erl, "./src"}])
+ end || F <- string:tokens("$1", " ")],
+ halt().
+endef
+endif
+
+ifneq ($(PROTO_FILES),)
+$(PROJECT).d:: $(PROTO_FILES)
+ $(verbose) mkdir -p ebin/ include/
+ $(if $(strip $?),$(proto_verbose) $(call erlang,$(call compile_proto.erl,$?)))
+endif
+endif
+endif
+
+# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: relx-rel relx-relup distclean-relx-rel run
+
+# Configuration.
+
+RELX ?= $(ERLANG_MK_TMP)/relx
+RELX_CONFIG ?= $(CURDIR)/relx.config
+
+RELX_URL ?= https://erlang.mk/res/relx-v3.27.0
+RELX_OPTS ?=
+RELX_OUTPUT_DIR ?= _rel
+RELX_REL_EXT ?=
+RELX_TAR ?= 1
+
+ifdef SFX
+ RELX_TAR = 1
+endif
+
+ifeq ($(firstword $(RELX_OPTS)),-o)
+ RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS))
+else
+ RELX_OPTS += -o $(RELX_OUTPUT_DIR)
+endif
+
+# Core targets.
+
+ifeq ($(IS_DEP),)
+ifneq ($(wildcard $(RELX_CONFIG)),)
+rel:: relx-rel
+
+relup:: relx-relup
+endif
+endif
+
+distclean:: distclean-relx-rel
+
+# Plugin-specific targets.
+
+$(RELX): | $(ERLANG_MK_TMP)
+ $(gen_verbose) $(call core_http_get,$(RELX),$(RELX_URL))
+ $(verbose) chmod +x $(RELX)
+
+relx-rel: $(RELX) rel-deps app
+ $(verbose) $(RELX) $(if $(filter 1,$V),-V 3) -c $(RELX_CONFIG) $(RELX_OPTS) release
+ $(verbose) $(MAKE) relx-post-rel
+ifeq ($(RELX_TAR),1)
+ $(verbose) $(RELX) $(if $(filter 1,$V),-V 3) -c $(RELX_CONFIG) $(RELX_OPTS) tar
+endif
+
+relx-relup: $(RELX) rel-deps app
+ $(verbose) $(RELX) $(if $(filter 1,$V),-V 3) -c $(RELX_CONFIG) $(RELX_OPTS) release
+ $(MAKE) relx-post-rel
+ $(verbose) $(RELX) $(if $(filter 1,$V),-V 3) -c $(RELX_CONFIG) $(RELX_OPTS) relup $(if $(filter 1,$(RELX_TAR)),tar)
+
+distclean-relx-rel:
+ $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR)
+
+# Default hooks.
+relx-post-rel::
+ $(verbose) :
+
+# Run target.
+
+ifeq ($(wildcard $(RELX_CONFIG)),)
+run::
+else
+
+define get_relx_release.erl
+ {ok, Config} = file:consult("$(call core_native_path,$(RELX_CONFIG))"),
+ {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),
+ Vsn = case Vsn0 of
+ {cmd, Cmd} -> os:cmd(Cmd);
+ semver -> "";
+ {semver, _} -> "";
+ VsnStr -> Vsn0
+ end,
+ Extended = case lists:keyfind(extended_start_script, 1, Config) of
+ {_, true} -> "1";
+ _ -> ""
+ end,
+ io:format("~s ~s ~s", [Name, Vsn, Extended]),
+ halt(0).
+endef
+
+RELX_REL := $(shell $(call erlang,$(get_relx_release.erl)))
+RELX_REL_NAME := $(word 1,$(RELX_REL))
+RELX_REL_VSN := $(word 2,$(RELX_REL))
+RELX_REL_CMD := $(if $(word 3,$(RELX_REL)),console)
+
+ifeq ($(PLATFORM),msys2)
+RELX_REL_EXT := .cmd
+endif
+
+run:: all
+ $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) $(RELX_REL_CMD)
+
+ifdef RELOAD
+rel::
+ $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) ping
+ $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) \
+ eval "io:format(\"~p~n\", [c:lm()])"
+endif
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "Relx targets:" \
+ " run Compile the project, build the release and run it"
+
+endif
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2014, M Robert Martin <rob@version2beta.com>
+# This file is contributed to erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: shell
+
+# Configuration.
+
+SHELL_ERL ?= erl
+SHELL_PATHS ?= $(CURDIR)/ebin $(TEST_DIR)
+SHELL_OPTS ?=
+
+ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS))
+
+# Core targets
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "Shell targets:" \
+ " shell Run an erlang shell with SHELL_OPTS or reasonable default"
+
+# Plugin-specific targets.
+
+$(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep))))
+
+ifneq ($(SKIP_DEPS),)
+build-shell-deps:
+else
+build-shell-deps: $(ALL_SHELL_DEPS_DIRS)
+ $(verbose) set -e; for dep in $(ALL_SHELL_DEPS_DIRS) ; do \
+ if [ -z "$(strip $(FULL))" ] && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \
+ :; \
+ else \
+ $(MAKE) -C $$dep IS_DEP=1; \
+ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \
+ fi \
+ done
+endif
+
+shell:: build-shell-deps
+ $(gen_verbose) $(SHELL_ERL) -pa $(SHELL_PATHS) $(SHELL_OPTS)
+
+# Copyright 2017, Stanislaw Klekot <dozzie@jarowit.net>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: distclean-sphinx sphinx
+
+# Configuration.
+
+SPHINX_BUILD ?= sphinx-build
+SPHINX_SOURCE ?= doc
+SPHINX_CONFDIR ?=
+SPHINX_FORMATS ?= html
+SPHINX_DOCTREES ?= $(ERLANG_MK_TMP)/sphinx.doctrees
+SPHINX_OPTS ?=
+
+#sphinx_html_opts =
+#sphinx_html_output = html
+#sphinx_man_opts =
+#sphinx_man_output = man
+#sphinx_latex_opts =
+#sphinx_latex_output = latex
+
+# Helpers.
+
+sphinx_build_0 = @echo " SPHINX" $1; $(SPHINX_BUILD) -N -q
+sphinx_build_1 = $(SPHINX_BUILD) -N
+sphinx_build_2 = set -x; $(SPHINX_BUILD)
+sphinx_build = $(sphinx_build_$(V))
+
+define sphinx.build
+$(call sphinx_build,$1) -b $1 -d $(SPHINX_DOCTREES) $(if $(SPHINX_CONFDIR),-c $(SPHINX_CONFDIR)) $(SPHINX_OPTS) $(sphinx_$1_opts) -- $(SPHINX_SOURCE) $(call sphinx.output,$1)
+
+endef
+
+define sphinx.output
+$(if $(sphinx_$1_output),$(sphinx_$1_output),$1)
+endef
+
+# Targets.
+
+ifneq ($(wildcard $(if $(SPHINX_CONFDIR),$(SPHINX_CONFDIR),$(SPHINX_SOURCE))/conf.py),)
+docs:: sphinx
+distclean:: distclean-sphinx
+endif
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "Sphinx targets:" \
+ " sphinx Generate Sphinx documentation." \
+ "" \
+ "ReST sources and 'conf.py' file are expected in directory pointed by" \
+ "SPHINX_SOURCE ('doc' by default). SPHINX_FORMATS lists formats to build (only" \
+ "'html' format is generated by default); target directory can be specified by" \
+ 'setting sphinx_$${format}_output, for example: sphinx_html_output = output/html' \
+ "Additional Sphinx options can be set in SPHINX_OPTS."
+
+# Plugin-specific targets.
+
+sphinx:
+ $(foreach F,$(SPHINX_FORMATS),$(call sphinx.build,$F))
+
+distclean-sphinx:
+ $(gen_verbose) rm -rf $(filter-out $(SPHINX_SOURCE),$(foreach F,$(SPHINX_FORMATS),$(call sphinx.output,$F)))
+
+# Copyright (c) 2017, Jean-Sébastien Pédron <jean-sebastien@rabbitmq.com>
+# This file is contributed to erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: show-ERL_LIBS show-ERLC_OPTS show-TEST_ERLC_OPTS
+
+show-ERL_LIBS:
+ @echo $(ERL_LIBS)
+
+show-ERLC_OPTS:
+ @$(foreach opt,$(ERLC_OPTS) -pa ebin -I include,echo "$(opt)";)
+
+show-TEST_ERLC_OPTS:
+ @$(foreach opt,$(TEST_ERLC_OPTS) -pa ebin -I include,echo "$(opt)";)
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+ifeq ($(filter triq,$(DEPS) $(TEST_DEPS)),triq)
+.PHONY: triq
+
+# Targets.
+
+tests:: triq
+
+define triq_check.erl
+ $(call cover.erl)
+ code:add_pathsa([
+ "$(call core_native_path,$(CURDIR)/ebin)",
+ "$(call core_native_path,$(DEPS_DIR)/*/ebin)",
+ "$(call core_native_path,$(TEST_DIR))"]),
+ try begin
+ CoverSetup(),
+ Res = case $(1) of
+ all -> [true] =:= lists:usort([triq:check(M) || M <- [$(call comma_list,$(3))]]);
+ module -> triq:check($(2));
+ function -> triq:check($(2))
+ end,
+ CoverExport("$(COVER_DATA_DIR)/triq.coverdata"),
+ Res
+ end of
+ true -> halt(0);
+ _ -> halt(1)
+ catch error:undef ->
+ io:format("Undefined property or module?~n~p~n", [erlang:get_stacktrace()]),
+ halt(0)
+ end.
+endef
+
+ifdef t
+ifeq (,$(findstring :,$(t)))
+triq: test-build cover-data-dir
+ $(verbose) $(call erlang,$(call triq_check.erl,module,$(t)))
+else
+triq: test-build cover-data-dir
+ $(verbose) echo Testing $(t)/0
+ $(verbose) $(call erlang,$(call triq_check.erl,function,$(t)()))
+endif
+else
+triq: test-build cover-data-dir
+ $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \
+ $(wildcard ebin/*.beam) $(call core_find,$(TEST_DIR)/,*.beam))))))
+ $(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined,$(MODULES)))
+endif
+endif
+
+# Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2015, Erlang Solutions Ltd.
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: xref distclean-xref
+
+# Configuration.
+
+ifeq ($(XREF_CONFIG),)
+ XREFR_ARGS :=
+else
+ XREFR_ARGS := -c $(XREF_CONFIG)
+endif
+
+XREFR ?= $(CURDIR)/xrefr
+export XREFR
+
+XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/1.1.0/xrefr
+
+# Core targets.
+
+help::
+ $(verbose) printf '%s\n' '' \
+ 'Xref targets:' \
+ ' xref Run Xrefr using $$XREF_CONFIG as config file if defined'
+
+distclean:: distclean-xref
+
+# Plugin-specific targets.
+
+$(XREFR):
+ $(gen_verbose) $(call core_http_get,$(XREFR),$(XREFR_URL))
+ $(verbose) chmod +x $(XREFR)
+
+xref: deps app $(XREFR)
+ $(gen_verbose) $(XREFR) $(XREFR_ARGS)
+
+distclean-xref:
+ $(gen_verbose) rm -rf $(XREFR)
+
+# Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+COVER_REPORT_DIR ?= cover
+COVER_DATA_DIR ?= $(COVER_REPORT_DIR)
+
+ifdef COVER
+COVER_APPS ?= $(notdir $(ALL_APPS_DIRS))
+COVER_DEPS ?=
+endif
+
+# Code coverage for Common Test.
+
+ifdef COVER
+ifdef CT_RUN
+ifneq ($(wildcard $(TEST_DIR)),)
+test-build:: $(TEST_DIR)/ct.cover.spec
+
+$(TEST_DIR)/ct.cover.spec: cover-data-dir
+ $(gen_verbose) printf "%s\n" \
+ "{incl_app, '$(PROJECT)', details}." \
+ "{incl_dirs, '$(PROJECT)', [\"$(call core_native_path,$(CURDIR)/ebin)\" \
+ $(foreach a,$(COVER_APPS),$(comma) \"$(call core_native_path,$(APPS_DIR)/$a/ebin)\") \
+ $(foreach d,$(COVER_DEPS),$(comma) \"$(call core_native_path,$(DEPS_DIR)/$d/ebin)\")]}." \
+ '{export,"$(call core_native_path,$(abspath $(COVER_DATA_DIR))/ct.coverdata)"}.' > $@
+
+CT_RUN += -cover $(TEST_DIR)/ct.cover.spec
+endif
+endif
+endif
+
+# Code coverage for other tools.
+
+ifdef COVER
+define cover.erl
+ CoverSetup = fun() ->
+ Dirs = ["$(call core_native_path,$(CURDIR)/ebin)"
+ $(foreach a,$(COVER_APPS),$(comma) "$(call core_native_path,$(APPS_DIR)/$a/ebin)")
+ $(foreach d,$(COVER_DEPS),$(comma) "$(call core_native_path,$(DEPS_DIR)/$d/ebin)")],
+ [begin
+ case filelib:is_dir(Dir) of
+ false -> false;
+ true ->
+ case cover:compile_beam_directory(Dir) of
+ {error, _} -> halt(1);
+ _ -> true
+ end
+ end
+ end || Dir <- Dirs]
+ end,
+ CoverExport = fun(Filename) -> cover:export(Filename) end,
+endef
+else
+define cover.erl
+ CoverSetup = fun() -> ok end,
+ CoverExport = fun(_) -> ok end,
+endef
+endif
+
+# Core targets
+
+ifdef COVER
+ifneq ($(COVER_REPORT_DIR),)
+tests::
+ $(verbose) $(MAKE) --no-print-directory cover-report
+endif
+
+cover-data-dir: | $(COVER_DATA_DIR)
+
+$(COVER_DATA_DIR):
+ $(verbose) mkdir -p $(COVER_DATA_DIR)
+else
+cover-data-dir:
+endif
+
+clean:: coverdata-clean
+
+ifneq ($(COVER_REPORT_DIR),)
+distclean:: cover-report-clean
+endif
+
+help::
+ $(verbose) printf "%s\n" "" \
+ "Cover targets:" \
+ " cover-report Generate a HTML coverage report from previously collected" \
+ " cover data." \
+ " all.coverdata Merge all coverdata files into all.coverdata." \
+ "" \
+ "If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \
+ "target tests additionally generates a HTML coverage report from the combined" \
+ "coverdata files from each of these testing tools. HTML reports can be disabled" \
+ "by setting COVER_REPORT_DIR to empty."
+
+# Plugin specific targets
+
+COVERDATA = $(filter-out $(COVER_DATA_DIR)/all.coverdata,$(wildcard $(COVER_DATA_DIR)/*.coverdata))
+
+.PHONY: coverdata-clean
+coverdata-clean:
+ $(gen_verbose) rm -f $(COVER_DATA_DIR)/*.coverdata $(TEST_DIR)/ct.cover.spec
+
+# Merge all coverdata files into one.
+define cover_export.erl
+ $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
+ cover:export("$(COVER_DATA_DIR)/$@"), halt(0).
+endef
+
+all.coverdata: $(COVERDATA) cover-data-dir
+ $(gen_verbose) $(call erlang,$(cover_export.erl))
+
+# These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to
+# empty if you want the coverdata files but not the HTML report.
+ifneq ($(COVER_REPORT_DIR),)
+
+.PHONY: cover-report-clean cover-report
+
+cover-report-clean:
+ $(gen_verbose) rm -rf $(COVER_REPORT_DIR)
+ifneq ($(COVER_REPORT_DIR),$(COVER_DATA_DIR))
+ $(if $(shell ls -A $(COVER_DATA_DIR)/),,$(verbose) rmdir $(COVER_DATA_DIR))
+endif
+
+ifeq ($(COVERDATA),)
+cover-report:
+else
+
+# Modules which include eunit.hrl always contain one line without coverage
+# because eunit defines test/0 which is never called. We compensate for this.
+EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \
+ grep -H -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \
+ | sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq))
+
+define cover_report.erl
+ $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
+ Ms = cover:imported_modules(),
+ [cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M)
+ ++ ".COVER.html", [html]) || M <- Ms],
+ Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms],
+ EunitHrlMods = [$(EUNIT_HRL_MODS)],
+ Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of
+ true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report],
+ TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]),
+ TotalN = lists:sum([N || {_, {_, N}} <- Report1]),
+ Perc = fun(Y, N) -> case Y + N of 0 -> 100; S -> round(100 * Y / S) end end,
+ TotalPerc = Perc(TotalY, TotalN),
+ {ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]),
+ io:format(F, "<!DOCTYPE html><html>~n"
+ "<head><meta charset=\"UTF-8\">~n"
+ "<title>Coverage report</title></head>~n"
+ "<body>~n", []),
+ io:format(F, "<h1>Coverage</h1>~n<p>Total: ~p%</p>~n", [TotalPerc]),
+ io:format(F, "<table><tr><th>Module</th><th>Coverage</th></tr>~n", []),
+ [io:format(F, "<tr><td><a href=\"~p.COVER.html\">~p</a></td>"
+ "<td>~p%</td></tr>~n",
+ [M, M, Perc(Y, N)]) || {M, {Y, N}} <- Report1],
+ How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))",
+ Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")",
+ io:format(F, "</table>~n"
+ "<p>Generated using ~s and erlang.mk on ~s.</p>~n"
+ "</body></html>", [How, Date]),
+ halt().
+endef
+
+cover-report:
+ $(verbose) mkdir -p $(COVER_REPORT_DIR)
+ $(gen_verbose) $(call erlang,$(cover_report.erl))
+
+endif
+endif # ifneq ($(COVER_REPORT_DIR),)
+
+# Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: sfx
+
+ifdef RELX_REL
+ifdef SFX
+
+# Configuration.
+
+SFX_ARCHIVE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/$(RELX_REL_NAME)-$(RELX_REL_VSN).tar.gz
+SFX_OUTPUT_FILE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME).run
+
+# Core targets.
+
+rel:: sfx
+
+# Plugin-specific targets.
+
+define sfx_stub
+#!/bin/sh
+
+TMPDIR=`mktemp -d`
+ARCHIVE=`awk '/^__ARCHIVE_BELOW__$$/ {print NR + 1; exit 0;}' $$0`
+FILENAME=$$(basename $$0)
+REL=$${FILENAME%.*}
+
+tail -n+$$ARCHIVE $$0 | tar -xzf - -C $$TMPDIR
+
+$$TMPDIR/bin/$$REL console
+RET=$$?
+
+rm -rf $$TMPDIR
+
+exit $$RET
+
+__ARCHIVE_BELOW__
+endef
+
+sfx:
+ $(verbose) $(call core_render,sfx_stub,$(SFX_OUTPUT_FILE))
+ $(gen_verbose) cat $(SFX_ARCHIVE) >> $(SFX_OUTPUT_FILE)
+ $(verbose) chmod +x $(SFX_OUTPUT_FILE)
+
+endif
+endif
+
+# Copyright (c) 2013-2017, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+# External plugins.
+
+DEP_PLUGINS ?=
+
+$(foreach p,$(DEP_PLUGINS),\
+ $(eval $(if $(findstring /,$p),\
+ $(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\
+ $(call core_dep_plugin,$p/plugins.mk,$p))))
+
+help:: help-plugins
+
+help-plugins::
+ $(verbose) :
+
+# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2015-2016, Jean-Sébastien Pédron <jean-sebastien@rabbitmq.com>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+# Fetch dependencies recursively (without building them).
+
+.PHONY: fetch-deps fetch-doc-deps fetch-rel-deps fetch-test-deps \
+ fetch-shell-deps
+
+.PHONY: $(ERLANG_MK_RECURSIVE_DEPS_LIST) \
+ $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \
+ $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \
+ $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \
+ $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST)
+
+fetch-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST)
+fetch-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST)
+fetch-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST)
+fetch-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST)
+fetch-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST)
+
+ifneq ($(SKIP_DEPS),)
+$(ERLANG_MK_RECURSIVE_DEPS_LIST) \
+$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \
+$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \
+$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \
+$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST):
+ $(verbose) :> $@
+else
+# By default, we fetch "normal" dependencies. They are also included no
+# matter the type of requested dependencies.
+#
+# $(ALL_DEPS_DIRS) includes $(BUILD_DEPS).
+
+$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS)
+$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_DOC_DEPS_DIRS)
+$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_REL_DEPS_DIRS)
+$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_TEST_DEPS_DIRS)
+$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_SHELL_DEPS_DIRS)
+
+# Allow to use fetch-deps and $(DEP_TYPES) to fetch multiple types of
+# dependencies with a single target.
+ifneq ($(filter doc,$(DEP_TYPES)),)
+$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_DOC_DEPS_DIRS)
+endif
+ifneq ($(filter rel,$(DEP_TYPES)),)
+$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_REL_DEPS_DIRS)
+endif
+ifneq ($(filter test,$(DEP_TYPES)),)
+$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_TEST_DEPS_DIRS)
+endif
+ifneq ($(filter shell,$(DEP_TYPES)),)
+$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_SHELL_DEPS_DIRS)
+endif
+
+ERLANG_MK_RECURSIVE_TMP_LIST := $(abspath $(ERLANG_MK_TMP)/recursive-tmp-deps-$(shell echo $$PPID).log)
+
+$(ERLANG_MK_RECURSIVE_DEPS_LIST) \
+$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \
+$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \
+$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \
+$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): | $(ERLANG_MK_TMP)
+ifeq ($(IS_APP)$(IS_DEP),)
+ $(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST)
+endif
+ $(verbose) touch $(ERLANG_MK_RECURSIVE_TMP_LIST)
+ $(verbose) set -e; for dep in $^ ; do \
+ if ! grep -qs ^$$dep$$ $(ERLANG_MK_RECURSIVE_TMP_LIST); then \
+ echo $$dep >> $(ERLANG_MK_RECURSIVE_TMP_LIST); \
+ if grep -qs -E "^[[:blank:]]*include[[:blank:]]+(erlang\.mk|.*/erlang\.mk|.*ERLANG_MK_FILENAME.*)$$" \
+ $$dep/GNUmakefile $$dep/makefile $$dep/Makefile; then \
+ $(MAKE) -C $$dep fetch-deps \
+ IS_DEP=1 \
+ ERLANG_MK_RECURSIVE_TMP_LIST=$(ERLANG_MK_RECURSIVE_TMP_LIST); \
+ fi \
+ fi \
+ done
+ifeq ($(IS_APP)$(IS_DEP),)
+ $(verbose) sort < $(ERLANG_MK_RECURSIVE_TMP_LIST) | \
+ uniq > $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted
+ $(verbose) cmp -s $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@ \
+ || mv $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@
+ $(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted
+ $(verbose) rm $(ERLANG_MK_RECURSIVE_TMP_LIST)
+endif
+endif # ifneq ($(SKIP_DEPS),)
+
+# List dependencies recursively.
+
+.PHONY: list-deps list-doc-deps list-rel-deps list-test-deps \
+ list-shell-deps
+
+list-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST)
+list-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST)
+list-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST)
+list-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST)
+list-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST)
+
+list-deps list-doc-deps list-rel-deps list-test-deps list-shell-deps:
+ $(verbose) cat $^
+
+# Query dependencies recursively.
+
+.PHONY: query-deps query-doc-deps query-rel-deps query-test-deps \
+ query-shell-deps
+
+QUERY ?= name fetch_method repo version
+
+define query_target
+$(1): $(2) clean-tmp-query.log
+ifeq ($(IS_APP)$(IS_DEP),)
+ $(verbose) rm -f $(4)
+endif
+ $(verbose) $(foreach dep,$(3),\
+ echo $(PROJECT): $(foreach q,$(QUERY),$(call query_$(q),$(dep))) >> $(4) ;)
+ $(if $(filter-out query-deps,$(1)),,\
+ $(verbose) set -e; for dep in $(3) ; do \
+ if grep -qs ^$$$$dep$$$$ $(ERLANG_MK_TMP)/query.log; then \
+ :; \
+ else \
+ echo $$$$dep >> $(ERLANG_MK_TMP)/query.log; \
+ $(MAKE) -C $(DEPS_DIR)/$$$$dep $$@ QUERY="$(QUERY)" IS_DEP=1 || true; \
+ fi \
+ done)
+ifeq ($(IS_APP)$(IS_DEP),)
+ $(verbose) touch $(4)
+ $(verbose) cat $(4)
+endif
+endef
+
+clean-tmp-query.log:
+ifeq ($(IS_DEP),)
+ $(verbose) rm -f $(ERLANG_MK_TMP)/query.log
+endif
+
+$(eval $(call query_target,query-deps,$(ERLANG_MK_RECURSIVE_DEPS_LIST),$(BUILD_DEPS) $(DEPS),$(ERLANG_MK_QUERY_DEPS_FILE)))
+$(eval $(call query_target,query-doc-deps,$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST),$(DOC_DEPS),$(ERLANG_MK_QUERY_DOC_DEPS_FILE)))
+$(eval $(call query_target,query-rel-deps,$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST),$(REL_DEPS),$(ERLANG_MK_QUERY_REL_DEPS_FILE)))
+$(eval $(call query_target,query-test-deps,$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST),$(TEST_DEPS),$(ERLANG_MK_QUERY_TEST_DEPS_FILE)))
+$(eval $(call query_target,query-shell-deps,$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST),$(SHELL_DEPS),$(ERLANG_MK_QUERY_SHELL_DEPS_FILE)))
diff --git a/deps/rabbitmq_management/include/rabbit_mgmt.hrl b/deps/rabbitmq_management/include/rabbit_mgmt.hrl
new file mode 100644
index 0000000000..fc07271dbe
--- /dev/null
+++ b/deps/rabbitmq_management/include/rabbit_mgmt.hrl
@@ -0,0 +1,19 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License at
+%% https://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+%% License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Original Code is RabbitMQ Management Console.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-define(AUTH_REALM, "Basic realm=\"RabbitMQ Management\"").
+
+-define(HEALTH_CHECK_FAILURE_STATUS, 503).
diff --git a/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema
new file mode 100644
index 0000000000..61ec859162
--- /dev/null
+++ b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema
@@ -0,0 +1,447 @@
+%% ----------------------------------------------------------------------------
+%% RabbitMQ Management Plugin
+%%
+%% See https://www.rabbitmq.com/management.html for details
+%% ----------------------------------------------------------------------------
+
+%% Load definitions from a JSON file or directory of files. See
+%% https://www.rabbitmq.com/management.html#load-definitions
+%%
+%% {load_definitions, "/path/to/schema.json"},
+%% {load_definitions, "/path/to/schemas"},
+{mapping, "management.load_definitions", "rabbitmq_management.load_definitions",
+ [{datatype, string},
+ {validators, ["file_accessible"]}]}.
+
+%% Log all requests to the management HTTP API to a file.
+%%
+%% {http_log_dir, "/path/to/access.log"},
+
+{mapping, "management.http_log_dir", "rabbitmq_management.http_log_dir",
+ [{datatype, string}]}.
+
+%% HTTP (TCP) listener options ========================================================
+
+%% HTTP listener consistent with Web STOMP and Web MQTT.
+%%
+%% {tcp_config, [{port, 15672},
+%% {ip, "127.0.0.1"}]}
+
+{mapping, "management.tcp.port", "rabbitmq_management.tcp_config.port",
+ [{datatype, integer}]}.
+{mapping, "management.tcp.ip", "rabbitmq_management.tcp_config.ip",
+ [{datatype, string},
+ {validators, ["is_ip"]}]}.
+
+{mapping, "management.tcp.compress", "rabbitmq_management.tcp_config.cowboy_opts.compress",
+ [{datatype, {enum, [true, false]}}]}.
+{mapping, "management.tcp.idle_timeout", "rabbitmq_management.tcp_config.cowboy_opts.idle_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+{mapping, "management.tcp.inactivity_timeout", "rabbitmq_management.tcp_config.cowboy_opts.inactivity_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+{mapping, "management.tcp.request_timeout", "rabbitmq_management.tcp_config.cowboy_opts.request_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+{mapping, "management.tcp.shutdown_timeout", "rabbitmq_management.tcp_config.cowboy_opts.shutdown_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+{mapping, "management.tcp.max_keepalive", "rabbitmq_management.tcp_config.cowboy_opts.max_keepalive",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+
+
+%% HTTPS (TLS) listener options ========================================================
+
+%% HTTPS listener consistent with Web STOMP and Web MQTT.
+%%
+%% {ssl_config, [{port, 15671},
+%% {ip, "127.0.0.1"},
+%% {cacertfile, "/path/to/cacert.pem"},
+%% {certfile, "/path/to/cert.pem"},
+%% {keyfile, "/path/to/key.pem"}]}
+
+{mapping, "management.ssl.port", "rabbitmq_management.ssl_config.port",
+ [{datatype, integer}]}.
+{mapping, "management.ssl.backlog", "rabbitmq_management.ssl_config.backlog",
+ [{datatype, integer}]}.
+{mapping, "management.ssl.ip", "rabbitmq_management.ssl_config.ip",
+ [{datatype, string}, {validators, ["is_ip"]}]}.
+{mapping, "management.ssl.certfile", "rabbitmq_management.ssl_config.certfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+{mapping, "management.ssl.keyfile", "rabbitmq_management.ssl_config.keyfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+{mapping, "management.ssl.cacertfile", "rabbitmq_management.ssl_config.cacertfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+{mapping, "management.ssl.password", "rabbitmq_management.ssl_config.password",
+ [{datatype, string}]}.
+
+{mapping, "management.ssl.verify", "rabbitmq_management.ssl_config.verify", [
+ {datatype, {enum, [verify_peer, verify_none]}}]}.
+
+{mapping, "management.ssl.fail_if_no_peer_cert", "rabbitmq_management.ssl_config.fail_if_no_peer_cert", [
+ {datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.ssl.honor_cipher_order", "rabbitmq_management.ssl_config.honor_cipher_order",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.ssl.honor_ecc_order", "rabbitmq_management.ssl_config.honor_ecc_order",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.ssl.reuse_sessions", "rabbitmq_management.ssl_config.reuse_sessions",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.ssl.secure_renegotiate", "rabbitmq_management.ssl_config.secure_renegotiate",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.ssl.client_renegotiation", "rabbitmq_management.ssl_config.client_renegotiation",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.ssl.depth", "rabbitmq_management.ssl_config.depth",
+ [{datatype, integer}, {validators, ["byte"]}]}.
+
+{mapping, "management.ssl.versions.$version", "rabbitmq_management.ssl_config.versions",
+ [{datatype, atom}]}.
+
+{translation, "rabbitmq_management.ssl_config.versions",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("management.ssl.versions", Conf),
+ [V || {_, V} <- Settings]
+end}.
+
+{mapping, "management.ssl.ciphers.$cipher", "rabbitmq_management.ssl_config.ciphers",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_management.ssl_config.ciphers",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("management.ssl.ciphers", Conf),
+ lists:reverse([V || {_, V} <- Settings])
+end}.
+
+{mapping, "management.ssl.compress", "rabbitmq_management.ssl_config.cowboy_opts.compress",
+ [{datatype, {enum, [true, false]}}]}.
+{mapping, "management.ssl.idle_timeout", "rabbitmq_management.ssl_config.cowboy_opts.idle_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+{mapping, "management.ssl.inactivity_timeout", "rabbitmq_management.ssl_config.cowboy_opts.inactivity_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+{mapping, "management.ssl.request_timeout", "rabbitmq_management.ssl_config.cowboy_opts.request_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+{mapping, "management.ssl.shutdown_timeout", "rabbitmq_management.ssl_config.cowboy_opts.shutdown_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+{mapping, "management.ssl.max_keepalive", "rabbitmq_management.ssl_config.cowboy_opts.max_keepalive",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+
+
+
+%% Legacy listener options ========================================================
+
+%% Legacy (pre-3.7.9) TCP listener format.
+%%
+%% {listener, [{port, 12345},
+%% {ip, "127.0.0.1"},
+%% {ssl, true},
+%% {ssl_opts, [{cacertfile, "/path/to/cacert.pem"},
+%% {certfile, "/path/to/cert.pem"},
+%% {keyfile, "/path/to/key.pem"}]}]},
+
+{mapping, "management.listener.port", "rabbitmq_management.listener.port",
+ [{datatype, integer}]}.
+
+{mapping, "management.listener.ip", "rabbitmq_management.listener.ip",
+ [{datatype, string},
+ {validators, ["is_ip"]}]}.
+
+{mapping, "management.listener.ssl", "rabbitmq_management.listener.ssl",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.server.compress", "rabbitmq_management.listener.cowboy_opts.compress",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.server.idle_timeout", "rabbitmq_management.listener.cowboy_opts.idle_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+
+{mapping, "management.listener.server.inactivity_timeout", "rabbitmq_management.listener.cowboy_opts.inactivity_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+
+{mapping, "management.listener.server.request_timeout", "rabbitmq_management.listener.cowboy_opts.request_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+
+{mapping, "management.listener.server.shutdown_timeout", "rabbitmq_management.listener.cowboy_opts.shutdown_timeout",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+
+{mapping, "management.listener.server.max_keepalive", "rabbitmq_management.listener.cowboy_opts.max_keepalive",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+
+%% Legacy HTTPS listener options ========================================================
+
+{mapping, "management.listener.ssl_opts", "rabbitmq_management.listener.ssl_opts", [
+ {datatype, {enum, [none]}}
+]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts",
+fun(Conf) ->
+ case cuttlefish:conf_get("management.listener.ssl_opts", Conf, undefined) of
+ none -> [];
+ _ -> cuttlefish:invalid("Invalid management.listener.ssl_opts")
+ end
+end}.
+
+{mapping, "management.listener.ssl_opts.verify", "rabbitmq_management.listener.ssl_opts.verify", [
+ {datatype, {enum, [verify_peer, verify_none]}}]}.
+
+{mapping, "management.listener.ssl_opts.fail_if_no_peer_cert", "rabbitmq_management.listener.ssl_opts.fail_if_no_peer_cert", [
+ {datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.cacertfile", "rabbitmq_management.listener.ssl_opts.cacertfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "management.listener.ssl_opts.certfile", "rabbitmq_management.listener.ssl_opts.certfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "management.listener.ssl_opts.cacerts.$name", "rabbitmq_management.listener.ssl_opts.cacerts",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.cacerts",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("management.listener.ssl_opts.cacerts", Conf),
+ [ list_to_binary(V) || {_, V} <- Settings ]
+end}.
+
+{mapping, "management.listener.ssl_opts.honor_cipher_order", "rabbitmq_management.listener.ssl_opts.honor_cipher_order",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.honor_ecc_order", "rabbitmq_management.listener.ssl_opts.honor_ecc_order",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.reuse_sessions", "rabbitmq_management.listener.ssl_opts.reuse_sessions",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.secure_renegotiate", "rabbitmq_management.listener.ssl_opts.secure_renegotiate",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.client_renegotiation", "rabbitmq_management.listener.ssl_opts.client_renegotiation",
+ [{datatype, {enum, [true, false]}}]}.
+
+
+{mapping, "management.listener.ssl_opts.versions.$version", "rabbitmq_management.listener.ssl_opts.versions",
+ [{datatype, atom}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.versions",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("management.listener.ssl_opts.versions", Conf),
+ [ V || {_, V} <- Settings ]
+end}.
+
+
+{mapping, "management.listener.ssl_opts.cert", "rabbitmq_management.listener.ssl_opts.cert",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.cert",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("management.listener.ssl_opts.cert", Conf))
+end}.
+
+{mapping, "management.listener.ssl_opts.crl_check", "rabbitmq_management.listener.ssl_opts.crl_check",
+ [{datatype, [{enum, [true, false, peer, best_effort]}]}]}.
+
+{mapping, "management.listener.ssl_opts.depth", "rabbitmq_management.listener.ssl_opts.depth",
+ [{datatype, integer}, {validators, ["byte"]}]}.
+
+{mapping, "management.listener.ssl_opts.dh", "rabbitmq_management.listener.ssl_opts.dh",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.dh",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("management.listener.ssl_opts.dh", Conf))
+end}.
+
+{mapping, "management.listener.ssl_opts.dhfile", "rabbitmq_management.listener.ssl_opts.dhfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "management.listener.ssl_opts.key.RSAPrivateKey", "rabbitmq_management.listener.ssl_opts.key",
+ [{datatype, string}]}.
+
+{mapping, "management.listener.ssl_opts.key.DSAPrivateKey", "rabbitmq_management.listener.ssl_opts.key",
+ [{datatype, string}]}.
+
+{mapping, "management.listener.ssl_opts.key.PrivateKeyInfo", "rabbitmq_management.listener.ssl_opts.key",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.key",
+fun(Conf) ->
+ case cuttlefish_variable:filter_by_prefix("management.listener.ssl_opts.key", Conf) of
+ [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)};
+ _ -> undefined
+ end
+end}.
+
+{mapping, "management.listener.ssl_opts.keyfile", "rabbitmq_management.listener.ssl_opts.keyfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "management.listener.ssl_opts.log_alert", "rabbitmq_management.listener.ssl_opts.log_alert",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.password", "rabbitmq_management.listener.ssl_opts.password",
+ [{datatype, string}]}.
+
+{mapping, "management.listener.ssl_opts.psk_identity", "rabbitmq_management.listener.ssl_opts.psk_identity",
+ [{datatype, string}]}.
+
+
+%% A custom path prefix for all HTTP request handlers.
+%%
+%% {path_prefix, "/a/prefix"},
+
+{mapping, "management.path_prefix", "rabbitmq_management.path_prefix",
+ [{datatype, string}]}.
+
+%% Login session timeout in minutes
+
+{mapping, "management.login_session_timeout", "rabbitmq_management.login_session_timeout", [
+ {datatype, integer}, {validators, ["non_negative_integer"]}
+]}.
+
+%%
+%% Inter-node query result caching
+%%
+
+{mapping, "management.db_cache_multiplier", "rabbitmq_management.management_db_cache_multiplier", [
+ {datatype, integer}, {validators, ["non_negative_integer"]}
+]}.
+
+
+%%
+%% CORS
+%%
+
+{mapping, "management.cors.allow_origins", "rabbitmq_management.cors_allow_origins", [
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "management.cors.allow_origins.$name", "rabbitmq_management.cors_allow_origins", [
+ {datatype, string}
+]}.
+
+{translation, "rabbitmq_management.cors_allow_origins",
+fun(Conf) ->
+ case cuttlefish:conf_get("management.cors.allow_origins", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("management.cors.allow_origins", Conf),
+ [V || {_, V} <- Settings]
+ end
+end}.
+
+
+{mapping, "management.cors.max_age", "rabbitmq_management.cors_max_age", [
+ {datatype, integer}, {validators, ["non_negative_integer"]}
+]}.
+
+{translation, "rabbitmq_management.cors_max_age",
+fun(Conf) ->
+ case cuttlefish:conf_get("management.cors.max_age", Conf, undefined) of
+ undefined -> cuttlefish:unset();
+ Value -> Value
+ end
+end}.
+
+
+%% CSP (https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
+
+{mapping, "management.csp.policy", "rabbitmq_management.content_security_policy", [
+ {datatype, string}
+]}.
+
+{translation, "rabbitmq_management.content_security_policy",
+fun(Conf) ->
+ case cuttlefish:conf_get("management.csp.policy", Conf, undefined) of
+ undefined -> cuttlefish:unset();
+ Value -> Value
+ end
+end}.
+
+
+%% HSTS (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)
+
+{mapping, "management.hsts.policy", "rabbitmq_management.strict_transport_security", [
+ {datatype, string}
+]}.
+
+{translation, "rabbitmq_management.strict_transport_security",
+fun(Conf) ->
+ case cuttlefish:conf_get("management.hsts.policy", Conf, undefined) of
+ undefined -> cuttlefish:unset();
+ Value -> Value
+ end
+end}.
+
+%% OAuth 2/SSO access only
+
+{mapping, "management.disable_basic_auth", "rabbitmq_management.disable_basic_auth",
+ [{datatype, {enum, [true, false]}}]}.
+
+%% Management only
+
+{mapping, "management.disable_stats", "rabbitmq_management.disable_management_stats", [
+ {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "management.enable_queue_totals", "rabbitmq_management.enable_queue_totals", [
+ {datatype, {enum, [true, false]}}]}.
+
+%% ===========================================================================
+%% Authorization
+
+{mapping, "management.enable_uaa", "rabbitmq_management.enable_uaa",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.uaa_client_id", "rabbitmq_management.uaa_client_id",
+ [{datatype, string}]}.
+
+{mapping, "management.uaa_location", "rabbitmq_management.uaa_location",
+ [{datatype, string}]}.
+
+%% ===========================================================================
+
+
+%% One of 'basic', 'detailed' or 'none'. See
+%% https://www.rabbitmq.com/management.html#fine-stats for more details.
+%% {rates_mode, basic},
+{mapping, "management.rates_mode", "rabbitmq_management.rates_mode",
+ [{datatype, {enum, [basic, detailed, none]}}]}.
+
+%% Configure how long aggregated data (such as message rates and queue
+%% lengths) is retained. Please read the plugin's documentation in
+%% https://www.rabbitmq.com/management.html#configuration for more
+%% details.
+%%
+%% {sample_retention_policies,
+%% [{global, [{60, 5}, {3600, 60}, {86400, 1200}]},
+%% {basic, [{60, 5}, {3600, 60}]},
+%% {detailed, [{10, 5}]}]}
+% ]},
+
+{mapping, "management.sample_retention_policies.$section.$interval",
+ "rabbitmq_management.sample_retention_policies",
+ [{datatype, integer}]}.
+
+{translation, "rabbitmq_management.sample_retention_policies",
+fun(Conf) ->
+ Global = cuttlefish_variable:filter_by_prefix("management.sample_retention_policies.global", Conf),
+ Basic = cuttlefish_variable:filter_by_prefix("management.sample_retention_policies.basic", Conf),
+ Detailed = cuttlefish_variable:filter_by_prefix("management.sample_retention_policies.detailed", Conf),
+ TranslateKey = fun("minute") -> 60;
+ ("hour") -> 3600;
+ ("day") -> 86400;
+ (Other) -> list_to_integer(Other)
+ end,
+ TranslatePolicy = fun(Section) ->
+ [ {TranslateKey(Key), Val} || {[_,_,_,Key], Val} <- Section ]
+ end,
+ [{global, TranslatePolicy(Global)},
+ {basic, TranslatePolicy(Basic)},
+ {detailed, TranslatePolicy(Detailed)}]
+end}.
+
+
+{validator, "is_dir", "is not directory",
+fun(File) ->
+ ReadFile = file:list_dir(File),
+ element(1, ReadFile) == ok
+end}.
diff --git a/deps/rabbitmq_management/priv/www/api/index.html b/deps/rabbitmq_management/priv/www/api/index.html
new file mode 100644
index 0000000000..a6e4921544
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/api/index.html
@@ -0,0 +1,2093 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>RabbitMQ Management HTTP API</title>
+ <style>
+ body { font: 12px Verdana,sans-serif; color: #444; padding: 8px 35px; }
+ td, th { font: 12px Verdana,sans-serif; color: #444; }
+ h1 { font-size: 2em; }
+ h2 { font-size: 1.5em; }
+ td.path { font-family: monospace; }
+ th { font-size 1em; font-weight: bold; }
+ table { border-collapse: collapse; }
+ table th, table td { vertical-align: top; border: 1px solid #bbb; padding: 5px; }
+ code { background: #ffa; }
+ pre { background: black; color: #0f0; padding: 10px; word-wrap: break-word;}
+ table pre { background: #ffa; color: black; }
+ </style>
+ </head>
+ <body>
+ <h1>RabbitMQ Management HTTP API</h1>
+
+ <h2>Introduction</h2>
+
+ <p>Apart from this help page, all URIs will serve only resources
+ of type <code>application/json</code>, and will require HTTP basic
+ authentication (using the standard RabbitMQ user database). The
+ default user is guest/guest.</p>
+
+ <p>Many URIs require the name of a virtual host as part of the
+ path, since names only uniquely identify objects within a virtual
+ host. As the default virtual host is called "<code>/</code>", this
+ will need to be encoded as "<code>%2F</code>".</p>
+
+ <p>PUTing a resource creates it. The JSON object you upload must
+ have certain mandatory keys (documented below) and may have
+ optional keys. Other keys are ignored. Missing mandatory keys
+ constitute an error.</p>
+
+ <p>Since bindings do not have names or IDs in AMQP we synthesise
+ one based on all its properties. Since predicting this name is
+ hard in the general case, you can also create bindings by POSTing
+ to a factory URI. See the example below.</p>
+
+ <p>Many URIs return lists. Such URIs can have the query string
+ parameters <code>sort</code> and <code>sort_reverse</code>
+ added. <code>sort</code> allows you to select a primary field to
+ sort by, and <code>sort_reverse</code> will reverse the sort order
+ if set to <code>true</code>. The <code>sort</code> parameter can
+ contain subfields separated by dots. This allows you to sort by a
+ nested component of the listed items; it does not allow you to
+ sort by more than one field. See the example below.</p>
+
+ <p>You can also restrict what information is returned per item
+ with the <code>columns</code> parameter. This is a comma-separated
+ list of subfields separated by dots. See the example below.</p>
+
+ <p>Most of the GET queries return many fields per
+ object. The second part of this guide covers those.</p>
+
+ <h2>Examples</h2>
+
+ <p>A few quick examples for Windows and Unix, using the command line
+ tool <code>curl</code>:</p>
+
+ <ul>
+ <li>
+ Get a list of vhosts:
+<pre>:: Windows
+C:\&gt; curl -i -u guest:guest http://localhost:15672/api/vhosts
+
+# Unix
+$ curl -i -u guest:guest http://localhost:15672/api/vhosts
+
+HTTP/1.1 200 OK
+cache-control: no-cache
+content-length: 196
+content-security-policy: default-src 'self'
+content-type: application/json
+date: Mon, 02 Sep 2019 07:51:49 GMT
+server: Cowboy
+vary: accept, accept-encoding, origin
+
+[{"cluster_state":{"rabbit@localhost":"running"},"description":"Default virtual host" <i>... (remainder elided)</i></pre>
+ </li>
+ <li>
+ Get a list of channels, fast publishers first, restricting the info
+ items we get back:
+<pre>:: Windows
+C:\&gt; curl -i -u guest:guest "http://localhost:15672/api/channels?sort=message_stats.publish_details.rate&amp;sort_reverse=true&amp;columns=name,message_stats.publish_details.rate,message_stats.deliver_get_details.rate"
+
+# Unix
+$ curl -i -u guest:guest 'http://localhost:15672/api/channels?sort=message_stats.publish_details.rate&amp;sort_reverse=true&amp;columns=name,message_stats.publish_details.rate,message_stats.deliver_get_details.rate'
+
+HTTP/1.1 200 OK
+cache-control: no-cache
+content-length: 2
+content-security-policy: default-src 'self'
+content-type: application/json
+date: Mon, 02 Sep 2019 07:54:35 GMT
+server: Cowboy
+vary: accept, accept-encoding, origin
+
+[{"message_stats":{"publish_details":{"rate" <i>... (remainder elided)</i></pre>
+ </li>
+ <li>
+ Create a new vhost:
+<pre>:: Windows
+C:\&gt; curl -i -u guest:guest -H "content-type:application/json" ^
+ -XPUT http://localhost:15672/api/vhosts/foo
+
+# Unix
+$ curl -i -u guest:guest -H "content-type:application/json" \
+ -XPUT http://localhost:15672/api/vhosts/foo
+
+HTTP/1.1 201 Created
+content-length: 0
+content-security-policy: default-src 'self'
+date: Mon, 02 Sep 2019 07:55:24 GMT
+server: Cowboy
+vary: accept, accept-encoding, origin</pre>
+ <p>Note: you must specify <code>application/json</code> as the
+ mime type.</p>
+ <p>Note: the name of the object is not needed in the JSON
+ object uploaded, since it is in the URI. As a virtual host
+ has no properties apart from its name, this means you do not
+ need to specify a body at all!</p>
+ </li>
+ <li>
+ Create a new exchange in the default virtual host:
+<pre>:: Windows
+C:\&gt; curl -i -u guest:guest -H "content-type:application/json" ^
+ -XPUT -d"{""type"":""direct"",""durable"":true}" ^
+ http://localhost:15672/api/exchanges/%2F/my-new-exchange
+
+# Unix
+$ curl -i -u guest:guest -H "content-type:application/json" \
+ -XPUT -d'{"type":"direct","durable":true}' \
+ http://localhost:15672/api/exchanges/%2F/my-new-exchange
+
+HTTP/1.1 201 Created
+content-length: 0
+content-security-policy: default-src 'self'
+date: Mon, 02 Sep 2019 07:56:06 GMT
+server: Cowboy
+vary: accept, accept-encoding, origin</pre>
+ <p>Note: we never return a body in response to a PUT or
+ DELETE, unless it fails.</p>
+ </li>
+ <li>
+ And delete it again:
+<pre>:: Windows
+C:\&gt; curl -i -u guest:guest -H "content-type:application/json" ^
+ -XDELETE http://localhost:15672/api/exchanges/%2F/my-new-exchange
+
+# Unix
+$ curl -i -u guest:guest -H "content-type:application/json" \
+ -XDELETE http://localhost:15672/api/exchanges/%2F/my-new-exchange
+
+HTTP/1.1 204 No Content
+content-security-policy: default-src 'self'
+date: Mon, 02 Sep 2019 07:56:59 GMT
+server: Cowboy
+vary: accept, accept-encoding, origin</pre>
+ </li>
+ </ul>
+
+ <h2>Reference</h2>
+
+ <table>
+ <tr>
+ <th>GET</th>
+ <th>PUT</th>
+ <th>DELETE</th>
+ <th>POST</th>
+ <th>Path</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/overview</td>
+ <td>Various random bits of information that describe the whole
+ system.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/cluster-name</td>
+ <td>Name identifying this RabbitMQ cluster.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/nodes</td>
+ <td>A list of nodes in the RabbitMQ cluster.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/nodes/<i>name</i></td>
+ <td>
+ An individual node in the RabbitMQ cluster. Add
+ "?memory=true" to get memory statistics, and "?binary=true"
+ to get a breakdown of binary memory use (may be expensive if
+ there are many small binaries in the system).
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/extensions</td>
+ <td>A list of extensions to the management plugin.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/definitions<br/>
+ /api/all-configuration <em>(deprecated)</em>
+ </td>
+ <td>
+ The server definitions - exchanges, queues, bindings, users,
+ virtual hosts, permissions, topic permissions, and parameters. Everything apart from
+ messages. POST to upload an existing set of definitions. Note
+ that:
+ <ul>
+ <li>
+ The definitions are merged. Anything already existing on
+ the server but not in the uploaded definitions is
+ untouched.
+ </li>
+ <li>
+ Conflicting definitions on immutable objects (exchanges,
+ queues and bindings) will be ignored. The existing definition
+ will be preserved.
+ </li>
+ <li>
+ Conflicting definitions on mutable objects will cause
+ the object in the server to be overwritten with the
+ object from the definitions.
+ </li>
+ <li>
+ In the event of an error you will be left with a
+ part-applied set of definitions.
+ </li>
+ </ul>
+ For convenience you may upload a file from a browser to this
+ URI (i.e. you can use <code>multipart/form-data</code> as
+ well as <code>application/json</code>) in which case the
+ definitions should be uploaded as a form field named
+ "file".
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/definitions/<i>vhost</i><br/>
+ </td>
+ <td>
+ The server definitions for a given virtual host -
+ exchanges, queues, bindings and policies.
+ POST to upload an existing set of definitions. Note that:
+ <ul>
+ <li>
+ The definitions are merged. Anything already existing on
+ the server but not in the uploaded definitions is
+ untouched.
+ </li>
+ <li>
+ Conflicting definitions on immutable objects (exchanges,
+ queues and bindings) will be ignored. The existing definition
+ will be preserved.
+ </li>
+ <li>
+ Conflicting definitions on mutable objects will cause
+ the object in the server to be overwritten with the
+ object from the definitions.
+ </li>
+ <li>
+ In the event of an error you will be left with a
+ part-applied set of definitions.
+ </li>
+ </ul>
+ For convenience you may upload a file from a browser to this
+ URI (i.e. you can use <code>multipart/form-data</code> as
+ well as <code>application/json</code>) in which case the
+ definitions should be uploaded as a form field named
+ "file".
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/connections</td>
+ <td>A list of all open connections.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/vhosts/<i>vhost</i>/connections</td>
+ <td>A list of all open connections in a specific vhost.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/connections/<i>name</i></td>
+ <td>
+ An individual connection. DELETEing it will close the
+ connection. Optionally set the "X-Reason" header when
+ DELETEing to provide a reason.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/connections/<i>name</i>/channels</td>
+ <td>
+ List of all channels for a given connection.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/channels</td>
+ <td>A list of all open channels.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/vhosts/<i>vhost</i>/channels</td>
+ <td>A list of all open channels in a specific vhost.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/channels/<i>channel</i></td>
+ <td>Details about an individual channel.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/consumers</td>
+ <td>A list of all consumers.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/consumers/<i>vhost</i></td>
+ <td>A list of all consumers in a given virtual host.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/exchanges</td>
+ <td>A list of all exchanges.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/exchanges/<i>vhost</i></td>
+ <td>A list of all exchanges in a given virtual host.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/exchanges/<i>vhost</i>/<i>name</i></td>
+ <td>
+ An individual exchange. To PUT an exchange, you will need a body looking something like this:
+ <pre>{"type":"direct","auto_delete":false,"durable":true,"internal":false,"arguments":{}}</pre>
+ The <code>type</code> key is mandatory; other keys are optional.
+ <p>
+ When DELETEing an exchange you can add the query string
+ parameter <code>if-unused=true</code>. This prevents the
+ delete from succeeding if the exchange is bound to a queue
+ or as a source to another exchange.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/exchanges/<i>vhost</i>/<i>name</i>/bindings/source</td>
+ <td>A list of all bindings in which a given exchange is the source.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/exchanges/<i>vhost</i>/<i>name</i>/bindings/destination</td>
+ <td>A list of all bindings in which a given exchange is the destination.</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/exchanges/<i>vhost</i>/<i>name</i>/publish</td>
+ <td>
+ Publish a message to a given exchange. You will need a body
+ looking something like:
+ <pre>{"properties":{},"routing_key":"my key","payload":"my body","payload_encoding":"string"}</pre>
+ All keys are mandatory. The <code>payload_encoding</code>
+ key should be either "string" (in which case the payload
+ will be taken to be the UTF-8 encoding of the payload field)
+ or "base64" (in which case the payload field is taken to be
+ base64 encoded).<br/>
+ If the message is published successfully, the response will
+ look like:
+ <pre>{"routed": true}</pre>
+ <code>routed</code> will be true if the message was sent to
+ at least one queue.
+ <p>
+ Please note that the HTTP API is not ideal for high
+ performance publishing; the need to create a new TCP
+ connection for each message published can limit message
+ throughput compared to AMQP or other protocols using
+ long-lived connections.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/queues</td>
+ <td>A list of all queues.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/queues/<i>vhost</i></td>
+ <td>A list of all queues in a given virtual host.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/queues/<i>vhost</i>/<i>name</i></td>
+ <td>
+ An individual queue. To PUT a queue, you will need a body looking something like this:
+ <pre>{"auto_delete":false,"durable":true,"arguments":{},"node":"rabbit@smacmullen"}</pre>
+ All keys are optional.
+ <p>
+ When DELETEing a queue you can add the query string
+ parameters <code>if-empty=true</code> and /
+ or <code>if-unused=true</code>. These prevent the delete
+ from succeeding if the queue contains messages, or has
+ consumers, respectively.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/queues/<i>vhost</i>/<i>name</i>/bindings</td>
+ <td>A list of all bindings on a given queue.</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/queues/<i>vhost</i>/<i>name</i>/contents</td>
+ <td>Contents of a queue. DELETE to purge. Note you can't GET this.</td>
+ </tr>
+
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/queues/<i>vhost</i>/<i>name</i>/actions</td>
+ <td>
+ Actions that can be taken on a queue. POST a body like:
+ <pre>{"action":"sync"}</pre> Currently the actions which are
+ supported are <code>sync</code> and <code>cancel_sync</code>.
+ </td>
+ </tr>
+
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/queues/<i>vhost</i>/<i>name</i>/get</td>
+ <td>
+ Get messages from a queue. (This is not an HTTP GET as it
+ will alter the state of the queue.) You should post a body looking like:
+ <pre>{"count":5,"ackmode":"ack_requeue_true","encoding":"auto","truncate":50000}</pre>
+ <ul>
+ <li><code>count</code> controls the maximum number of
+ messages to get. You may get fewer messages than this if
+ the queue cannot immediately provide them.</li>
+ <li><code>ackmode</code> determines whether the messages will be
+ removed from the queue. If ackmode is ack_requeue_true or reject_requeue_true they will be requeued -
+ if ackmode is ack_requeue_false or reject_requeue_false they will be removed.
+ <li><code>encoding</code> must be either "auto" (in which case the
+ payload will be returned as a string if it is valid UTF-8, and
+ base64 encoded otherwise), or "base64" (in which case the payload
+ will always be base64 encoded).</li>
+ <li>If <code>truncate</code> is present it will truncate the
+ message payload if it is larger than the size given (in bytes).</li>
+ </ul>
+ <p><code>truncate</code> is optional; all other keys are mandatory.</p>
+ <p>
+ Please note that the get path in the HTTP API is intended
+ for diagnostics etc - it does not implement reliable
+ delivery and so should be treated as a sysadmin's tool
+ rather than a general API for messaging.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/bindings</td>
+ <td>A list of all bindings.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/bindings/<i>vhost</i></td>
+ <td>A list of all bindings in a given virtual host.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/bindings/<i>vhost</i>/e/<i>exchange</i>/q/<i>queue</i></td>
+ <td>
+ <p>
+ A list of all bindings between an exchange and a
+ queue. Remember, an exchange and a queue can be bound
+ together many times!
+ </p>
+ <p>
+ To create a new binding, POST to this
+ URI. Request body should be a JSON object optionally containing
+ two fields, <code>routing_key</code> (a string) and <code>arguments</code> (a map of optional arguments):
+ <pre>{"routing_key":"my_routing_key", "arguments":{"x-arg": "value"}}</pre>
+ All keys are optional.
+ The response will contain a <code>Location</code> header
+ telling you the URI of your new binding.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/bindings/<i>vhost</i>/e/<i>exchange</i>/q/<i>queue</i>/<i>props</i></td>
+ <td>An individual binding between an exchange and a queue.
+ The <i>props</i> part of the URI is a "name" for the binding
+ composed of its routing key and a hash of its
+ arguments. <i>props</i> is the field named "properties_key"
+ from a bindings listing response.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/bindings/<i>vhost</i>/e/<i>source</i>/e/<i>destination</i></td>
+ <td>
+ <p>
+ A list of all bindings between two exchanges, similar to
+ the list of all bindings between an exchange and a queue,
+ above.
+ </p>
+ <p>
+ <p>
+ To create a new binding, POST to this
+ URI. Request body should be a JSON object optionally containing
+ two fields, <code>routing_key</code> (a string) and <code>arguments</code> (a map of optional arguments):
+ <pre>{"routing_key":"my_routing_key", "arguments":{"x-arg": "value"}}</pre>
+ All keys are optional.
+ The response will contain a <code>Location</code> header
+ telling you the URI of your new binding.
+ </p>
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/bindings/<i>vhost</i>/e/<i>source</i>/e/<i>destination</i>/<i>props</i></td>
+ <td>
+ An individual binding between two exchanges. Similar to
+ the individual binding between an exchange and a queue,
+ above.
+ </tD>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/vhosts</td>
+ <td>A list of all vhosts.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/vhosts/<i>name</i></td>
+ <td>An individual virtual host. As a virtual host usually only
+ has a name, you do not need an HTTP body when PUTing one of
+ these. To set metadata on creation, provide a body like the following:
+ <pre>{"description":"virtual host description", "tags":"accounts,production"}</pre>
+ <code>tags</code> is a comma-separated list of tags.
+ These metadata fields are optional.
+ To enable / disable tracing, provide a body looking like:
+ <pre>{"tracing":true}</pre></td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/vhosts/<i>name</i>/permissions</td>
+ <td>A list of all permissions for a given virtual host.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/vhosts/<i>name</i>/topic-permissions</td>
+ <td>A list of all topic permissions for a given virtual host.</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/vhosts/<i>name</i>/start/<i>node</i></td>
+ <td>Starts virtual host <i>name</i> on node <i>node</i>.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/users/</td>
+ <td>A list of all users.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/users/without-permissions</td>
+ <td>A list of users that do not have access to any virtual host.</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/users/bulk-delete</td>
+ <td>Bulk deletes a list of users. Request body must contain the list:
+ <pre>{"users" : ["user1", "user2", "user3"]}</pre></td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/users/<i>name</i></td>
+ <td>An individual user. To PUT a user, you will need a body looking something like this:
+<pre>{"password":"secret","tags":"administrator"}</pre>
+or:
+<pre>{"password_hash":"2lmoth8l4H0DViLaK9Fxi6l9ds8=", "tags":"administrator"}</pre>
+ The <code>tags</code> key is mandatory. Either
+ <code>password</code> or <code>password_hash</code>
+ can be set. If neither are set the user will not be able to log in with a password,
+ but other mechanisms like client certificates may be used.
+ Setting <code>password_hash</code> to <code>""</code> will ensure the
+ user cannot use a password to log in. <code>tags</code> is a
+ comma-separated list of tags for the user. Currently recognised tags
+ are <code>administrator</code>, <code>monitoring</code> and <code>management</code>.
+ <code>password_hash</code> must be generated using the algorithm described
+ <a href="https://rabbitmq.com/passwords.html#computing-password-hash">here</a>.
+ You may also specify the hash function being used by adding the <code>hashing_algorithm</code>
+ key to the body. Currently recognised algorithms are <code>rabbit_password_hashing_sha256</code>,
+ <code>rabbit_password_hashing_sha512</code>, and <code>rabbit_password_hashing_md5</code>.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/users/<i>user</i>/permissions</td>
+ <td>A list of all permissions for a given user.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/users/<i>user</i>/topic-permissions</td>
+ <td>A list of all topic permissions for a given user.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/user-limits</td>
+ <td>
+ Lists per-user limits for all users.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/user-limits/<i>user</i></td>
+ <td>
+ Lists per-user limits for a specific user.
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/user-limits/<i>user</i>/<i>name</i></td>
+ <td>
+ Set or delete per-user limit for <code>user</code>. The <code>name</code> URL path element
+ refers to the name of the limit (<code>max-connections</code>, <code>max-channels</code>).
+ Limits are set using a JSON document in the body: <pre>{"value": 100}</pre>. Example
+ request:</br>
+ <pre>curl -4u 'guest:guest' -H 'content-type:application/json' -X PUT localhost:15672/api/user-limits/guest/max-connections -d '{"value": 50}'</pre>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/whoami</td>
+ <td>Details of the currently authenticated user.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/permissions</td>
+ <td>A list of all permissions for all users.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/permissions/<i>vhost</i>/<i>user</i></td>
+ <td>An individual permission of a user and virtual host. To PUT a permission, you will need a body looking something like this:
+<pre>{"configure":".*","write":".*","read":".*"}</pre>
+ All keys are mandatory.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/topic-permissions</td>
+ <td>A list of all topic permissions for all users.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/topic-permissions/<i>vhost</i>/<i>user</i></td>
+ <td>Topic permissions for a user and virtual host. To PUT a topic permission, you will need a body looking something like this:
+ <pre>{"exchange":"amq.topic","write":"^a","read":".*"}</pre>
+ All keys are mandatory.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/parameters</td>
+ <td>A list of all vhost-scoped parameters.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/parameters/<i>component</i></td>
+ <td>A list of all vhost-scoped parameters for a given component.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/parameters/<i>component</i>/<i>vhost</i></td>
+ <td>A list of all vhost-scoped parameters for a given component and virtual host.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/parameters/<i>component</i>/<i>vhost</i>/<i>name</i></td>
+ <td>An individual vhost-scoped parameter. To PUT a parameter, you will need a body looking something like this:
+<pre>{"vhost": "/","component":"federation","name":"local_username","value":"guest"}</pre>
+</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/global-parameters</td>
+ <td>A list of all global parameters.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/global-parameters/<i>name</i></td>
+ <td>An individual global parameter. To PUT a parameter, you will need a body looking something like this:
+<pre>{"name":"user_vhost_mapping","value":{"guest":"/","rabbit":"warren"}}</pre>
+ </td>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/policies</td>
+ <td>A list of all policies.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/policies/<i>vhost</i></td>
+ <td>A list of all policies in a given virtual host.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/policies/<i>vhost</i>/<i>name</i></td>
+ <td>
+ An individual policy. To PUT a policy, you will need a body looking something like this:
+<pre>{"pattern":"^amq.", "definition": {"federation-upstream-set":"all"}, "priority":0, "apply-to": "all"}</pre>
+ <code>pattern</code> and <code>definition</code> are mandatory, <code>priority</code> and <code>apply-to</code> are optional.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/operator-policies</td>
+ <td>A list of all operator policy overrides.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/operator-policies/<i>vhost</i></td>
+ <td>A list of all operator policy overrides in a given virtual host.</td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/operator-policies/<i>vhost</i>/<i>name</i></td>
+ <td>
+ An individual operator policy. To PUT a policy, you will need a body looking something like this:
+<pre>{"pattern":"^amq.", "definition": {"expires":100}, "priority":0, "apply-to": "queues"}</pre>
+ <code>pattern</code> and <code>definition</code> are mandatory, <code>priority</code> and <code>apply-to</code> are optional.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/aliveness-test/<i>vhost</i></td>
+ <td>
+ Declares a test queue on the target node, then publishes and consumes a
+ message. Intended to be used as a very basic health check.
+ Responds a 200 OK if the check succeeded,
+ otherwise responds with a 503 Service Unavailable.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/health/checks/alarms</td>
+ <td>
+ Responds a 200 OK if there are no alarms in effect in the cluster,
+ otherwise responds with a 503 Service Unavailable.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/health/checks/local-alarms</td>
+ <td>
+ Responds a 200 OK if there are no local alarms in effect on the target node,
+ otherwise responds with a 503 Service Unavailable.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/health/checks/certificate-expiration/<i>within</i>/<i>unit</i></td>
+ <td>
+ <p>
+ Checks the expiration date on the certificates for every listener configured to use TLS.
+ Responds a 200 OK if all certificates are valid (have not expired),
+ otherwise responds with a 503 Service Unavailable.
+ </p>
+ <p>
+ Valid units: days, weeks, months, years. The value of the <i>within</i> argument is the number of
+ units. So, when <i>within</i> is 2 and <i>unit</i> is "months", the expiration period used by the check
+ will be the next two months.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/health/checks/port-listener/<i>port</i></td>
+ <td>
+ Responds a 200 OK if there is an active listener on the give port,
+ otherwise responds with a 503 Service Unavailable.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/health/checks/protocol-listener/<i>protocol</i></td>
+ <td>
+ Responds a 200 OK if there is an active listener for the given protocol,
+ otherwise responds with a 503 Service Unavailable. Valid protocol names are: amqp091, amqp10, mqtt, stomp, web-mqtt, web-stomp.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/health/checks/virtual-hosts</td>
+ <td>
+ Responds a 200 OK if all virtual hosts and running on the target node,
+ otherwise responds with a 503 Service Unavailable.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/health/checks/node-is-mirror-sync-critical</td>
+ <td>
+ Checks if there are classic mirrored queues without synchronised mirrors online
+ (queues that would potentially lose data if the target node is shut down).
+ Responds a 200 OK if there are no such classic mirrored queues,
+ otherwise responds with a 503 Service Unavailable.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/health/checks/node-is-quorum-critical</td>
+ <td>
+ Checks if there are quorum queues with minimum online quorum (queues that
+ would lose their quorum and availability if the target node is shut down).
+ Responds a 200 OK if there are no such quorum queues,
+ otherwise responds with a 503 Service Unavailable.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/vhost-limits</td>
+ <td>
+ Lists per-vhost limits for all vhosts.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/vhost-limits/<i>vhost</i></td>
+ <td>
+ Lists per-vhost limits for specific vhost.
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>X</td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/vhost-limits/<i>vhost</i>/<i>name</i></td>
+ <td>
+ Set or delete per-vhost limit for <code>vhost</code>. The <code>name</code> URL path element
+ refers to the name of the limit (<code>max-connections</code>, <code>max-queues</code>).
+ Limits are set using a JSON document in the body: <pre>{"value": 100}</pre>. Example
+ request:</br>
+ <pre>curl -4u 'guest:guest' -H 'content-type:application/json' -X PUT localhost:15672/api/vhost-limits/my-vhost/max-connections -d '{"value": 50}'</pre>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/auth</td>
+ <td>
+ Details about the OAuth2 configuration. It will return HTTP
+ status 200 with body: <pre>{"enable_uaa":"boolean", "uaa_client_id":"string", "uaa_location":"string"}</pre>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td>X</td>
+ <td class="path">/api/rebalance/queues</td>
+ <td>
+ Rebalances all queues in all vhosts. This operation is asynchronous therefore please check
+ the RabbitMQ log file for messages regarding the success or failure of the operation.
+ <pre>curl -4u 'guest:guest' -XPOST localhost:15672/api/rebalance/queues/</pre>
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="path">/api/federation-links<br/>
+ /api/federation-links/<i>vhost</i></td>
+ <td>
+ Provides status for all federation links. Requires the <code>rabbitmq_federation_management</code> plugin to be enabled.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/auth/attempts/<i>node</i></td>
+ <td>
+ A list of authentication attempts.
+ </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td></td>
+ <td>X</td>
+ <td></td>
+ <td class="path">/api/auth/attempts/<i>node</i>/source</td>
+ <td>
+ A list of authentication attempts by remote address and username.
+ </td>
+ </tr>
+ </table>
+
+
+ <h2>HTTP API Stats</h2>
+ <p>
+ Most of the GET requests you can issue to the HTTP API return
+ JSON objects with a large number of keys. While a few of these
+ keys represent things you set yourself in a PUT request or AMQP
+ command (e.g. queue durability or arguments), most of them
+ represent statistics to do with the object in question. This
+ page attempts to document them.
+ </p>
+
+ <p>
+ It should be read in conjunction with the manual page
+ for <code>rabbitmqctl</code> (see your installation if on Unix / Linux,
+ or <a href="https://www.rabbitmq.com/rabbitmqctl.8.html">the
+ RabbitMQ website</a> for the latest version). Any field which can
+ be returned by a command of the form <code>rabbitmqctl
+ list_<i>something</i></code> will also be returned in the
+ equivalent part of the HTTP API, so all those keys are not
+ documented here. However, the HTTP API also adds a lot of extra
+ fields which are not available in <code>rabbitmqctl</code>.
+ </p>
+
+ <h2>_details objects</h2>
+ <p>
+ Many fields represent a count of some kind: queue length,
+ messages acknowledged, bytes received and so on. Such absolute
+ counts returned by the HTTP API will often have a
+ corresponding <code>_details</code> object which offers
+ information on how this count has changed. So for example, from
+ a queue:
+ </p>
+<pre> "messages": 123619,
+ "messages_details": {
+ "avg": 41206.333333333336,
+ "avg_rate": 1030.1583333333333,
+ "rate": 24723.8,
+ "samples": [
+ {
+ "sample": 123619,
+ "timestamp": 1400680560000
+ },
+ {
+ "sample": 0,
+ "timestamp": 1400680500000
+ },
+ {
+ "sample": 0,
+ "timestamp": 1400680440000
+ }
+ ]
+ }</pre>
+
+ <p>
+ Here we have a <code>messages</code> count (the total messages
+ in the queue), with some additional data:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>avg</code></td>
+ <td>
+ The average value for the requested time period (see below).
+ </td>
+ </tr>
+ <tr>
+ <td><code>avg_rate</code></td>
+ <td>
+ The average rate for the requested time period.
+ </td>
+ </tr>
+ <tr>
+ <td><code>rate</code></td>
+ <td>
+ How much the count has changed per second in the most recent
+ sampling interval.
+ </td>
+ </tr>
+ <tr>
+ <td><code>samples</code></td>
+ <td>
+ Snapshots showing how the value has changed over the
+ requested time period.
+ </td>
+ </tr>
+ </table>
+
+ <p>
+ <code>avg</code>, <code>avg_rate</code> and <code>samples</code>
+ will only appear if you request a specific time period by
+ appending query parameters to the URL. To do this you need to
+ set an age and an increment for the samples you want. The end of
+ the range returned will always correspond to the present.
+ </p>
+
+ <p>
+ Different types of data take different query parameters to
+ return samples, as in the following table. You can specify more
+ than one set of parameters if the resource you are requesting
+ can generate more than one type of sample (for example, queues
+ can return message rates and queue lengths).
+ </p>
+
+ <table>
+ <tr>
+ <td>Messages sent and received</td>
+ <td><code>msg_rates_age</code> / <code>msg_rates_incr</code></td>
+ </tr>
+ <tr>
+ <td>Bytes sent and received</td>
+ <td><code>data_rates_age</code> / <code>data_rates_incr</code>
+ </td>
+ </tr>
+ <tr>
+ <td>Queue lengths</td>
+ <td><code>lengths_age</code> / <code>lengths_incr</code></td>
+ </tr>
+ <tr>
+ <td>Node statistics (e.g. file descriptors, disk space free)</td>
+ <td><code>node_stats_age</code> / <code>node_stats_incr</code></td>
+ </tr>
+ </table>
+
+ <p>
+ For example,
+ appending <code>?lengths_age=3600&lengths_incr=60</code> will
+ return the last hour's data on queue lengths, with a sample for
+ every minute.
+ </p>
+
+ <h2>message_stats objects</h2>
+ <p>
+ Many objects (including queues, exchanges and channels) will
+ return counts of messages passing through them. These are
+ included in a <code>message_stats</code> object (which in turn
+ will contain <code>_details</code> objects for each count, as
+ described above).
+ </p>
+ <p>
+ These can contain:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>publish</code></td>
+ <td>
+ Count of messages published.
+ </td>
+ </tr>
+ <tr>
+ <td><code>publish_in</code></td>
+ <td>
+ Count of messages published "in" to an exchange, i.e. not
+ taking account of routing.
+ </td>
+ </tr>
+ <tr>
+ <td><code>publish_out</code></td>
+ <td>
+ Count of messages published "out" of an exchange,
+ i.e. taking account of routing.
+ </td>
+ </tr>
+ <tr>
+ <td><code>confirm</code></td>
+ <td>
+ Count of messages confirmed.
+ </td>
+ </tr>
+ <tr>
+ <td><code>deliver</code></td>
+ <td>
+ Count of messages delivered in acknowledgement mode to consumers.
+ </td>
+ </tr>
+ <tr>
+ <td><code>deliver_no_ack</code></td>
+ <td>
+ Count of messages delivered in no-acknowledgement mode to consumers.
+ </td>
+ </tr>
+ <tr>
+ <td><code>get</code></td>
+ <td>
+ Count of messages delivered in acknowledgement mode in
+ response to basic.get.
+ </td>
+ </tr>
+ <tr>
+ <td><code>get_no_ack</code></td>
+ <td>
+ Count of messages delivered in no-acknowledgement mode in
+ response to basic.get.
+ </td>
+ </tr>
+ <tr>
+ <td><code>deliver_get</code></td>
+ <td>
+ Sum of all four of the above.
+ </td>
+ </tr>
+ <tr>
+ <td><code>redeliver</code></td>
+ <td>
+ Count of subset of messages in <code>deliver_get</code>
+ which had the redelivered flag set.
+ </td>
+ </tr>
+ <tr>
+ <td><code>drop_unroutable</code></td>
+ <td>
+ Count of messages dropped as unroutable.
+ </td>
+ </tr>
+ <tr>
+ <td><code>return_unroutable</code></td>
+ <td>
+ Count of messages returned to the publisher as unroutable.
+ </td>
+ </tr>
+ </table>
+
+ <p>
+ Only fields for which some activity has taken place will appear.
+ </p>
+
+ <h2>Detailed message stats objects</h2>
+ <p>
+ In addition, queues, exchanges and channels can return a
+ breakdown of message stats for each of their neighbours
+ (i.e. adjacent objects in the chain: channel -> exchange ->
+ queue -> channel). This will only happen if
+ the <code>rates_mode</code> configuration item has been switched
+ to <code>detailed</code> from its default of <code>basic</code>.
+ </p>
+ <p>
+ As this possibly constitutes a large quantity of data, it is also
+ only returned when querying a single channel, queue or exchange
+ rather than a list. Note also that the default sample retention
+ policy means that these detailed message stats do not retain
+ historical data for more than a few seconds.
+ </p>
+ <p>
+ The detailed message stats objects have different names
+ depending on where they are (documented below). Each set of
+ detailed stats consists of a list of objects with two fields,
+ one identifying the partner object and one <code>stats</code>
+ which is a message_stats object as described above.
+ </p>
+ <p>
+ For example, from a queue:
+ </p>
+ <pre> "incoming": [
+ {
+ "stats": {
+ "publish": 352593,
+ "publish_details": {
+ "rate": 100.2
+ }
+ },
+ "exchange": {
+ "name": "my-exchange",
+ "vhost": "/"
+ }
+ }
+ {
+ "stats": {
+ "publish": 543784,
+ "publish_details": {
+ "rate": 54.6
+ }
+ },
+ "exchange": {
+ "name": "amq.topic",
+ "vhost": "/"
+ }
+ }
+ ],</pre>
+
+ <p>
+ This queue is currently receiving messages from two exchanges:
+ 100.2 msg/s from "my-exchange" and 54.6 msg/s from "amq.topic".
+ </p>
+
+ <h2>/api/overview</h2>
+
+ <p>
+ This has the following fields:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>cluster_name</code></td>
+ <td>
+ The name of the entire cluster, as set with <code>rabbitmqctl
+ set_cluster_name</code>.
+ </td>
+ </tr>
+ <tr>
+ <td><code>contexts</code></td>
+ <td>
+ A list of web application contexts in the cluster.
+ </td>
+ </tr>
+ <tr>
+ <td><code>erlang_full_version</code></td>
+ <td>
+ A string with extended detail about the Erlang VM and how it
+ was compiled, for the node connected to.
+ </td>
+ </tr>
+ <tr>
+ <td><code>erlang_version</code></td>
+ <td>
+ A string with the Erlang version of the node connected
+ to. As clusters should all run the same version this can be
+ taken as representing the cluster.
+ </td>
+ </tr>
+ <tr>
+ <td><code>exchange_types</code></td>
+ <td>
+ A list of all exchange types available.
+ </td>
+ </tr>
+ <tr>
+ <td><code>listeners</code></td>
+ <td>
+ All (non-HTTP) network listeners for all nodes in the
+ cluster. (See <code>contexts</code>
+ in <code>/api/nodes</code> for HTTP).
+ </td>
+ </tr>
+ <tr>
+ <td><code>management_version</code></td>
+ <td>
+ Version of the management plugin in use.
+ </td>
+ </tr>
+ <tr>
+ <td><code>message_stats</code></td>
+ <td>
+ A message_stats object for everything the user can see - for
+ all vhosts regardless of permissions in the case
+ of <code>monitoring</code> and <code>administrator</code>
+ users, and for all vhosts the user has access to for other
+ users.
+ </td>
+ </tr>
+ <tr>
+ <td><code>node</code></td>
+ <td>
+ The name of the cluster node this management plugin instance
+ is running on.
+ </td>
+ </tr>
+ <tr>
+ <td><code>object_totals</code></td>
+ <td>
+ An object containing global counts of all connections,
+ channels, exchanges, queues and consumers, subject to the
+ same visibility rules as for <code>message_stats</code>.
+ </td>
+ </tr>
+ <tr>
+ <td><code>queue_totals</code></td>
+ <td>
+ An object containing sums of
+ the <code>messages</code>, <code>messages_ready</code>
+ and <code>messages_unacknowledged</code> fields for all
+ queues, again subject to the same visibility rules as
+ for <code>message_stats</code>.
+ </td>
+ </tr>
+ <tr>
+ <td><code>rabbitmq_version</code></td>
+ <td>
+ Version of RabbitMQ on the node which processed this request.
+ </td>
+ </tr>
+ <tr>
+ <td><code>rates_mode</code></td>
+ <td>
+ 'none', 'basic' or 'detailed'.
+ </td>
+ </tr>
+ <tr>
+ <td><code>statistics_db_event_queue</code></td>
+ <td>
+ Number of outstanding statistics events yet to be processed
+ by the database.
+ </td>
+ </tr>
+ <tr>
+ <td><code>statistics_db_node</code></td>
+ <td>
+ Name of the cluster node hosting the management statistics database.
+ </td>
+ </tr>
+ </table>
+
+ <h2>/api/nodes</h2>
+
+ <p>
+ This has the following fields:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>applications</code></td>
+ <td>
+ List of all Erlang applications running on the node.
+ </td>
+ </tr>
+ <tr>
+ <td><code>auth_mechanisms</code></td>
+ <td>
+ List of all SASL authentication mechanisms installed on the node.
+ </td>
+ </tr>
+ <tr>
+ <td><code>cluster_links</code></td>
+ <td>
+ A list of the other nodes in the cluster. For each node,
+ there are details of the TCP connection used to connect to
+ it and statistics on data that has been transferred.
+ </td>
+ </tr>
+ <tr>
+ <td><code>config_files</code></td>
+ <td>
+ List of config files read by the node.
+ </td>
+ </tr>
+ <tr>
+ <td><code>contexts</code></td>
+ <td>
+ List of all HTTP listeners on the node.
+ </td>
+ </tr>
+ <tr>
+ <td><code>db_dir</code></td>
+ <td>
+ Location of the persistent storage used by the node.
+ </td>
+ </tr>
+ <tr>
+ <td><code>disk_free</code></td>
+ <td>
+ Disk free space in bytes.
+ </td>
+ </tr>
+ <tr>
+ <td><code>disk_free_alarm</code></td>
+ <td>
+ Whether the disk alarm has gone off.
+ </td>
+ </tr>
+ <tr>
+ <td><code>disk_free_limit</code></td>
+ <td>
+ Point at which the disk alarm will go off.
+ </td>
+ </tr>
+ <tr>
+ <td><code>enabled_plugins</code></td>
+ <td>
+ List of plugins which are both explicitly enabled and running.
+ </td>
+ </tr>
+ <tr>
+ <td><code>exchange_types</code></td>
+ <td>
+ Exchange types available on the node.
+ </td>
+ </tr>
+ <tr>
+ <td><code>fd_total</code></td>
+ <td>
+ File descriptors available.
+ </td>
+ </tr>
+ <tr>
+ <td><code>fd_used</code></td>
+ <td>
+ Used file descriptors.
+ </td>
+ </tr>
+ <tr>
+ <td><code>io_read_avg_time</code></td>
+ <td>
+ Average wall time (milliseconds) for each disk read operation in
+ the last statistics interval.
+ </td>
+ </tr>
+ <tr>
+ <td><code>io_read_bytes</code></td>
+ <td>
+ Total number of bytes read from disk by the persister.
+ </td>
+ </tr>
+ <tr>
+ <td><code>io_read_count</code></td>
+ <td>
+ Total number of read operations by the persister.
+ </td>
+ </tr>
+ <tr>
+ <td><code>io_reopen_count</code></td>
+ <td>
+ Total number of times the persister has needed to recycle
+ file handles between queues. In an ideal world this number
+ will be zero; if the number is large, performance might be
+ improved by increasing the number of file handles available
+ to RabbitMQ.
+ </td>
+ </tr>
+ <tr>
+ <td><code>io_seek_avg_time</code></td>
+ <td>
+ Average wall time (milliseconds) for each seek operation in
+ the last statistics interval.
+ </td>
+ </tr>
+ </tr>
+ <tr>
+ <td><code>io_seek_count</code></td>
+ <td>
+ Total number of seek operations by the persister.
+ </td>
+ </tr>
+ <tr>
+ <td><code>io_sync_avg_time</code></td>
+ <td>
+ Average wall time (milliseconds) for each fsync() operation in
+ the last statistics interval.
+ </td>
+ </tr>
+ </tr>
+ <tr>
+ <td><code>io_sync_count</code></td>
+ <td>
+ Total number of fsync() operations by the persister.
+ </td>
+ </tr>
+ <tr>
+ <td><code>io_write_avg_time</code></td>
+ <td>
+ Average wall time (milliseconds) for each disk write operation in
+ the last statistics interval.
+ </td>
+ </tr>
+ <tr>
+ <td><code>io_write_bytes</code></td>
+ <td>
+ Total number of bytes written to disk by the persister.
+ </td>
+ </tr>
+ <tr>
+ <td><code>io_write_count</code></td>
+ <td>
+ Total number of write operations by the persister.
+ </td>
+ </tr>
+ <tr>
+ <td><code>log_files</code></td>
+ <td>
+ List of log files used by the node. If the node also sends
+ messages to stdout, "<code>&lt;stdout&gt;</code>" is also
+ reported in the list.
+ </td>
+ </tr>
+ <tr>
+ <td><code>mem_used</code></td>
+ <td>
+ Memory used in bytes.
+ </td>
+ </tr>
+ <tr>
+ <td><code>mem_alarm</code></td>
+ <td>
+ Whether the memory alarm has gone off.
+ </td>
+ </tr>
+ <tr>
+ <td><code>mem_limit</code></td>
+ <td>
+ Point at which the memory alarm will go off.
+ </td>
+ </tr>
+ <tr>
+ <td><code>mnesia_disk_tx_count</code></td>
+ <td>
+ Number of Mnesia transactions which have been performed that
+ required writes to disk. (e.g. creating a durable
+ queue). Only transactions which originated on this node are
+ included.
+ </td>
+ </tr>
+ <tr>
+ <td><code>mnesia_ram_tx_count</code></td>
+ <td>
+ Number of Mnesia transactions which have been performed that
+ did not require writes to disk. (e.g. creating a transient
+ queue). Only transactions which originated on this node are
+ included.
+ </td>
+ </tr>
+ <tr>
+ <td><code>msg_store_read_count</code></td>
+ <td>
+ Number of messages which have been read from the message store.
+ </td>
+ </tr>
+ <tr>
+ <td><code>msg_store_write_count</code></td>
+ <td>
+ Number of messages which have been written to the message store.
+ </td>
+ </tr>
+ <tr>
+ <td><code>name</code></td>
+ <td>
+ Node name.
+ </td>
+ </tr>
+ <tr>
+ <td><code>net_ticktime</code></td>
+ <td>
+ Current kernel net_ticktime setting for the node.
+ </td>
+ </tr>
+ <tr>
+ <td><code>os_pid</code></td>
+ <td>
+ Process identifier for the Operating System under which this
+ node is running.
+ </td>
+ </tr>
+ <tr>
+ <td><code>partitions</code></td>
+ <td>
+ List of network partitions this node is seeing.
+ </td>
+ </tr>
+ <tr>
+ <td><code>proc_total</code></td>
+ <td>
+ Maximum number of Erlang processes.
+ </td>
+ </tr>
+ <tr>
+ <td><code>proc_used</code></td>
+ <td>
+ Number of Erlang processes in use.
+ </td>
+ </tr>
+ <tr>
+ <td><code>processors</code></td>
+ <td>
+ Number of cores detected and usable by Erlang.
+ </td>
+ </tr>
+ <tr>
+ <td><code>queue_index_journal_write_count</code></td>
+ <td>
+ Number of records written to the queue index journal. Each
+ record represents a message being published to a queue,
+ being delivered from a queue, and being acknowledged in a
+ queue.
+ </td>
+ </tr>
+ <tr>
+ <td><code>queue_index_read_count</code></td>
+ <td>
+ Number of records read from the queue index.
+ </td>
+ </tr>
+ <tr>
+ <td><code>queue_index_write_count</code></td>
+ <td>
+ Number of records written to the queue index.
+ </td>
+ </tr>
+ <tr>
+ <td><code>rates_mode</code></td>
+ <td>
+ 'none', 'basic' or 'detailed'.
+ </td>
+ </tr>
+ <tr>
+ <td><code>run_queue</code></td>
+ <td>
+ Average number of Erlang processes waiting to run.
+ </td>
+ </tr>
+ <tr>
+ <td><code>running</code></td>
+ <td>
+ Boolean for whether this node is up. Obviously if this is
+ false, most other stats will be missing.
+ </td>
+ </tr>
+ <tr>
+ <td><code>sasl_log_file</code></td>
+ <td>
+ Location of <a href="https://www.erlang.org/doc/man/sasl_app.html">sasl</a> log file.
+ </td>
+ </tr>
+ <tr>
+ <td><code>sockets_total</code></td>
+ <td>
+ File descriptors available for use as sockets.
+ </td>
+ </tr>
+ <tr>
+ <td><code>sockets_used</code></td>
+ <td>
+ File descriptors used as sockets.
+ </td>
+ </tr>
+ <tr>
+ <td><code>type</code></td>
+ <td>
+ 'disc' or 'ram'.
+ </td>
+ </tr>
+ <tr>
+ <td><code>uptime</code></td>
+ <td>
+ Time since the Erlang VM started, in milliseconds.
+ </td>
+ </tr>
+ </table>
+
+ <h2>/api/nodes/(name)</h2>
+
+ <p>
+ All of the above, plus:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>memory</code></td>
+ <td>
+ Detailed memory use statistics. Only appears
+ if <code>?memory=true</code> is appended to the URL.
+ </td>
+ </tr>
+ <tr>
+ <td><code>binary</code></td>
+ <td>
+ Detailed breakdown of the owners of binary memory. Only
+ appears if <code>?binary=true</code> is appended to the
+ URL. Note that this can be an expensive query if there are
+ many small binaries in the system.
+ </td>
+ </tr>
+ </table>
+
+ <h2>/api/connections</h2>
+ <h2>/api/connections/(name)</h2>
+
+ <p>
+ See documentation for <code>rabbitmqctl
+ list_connections</code>. No additional fields,
+ although <code>pid</code> is replaced by <code>node</code>.
+ </p>
+
+ <p>
+ Note also that while non-AMQP connections will appear in this
+ list (unlike <code>rabbitmqctl list_connections</code>), they
+ will omit many of the connection-level statistics.
+ </p>
+
+ <h2>/api/connections/(name)/channels</h2>
+ <h2>/api/channels</h2>
+
+ <p>
+ See documentation for <code>rabbitmqctl list_channels</code>,
+ with <code>pid</code> replaced by <code>node</code>, plus:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>connection_details</code></td>
+ <td>
+ Some basic details about the owning connection.
+ </td>
+ </tr>
+ <tr>
+ <td><code>message_stats</code></td>
+ <td>
+ See the section on message_stats above.
+ </td>
+ </tr>
+ </table>
+
+ <h2>/api/channels/(name)</h2>
+
+ <p>
+ All the above, plus
+ </p>
+
+ <table>
+ <tr>
+ <td><code>publishes</code></td>
+ <td>
+ Detailed message stats (see section above) for publishes to
+ exchanges.
+ </td>
+ </tr>
+ <tr>
+ <td><code>deliveries</code></td>
+ <td>
+ Detailed message stats for deliveries from queues.
+ </td>
+ </tr>
+ <tr>
+ <td><code>consumer_details</code></td>
+ <td>
+ List of consumers on this channel, with some details on each.
+ </td>
+ </tr>
+ </table>
+
+ <h2>/api/exchanges</h2>
+ <h2>/api/exchanges/(vhost)</h2>
+
+ <p>
+ See documentation for <code>rabbitmqctl list_exchanges</code>, plus:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>message_stats</code></td>
+ <td>
+ See the section on message_stats above.
+ </td>
+ </tr>
+ </table>
+
+ <h2>/api/exchanges/(vhost)/(name)</h2>
+
+ <p>
+ All the above, plus:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>incoming</code></td>
+ <td>
+ Detailed message stats (see section above) for publishes
+ from channels into this exchange.
+ </td>
+ </tr>
+ <tr>
+ <td><code>outgoing</code></td>
+ <td>
+ Detailed message stats for publishes from this exchange into
+ queues.
+ </td>
+ </tr>
+ </table>
+
+ <h2>/api/queues</h2>
+ <h2>/api/queues/(vhost)</h2>
+
+ <p>
+ See documentation for <code>rabbitmqctl list_queues</code>, with
+ all references to <code>pid</code>s replaced by <code>node</code>s
+ plus:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>message_stats</code></td>
+ <td>
+ See the section on message_stats above.
+ </td>
+ </tr>
+ </table>
+
+ <h2>/api/queues/(vhost)/(name)</h2>
+
+ <p>
+ All the above, plus:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>incoming</code></td>
+ <td>
+ Detailed message stats (see section above) for
+ publishes from exchanges into this queue.
+ </td>
+ </tr>
+ <tr>
+ <td><code>deliveries</code></td>
+ <td>
+ Detailed message stats for deliveries from this queue into
+ channels.
+ </td>
+ </tr>
+ <tr>
+ <td><code>consumer_details</code></td>
+ <td>
+ List of consumers on this channel, with some details on each.
+ </td>
+ </tr>
+ </table>
+
+ <h2>/api/vhosts/</h2>
+ <h2>/api/vhosts/(name)</h2>
+
+ <p>
+ All the fields from <code>rabbitmqctl list_vhosts</code>
+ (i.e. <code>name</code> and <code>tracing</code>) plus:
+ </p>
+
+ <table>
+ <tr>
+ <td><code>message_stats</code></td>
+ <td>
+ Global message_stats for this vhost. Note that activity for
+ other users in this vhost <b>is</b> shown, even for users
+ without the <code>monitoring</code> tag.
+ </td>
+ </tr>
+ <tr>
+ <td><code>messages</code> <code>messages_ready</code> <code>messages_acknowledged</code></td>
+ <td>
+ Sum of these fields for all queues in the vhost.
+ </td>
+ </tr>
+ <tr>
+ <td><code>recv_oct</code> <code>send_oct</code></td>
+ <td>
+ Sum of these fields for all connections to the vhost.
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/deps/rabbitmq_management/priv/www/cli/index.html b/deps/rabbitmq_management/priv/www/cli/index.html
new file mode 100644
index 0000000000..73f79da693
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/cli/index.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>rabbitmqadmin</title>
+ <style>
+ body { font: 12px Verdana,sans-serif; color: #444; padding: 8px 35px; }
+ h1 { font-size: 2em; }
+ code { background: #ffa; }
+ </style>
+ </head>
+ <body>
+ <h1>rabbitmqadmin</h1>
+
+ <p>
+ <code>rabbitmqadmin</code> is an HTTP API-based management tool. It uses the same API as RabbitMQ management UI
+ and provides access to the subset of the functionality in the UI from the command line.
+ It is not meant to be a replacement for <a href="https://www.rabbitmq.com/man/rabbitmqctl.1.man.html">rabbitmqctl</a>
+ as there are operations that HTTP API intentionally does not expose.
+ </p>
+
+ <p>
+ To use it, <a href="rabbitmqadmin">download it</a> from this node (right click,
+ Save As), make sure it is executable, and drop it in your <code>PATH</code>. Note that
+ many browsers will rename the
+ file <code>rabbitmqadmin.txt</code>. <code>rabbitmqadmin</code> requires Python
+ 2.6 or later (both 2.x and 3.x series are supported).
+ To use <code>rabbitmqadmin</code> with HTTPS, Python 2.7.9 is the minimum supported version.
+ </p>
+
+ <p>
+ See <a href="https://www.rabbitmq.com/management-cli.html">the
+ rabbitmqadmin page on the website</a> for more information on
+ its use, or invoke <code>rabbitmqadmin --help</code> for usage
+ instructions.
+ </p>
+
+ <p>
+ Windows users will need to ensure Python is on
+ their <code>PATH</code>, and invoke rabbitmqadmin as <code>python.exe
+ rabbitmqadmin</code>.
+ </p>
+ </body>
+</html>
diff --git a/deps/rabbitmq_management/priv/www/css/evil.css b/deps/rabbitmq_management/priv/www/css/evil.css
new file mode 100644
index 0000000000..016ceb2eea
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/css/evil.css
@@ -0,0 +1 @@
+#login { text-align: center; }
diff --git a/deps/rabbitmq_management/priv/www/css/main.css b/deps/rabbitmq_management/priv/www/css/main.css
new file mode 100644
index 0000000000..05b8500e3f
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/css/main.css
@@ -0,0 +1,361 @@
+body { font: 12px Verdana, sans-serif; color: #484848; padding: 0; margin: 0; }
+input, button, a.button { font: 12px Verdana, sans-serif; }
+
+a { font-weight: bold; color: #444; text-decoration: none; }
+a:hover { color: #F60; }
+
+#outer { padding: 0 0 1em 0; width: 95%; margin: auto; }
+
+#login table { margin: auto; }
+#login p { text-align: center; }
+
+#logo { padding: 0 0 2em 0; }
+#logo img { margin: 1em 0 -0.3em 1em; border: none; }
+
+#versions abbr { background: #f0f0f0; margin: 0 0 0 1em; }
+
+.status-ok { }
+.status-error { color: #F00; }
+.status-timeout { color: #99EBFF; }
+
+#debug { position: fixed; bottom: 0; z-index: 9; width: 100%; text-align: center; padding: 0; margin: 0; }
+#debug p { background: #F60; color: white; margin: 0; padding: 1em; font-size: 2em; }
+
+#header { background: white; position: fixed; z-index: 1; width: 95%; margin: auto; padding: 1em 0 0 0; border-bottom: 1px solid #666; }
+
+#topnav { float: right; padding: 0; margin: 0; list-style-type: none; }
+#topnav form { display: inline; }
+#topnav input[type=submit] { padding: 3px 7px; display: inline; }
+#topnav li { text-align: right; padding: 2px 0; }
+
+#menu ul { padding: 0; margin: 0; overflow: auto; }
+#menu li { float: left; list-style-type: none; padding: 0 0.1em 0 0; }
+#menu li a { display: block; padding: 0.7em 1.3em; margin-right: 5px; }
+#menu a:hover { background-color: #F60; color: white; -moz-border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0; }
+#menu a.selected { background-color: #666; color: white; -moz-border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0; }
+#vhost-form { float: right; padding: 0; margin: 0; }
+
+#main { padding-top: 10em; }
+#main.with-rhs { margin-right: 210px; }
+#rhs { float: right; width: 200px; background-color: white; position: relative; padding-top: 10em; }
+#rhs ul { padding: 0; margin: 10px 0 0 0; }
+#rhs li { list-style-type: none; padding: 0; margin-bottom: 5px; }
+#rhs a { display: block; padding: 0.7em; font-weight: bold; text-decoration: none; }
+#rhs a:hover { background-color: #F60; color: white; -moz-border-radius: 8px 0 0 8px; border-radius: 8px 0 0 8px; }
+#rhs a.selected { background-color: #666; color: white; -moz-border-radius: 8px 0 0 8px; border-radius: 8px 0 0 8px; }
+
+h1 { font-size: 2em; font-weight: normal; padding: 0; margin-bottom: 0; }
+b, dt { color: black; font-weight: normal; }
+dd { margin-bottom: 5px; }
+div.box, div.section, div.section-hidden { overflow: auto; width: 100%; }
+
+.left { float: left; }
+.right { float: right; }
+.clear { clear: both; }
+
+.shortinput { width: 50px; text-align: right; }
+
+.help:after { content: '?'; }
+.help,
+.popup-options-link { background-color: #E4E4E4; padding: 2px 4px; cursor: pointer; }
+table th .help,
+table th .popup-options-link { border: none; }
+.help:hover,
+.popup-options-link:hover,
+.popup-owner { background-color: #F60; color: white; }
+
+.rate-visibility-option { cursor: pointer; padding: 4px; background: #fafafa; border: 1px solid #f0f0f0; border-radius: 3px; display:block; }
+.rate-visibility-option:hover { background: #ddf;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ddf),color-stop(1, #bbf));
+ border: 1px solid #88d;
+ border-radius: 3px; }
+
+.rate-visibility-option-hidden { text-decoration: line-through; color: #888; }
+
+
+table.legend { float: left; }
+table.legend th { padding: 4px 10px 4px 0; width: 80px; }
+table.legend td { padding: 4px 0 4px 10px; width: 130px; }
+
+.tag-link, .argument-link { color: #444; cursor: pointer; font-weight: bold; }
+.tag-link:hover, .argument-link:hover { color: #F60; }
+
+.filter { overflow: auto; width: 100%; margin-bottom: 10px; }
+.filter table { float: left; }
+.filter label { margin-top: 4px;}
+.filter input#filter-regex-mode { vertical-align: middle; }
+.filter p#filter-truncate { float: right; padding: 4px; margin: 0; }
+.filter p.filter-warning { border-radius: 5px; background: #ff8; }
+.filter-active { background: #99EBFF; border-radius: 5px; }
+.filter-highlight { background: #99EBFF; }
+
+input#truncate { width: 50px; text-align: right; }
+
+table { border-collapse: collapse; }
+table th { font-weight: normal; color: black; padding: 6px 5px 5px 5px; line-height: 1em; }
+table td { padding: 2px 5px; }
+table.list th, table.list td { vertical-align: top; min-width: 5em; width: auto; }
+
+table.list { border-width: 1px; margin-bottom: 1em; }
+table.list th, table.list td { border: 1px solid #ccc; }
+table.list th { text-align: left; }
+table.list th.plus-minus { border: none; min-width: 2em; }
+table.list td a { display: block; color: black; text-decoration: none; font-weight: bold; }
+table.list td a:hover { color: #F60; }
+table.list th a.sort { display: block; width: 100%; cursor: pointer; color: black; font-weight: bold; }
+table.list th a.sort .arrow { color: #F60; }
+table.list td p { margin: 0; padding: 1px 0 0 0; }
+table.list td p.warning { margin: 0; padding: 5px; }
+
+table.list td.plain, table.list td.plain td, table.list td.plain th { border: none; background: none; }
+table.list th.plain { border-left: none; border-top: none; border-right: none; background: none; }
+table.list th.plain h3 { margin: 0; border: 0; }
+
+#main .internal-purpose, #main .internal-purpose * { color: #aaa; }
+
+div.section table.list, div.section-hidden table.list { margin-bottom: 0; }
+
+div.memory-bar { margin: 10px 0 5px 0; border-radius: 5px; border: 1px solid #ddd; float: left; }
+div.memory-section { float: left; height: 30px; }
+div.colour-key { float: left; width: 10px; height: 10px; margin: 3px 5px 0 0;}
+div.memory-info { float: left; padding: 10px 10px 0 0; }
+button.memory-button { margin-top: 10px; }
+
+div.memory_queue { background: #bd4688; }
+div.memory_binary { background: url(../img/bg-binary.png); }
+div.memory_conn { background: #dada66; }
+div.memory_proc { background: #6abf59; }
+div.memory_table { background: #6679da; }
+div.memory_system { background: #999; }
+div.memory_unused { background: #955; }
+
+div.memory-bar div.memory_queue { border-right: solid 1px #eb50a6; }
+div.memory-bar div.memory_binary { border-right: solid 1px #eb50a6; }
+div.memory-bar div.memory_conn { border-right: solid 1px #ebeb8d; }
+div.memory-bar div.memory_proc { border-right: solid 1px #79da66; }
+div.memory-bar div.memory_table { border-right: solid 1px #8d9ceb; }
+div.memory-bar div.memory_system { border-right: solid 1px #bbb; }
+div.memory-bar div.memory_unused { border-right: solid 1px #bbb; }
+
+sub { display: block; font-size: 0.8em; color: #888; }
+small { font-size: 0.8em; color: #888; }
+#main sub a { color: #888; }
+#main sub a:hover { color: #444; }
+table.argument-links { color: #888; }
+table.argument-links td { vertical-align: top; }
+.unknown { color: #888; }
+
+table.facts { float: left; }
+table.facts th, table.legend th { color: black; text-align: right; border-right: 1px solid #ccc; }
+table.facts th, table.facts td { vertical-align: top; padding: 0 10px 10px 10px; }
+table.facts th.horizontal { border-right: none; padding: 0 10px 5px 10px; }
+
+table.facts-long th { text-align: right; font-weight: bold; }
+table.facts-long th, table.facts-long td { vertical-align: top; }
+
+table.facts-l { margin-right: 50px; }
+
+table.mini th { border: none; padding: 0 2px 2px 2px; text-align: right; }
+table.mini td { border: none; padding: 0 2px 2px 2px; }
+
+tr.alt1>td {
+ background: #eee;
+ background: -moz-linear-gradient(center top, #f0f0f0 0%,#e0e0e0 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #f0f0f0),color-stop(1, #e0e0e0));
+}
+tr.alt2>td {
+ background: #fff;
+ background: -moz-linear-gradient(center top, #F8F8F8 0%,#ffffff 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #F8F8F8),color-stop(1, #ffffff));
+}
+
+td span,
+td abbr {
+ display: inline-block;
+ padding: 2px 4px;
+ margin: 0 0 3px 0;
+}
+
+div.status-bar, div.status-red, div.status-yellow, div.status-green, div.status-grey { text-align: center; }
+div.status-bar-main, div.status-red, div.status-yellow, div.status-green, div.status-grey { border-radius: 3px; -moz-border-radius: 3px; padding: 3px; }
+div.status-bar sub { white-space: nowrap; }
+
+div.status-bar .grey, div.status-grey { background: #ddd; }
+div.status-bar .red, div.status-red { background: #ff7a7a; color: white; }
+div.status-bar .yellow, div.status-yellow { background: #ffff7b; }
+div.status-bar .green, div.status-green { background: #98f898; }
+div.status-bar .red-dark { background: #e24545; color: white; }
+/* yellow-dark and green-dark can never happen */
+div.status-bar .red *, div.status-bar .red-dark *, div.status-red * { color: white; }
+
+div.status-key-grey { background: #ddd; }
+div.status-key-red { background: #ff7a7a; color: white; }
+div.status-key-yellow { background: #ffff7b; }
+div.status-key-green { background: #98f898; }
+
+.l { text-align: left !important; }
+.c { text-align: center !important; }
+.r { text-align: right !important; }
+.t { vertical-align: top !important; }
+
+div.form-popup-warn,
+div.form-popup-info,
+div.form-popup-help,
+div.form-popup-options {
+ -moz-border-radius: 5px 0 0 5px;
+ background: #EEE;
+ border-radius: 5px 0 0 5px;
+ border: 1px solid #ccc;
+ right: 0;
+ margin: 10px 0 10px 0;
+ padding: 15px;
+ position: fixed;
+ top: 0;
+}
+div.form-popup-warn,
+div.form-popup-info,
+div.form-popup-help {
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ left: 50%;
+ margin-left: -250px;
+ text-align: left;
+ top: 25%;
+ width: 500px;
+ z-index: 2;
+}
+p.warning, div.form-popup-warn { background: #FF9; }
+
+div.form-popup-options { z-index: 3; }
+
+div.form-popup-warn span,
+div.form-popup-info span,
+div.form-popup-help span,
+div.form-popup-options span {
+ color: white;
+ background-color: #666;
+ cursor: pointer;
+ padding: 4px 8px;
+ border-radius: 5px;
+ -moz-border-radius: 5px;
+}
+div.form-popup-warn span:hover,
+div.form-popup-info span:hover,
+div.form-popup-help span:hover,
+div.form-popup-options span:hover {
+ background-color: #F60;
+ cursor: pointer;
+}
+
+p.warning { padding: 15px; border-radius: 5px; -moz-border-radius: 5px; text-align: center; }
+
+.highlight { min-width: 120px; font-size: 120%; text-align:center; padding:10px; background-color: #ddd; margin: 0 20px 0 0; color: #888; border-radius: 5px; -moz-border-radius: 5px; }
+.highlight strong { font-size: 2em; display: block; color: #444; font-weight: normal; }
+.highlight { float: left; }
+
+.chart { margin: 0 20px 20px 0; float: left; }
+.chart-small { width: 400px; height: 100px; }
+.chart-medium { width: 600px; height: 200px; }
+.chart-large { width: 800px; height: 300px; }
+
+ul#global-counts { overflow: hidden; list-style-type: none; margin: 0; padding: 0; }
+ul#global-counts li { float: left; margin: 0 1em 0 0; }
+
+div.section, div.section-hidden { margin: 0 0 1em 0; }
+div.section-invisible div.hider { display: none; }
+div.section div.hider, div.section-hidden div.hider { padding: 0.5em 0; }
+div.section h2, div.section-hidden h2 { font-size: 1em; padding: 5px 5px 5px 25px; cursor: pointer; margin: 0; }
+div.section h2:hover, div.section-hidden h2:hover { color: black; }
+div.section-invisible h2 { background: white; background-image: url(../img/collapse.png); background-repeat:no-repeat; background-position:4px 4px; }
+div.section-visible h2 { background: #F8F8F8; background-image: url(../img/expand.png); background-repeat:no-repeat; background-position:4px 4px; }
+
+form { margin: 0; }
+form.inline-form { float: left; }
+form.inline-form-right { float: right; padding-left: 5px; }
+input, select { padding: 0.2em; }
+input[type=text], input[type=password] { font: 1.1em Andale Mono, Lucidatypewriter, Courier New, Courier, monospace; border: 1px solid #ccc; }
+textarea { width: 600px; height: 200px; border: 1px solid #ccc; }
+.mand { color: #f88; padding: 0 5px;}
+input[type=submit].wait { cursor: wait; }
+
+table.form { margin-bottom: 0.5em; }
+table.form th { text-align: right; vertical-align: top; }
+table.form input[type=text], table.form input[type=password] { width: 200px; }
+table.form input[type=text].wide, table.form input[type=password].wide { width: 300px; }
+table.form select { width: auto; }
+table.form select.narrow { width: 110px; }
+table.form .multifield { margin: 0; padding: 0; }
+table.form .multifield td { margin: 0; padding: 0; vertical-align: top; }
+table.form .multifield td.equals { padding: 3px; }
+table.form .multifield td input { float: left; }
+table.form .multifield td select { width: auto; display: block; float: left; margin-left: 5px; }
+table.form label { margin-top: 5px; display: block; }
+
+table.form table.subform { margin-bottom: 5px; }
+table.form table.subform th { text-align: left; }
+table.form table.subform th, table.form table.subform td { padding: 0; }
+
+.multifield-sub { border: 1px solid #ddd; background: #F8F8F8; padding: 10px; border-radius: 5px; -moz-border-radius: 5px; float: left; margin-bottom: 10px; }
+
+label.radio, label.checkbox { padding: 5px; cursor: pointer; border-radius: 5px; -moz-border-radius: 5px; border: 1px solid #ccc; }
+
+table.two-col-layout { width: 100%; }
+table.two-col-layout > tbody > tr > td { width: 50%; vertical-align: top; }
+
+input[type=submit], button, a.button { padding: 8px; border-radius: 5px; -moz-border-radius: 5px; text-decoration: none !important; cursor: pointer; display: block; font-weight: normal !important; }
+table.list input[type=submit], table.list button { padding: 3px 7px; margin: 0 0 3px 0; }
+table.list input[type=submit], table.list button, table.list a.button { padding: 3px 7px; margin: 0 0 3px 0; }
+
+input[type=submit], button, a.button {
+ background: #666;
+ color: #FFF !important;
+ border: 0;
+}
+input[type=submit]:hover, button:hover, a.button:hover {
+ background: #F60;
+ text-decoration: none !important;
+}
+
+input[type=submit][disabled], button[disabled], a.button.disabled { pointer-events: none; background: #aaa; }
+input[type=submit][disabled]:hover, button[disabled]:hover, a.button.disabled { background: #aaa; }
+
+h3 { padding: 0 0 2px 0; margin: 1em 0 1em 0; font-size: 1em; border-bottom: 1px solid #E4E4E4; font-weight: normal; }
+
+abbr { background: #99EBFF; padding: 2px 4px; border-radius: 5px; -moz-border-radius: 5px; border: none; cursor: default; text-decoration: none; }
+
+table.list td abbr a { display: inline; width: auto; }
+
+abbr.warning { background: red; }
+
+.status-red abbr, .status-yellow abbr, .status-green abbr, .status-grey abbr, small abbr, abbr.normal { background: none; color: inherit; padding: 0; border-bottom: 1px dotted; cursor: default; }
+
+abbr.status-grey { background: #ddd; }
+abbr.status-green { background: #98f898; }
+abbr.status-yellow { background: #ffff7b; }
+abbr.status-red { background: #ff7a7a; color: white; }
+
+abbr.type { background: none; color: inherit; padding: 0; border-bottom: 1px dotted #ddd; cursor: default; }
+
+div.bindings-wrapper { display: inline-block; }
+div.bindings-wrapper table { margin: auto; }
+div.bindings-wrapper p { margin: 10px; text-align: center; }
+div.bindings-wrapper span.exchange { border: 1px solid #bbb; padding: 10px; border-radius: 5px; -moz-border-radius: 5px; }
+div.bindings-wrapper span.queue { border: 1px solid #666; padding: 10px; }
+div.bindings-wrapper td span.exchange, div.bindings-wrapper td span.queue { background: white; display: block; }
+div.bindings-wrapper span.exchange a, div.bindings-wrapper span.queue a { font-weight: normal !important; }
+div.bindings-wrapper p.arrow { font-size: 200%; }
+
+#footer { overflow: auto; width: 100%; border-top: 1px solid #666; }
+#footer ul { list-style-type: none; padding: 0; margin: 0; }
+#footer ul li { float: left; }
+#footer ul li a { display: block; padding: 0.7em 1em; }
+
+#scratch { display: none; }
+
+.highlight, .mini-highlight, .micro-highlight {
+ background: -moz-linear-gradient(center top, #f0f0f0 0%,#e0e0e0 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #f0f0f0),color-stop(1, #e0e0e0));
+ border: 1px solid #e0e0e0;
+}
+
+table.dynamic-shovels td label {width: 200px; margin-right:10px;padding: 4px 0px 5px 0px}
diff --git a/deps/rabbitmq_management/priv/www/favicon.ico b/deps/rabbitmq_management/priv/www/favicon.ico
new file mode 100644
index 0000000000..cc5b8b9217
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/favicon.ico
Binary files differ
diff --git a/deps/rabbitmq_management/priv/www/img/bg-binary.png b/deps/rabbitmq_management/priv/www/img/bg-binary.png
new file mode 100644
index 0000000000..dc136bfe8d
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/img/bg-binary.png
Binary files differ
diff --git a/deps/rabbitmq_management/priv/www/img/bg-green-dark.png b/deps/rabbitmq_management/priv/www/img/bg-green-dark.png
new file mode 100644
index 0000000000..d121c00e6a
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/img/bg-green-dark.png
Binary files differ
diff --git a/deps/rabbitmq_management/priv/www/img/bg-red-dark.png b/deps/rabbitmq_management/priv/www/img/bg-red-dark.png
new file mode 100644
index 0000000000..af571cde93
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/img/bg-red-dark.png
Binary files differ
diff --git a/deps/rabbitmq_management/priv/www/img/bg-red.png b/deps/rabbitmq_management/priv/www/img/bg-red.png
new file mode 100644
index 0000000000..90fe4a9dea
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/img/bg-red.png
Binary files differ
diff --git a/deps/rabbitmq_management/priv/www/img/bg-yellow-dark.png b/deps/rabbitmq_management/priv/www/img/bg-yellow-dark.png
new file mode 100644
index 0000000000..7b2018afda
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/img/bg-yellow-dark.png
Binary files differ
diff --git a/deps/rabbitmq_management/priv/www/img/collapse.png b/deps/rabbitmq_management/priv/www/img/collapse.png
new file mode 100644
index 0000000000..20b09a038b
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/img/collapse.png
Binary files differ
diff --git a/deps/rabbitmq_management/priv/www/img/expand.png b/deps/rabbitmq_management/priv/www/img/expand.png
new file mode 100644
index 0000000000..4b5a8d871e
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/img/expand.png
Binary files differ
diff --git a/deps/rabbitmq_management/priv/www/img/rabbitmqlogo-master-copy.svg b/deps/rabbitmq_management/priv/www/img/rabbitmqlogo-master-copy.svg
new file mode 100644
index 0000000000..4f3bad62e2
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/img/rabbitmqlogo-master-copy.svg
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="188.29454"
+ height="26.193336"
+ viewBox="0 0 188.29454 26.193336"
+ version="1.1"
+ id="svg42"
+ sodipodi:docname="rabbitmqlogo.svg"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
+ <metadata
+ id="metadata46">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Artboard Copy</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="3838"
+ inkscape:window-height="2128"
+ id="namedview44"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="5.6806225"
+ inkscape:cx="56.451823"
+ inkscape:cy="49.532666"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg42" />
+ <!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
+ <title
+ id="title2">Artboard Copy</title>
+ <desc
+ id="desc4">Created with Sketch.</desc>
+ <defs
+ id="defs6" />
+ <g
+ id="g996">
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="Shape"
+ d="M 22.870867,10.622731 H 15.346611 C 14.744892,10.550951 14.286702,10.020888 14.286702,9.3969602 V 1.5012317 c 0,-0.67362166 -0.546517,-1.21472753 -1.214481,-1.21472753 h -2.633213 c -0.6734844,0 -1.2144801,0.54662736 -1.2144801,1.21472753 V 9.4797825 C 9.1914058,10.081625 8.694573,10.567516 8.0983737,10.622731 H 6.1276038 C 5.5479656,10.550951 5.1008162,10.06506 5.0621736,9.4797825 V 1.5012317 c 0,-0.67362166 -0.546516,-1.21472753 -1.21448,-1.21472753 H 1.21448 C 0.54099565,0.28650417 0,0.83313153 0,1.5012317 V 11.837458 24.542403 c 0,0.673622 0.54651602,1.214727 1.21448,1.214727 h 2.6332136 6.5913144 2.633213 9.804166 c 0.673485,0 1.214481,-0.546627 1.214481,-1.214727 V 11.837458 c -0.0055,-0.673621 -0.546516,-1.214727 -1.220001,-1.214727 z m -3.466788,8.712908 c 0,0.673622 -0.546516,1.214728 -1.21448,1.214728 h -2.633214 c -0.673484,0 -1.21448,-0.546628 -1.21448,-1.214728 V 16.85097 c 0,-0.673622 0.546516,-1.214728 1.21448,-1.214728 h 2.633214 c 0.673484,0 1.21448,0.546627 1.21448,1.214728 z" />
+ <g
+ id="g984">
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path21"
+ d="M 41.623543,25.779216 40.3649,19.904353 c -0.182172,-0.877917 -0.375385,-1.656447 -0.596199,-2.330068 -0.215294,-0.679144 -0.518915,-1.247857 -0.89982,-1.711662 -0.386425,-0.463805 -0.888778,-0.811659 -1.512579,-1.054604 -0.623801,-0.237424 -1.440815,-0.358897 -2.451042,-0.358897 h -2.644254 v 11.330094 h -2.88163 V 0.33067608 h 6.304256 c 1.043348,0 2.053575,0.11042977 3.0362,0.32576782 0.977104,0.21533805 1.843801,0.5742348 2.594571,1.0711688 0.750769,0.4969339 1.352489,1.1595126 1.810679,1.9822143 0.45819,0.8227018 0.684525,1.8331342 0.684525,3.0368187 0,0.9220886 -0.110407,1.7392689 -0.325701,2.4404979 -0.215295,0.701229 -0.540996,1.3085924 -0.971584,1.8165694 -0.430589,0.507977 -0.971584,0.938653 -1.611947,1.286507 -0.640362,0.347854 -1.396652,0.62945 -2.257828,0.844788 v 0.07178 c 0.839095,0.215338 1.512579,0.491413 2.014932,0.81718 0.502353,0.33129 0.90534,0.723316 1.20896,1.181599 0.2981,0.458284 0.529955,0.982825 0.695566,1.573624 0.165611,0.590799 0.320181,1.269943 0.474751,2.026386 l 1.468417,6.97364 z M 40.922457,7.0503276 c 0,-1.0546043 -0.187693,-1.8773061 -0.557557,-2.4570624 C 39.995036,4.0135089 39.498203,3.5828328 38.890963,3.3067584 38.278202,3.030684 37.582637,2.8650393 36.804265,2.8098244 36.025894,2.7546095 35.225441,2.7270021 34.408427,2.7270021 h -2.141901 v 9.3313159 h 1.584345 c 0.866697,0 1.716833,-0.06074 2.566969,-0.187731 0.850136,-0.126994 1.600905,-0.36994 2.268869,-0.728836 0.667964,-0.358897 1.20896,-0.866874 1.622987,-1.5239313 0.402987,-0.6570571 0.612761,-1.5128878 0.612761,-2.5674921 z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path23"
+ d="M 57.776127,25.779216 V 23.45467 h -0.07176 c -0.242896,0.458283 -0.563077,0.855831 -0.971584,1.198163 -0.408507,0.342332 -0.855657,0.629449 -1.341449,0.85583 -0.485792,0.226381 -0.999186,0.397548 -1.529141,0.513499 -0.535475,0.115951 -1.048869,0.171166 -1.540181,0.171166 -0.75629,0 -1.429774,-0.115951 -2.014933,-0.353375 -0.590679,-0.231903 -1.081991,-0.563192 -1.473937,-0.988347 -0.397466,-0.425154 -0.701086,-0.944174 -0.91086,-1.551538 -0.209774,-0.607364 -0.314661,-1.280985 -0.314661,-2.026386 0,-0.673622 0.132489,-1.269943 0.397466,-1.800005 0.264978,-0.530063 0.612761,-0.993868 1.05439,-1.396937 0.436109,-0.403069 0.943982,-0.745401 1.52362,-1.03804 0.579639,-0.292639 1.175838,-0.524541 1.794119,-0.701229 0.61828,-0.182209 1.242081,-0.314725 1.865883,-0.397547 0.623801,-0.08282 1.208959,-0.126994 1.744435,-0.126994 0.298099,0 0.590679,-0.0055 0.866697,-0.01104 0.276018,-0.0055 0.563077,-0.03865 0.866697,-0.09939 v -0.375461 c 0,-0.646014 -0.01104,-1.292028 -0.0276,-1.938043 -0.01656,-0.646014 -0.126968,-1.22577 -0.325702,-1.739268 -0.198733,-0.513499 -0.524434,-0.933132 -0.971584,-1.247857 -0.447149,-0.314725 -1.115113,-0.474848 -1.987331,-0.474848 -0.518914,0 -0.982624,0.04969 -1.396652,0.143559 -0.414027,0.09387 -0.772851,0.248467 -1.07095,0.463805 -0.2981,0.215338 -0.540996,0.502455 -0.728688,0.861352 -0.187693,0.358897 -0.314661,0.800616 -0.386426,1.325157 h -2.793304 c 0.01104,-0.888959 0.198733,-1.639882 0.568598,-2.258289 0.364344,-0.6184063 0.844616,-1.1208617 1.429774,-1.5128874 0.590679,-0.3920257 1.258643,-0.6736216 2.009412,-0.8558307 0.75077,-0.1822091 1.51258,-0.270553 2.296472,-0.270553 1.992851,0 3.472309,0.4527621 4.438372,1.3527647 0.966064,0.9000024 1.451856,2.3300684 1.451856,4.2846754 v 12.31844 z M 56.224905,17.81723 c -0.563077,0 -1.181358,0.06074 -1.843801,0.187731 -0.662444,0.126994 -1.286245,0.331289 -1.865883,0.623928 -0.579639,0.292639 -1.05991,0.6681 -1.451856,1.142948 -0.391946,0.474848 -0.585159,1.060126 -0.585159,1.755833 0,0.745401 0.226335,1.358286 0.673485,1.838656 0.45267,0.480369 1.048869,0.717794 1.794118,0.717794 0.441629,0 0.872218,-0.06626 1.280725,-0.209817 0.408506,-0.138037 0.789411,-0.325768 1.142715,-0.568713 0.353303,-0.237424 0.673484,-0.530063 0.966063,-0.866874 0.287059,-0.336811 0.524435,-0.701229 0.701087,-1.098776 0.143529,-0.325768 0.259457,-0.623929 0.353303,-0.88896 0.09385,-0.265031 0.160091,-0.541106 0.209774,-0.811659 0.04968,-0.270553 0.07728,-0.546627 0.08833,-0.839266 0.01104,-0.287117 0.01656,-0.618407 0.01656,-0.988346 h -1.479458 z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path25"
+ d="m 79.200659,17.18778 c 0,1.153992 -0.115927,2.269332 -0.353303,3.351544 -0.237376,1.082212 -0.629321,2.042951 -1.181358,2.882217 -0.552036,0.839266 -1.275204,1.512888 -2.169503,2.015343 -0.894299,0.502456 -2.009412,0.756444 -3.33982,0.756444 -1.236561,0 -2.27439,-0.193252 -3.113485,-0.574235 -0.839095,-0.386504 -1.5181,-0.916567 -2.025973,-1.601231 -0.507874,-0.684665 -0.877738,-1.490802 -1.098553,-2.423934 -0.220814,-0.92761 -0.331222,-1.943564 -0.331222,-3.036819 V 0.33067608 h 2.500725 V 11.136229 h 0.08833 c 0.15457,-0.530063 0.391946,-0.993868 0.701086,-1.402458 0.314661,-0.4085902 0.679005,-0.7509225 1.098552,-1.0380399 0.419548,-0.2815959 0.883259,-0.4969339 1.385612,-0.6460141 0.502353,-0.1490802 1.026787,-0.2263811 1.567783,-0.2263811 0.839095,0 1.578824,0.1325158 2.213666,0.4030687 0.634842,0.270553 1.186878,0.6404927 1.645068,1.1098192 0.463711,0.4693265 0.850136,1.0104322 1.159277,1.6288392 0.314661,0.618407 0.557557,1.275464 0.739729,1.971171 0.182172,0.695708 0.30914,1.40798 0.386425,2.136816 0.08833,0.723315 0.126968,1.430066 0.126968,2.11473 z m -2.88163,-0.06626 c 0,-0.40859 -0.02208,-0.866873 -0.06072,-1.37485 -0.04416,-0.507977 -0.115927,-1.032518 -0.215294,-1.562581 -0.09937,-0.535585 -0.242896,-1.054605 -0.425068,-1.562582 -0.182172,-0.507977 -0.414027,-0.960739 -0.701086,-1.358286 -0.287059,-0.397547 -0.634842,-0.717793 -1.043349,-0.960739 -0.408507,-0.248467 -0.883258,-0.369939 -1.424253,-0.369939 -0.574118,0 -1.087512,0.132515 -1.529141,0.397547 -0.441629,0.265031 -0.828055,0.612885 -1.153756,1.043561 -0.325702,0.430676 -0.5962,0.922089 -0.811494,1.474238 -0.215294,0.552148 -0.386425,1.11534 -0.513394,1.689575 -0.126968,0.574235 -0.215294,1.137427 -0.270498,1.689575 -0.0552,0.552149 -0.08281,1.043562 -0.08281,1.474238 0,0.673621 0.04968,1.380372 0.143529,2.120251 0.09385,0.745401 0.287059,1.435587 0.568598,2.070559 0.281538,0.634971 0.684525,1.159512 1.20896,1.573624 0.524434,0.414111 1.21448,0.618407 2.081177,0.618407 0.574118,0 1.081991,-0.11043 1.52362,-0.325768 0.436109,-0.215338 0.811494,-0.507977 1.126154,-0.872396 0.314661,-0.364418 0.568598,-0.789572 0.772851,-1.269942 0.204254,-0.480369 0.364344,-0.977303 0.485792,-1.490802 0.121448,-0.513498 0.204254,-1.032518 0.253937,-1.546017 0.04416,-0.513498 0.06624,-0.999389 0.06624,-1.457673 z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path27"
+ d="m 96.937588,17.18778 c 0,1.153992 -0.115927,2.269332 -0.353303,3.351544 -0.237376,1.082212 -0.629321,2.042951 -1.181358,2.882217 -0.552036,0.839266 -1.275204,1.512888 -2.169503,2.015343 -0.894299,0.502456 -2.009412,0.756444 -3.33982,0.756444 -1.236561,0 -2.27439,-0.193252 -3.113485,-0.574235 -0.839095,-0.386504 -1.5181,-0.916567 -2.025974,-1.601231 -0.507873,-0.684665 -0.877737,-1.490802 -1.098552,-2.423934 -0.220815,-0.92761 -0.331222,-1.943564 -0.331222,-3.036819 V 0.33067608 h 2.500725 V 11.136229 h 0.08833 c 0.15457,-0.530063 0.391946,-0.993868 0.701086,-1.402458 0.314661,-0.4085902 0.679005,-0.7509225 1.098552,-1.0380399 0.419548,-0.2815959 0.883259,-0.4969339 1.385612,-0.6460141 0.502353,-0.1490802 1.026787,-0.2263811 1.567783,-0.2263811 0.839095,0 1.578824,0.1325158 2.213666,0.4030687 0.634842,0.270553 1.186878,0.6404927 1.645068,1.1098192 0.463711,0.4693265 0.850136,1.0104322 1.159277,1.6288392 0.31466,0.618407 0.557556,1.275464 0.739729,1.971171 0.182171,0.695708 0.30914,1.40798 0.386425,2.136816 0.08833,0.723315 0.126968,1.430066 0.126968,2.11473 z m -2.88163,-0.06626 c 0,-0.40859 -0.02208,-0.866873 -0.06072,-1.37485 -0.04416,-0.507977 -0.115927,-1.032518 -0.215294,-1.562581 -0.09937,-0.535585 -0.242896,-1.054605 -0.425068,-1.562582 -0.182172,-0.507977 -0.414027,-0.960739 -0.701086,-1.358286 -0.287059,-0.397547 -0.634842,-0.717793 -1.043349,-0.960739 -0.408507,-0.248467 -0.883258,-0.369939 -1.424254,-0.369939 -0.574117,0 -1.087511,0.132515 -1.52914,0.397547 -0.441629,0.265031 -0.828055,0.612885 -1.153756,1.043561 -0.325702,0.430676 -0.5962,0.922089 -0.811494,1.474238 -0.215294,0.552148 -0.386425,1.11534 -0.513394,1.689575 -0.126968,0.574235 -0.215294,1.137427 -0.270498,1.689575 -0.0552,0.552149 -0.08281,1.043562 -0.08281,1.474238 0,0.673621 0.04968,1.380372 0.143529,2.120251 0.09385,0.745401 0.287059,1.435587 0.568598,2.070559 0.281538,0.634971 0.684525,1.159512 1.20896,1.573624 0.524434,0.414111 1.21448,0.618407 2.081177,0.618407 0.574118,0 1.081991,-0.11043 1.52362,-0.325768 0.436109,-0.215338 0.811494,-0.507977 1.126154,-0.872396 0.314661,-0.364418 0.568598,-0.789572 0.772851,-1.269942 0.204254,-0.480369 0.364344,-0.977303 0.485792,-1.490802 0.121448,-0.513498 0.204254,-1.032518 0.253937,-1.546017 0.04416,-0.513498 0.06624,-0.999389 0.06624,-1.457673 z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path29"
+ d="M 101.36492,3.6435692 V 0.33067608 h 3.31222 V 3.6435692 Z m 0.39747,22.1356468 V 8.2208831 h 2.50072 V 25.779216 Z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path31"
+ d="m 114.15008,10.32457 v 10.805553 c 0,0.938653 0.22634,1.661968 0.68453,2.180988 0.45819,0.51902 1.18688,0.773009 2.18606,0.773009 0.34779,0 0.64588,-0.02761 0.88326,-0.08282 0.2429,-0.05522 0.51891,-0.143558 0.83358,-0.25951 v 2.252767 c -0.36987,0.06074 -0.73973,0.11043 -1.1096,0.143559 -0.36434,0.03865 -0.73421,0.05522 -1.10959,0.05522 -0.82806,0 -1.5457,-0.0773 -2.15294,-0.231902 -0.60724,-0.154602 -1.11512,-0.408591 -1.52362,-0.767487 -0.40851,-0.353376 -0.71213,-0.817181 -0.91086,-1.396937 -0.19874,-0.579756 -0.2981,-1.292028 -0.2981,-2.142337 V 10.32457 h -2.86507 V 8.2153616 h 2.86507 V 4.1294602 l 2.50072,-1.5846672 v 5.6705686 h 4.73647 V 10.32457 Z" />
+ <polygon
+ style="fill:#b8b8b8;fill-rule:nonzero;stroke:none;stroke-width:1"
+ points="113.99551,3.5589046 106.50438,25.943019 103.66139,25.943019 96.186819,3.5589046 96.098493,3.5589046 96.098493,25.943019 93.526004,25.943019 93.526004,0.5 97.848449,0.5 105.06908,22.149756 105.14085,22.149756 112.37805,0.5 116.64529,0.5 116.64529,25.94854 114.06728,25.94854 114.06728,3.5589046 "
+ id="polygon33"
+ transform="translate(29.379376,-0.10000002)" />
+ <path
+ style="fill:#b8b8b8;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path35"
+ d="m 160.212,25.857131 c -1.18688,0 -2.24127,-0.176688 -3.15213,-0.530063 -0.91086,-0.353376 -1.71131,-0.839267 -2.39583,-1.457673 -0.68453,-0.618407 -1.25865,-1.341722 -1.72788,-2.169945 -0.46923,-0.828224 -0.84461,-1.711662 -1.13167,-2.655836 -0.28706,-0.944175 -0.49683,-1.921478 -0.6238,-2.937432 -0.12697,-1.015954 -0.1877,-2.020865 -0.1877,-3.014733 0,-1.054604 0.0552,-2.109208 0.17114,-3.158291 0.11592,-1.049083 0.30362,-2.0595154 0.57411,-3.0368189 0.2705,-0.9773034 0.63485,-1.8773061 1.08752,-2.7220938 0.45819,-0.8392662 1.0323,-1.5681027 1.72787,-2.1865094 0.69557,-0.6184067 1.52362,-1.10429774 2.47312,-1.457673 C 157.98177,0.17668763 159.09689,0 160.36657,0 c 1.23656,0 2.31855,0.18220912 3.2515,0.54662736 0.92742,0.36441822 1.72787,0.86687374 2.39583,1.49632344 0.66797,0.6294496 1.22,1.3693291 1.65611,2.2251598 0.43611,0.8503092 0.7839,1.7613548 1.03783,2.7276153 0.25394,0.9662605 0.43059,1.9711714 0.52996,3.0147331 0.0994,1.043561 0.15457,2.070558 0.15457,3.08099 0,0.491413 -0.0166,1.054605 -0.0552,1.684054 -0.0386,0.62945 -0.0994,1.286507 -0.18769,1.971172 -0.0883,0.684664 -0.21529,1.37485 -0.37538,2.070558 -0.16009,0.695707 -0.36987,1.352764 -0.6238,1.96565 -0.25394,0.612885 -0.55756,1.170555 -0.91086,1.673011 -0.35331,0.502455 -0.76734,0.900002 -1.23105,1.18712 l 0.0718,0.07178 5.23882,-0.325768 v 2.468106 z m 6.05032,-12.804332 c 0,-0.662579 -0.0221,-1.385894 -0.0607,-2.169945 -0.0442,-0.784052 -0.13249,-1.5736244 -0.2705,-2.3576758 C 165.79309,7.7411269 165.58884,6.97364 165.32938,6.239282 165.06992,5.504924 164.7111,4.8478669 164.27499,4.2736321 163.83336,3.7049188 163.29789,3.2466352 162.66304,2.904303 162.0282,2.5619707 161.26087,2.3908045 160.37209,2.3908045 c -0.93846,0 -1.73891,0.1656447 -2.40688,0.496934 -0.66796,0.3312893 -1.23104,0.7730084 -1.69475,1.3306787 -0.46371,0.5576703 -0.83357,1.198163 -1.12063,1.9269995 -0.28154,0.7288365 -0.49683,1.4852804 -0.6514,2.2803747 -0.14905,0.7950944 -0.24842,1.5901886 -0.2981,2.3852826 -0.0497,0.800616 -0.0718,1.546017 -0.0718,2.241725 0,0.662578 0.0276,1.37485 0.0883,2.142337 0.0607,0.767487 0.16561,1.534974 0.3257,2.307982 0.15457,0.767487 0.37538,1.512888 0.65692,2.230682 0.28154,0.717793 0.64589,1.358286 1.09303,1.910435 0.44715,0.552149 0.98815,0.993868 1.62299,1.325157 0.63484,0.331289 1.39113,0.496934 2.25231,0.496934 0.97158,0 1.79964,-0.154602 2.47864,-0.469327 0.67901,-0.314724 1.24208,-0.734358 1.69475,-1.258899 0.45267,-0.530063 0.80598,-1.137427 1.06543,-1.827613 0.25946,-0.690186 0.45267,-1.424544 0.57964,-2.197552 0.12697,-0.773008 0.20426,-1.562581 0.23186,-2.357675 0.0276,-0.806138 0.0442,-1.573625 0.0442,-2.302461 z" />
+ </g>
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:1.25;font-family:Raleway-v4020;-inkscape-font-specification:'Raleway-v4020, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#b8b8b8;fill-opacity:1;stroke:none"
+ x="175.17902"
+ y="25.857132"
+ id="text956"><tspan
+ sodipodi:role="line"
+ id="tspan954"
+ x="175.17902"
+ y="25.857132"
+ style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:9.33333302px;font-family:Raleway-v4020;-inkscape-font-specification:'Raleway-v4020 Semi-Bold';fill:#b8b8b8;fill-opacity:1">TM</tspan></text>
+</svg>
diff --git a/deps/rabbitmq_management/priv/www/img/rabbitmqlogo.svg b/deps/rabbitmq_management/priv/www/img/rabbitmqlogo.svg
new file mode 100644
index 0000000000..b0b1af0a3a
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/img/rabbitmqlogo.svg
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="188.29454"
+ height="26.193336"
+ viewBox="0 0 188.29454 26.193336"
+ version="1.1"
+ id="svg42"
+ sodipodi:docname="rabbitmqlogo.svg"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
+ <metadata
+ id="metadata46">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Artboard Copy</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="3838"
+ inkscape:window-height="2128"
+ id="namedview44"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="5.6806225"
+ inkscape:cx="56.451823"
+ inkscape:cy="49.532666"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg42" />
+ <!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
+ <title
+ id="title2">Artboard Copy</title>
+ <desc
+ id="desc4">Created with Sketch.</desc>
+ <defs
+ id="defs6" />
+ <g
+ id="g996">
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="Shape"
+ d="M 22.870867,10.622731 H 15.346611 C 14.744892,10.550951 14.286702,10.020888 14.286702,9.3969602 V 1.5012317 c 0,-0.67362166 -0.546517,-1.21472753 -1.214481,-1.21472753 h -2.633213 c -0.6734844,0 -1.2144801,0.54662736 -1.2144801,1.21472753 V 9.4797825 C 9.1914058,10.081625 8.694573,10.567516 8.0983737,10.622731 H 6.1276038 C 5.5479656,10.550951 5.1008162,10.06506 5.0621736,9.4797825 V 1.5012317 c 0,-0.67362166 -0.546516,-1.21472753 -1.21448,-1.21472753 H 1.21448 C 0.54099565,0.28650417 0,0.83313153 0,1.5012317 V 11.837458 24.542403 c 0,0.673622 0.54651602,1.214727 1.21448,1.214727 h 2.6332136 6.5913144 2.633213 9.804166 c 0.673485,0 1.214481,-0.546627 1.214481,-1.214727 V 11.837458 c -0.0055,-0.673621 -0.546516,-1.214727 -1.220001,-1.214727 z m -3.466788,8.712908 c 0,0.673622 -0.546516,1.214728 -1.21448,1.214728 h -2.633214 c -0.673484,0 -1.21448,-0.546628 -1.21448,-1.214728 V 16.85097 c 0,-0.673622 0.546516,-1.214728 1.21448,-1.214728 h 2.633214 c 0.673484,0 1.21448,0.546627 1.21448,1.214728 z" />
+ <g
+ id="g984">
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path21"
+ d="M 41.623543,25.779216 40.3649,19.904353 c -0.182172,-0.877917 -0.375385,-1.656447 -0.596199,-2.330068 -0.215294,-0.679144 -0.518915,-1.247857 -0.89982,-1.711662 -0.386425,-0.463805 -0.888778,-0.811659 -1.512579,-1.054604 -0.623801,-0.237424 -1.440815,-0.358897 -2.451042,-0.358897 h -2.644254 v 11.330094 h -2.88163 V 0.33067608 h 6.304256 c 1.043348,0 2.053575,0.11042977 3.0362,0.32576782 0.977104,0.21533805 1.843801,0.5742348 2.594571,1.0711688 0.750769,0.4969339 1.352489,1.1595126 1.810679,1.9822143 0.45819,0.8227018 0.684525,1.8331342 0.684525,3.0368187 0,0.9220886 -0.110407,1.7392689 -0.325701,2.4404979 -0.215295,0.701229 -0.540996,1.3085924 -0.971584,1.8165694 -0.430589,0.507977 -0.971584,0.938653 -1.611947,1.286507 -0.640362,0.347854 -1.396652,0.62945 -2.257828,0.844788 v 0.07178 c 0.839095,0.215338 1.512579,0.491413 2.014932,0.81718 0.502353,0.33129 0.90534,0.723316 1.20896,1.181599 0.2981,0.458284 0.529955,0.982825 0.695566,1.573624 0.165611,0.590799 0.320181,1.269943 0.474751,2.026386 l 1.468417,6.97364 z M 40.922457,7.0503276 c 0,-1.0546043 -0.187693,-1.8773061 -0.557557,-2.4570624 C 39.995036,4.0135089 39.498203,3.5828328 38.890963,3.3067584 38.278202,3.030684 37.582637,2.8650393 36.804265,2.8098244 36.025894,2.7546095 35.225441,2.7270021 34.408427,2.7270021 h -2.141901 v 9.3313159 h 1.584345 c 0.866697,0 1.716833,-0.06074 2.566969,-0.187731 0.850136,-0.126994 1.600905,-0.36994 2.268869,-0.728836 0.667964,-0.358897 1.20896,-0.866874 1.622987,-1.5239313 0.402987,-0.6570571 0.612761,-1.5128878 0.612761,-2.5674921 z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path23"
+ d="M 57.776127,25.779216 V 23.45467 h -0.07176 c -0.242896,0.458283 -0.563077,0.855831 -0.971584,1.198163 -0.408507,0.342332 -0.855657,0.629449 -1.341449,0.85583 -0.485792,0.226381 -0.999186,0.397548 -1.529141,0.513499 -0.535475,0.115951 -1.048869,0.171166 -1.540181,0.171166 -0.75629,0 -1.429774,-0.115951 -2.014933,-0.353375 -0.590679,-0.231903 -1.081991,-0.563192 -1.473937,-0.988347 -0.397466,-0.425154 -0.701086,-0.944174 -0.91086,-1.551538 -0.209774,-0.607364 -0.314661,-1.280985 -0.314661,-2.026386 0,-0.673622 0.132489,-1.269943 0.397466,-1.800005 0.264978,-0.530063 0.612761,-0.993868 1.05439,-1.396937 0.436109,-0.403069 0.943982,-0.745401 1.52362,-1.03804 0.579639,-0.292639 1.175838,-0.524541 1.794119,-0.701229 0.61828,-0.182209 1.242081,-0.314725 1.865883,-0.397547 0.623801,-0.08282 1.208959,-0.126994 1.744435,-0.126994 0.298099,0 0.590679,-0.0055 0.866697,-0.01104 0.276018,-0.0055 0.563077,-0.03865 0.866697,-0.09939 v -0.375461 c 0,-0.646014 -0.01104,-1.292028 -0.0276,-1.938043 -0.01656,-0.646014 -0.126968,-1.22577 -0.325702,-1.739268 -0.198733,-0.513499 -0.524434,-0.933132 -0.971584,-1.247857 -0.447149,-0.314725 -1.115113,-0.474848 -1.987331,-0.474848 -0.518914,0 -0.982624,0.04969 -1.396652,0.143559 -0.414027,0.09387 -0.772851,0.248467 -1.07095,0.463805 -0.2981,0.215338 -0.540996,0.502455 -0.728688,0.861352 -0.187693,0.358897 -0.314661,0.800616 -0.386426,1.325157 h -2.793304 c 0.01104,-0.888959 0.198733,-1.639882 0.568598,-2.258289 0.364344,-0.6184063 0.844616,-1.1208617 1.429774,-1.5128874 0.590679,-0.3920257 1.258643,-0.6736216 2.009412,-0.8558307 0.75077,-0.1822091 1.51258,-0.270553 2.296472,-0.270553 1.992851,0 3.472309,0.4527621 4.438372,1.3527647 0.966064,0.9000024 1.451856,2.3300684 1.451856,4.2846754 v 12.31844 z M 56.224905,17.81723 c -0.563077,0 -1.181358,0.06074 -1.843801,0.187731 -0.662444,0.126994 -1.286245,0.331289 -1.865883,0.623928 -0.579639,0.292639 -1.05991,0.6681 -1.451856,1.142948 -0.391946,0.474848 -0.585159,1.060126 -0.585159,1.755833 0,0.745401 0.226335,1.358286 0.673485,1.838656 0.45267,0.480369 1.048869,0.717794 1.794118,0.717794 0.441629,0 0.872218,-0.06626 1.280725,-0.209817 0.408506,-0.138037 0.789411,-0.325768 1.142715,-0.568713 0.353303,-0.237424 0.673484,-0.530063 0.966063,-0.866874 0.287059,-0.336811 0.524435,-0.701229 0.701087,-1.098776 0.143529,-0.325768 0.259457,-0.623929 0.353303,-0.88896 0.09385,-0.265031 0.160091,-0.541106 0.209774,-0.811659 0.04968,-0.270553 0.07728,-0.546627 0.08833,-0.839266 0.01104,-0.287117 0.01656,-0.618407 0.01656,-0.988346 h -1.479458 z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path25"
+ d="m 79.200659,17.18778 c 0,1.153992 -0.115927,2.269332 -0.353303,3.351544 -0.237376,1.082212 -0.629321,2.042951 -1.181358,2.882217 -0.552036,0.839266 -1.275204,1.512888 -2.169503,2.015343 -0.894299,0.502456 -2.009412,0.756444 -3.33982,0.756444 -1.236561,0 -2.27439,-0.193252 -3.113485,-0.574235 -0.839095,-0.386504 -1.5181,-0.916567 -2.025973,-1.601231 -0.507874,-0.684665 -0.877738,-1.490802 -1.098553,-2.423934 -0.220814,-0.92761 -0.331222,-1.943564 -0.331222,-3.036819 V 0.33067608 h 2.500725 V 11.136229 h 0.08833 c 0.15457,-0.530063 0.391946,-0.993868 0.701086,-1.402458 0.314661,-0.4085902 0.679005,-0.7509225 1.098552,-1.0380399 0.419548,-0.2815959 0.883259,-0.4969339 1.385612,-0.6460141 0.502353,-0.1490802 1.026787,-0.2263811 1.567783,-0.2263811 0.839095,0 1.578824,0.1325158 2.213666,0.4030687 0.634842,0.270553 1.186878,0.6404927 1.645068,1.1098192 0.463711,0.4693265 0.850136,1.0104322 1.159277,1.6288392 0.314661,0.618407 0.557557,1.275464 0.739729,1.971171 0.182172,0.695708 0.30914,1.40798 0.386425,2.136816 0.08833,0.723315 0.126968,1.430066 0.126968,2.11473 z m -2.88163,-0.06626 c 0,-0.40859 -0.02208,-0.866873 -0.06072,-1.37485 -0.04416,-0.507977 -0.115927,-1.032518 -0.215294,-1.562581 -0.09937,-0.535585 -0.242896,-1.054605 -0.425068,-1.562582 -0.182172,-0.507977 -0.414027,-0.960739 -0.701086,-1.358286 -0.287059,-0.397547 -0.634842,-0.717793 -1.043349,-0.960739 -0.408507,-0.248467 -0.883258,-0.369939 -1.424253,-0.369939 -0.574118,0 -1.087512,0.132515 -1.529141,0.397547 -0.441629,0.265031 -0.828055,0.612885 -1.153756,1.043561 -0.325702,0.430676 -0.5962,0.922089 -0.811494,1.474238 -0.215294,0.552148 -0.386425,1.11534 -0.513394,1.689575 -0.126968,0.574235 -0.215294,1.137427 -0.270498,1.689575 -0.0552,0.552149 -0.08281,1.043562 -0.08281,1.474238 0,0.673621 0.04968,1.380372 0.143529,2.120251 0.09385,0.745401 0.287059,1.435587 0.568598,2.070559 0.281538,0.634971 0.684525,1.159512 1.20896,1.573624 0.524434,0.414111 1.21448,0.618407 2.081177,0.618407 0.574118,0 1.081991,-0.11043 1.52362,-0.325768 0.436109,-0.215338 0.811494,-0.507977 1.126154,-0.872396 0.314661,-0.364418 0.568598,-0.789572 0.772851,-1.269942 0.204254,-0.480369 0.364344,-0.977303 0.485792,-1.490802 0.121448,-0.513498 0.204254,-1.032518 0.253937,-1.546017 0.04416,-0.513498 0.06624,-0.999389 0.06624,-1.457673 z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path27"
+ d="m 96.937588,17.18778 c 0,1.153992 -0.115927,2.269332 -0.353303,3.351544 -0.237376,1.082212 -0.629321,2.042951 -1.181358,2.882217 -0.552036,0.839266 -1.275204,1.512888 -2.169503,2.015343 -0.894299,0.502456 -2.009412,0.756444 -3.33982,0.756444 -1.236561,0 -2.27439,-0.193252 -3.113485,-0.574235 -0.839095,-0.386504 -1.5181,-0.916567 -2.025974,-1.601231 -0.507873,-0.684665 -0.877737,-1.490802 -1.098552,-2.423934 -0.220815,-0.92761 -0.331222,-1.943564 -0.331222,-3.036819 V 0.33067608 h 2.500725 V 11.136229 h 0.08833 c 0.15457,-0.530063 0.391946,-0.993868 0.701086,-1.402458 0.314661,-0.4085902 0.679005,-0.7509225 1.098552,-1.0380399 0.419548,-0.2815959 0.883259,-0.4969339 1.385612,-0.6460141 0.502353,-0.1490802 1.026787,-0.2263811 1.567783,-0.2263811 0.839095,0 1.578824,0.1325158 2.213666,0.4030687 0.634842,0.270553 1.186878,0.6404927 1.645068,1.1098192 0.463711,0.4693265 0.850136,1.0104322 1.159277,1.6288392 0.31466,0.618407 0.557556,1.275464 0.739729,1.971171 0.182171,0.695708 0.30914,1.40798 0.386425,2.136816 0.08833,0.723315 0.126968,1.430066 0.126968,2.11473 z m -2.88163,-0.06626 c 0,-0.40859 -0.02208,-0.866873 -0.06072,-1.37485 -0.04416,-0.507977 -0.115927,-1.032518 -0.215294,-1.562581 -0.09937,-0.535585 -0.242896,-1.054605 -0.425068,-1.562582 -0.182172,-0.507977 -0.414027,-0.960739 -0.701086,-1.358286 -0.287059,-0.397547 -0.634842,-0.717793 -1.043349,-0.960739 -0.408507,-0.248467 -0.883258,-0.369939 -1.424254,-0.369939 -0.574117,0 -1.087511,0.132515 -1.52914,0.397547 -0.441629,0.265031 -0.828055,0.612885 -1.153756,1.043561 -0.325702,0.430676 -0.5962,0.922089 -0.811494,1.474238 -0.215294,0.552148 -0.386425,1.11534 -0.513394,1.689575 -0.126968,0.574235 -0.215294,1.137427 -0.270498,1.689575 -0.0552,0.552149 -0.08281,1.043562 -0.08281,1.474238 0,0.673621 0.04968,1.380372 0.143529,2.120251 0.09385,0.745401 0.287059,1.435587 0.568598,2.070559 0.281538,0.634971 0.684525,1.159512 1.20896,1.573624 0.524434,0.414111 1.21448,0.618407 2.081177,0.618407 0.574118,0 1.081991,-0.11043 1.52362,-0.325768 0.436109,-0.215338 0.811494,-0.507977 1.126154,-0.872396 0.314661,-0.364418 0.568598,-0.789572 0.772851,-1.269942 0.204254,-0.480369 0.364344,-0.977303 0.485792,-1.490802 0.121448,-0.513498 0.204254,-1.032518 0.253937,-1.546017 0.04416,-0.513498 0.06624,-0.999389 0.06624,-1.457673 z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path29"
+ d="M 101.36492,3.6435692 V 0.33067608 h 3.31222 V 3.6435692 Z m 0.39747,22.1356468 V 8.2208831 h 2.50072 V 25.779216 Z" />
+ <path
+ style="fill:#ff6600;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path31"
+ d="m 114.15008,10.32457 v 10.805553 c 0,0.938653 0.22634,1.661968 0.68453,2.180988 0.45819,0.51902 1.18688,0.773009 2.18606,0.773009 0.34779,0 0.64588,-0.02761 0.88326,-0.08282 0.2429,-0.05522 0.51891,-0.143558 0.83358,-0.25951 v 2.252767 c -0.36987,0.06074 -0.73973,0.11043 -1.1096,0.143559 -0.36434,0.03865 -0.73421,0.05522 -1.10959,0.05522 -0.82806,0 -1.5457,-0.0773 -2.15294,-0.231902 -0.60724,-0.154602 -1.11512,-0.408591 -1.52362,-0.767487 -0.40851,-0.353376 -0.71213,-0.817181 -0.91086,-1.396937 -0.19874,-0.579756 -0.2981,-1.292028 -0.2981,-2.142337 V 10.32457 h -2.86507 V 8.2153616 h 2.86507 V 4.1294602 l 2.50072,-1.5846672 v 5.6705686 h 4.73647 V 10.32457 Z" />
+ <polygon
+ style="fill:#b8b8b8;fill-rule:nonzero;stroke:none;stroke-width:1"
+ points="113.99551,3.5589046 106.50438,25.943019 103.66139,25.943019 96.186819,3.5589046 96.098493,3.5589046 96.098493,25.943019 93.526004,25.943019 93.526004,0.5 97.848449,0.5 105.06908,22.149756 105.14085,22.149756 112.37805,0.5 116.64529,0.5 116.64529,25.94854 114.06728,25.94854 114.06728,3.5589046 "
+ id="polygon33"
+ transform="translate(29.379376,-0.10000002)" />
+ <path
+ style="fill:#b8b8b8;fill-rule:nonzero;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ id="path35"
+ d="m 160.212,25.857131 c -1.18688,0 -2.24127,-0.176688 -3.15213,-0.530063 -0.91086,-0.353376 -1.71131,-0.839267 -2.39583,-1.457673 -0.68453,-0.618407 -1.25865,-1.341722 -1.72788,-2.169945 -0.46923,-0.828224 -0.84461,-1.711662 -1.13167,-2.655836 -0.28706,-0.944175 -0.49683,-1.921478 -0.6238,-2.937432 -0.12697,-1.015954 -0.1877,-2.020865 -0.1877,-3.014733 0,-1.054604 0.0552,-2.109208 0.17114,-3.158291 0.11592,-1.049083 0.30362,-2.0595154 0.57411,-3.0368189 0.2705,-0.9773034 0.63485,-1.8773061 1.08752,-2.7220938 0.45819,-0.8392662 1.0323,-1.5681027 1.72787,-2.1865094 0.69557,-0.6184067 1.52362,-1.10429774 2.47312,-1.457673 C 157.98177,0.17668763 159.09689,0 160.36657,0 c 1.23656,0 2.31855,0.18220912 3.2515,0.54662736 0.92742,0.36441822 1.72787,0.86687374 2.39583,1.49632344 0.66797,0.6294496 1.22,1.3693291 1.65611,2.2251598 0.43611,0.8503092 0.7839,1.7613548 1.03783,2.7276153 0.25394,0.9662605 0.43059,1.9711714 0.52996,3.0147331 0.0994,1.043561 0.15457,2.070558 0.15457,3.08099 0,0.491413 -0.0166,1.054605 -0.0552,1.684054 -0.0386,0.62945 -0.0994,1.286507 -0.18769,1.971172 -0.0883,0.684664 -0.21529,1.37485 -0.37538,2.070558 -0.16009,0.695707 -0.36987,1.352764 -0.6238,1.96565 -0.25394,0.612885 -0.55756,1.170555 -0.91086,1.673011 -0.35331,0.502455 -0.76734,0.900002 -1.23105,1.18712 l 0.0718,0.07178 5.23882,-0.325768 v 2.468106 z m 6.05032,-12.804332 c 0,-0.662579 -0.0221,-1.385894 -0.0607,-2.169945 -0.0442,-0.784052 -0.13249,-1.5736244 -0.2705,-2.3576758 C 165.79309,7.7411269 165.58884,6.97364 165.32938,6.239282 165.06992,5.504924 164.7111,4.8478669 164.27499,4.2736321 163.83336,3.7049188 163.29789,3.2466352 162.66304,2.904303 162.0282,2.5619707 161.26087,2.3908045 160.37209,2.3908045 c -0.93846,0 -1.73891,0.1656447 -2.40688,0.496934 -0.66796,0.3312893 -1.23104,0.7730084 -1.69475,1.3306787 -0.46371,0.5576703 -0.83357,1.198163 -1.12063,1.9269995 -0.28154,0.7288365 -0.49683,1.4852804 -0.6514,2.2803747 -0.14905,0.7950944 -0.24842,1.5901886 -0.2981,2.3852826 -0.0497,0.800616 -0.0718,1.546017 -0.0718,2.241725 0,0.662578 0.0276,1.37485 0.0883,2.142337 0.0607,0.767487 0.16561,1.534974 0.3257,2.307982 0.15457,0.767487 0.37538,1.512888 0.65692,2.230682 0.28154,0.717793 0.64589,1.358286 1.09303,1.910435 0.44715,0.552149 0.98815,0.993868 1.62299,1.325157 0.63484,0.331289 1.39113,0.496934 2.25231,0.496934 0.97158,0 1.79964,-0.154602 2.47864,-0.469327 0.67901,-0.314724 1.24208,-0.734358 1.69475,-1.258899 0.45267,-0.530063 0.80598,-1.137427 1.06543,-1.827613 0.25946,-0.690186 0.45267,-1.424544 0.57964,-2.197552 0.12697,-0.773008 0.20426,-1.562581 0.23186,-2.357675 0.0276,-0.806138 0.0442,-1.573625 0.0442,-2.302461 z" />
+ </g>
+ </g>
+ <g
+ aria-label="TM"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:1.25;font-family:Raleway-v4020;-inkscape-font-specification:'Raleway-v4020, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#b8b8b8;fill-opacity:1;stroke:none"
+ id="text956">
+ <path
+ d="m 180.78835,20.154465 v -0.923999 h -5.46933 v 0.923999 h 2.212 v 5.702667 h 1.05466 v -5.702667 z"
+ style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:9.33333302px;font-family:Raleway-v4020;-inkscape-font-specification:'Raleway-v4020 Semi-Bold';fill:#b8b8b8;fill-opacity:1"
+ id="path878" />
+ <path
+ d="m 187.23987,25.857132 h 1.05467 v -6.626666 h -1.12 l -2.19334,4.050666 -2.19333,-4.050666 h -1.12 v 6.626666 h 1.04533 v -4.722667 l 1.96,3.584 h 0.616 l 1.95067,-3.584 z"
+ style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:9.33333302px;font-family:Raleway-v4020;-inkscape-font-specification:'Raleway-v4020 Semi-Bold';fill:#b8b8b8;fill-opacity:1"
+ id="path880" />
+ </g>
+</svg>
diff --git a/deps/rabbitmq_management/priv/www/index.html b/deps/rabbitmq_management/priv/www/index.html
new file mode 100644
index 0000000000..3e8ce4d433
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/index.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>RabbitMQ Management</title>
+ <script src="js/ejs-1.0.min.js" type="text/javascript"></script>
+ <script src="js/jquery-3.5.1.min.js"></script>
+ <script src="js/jquery.flot-0.8.1.min.js" type="text/javascript"></script>
+ <script src="js/jquery.flot-0.8.1.time.min.js" type="text/javascript"></script>
+ <script src="js/sammy-0.7.6.min.js" type="text/javascript"></script>
+ <script src="js/json2-2016.10.28.js" type="text/javascript"></script>
+ <script src="js/base64.js" type="text/javascript"></script>
+ <script src="js/global.js" type="text/javascript"></script>
+ <script src="js/main.js" type="text/javascript"></script>
+ <script src="js/prefs.js" type="text/javascript"></script>
+ <script src="js/formatters.js" type="text/javascript"></script>
+ <script src="js/charts.js" type="text/javascript"></script>
+ <script src="js/singular/singular.js" type="application/javascript"></script>
+
+ <link href="css/main.css" rel="stylesheet" type="text/css"/>
+ <link href="favicon.ico" rel="shortcut icon" type="image/x-icon"/>
+
+ <script type="application/javascript">
+ var uaa_logged_in = false;
+ var uaa_invalid = false;
+ var auth = JSON.parse(sync_get('/auth'));
+ enable_uaa = auth.enable_uaa;
+ uaa_client_id = auth.uaa_client_id;
+ uaa_location = auth.uaa_location;
+ if (enable_uaa) {
+ Singular.init({
+ singularLocation: './js/singular/',
+ uaaLocation: uaa_location,
+ clientId: uaa_client_id,
+ onIdentityChange: function (identity) {
+ uaa_logged_in = true;
+ start_app_login();
+ },
+ onLogout: function () {
+ uaa_logged_in = false;
+ var hash = window.location.hash.substring(1);
+ var params = {}
+ hash.split('&').map(hk => {
+ let temp = hk.split('=');
+ params[temp[0]] = temp[1]
+ });
+ if (params.error) {
+ uaa_invalid = true;
+ replace_content('login-status', '<p class="warning">' + decodeURIComponent(params.error) + ':' + decodeURIComponent(params.error_description) + '</p> <button id="loginWindow" onclick="uaa_login_window()">Click here to log out</button>');
+ } else {
+ replace_content('login-status', '<button id="loginWindow" onclick="uaa_login_window()">Click here to log in</button>');
+ }
+ }
+ });
+ }
+ </script>
+
+<!--[if lte IE 8]>
+ <script src="js/excanvas.min.js" type="text/javascript"></script>
+ <link href="css/evil.css" rel="stylesheet" type="text/css"/>
+<![endif]-->
+ </head>
+
+ <body>
+ <div id="outer"></div>
+ <div id="debug"></div>
+ <div id="scratch"></div>
+ </body>
+</html>
diff --git a/deps/rabbitmq_management/priv/www/js/base64.js b/deps/rabbitmq_management/priv/www/js/base64.js
new file mode 100644
index 0000000000..7955b3a028
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/base64.js
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2010 Nick Galbreath
+ * https://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/* base64 encode/decode compatible with window.btoa/atob
+ *
+ * window.atob/btoa is a Firefox extension to convert binary data (the "b")
+ * to base64 (ascii, the "a").
+ *
+ * It is also found in Safari and Chrome. It is not available in IE.
+ *
+ * if (!window.btoa) window.btoa = base64.encode
+ * if (!window.atob) window.atob = base64.decode
+ *
+ * The original spec's for atob/btoa are a bit lacking
+ * https://developer.mozilla.org/en/DOM/window.atob
+ * https://developer.mozilla.org/en/DOM/window.btoa
+ *
+ * window.btoa and base64.encode takes a string where charCodeAt is [0,255]
+ * If any character is not [0,255], then an exception is thrown.
+ *
+ * window.atob and base64.decode take a base64-encoded string
+ * If the input length is not a multiple of 4, or contains invalid characters
+ * then an exception is thrown.
+ */
+base64 = {};
+base64.PADCHAR = '=';
+base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+base64.getbyte64 = function(s,i) {
+ // This is oddly fast, except on Chrome/V8.
+ // Minimal or no improvement in performance by using a
+ // object with properties mapping chars to value (eg. 'A': 0)
+ var idx = base64.ALPHA.indexOf(s.charAt(i));
+ if (idx == -1) {
+ throw "Cannot decode base64";
+ }
+ return idx;
+}
+
+base64.decode = function(s) {
+ // convert to string
+ s = "" + s;
+ var getbyte64 = base64.getbyte64;
+ var pads, i, b10;
+ var imax = s.length
+ if (imax == 0) {
+ return s;
+ }
+
+ if (imax % 4 != 0) {
+ throw "Cannot decode base64";
+ }
+
+ pads = 0
+ if (s.charAt(imax -1) == base64.PADCHAR) {
+ pads = 1;
+ if (s.charAt(imax -2) == base64.PADCHAR) {
+ pads = 2;
+ }
+ // either way, we want to ignore this last block
+ imax -= 4;
+ }
+
+ var x = [];
+ for (i = 0; i < imax; i += 4) {
+ b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
+ (getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
+ x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
+ }
+
+ switch (pads) {
+ case 1:
+ b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6)
+ x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
+ break;
+ case 2:
+ b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
+ x.push(String.fromCharCode(b10 >> 16));
+ break;
+ }
+ return x.join('');
+}
+
+base64.getbyte = function(s,i) {
+ var x = s.charCodeAt(i);
+ if (x > 255) {
+ throw "INVALID_CHARACTER_ERR: DOM Exception 5";
+ }
+ return x;
+}
+
+
+base64.encode = function(s) {
+ if (arguments.length != 1) {
+ throw "SyntaxError: Not enough arguments";
+ }
+ var padchar = base64.PADCHAR;
+ var alpha = base64.ALPHA;
+ var getbyte = base64.getbyte;
+
+ var i, b10;
+ var x = [];
+
+ // convert to string
+ s = "" + s;
+
+ var imax = s.length - s.length % 3;
+
+ if (s.length == 0) {
+ return s;
+ }
+ for (i = 0; i < imax; i += 3) {
+ b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
+ x.push(alpha.charAt(b10 >> 18));
+ x.push(alpha.charAt((b10 >> 12) & 0x3F));
+ x.push(alpha.charAt((b10 >> 6) & 0x3f));
+ x.push(alpha.charAt(b10 & 0x3f));
+ }
+ switch (s.length - imax) {
+ case 1:
+ b10 = getbyte(s,i) << 16;
+ x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
+ padchar + padchar);
+ break;
+ case 2:
+ b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
+ x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
+ alpha.charAt((b10 >> 6) & 0x3f) + padchar);
+ break;
+ }
+ return x.join('');
+}
diff --git a/deps/rabbitmq_management/priv/www/js/charts.js b/deps/rabbitmq_management/priv/www/js/charts.js
new file mode 100644
index 0000000000..97c66c40a7
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/charts.js
@@ -0,0 +1,329 @@
+//
+// Formatting side
+//
+
+function message_rates(id, stats) {
+ var items = [['Publish', 'publish'],
+ ['Publisher confirm', 'confirm'],
+ ['Publish (In)', 'publish_in'],
+ ['Publish (Out)', 'publish_out'],
+ ['Deliver (manual ack)', 'deliver'],
+ ['Deliver (auto ack)', 'deliver_no_ack'],
+ ['Consumer ack', 'ack'],
+ ['Redelivered', 'redeliver'],
+ ['Get (manual ack)', 'get'],
+ ['Get (auto ack)', 'get_no_ack'],
+ ['Get (empty)', 'get_empty'],
+ ['Unroutable (return)', 'return_unroutable'],
+ ['Unroutable (drop)', 'drop_unroutable'],
+ ['Disk read', 'disk_reads'],
+ ['Disk write', 'disk_writes']];
+ return rates_chart_or_text(id, stats, items, fmt_rate, fmt_rate_axis, true, 'Message rates', 'message-rates');
+}
+
+function queue_lengths(id, stats) {
+ var items = [['Ready', 'messages_ready'],
+ ['Unacked', 'messages_unacknowledged'],
+ ['Total', 'messages']];
+ return rates_chart_or_text(id, stats, items, fmt_num_thousands, fmt_plain_axis, false, 'Queued messages', 'queued-messages');
+}
+
+function data_rates(id, stats) {
+ var items = [['From client', 'recv_oct'], ['To client', 'send_oct']];
+ return rates_chart_or_text(id, stats, items, fmt_rate_bytes, fmt_rate_bytes_axis, true, 'Data rates');
+}
+
+function data_reductions(id, stats) {
+ var items = [['Reductions', 'reductions']];
+ return rates_chart_or_text(id, stats, items, fmt_rate, fmt_rate_axis, true, 'Reductions (per second)', 'process-reductions');
+}
+
+function rates_chart_or_text(id, stats, items, fmt, axis_fmt, chart_rates,
+ heading, heading_help) {
+ var prefix = chart_h3(id, heading, heading_help);
+
+ return prefix + rates_chart_or_text_no_heading(
+ id, id, stats, items, fmt, axis_fmt, chart_rates);
+}
+
+function rates_chart_or_text_no_heading(type_id, id, stats, items,
+ fmt, axis_fmt, chart_rates) {
+ var mode = get_pref('rate-mode-' + type_id);
+ var range = get_pref('chart-range');
+ var res;
+ if (keys(stats).length > 0) {
+ if (mode == 'chart') {
+ res = rates_chart(
+ type_id, id, items, stats, fmt, axis_fmt, 'full', chart_rates);
+ }
+ else {
+ res = rates_text(items, stats, mode, fmt, chart_rates);
+ }
+ if (res == "") res = '<p>Waiting for data...</p>';
+ }
+ else {
+ res = '<p>Currently idle</p>';
+ }
+ return res;
+}
+
+function chart_h3(id, heading, heading_help) {
+ var mode = get_pref('rate-mode-' + id);
+ var range = get_pref('chart-range');
+ return '<h3>' + heading +
+ ' <span class="popup-options-link" title="Click to change" ' +
+ 'type="rate" for="' + id + '">' + prefix_title(mode, range) +
+ '</span>' + (heading_help == undefined ? '' :
+ ' <span class="help" id="' + heading_help + '"></span>') +
+ '</h3>';
+}
+
+function prefix_title(mode, range) {
+ var desc = ALL_CHART_RANGES[range];
+ if (mode == 'chart') {
+ return desc.toLowerCase();
+ }
+ else if (mode == 'curr') {
+ return 'current value';
+ }
+ else {
+ return 'moving average: ' + desc.toLowerCase();
+ }
+}
+
+function node_stat_count(used_key, limit_key, stats, thresholds) {
+ var used = stats[used_key];
+ var limit = stats[limit_key];
+ if (typeof used == 'number') {
+ return node_stat(used_key, 'Used', limit_key, 'available', stats,
+ fmt_plain, fmt_plain_axis,
+ fmt_color(used / limit, thresholds));
+ } else {
+ return used;
+ }
+}
+
+function node_stat_count_bar(used_key, limit_key, stats, thresholds) {
+ var used = stats[used_key];
+ var limit = stats[limit_key];
+ if (typeof used == 'number') {
+ return node_stat_bar(used_key, limit_key, 'available', stats,
+ fmt_plain_axis,
+ fmt_color(used / limit, thresholds));
+ } else {
+ return used;
+ }
+}
+
+function node_stat(used_key, used_name, limit_key, suffix, stats, fmt,
+ axis_fmt, colour, help, invert) {
+ if (get_pref('rate-mode-node-stats') == 'chart') {
+ var items = [[used_name, used_key], ['Limit', limit_key]];
+ add_fake_limit_details(used_key, limit_key, stats);
+ return rates_chart('node-stats', 'node-stats-' + used_key, items, stats,
+ fmt, axis_fmt, 'node', false);
+ } else {
+ return node_stat_bar(used_key, limit_key, suffix, stats, axis_fmt,
+ colour, help, invert);
+ }
+}
+
+function add_fake_limit_details(used_key, limit_key, stats) {
+ var source = stats[used_key + '_details'].samples;
+ var limit = stats[limit_key];
+ var dest = [];
+ for (var i in source) {
+ dest[i] = {sample: limit, timestamp: source[i].timestamp};
+ }
+ stats[limit_key + '_details'] = {samples: dest};
+}
+
+function node_stat_bar(used_key, limit_key, suffix, stats, fmt, colour,
+ help, invert) {
+ var used = stats[used_key];
+ var limit = stats[limit_key];
+ var width = 120;
+
+ var res = '';
+ var other_colour = colour;
+ var ratio = invert ? (limit / used) : (used / limit);
+ if (ratio > 1) {
+ ratio = 1 / ratio;
+ inverted = true;
+ colour += '-dark';
+ }
+ else {
+ other_colour += '-dark';
+ }
+ var offset = Math.round(width * (1 - ratio));
+
+ res += '<div class="status-bar" style="width: ' + width + 'px;">';
+ res += '<div class="status-bar-main ' + colour + '" style="background-image: url(img/bg-' + other_colour + '.png); background-position: -' + offset + 'px 0px; background-repeat: no-repeat;">';
+ res += fmt(used);
+ if (help != null) {
+ res += ' <span class="help" id="' + help + '"></span>';
+ }
+ res += '</div>'; // status-bar-main
+ res += '<sub>' + fmt(limit) + ' ' + suffix + '</sub>';
+ res += '</div>'; // status-bar
+
+ return res;
+}
+
+function node_stats_prefs() {
+ return chart_h3('node-stats', 'Node statistics');
+}
+
+function rates_chart(type_id, id, items, stats, fmt, axis_fmt, type,
+ chart_rates) {
+ function show(key) {
+ return get_pref('chart-line-' + id + key) === 'true';
+ }
+
+ var size = get_pref('chart-size-' + type_id);
+ var legend = [];
+ chart_data[id] = {};
+ chart_data[id]['data'] = {};
+ chart_data[id]['fmt'] = axis_fmt;
+ var ix = 0;
+ for (var i in items) {
+ var name = items[i][0];
+ var key = items[i][1];
+ var key_details = key + '_details';
+ if (key_details in stats) {
+ if (show(key)) {
+ chart_data[id]['data'][name] = stats[key_details];
+ chart_data[id]['data'][name].ix = ix;
+ }
+ var value = chart_rates ? pick_rate(fmt, stats, key) :
+ pick_abs(fmt, stats, key);
+ legend.push({name: name,
+ key: key,
+ value: value,
+ show: show(key)});
+ ix++;
+ }
+ }
+ var html = '<div class="box"><div id="chart-' + id +
+ '" class="chart chart-' + type + ' chart-' + size +
+ (chart_rates ? ' chart-rates' : '') + '"></div>';
+ html += '<table class="legend">';
+ for (var i = 0; i < legend.length; i++) {
+ if (i % 3 == 0 && i < legend.length - 1) {
+ html += '</table><table class="legend">';
+ }
+
+ html += '<tr><th><span title="Click to toggle line" ';
+ html += 'class="rate-visibility-option';
+ html += legend[i].show ? '' : ' rate-visibility-option-hidden';
+ html += '" data-pref="chart-line-' + id + legend[i].key + '">';
+ html += legend[i].name + '</span></th><td>';
+ html += '<div class="colour-key" style="background: ' + chart_colors[type][i];
+ html += ';"></div>' + legend[i].value + '</td></tr>'
+ }
+ html += '</table></div>';
+ return legend.length > 0 ? html : '';
+}
+
+function rates_text(items, stats, mode, fmt, chart_rates) {
+ var res = '';
+ for (var i in items) {
+ var name = items[i][0];
+ var key = items[i][1];
+ var key_details = key + '_details';
+ if (key_details in stats) {
+ var details = stats[key_details];
+ res += '<div class="highlight">' + name + '<strong>';
+ res += chart_rates ? pick_rate(fmt, stats, key, mode) :
+ pick_abs(fmt, stats, key, mode);
+ res += '</strong></div>';
+ }
+ }
+ return res == '' ? '' : '<div class="box">' + res + '</div>';
+}
+
+//
+// Rendering side
+//
+
+function render_charts() {
+ $('.chart').map(function() {
+ render_chart($(this));
+ });
+}
+
+var chart_colors = {full: ['#edc240', '#afd8f8', '#cb4b4b', '#4da74d', '#9440ed', '#666666', '#aaaaaa',
+ '#7c79c3', '#8e6767', '#67808e', '#e5e4ae', '#4b4a55', '#bba0c1'],
+ node: ['#6ae26a', '#e24545']};
+
+var chart_chrome = {
+ series: { lines: { show: true } },
+ grid: { borderWidth: 2, borderColor: "#aaa" },
+ xaxis: { tickColor: "#fff", mode: "time", timezone: "browser" },
+ yaxis: { tickColor: "#eee", min: 0 },
+ legend: { show: false }
+};
+
+function chart_fill(mode, i) {
+ return mode =='node' && i == 0;
+}
+
+function render_chart(div) {
+ var id = div.attr('id').substring('chart-'.length);
+ var rate_mode = div.hasClass('chart-rates');
+ var out_data = [];
+ var data = chart_data[id]['data'];
+ var fmt = chart_data[id]['fmt'];
+
+ var mode = div.hasClass('chart-full') ? 'full': 'node';
+ var colors = chart_colors[mode];
+
+ for (var name in data) {
+ var series = data[name];
+ var samples = series.samples;
+ var i = series.ix;
+ var d = [];
+ for (var j = 1; j < samples.length; j++) {
+ var x = samples[j].timestamp;
+ var y;
+ if (rate_mode) {
+ // TODO This doesn't work well if you are looking at
+ // stuff in the browser that is finer granularity than
+ // the data we have in the DB (and thus we get
+ // duplicated entries). Do we care? We should just
+ // never allow that...
+ y = (samples[j - 1].sample - samples[j].sample) * 1000 /
+ (samples[j - 1].timestamp - samples[j].timestamp);
+ }
+ else {
+ y = samples[j].sample;
+ }
+ d.push([x, y]);
+ }
+ out_data.push({data: d, color: colors[i], shadowSize: 0,
+ lines: {show: true, fill: chart_fill(mode, i)}});
+ }
+ chart_data[id] = {};
+
+ chart_chrome.yaxis.tickFormatter = fmt_y_axis(fmt);
+ $.plot(div, out_data, chart_chrome);
+}
+
+function fmt_y_axis(fmt) {
+ return function (val, axis) {
+ // axis.ticks seems to include the bottom value but not the top
+ if (axis.max == 1 && axis.ticks.length > 1) {
+ var newTicks = [axis.ticks[0]];
+ axis.ticks = newTicks;
+ }
+ return fmt(val, axis.max);
+ }
+}
+
+function update_rate_options(sammy) {
+ var id = sammy.params['id'];
+ store_pref('rate-mode-' + id, sammy.params['mode']);
+ store_pref('chart-size-' + id, sammy.params['size']);
+ store_pref('chart-range', sammy.params['range']);
+ partial_update();
+}
diff --git a/deps/rabbitmq_management/priv/www/js/dispatcher.js b/deps/rabbitmq_management/priv/www/js/dispatcher.js
new file mode 100644
index 0000000000..d2842c2da8
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/dispatcher.js
@@ -0,0 +1,345 @@
+dispatcher_add(function(sammy) {
+ function path(p, r, t) {
+ sammy.get(p, function() {
+ render(r, t, p);
+ });
+ }
+ sammy.get('#/', function() {
+ var reqs = {'overview': {path: '/overview',
+ options: {ranges: ['lengths-over',
+ 'msg-rates-over']}},
+ 'vhosts': '/vhosts'};
+ if (user_monitor) {
+ reqs['nodes'] = '/nodes';
+ }
+ render(reqs, 'overview', '#/');
+ });
+
+ path('#/cluster-name', {'cluster_name': '/cluster-name'}, 'cluster-name');
+ sammy.put('#/cluster-name', function() {
+ if (sync_put(this, '/cluster-name')) {
+ setup_global_vars();
+ update();
+ }
+ return false;
+ });
+
+ sammy.get('#/nodes/:name', function() {
+ var name = esc(this.params['name']);
+ render({'node': {path: '/nodes/' + name,
+ options: {ranges: ['node-stats']}}},
+ 'node', '');
+ });
+
+ sammy.get('#/connections', function() {
+ renderConnections();
+ });
+
+
+ sammy.get('#/connections/:name', function() {
+ var name = esc(this.params['name']);
+ render({'connection': {path: '/connections/' + name,
+ options: {ranges: ['data-rates-conn']}},
+ 'channels': '/connections/' + name + '/channels'},
+ 'connection', '#/connections');
+ });
+ sammy.del('#/connections', function() {
+ var options = {headers: {
+ 'X-Reason': this.params['reason']
+ }};
+ if (sync_delete(this, '/connections/:name', options)) {
+ go_to('#/connections');
+ }
+
+ return false;
+ });
+
+ sammy.get('#/channels', function() {
+ renderChannels();
+ });
+
+ sammy.get('#/channels/:name', function() {
+ render({'channel': {path: '/channels/' + esc(this.params['name']),
+ options:{ranges:['data-rates-ch','msg-rates-ch']}}},
+ 'channel', '#/channels');
+ });
+
+
+ sammy.get('#/exchanges', function() {
+ renderExchanges();
+ });
+
+
+ sammy.get('#/exchanges/:vhost/:name', function() {
+ var path = '/exchanges/' + esc(this.params['vhost']) + '/' + esc(this.params['name']);
+ render({'exchange': {path: path,
+ options: {ranges:['msg-rates-x']}},
+ 'bindings_source': path + '/bindings/source',
+ 'bindings_destination': path + '/bindings/destination'},
+ 'exchange', '#/exchanges');
+ });
+ sammy.put('#/exchanges', function() {
+ if (sync_put(this, '/exchanges/:vhost/:name'))
+ update();
+ return false;
+ });
+ sammy.del('#/exchanges', function() {
+ if (sync_delete(this, '/exchanges/:vhost/:name'))
+ go_to('#/exchanges');
+ return false;
+ });
+ sammy.post('#/exchanges/publish', function() {
+ publish_msg(this.params);
+ return false;
+ });
+
+ sammy.get('#/queues', function() {
+ renderQueues();
+ });
+
+ sammy.get('#/queues/:vhost/:name', function() {
+ var path = '/queues/' + esc(this.params['vhost']) + '/' + esc(this.params['name']);
+ render({'queue': {path: path,
+ options: {ranges:['lengths-q', 'msg-rates-q', 'data-rates-q']}},
+ 'bindings': path + '/bindings'}, 'queue', '#/queues');
+ });
+ sammy.put('#/queues', function() {
+ if (sync_put(this, '/queues/:vhost/:name'))
+ update();
+ return false;
+ });
+ sammy.del('#/queues', function() {
+ if (this.params['mode'] == 'delete') {
+ if (sync_delete(this, '/queues/:vhost/:name'))
+ go_to('#/queues');
+ }
+ else if (this.params['mode'] == 'purge') {
+ if (sync_delete(this, '/queues/:vhost/:name/contents')) {
+ show_popup('info', "Queue purged");
+ partial_update();
+ }
+ }
+ return false;
+ });
+ sammy.post('#/queues/get', function() {
+ get_msgs(this.params);
+ return false;
+ });
+ sammy.post('#/queues/actions', function() {
+ if (sync_post(this, '/queues/:vhost/:name/actions'))
+ // We can't refresh fast enough, it's racy. So grey
+ // the button and wait for a normal refresh.
+ $('#action-button').addClass('wait').prop('disabled', true);
+ return false;
+ });
+ sammy.post('#/bindings', function() {
+ if (sync_post(this, '/bindings/:vhost/e/:source/:destination_type/:destination'))
+ update();
+ return false;
+ });
+ sammy.del('#/bindings', function() {
+ if (sync_delete(this, '/bindings/:vhost/e/:source/:destination_type/:destination/:properties_key'))
+ update();
+ return false;
+ });
+
+ path('#/vhosts', {'vhosts': {path: '/vhosts',
+ options: {sort:true}},
+ 'permissions': '/permissions'}, 'vhosts');
+ sammy.get('#/vhosts/:id', function() {
+ render({'vhost': {path: '/vhosts/' + esc(this.params['id']),
+ options: {ranges: ['lengths-vhost',
+ 'msg-rates-vhost',
+ 'data-rates-vhost']}},
+ 'permissions': '/vhosts/' + esc(this.params['id']) + '/permissions',
+ 'topic_permissions': '/vhosts/' + esc(this.params['id']) + '/topic-permissions',
+ 'users': '/users/',
+ 'exchanges' : '/exchanges/' + esc(this.params['id'])},
+ 'vhost', '#/vhosts');
+ });
+
+ sammy.put('#/vhosts', function() {
+ if (sync_put(this, '/vhosts/:name')) {
+ update_vhosts();
+ update();
+ }
+ return false;
+ });
+ sammy.del('#/vhosts', function() {
+ if (sync_delete(this, '/vhosts/:name')) {
+ update_vhosts();
+ go_to('#/vhosts');
+ }
+ return false;
+ });
+
+ path('#/users', {'users': {path: '/users',
+ options: {sort:true}},
+ 'permissions': '/permissions'}, 'users');
+ sammy.get('#/users/:id', function() {
+ var vhosts = JSON.parse(sync_get('/vhosts'));
+ render({'user': '/users/' + esc(this.params['id']),
+ 'permissions': '/users/' + esc(this.params['id']) + '/permissions',
+ 'topic_permissions': '/users/' + esc(this.params['id']) + '/topic-permissions',
+ 'vhosts': '/vhosts/',
+ 'exchanges': '/exchanges/' + esc(vhosts[0].name)}, 'user',
+ '#/users');
+ });
+ sammy.put('#/users-add', function() {
+ res = sync_put(this, '/users/:username');
+ if (res) {
+ if (res.http_status === 204) {
+ username = res.req_params.username;
+ show_popup('warn', "Updated an existing user: '" + username + "'");
+ }
+ update();
+ }
+ return false;
+ });
+ sammy.put('#/users-modify', function() {
+ if (sync_put(this, '/users/:username'))
+ go_to('#/users');
+ return false;
+ });
+ sammy.del('#/users', function() {
+ if (sync_delete(this, '/users/:username'))
+ go_to('#/users');
+ return false;
+ });
+
+ path('#/feature-flags', {'feature_flags': {path: '/feature-flags',
+ options: {sort:true}},
+ 'permissions': '/permissions'}, 'feature-flags');
+ sammy.put('#/feature-flags-enable', function() {
+ if (sync_put(this, '/feature-flags/:name/enable'))
+ update();
+ return false;
+ });
+
+ sammy.put('#/permissions', function() {
+ if (sync_put(this, '/permissions/:vhost/:username'))
+ update();
+ return false;
+ });
+ sammy.del('#/permissions', function() {
+ if (sync_delete(this, '/permissions/:vhost/:username'))
+ update();
+ return false;
+ });
+ sammy.put('#/topic-permissions', function() {
+ if (sync_put(this, '/topic-permissions/:vhost/:username'))
+ update();
+ return false;
+ });
+ sammy.del('#/topic-permissions', function() {
+ if (sync_delete(this, '/topic-permissions/:vhost/:username/:exchange'))
+ update();
+ return false;
+ });
+ path('#/policies', {'policies': '/policies',
+ 'operator_policies': '/operator-policies',
+ 'vhosts': '/vhosts'}, 'policies');
+ sammy.get('#/policies/:vhost/:id', function() {
+ render({'policy': '/policies/' + esc(this.params['vhost'])
+ + '/' + esc(this.params['id'])},
+ 'policy', '#/policies');
+ });
+ sammy.put('#/policies', function() {
+ put_cast_params(this, '/policies/:vhost/:name',
+ ['name', 'pattern', 'policy'], ['priority'], []);
+ return false;
+ });
+ sammy.del('#/policies', function() {
+ if (sync_delete(this, '/policies/:vhost/:name'))
+ go_to('#/policies');
+ return false;
+ });
+ sammy.put('#/operator_policies', function() {
+ this.params = rename_multifield(this.params, "definitionop", "definition");
+ put_cast_params(this, '/operator-policies/:vhost/:name',
+ ['name', 'pattern', 'policy'], ['priority'], []);
+ return false;
+ });
+ sammy.del('#/operator_policies', function() {
+ if (sync_delete(this, '/operator-policies/:vhost/:name'))
+ update();
+ });
+
+ sammy.put('#/logout', function() {
+ // clear a local storage value used by earlier versions
+ clear_pref('auth');
+ clear_cookie_value('auth');
+ if (uaa_logged_in) {
+ clear_pref('uaa_token');
+ var redirect;
+ if (window.location.hash != "") {
+ redirect = window.location.href.split(window.location.hash)[0];
+ } else {
+ redirect = window.location.href
+ };
+ uaa_logged_in = false;
+ var logoutRedirectUrl = Singular.properties.uaaLocation + '/logout.do?client_id=' + Singular.properties.clientId + '&redirect=' + redirect;
+ get(logoutRedirectUrl, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", function(req) { });
+ }
+ location.reload();
+ });
+
+ sammy.put('#/rate-options', function() {
+ update_rate_options(this);
+ });
+ sammy.put('#/column-options', function() {
+ update_column_options(this);
+ });
+ path('#/limits', {'limits': '/vhost-limits',
+ 'user_limits': '/user-limits',
+ 'users': '/users',
+ 'vhosts': '/vhosts'}, 'limits');
+
+ sammy.put('#/limits', function() {
+ var valAsInt = parseInt(this.params.value);
+ if (isNaN(valAsInt)) {
+ var e = 'Validation failed\n\n' +
+ this.params.name + ' should be a number, actually was "' +
+ this.params.value + '"';
+ show_popup('warn', fmt_escape_html(e));
+ } else {
+ this.params.value = valAsInt;
+ if (sync_put(this, '/vhost-limits/:vhost/:name')) {
+ update();
+ }
+ }
+ });
+ sammy.put('#/user-limits', function() {
+ var valAsInt = parseInt(this.params.value);
+ if (isNaN(valAsInt)) {
+ var e = 'Validation failed\n\n' +
+ this.params.name + ' should be a number, actually was "' +
+ this.params.value + '"';
+ show_popup('warn', fmt_escape_html(e));
+ } else {
+ this.params.value = valAsInt;
+ if (sync_put(this, '/user-limits/:user/:name')) {
+ update();
+ }
+ }
+ });
+ sammy.post('#/restart_vhost', function(){
+ if(sync_post(this, '/vhosts/:vhost/start/:node')) update();
+ })
+ sammy.del('#/limits', function() {
+ if (sync_delete(this, '/vhost-limits/:vhost/:name')) update();
+ });
+ sammy.del('#/user-limits', function() {
+ if (sync_delete(this, '/user-limits/:user/:name')) update();
+ });
+ sammy.del("#/reset", function(){
+ if(sync_delete(this, '/reset')){
+ update();
+ }
+ });
+ sammy.del("#/reset_node", function(){
+ if(sync_delete(this, '/reset/:node')){
+ update();
+ }
+ });
+});
diff --git a/deps/rabbitmq_management/priv/www/js/ejs-1.0.js b/deps/rabbitmq_management/priv/www/js/ejs-1.0.js
new file mode 100644
index 0000000000..259d87f748
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/ejs-1.0.js
@@ -0,0 +1,505 @@
+(function(){
+
+
+var rsplit = function(string, regex) {
+ var result = regex.exec(string),retArr = new Array(), first_idx, last_idx, first_bit;
+ while (result != null)
+ {
+ first_idx = result.index; last_idx = regex.lastIndex;
+ if ((first_idx) != 0)
+ {
+ first_bit = string.substring(0,first_idx);
+ retArr.push(string.substring(0,first_idx));
+ string = string.slice(first_idx);
+ }
+ retArr.push(result[0]);
+ string = string.slice(result[0].length);
+ result = regex.exec(string);
+ }
+ if (! string == '')
+ {
+ retArr.push(string);
+ }
+ return retArr;
+},
+chop = function(string){
+ return string.substr(0, string.length - 1);
+},
+extend = function(d, s){
+ for(var n in s){
+ if(s.hasOwnProperty(n)) d[n] = s[n]
+ }
+}
+
+
+EJS = function( options ){
+ options = typeof options == "string" ? {view: options} : options
+ this.set_options(options);
+ if(options.precompiled){
+ this.template = {};
+ this.template.process = options.precompiled;
+ EJS.update(this.name, this);
+ return;
+ }
+ if(options.element)
+ {
+ if(typeof options.element == 'string'){
+ var name = options.element
+ options.element = document.getElementById( options.element )
+ if(options.element == null) throw name+'does not exist!'
+ }
+ if(options.element.value){
+ this.text = options.element.value
+ }else{
+ this.text = options.element.innerHTML
+ }
+ this.name = options.element.id
+ this.type = '['
+ }else if(options.url){
+ options.url = EJS.endExt(options.url, this.extMatch);
+ this.name = this.name ? this.name : options.url;
+ var url = options.url
+ //options.view = options.absolute_url || options.view || options.;
+ var template = EJS.get(this.name /*url*/, this.cache);
+ if (template) return template;
+ if (template == EJS.INVALID_PATH) return null;
+ try{
+ this.text = EJS.request( url+(this.cache ? '' : '?'+Math.random() ));
+ }catch(e){}
+
+ if(this.text == null){
+ throw( {type: 'EJS', message: 'There is no template at '+url} );
+ }
+ //this.name = url;
+ }
+ var template = new EJS.Compiler(this.text, this.type);
+
+ template.compile(options, this.name);
+
+
+ EJS.update(this.name, this);
+ this.template = template;
+};
+/* @Prototype*/
+EJS.prototype = {
+ /**
+ * Renders an object with extra view helpers attached to the view.
+ * @param {Object} object data to be rendered
+ * @param {Object} extra_helpers an object with additonal view helpers
+ * @return {String} returns the result of the string
+ */
+ render : function(object, extra_helpers){
+ object = object || {};
+ this._extra_helpers = extra_helpers;
+ var v = new EJS.Helpers(object, extra_helpers || {});
+ return this.template.process.call(object, object,v);
+ },
+ update : function(element, options){
+ if(typeof element == 'string'){
+ element = document.getElementById(element)
+ }
+ if(options == null){
+ _template = this;
+ return function(object){
+ EJS.prototype.update.call(_template, element, object)
+ }
+ }
+ if(typeof options == 'string'){
+ params = {}
+ params.url = options
+ _template = this;
+ params.onComplete = function(request){
+ var object = eval( request.responseText )
+ EJS.prototype.update.call(_template, element, object)
+ }
+ EJS.ajax_request(params)
+ }else
+ {
+ element.innerHTML = this.render(options)
+ }
+ },
+ out : function(){
+ return this.template.out;
+ },
+ /**
+ * Sets options on this view to be rendered with.
+ * @param {Object} options
+ */
+ set_options : function(options){
+ this.type = options.type || EJS.type;
+ this.cache = options.cache != null ? options.cache : EJS.cache;
+ this.text = options.text || null;
+ this.name = options.name || null;
+ this.ext = options.ext || EJS.ext;
+ this.extMatch = new RegExp(this.ext.replace(/\./, '\.'));
+ }
+};
+EJS.endExt = function(path, match){
+ if(!path) return null;
+ match.lastIndex = 0
+ return path+ (match.test(path) ? '' : this.ext )
+}
+
+
+
+
+/* @Static*/
+EJS.Scanner = function(source, left, right) {
+
+ extend(this,
+ {left_delimiter: left +'%',
+ right_delimiter: '%'+right,
+ double_left: left+'%%',
+ double_right: '%%'+right,
+ left_equal: left+'%=',
+ left_comment: left+'%#'})
+
+ this.SplitRegexp = left=='[' ? /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/ : new RegExp('('+this.double_left+')|(%%'+this.double_right+')|('+this.left_equal+')|('+this.left_comment+')|('+this.left_delimiter+')|('+this.right_delimiter+'\n)|('+this.right_delimiter+')|(\n)') ;
+
+ this.source = source;
+ this.stag = null;
+ this.lines = 0;
+};
+
+EJS.Scanner.to_text = function(input){
+ if(input == null || input === undefined)
+ return '';
+ if(input instanceof Date)
+ return input.toDateString();
+ if(input.toString)
+ return input.toString();
+ return '';
+};
+
+EJS.Scanner.prototype = {
+ scan: function(block) {
+ scanline = this.scanline;
+ regex = this.SplitRegexp;
+ if (! this.source == '')
+ {
+ var source_split = rsplit(this.source, /\n/);
+ for(var i=0; i<source_split.length; i++) {
+ var item = source_split[i];
+ this.scanline(item, regex, block);
+ }
+ }
+ },
+ scanline: function(line, regex, block) {
+ this.lines++;
+ var line_split = rsplit(line, regex);
+ for(var i=0; i<line_split.length; i++) {
+ var token = line_split[i];
+ if (token != null) {
+ try{
+ block(token, this);
+ }catch(e){
+ throw {type: 'EJS.Scanner', line: this.lines};
+ }
+ }
+ }
+ }
+};
+
+
+EJS.Buffer = function(pre_cmd, post_cmd) {
+ this.line = new Array();
+ this.script = "";
+ this.pre_cmd = pre_cmd;
+ this.post_cmd = post_cmd;
+ for (var i=0; i<this.pre_cmd.length; i++)
+ {
+ this.push(pre_cmd[i]);
+ }
+};
+EJS.Buffer.prototype = {
+
+ push: function(cmd) {
+ this.line.push(cmd);
+ },
+
+ cr: function() {
+ this.script = this.script + this.line.join('; ');
+ this.line = new Array();
+ this.script = this.script + "\n";
+ },
+
+ close: function() {
+ if (this.line.length > 0)
+ {
+ for (var i=0; i<this.post_cmd.length; i++){
+ this.push(pre_cmd[i]);
+ }
+ this.script = this.script + this.line.join('; ');
+ line = null;
+ }
+ }
+
+};
+
+
+EJS.Compiler = function(source, left) {
+ this.pre_cmd = ['var ___ViewO = [];'];
+ this.post_cmd = new Array();
+ this.source = ' ';
+ if (source != null)
+ {
+ if (typeof source == 'string')
+ {
+ source = source.replace(/\r\n/g, "\n");
+ source = source.replace(/\r/g, "\n");
+ this.source = source;
+ }else if (source.innerHTML){
+ this.source = source.innerHTML;
+ }
+ if (typeof this.source != 'string'){
+ this.source = "";
+ }
+ }
+ left = left || '<';
+ var right = '>';
+ switch(left) {
+ case '[':
+ right = ']';
+ break;
+ case '<':
+ break;
+ default:
+ throw left+' is not a supported deliminator';
+ break;
+ }
+ this.scanner = new EJS.Scanner(this.source, left, right);
+ this.out = '';
+};
+EJS.Compiler.prototype = {
+ compile: function(options, name) {
+ options = options || {};
+ this.out = '';
+ var put_cmd = "___ViewO.push(";
+ var insert_cmd = put_cmd;
+ var buff = new EJS.Buffer(this.pre_cmd, this.post_cmd);
+ var content = '';
+ var clean = function(content)
+ {
+ content = content.replace(/\\/g, '\\\\');
+ content = content.replace(/\n/g, '\\n');
+ content = content.replace(/"/g, '\\"');
+ return content;
+ };
+ this.scanner.scan(function(token, scanner) {
+ if (scanner.stag == null)
+ {
+ switch(token) {
+ case '\n':
+ content = content + "\n";
+ buff.push(put_cmd + '"' + clean(content) + '");');
+ buff.cr();
+ content = '';
+ break;
+ case scanner.left_delimiter:
+ case scanner.left_equal:
+ case scanner.left_comment:
+ scanner.stag = token;
+ if (content.length > 0)
+ {
+ buff.push(put_cmd + '"' + clean(content) + '")');
+ }
+ content = '';
+ break;
+ case scanner.double_left:
+ content = content + scanner.left_delimiter;
+ break;
+ default:
+ content = content + token;
+ break;
+ }
+ }
+ else {
+ switch(token) {
+ case scanner.right_delimiter:
+ switch(scanner.stag) {
+ case scanner.left_delimiter:
+ if (content[content.length - 1] == '\n')
+ {
+ content = chop(content);
+ buff.push(content);
+ buff.cr();
+ }
+ else {
+ buff.push(content);
+ }
+ break;
+ case scanner.left_equal:
+ buff.push(insert_cmd + "(EJS.Scanner.to_text(" + content + ")))");
+ break;
+ }
+ scanner.stag = null;
+ content = '';
+ break;
+ case scanner.double_right:
+ content = content + scanner.right_delimiter;
+ break;
+ default:
+ content = content + token;
+ break;
+ }
+ }
+ });
+ if (content.length > 0)
+ {
+ // Chould be content.dump in Ruby
+ buff.push(put_cmd + '"' + clean(content) + '")');
+ }
+ buff.close();
+ this.out = buff.script + ";";
+ var to_be_evaled = '/*'+name+'*/this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {'+this.out+" return ___ViewO.join('');}}}catch(e){e.lineNumber=null;throw e;}};";
+
+ try{
+ eval(to_be_evaled);
+ }catch(e){
+ if(typeof JSLINT != 'undefined'){
+ JSLINT(this.out);
+ for(var i = 0; i < JSLINT.errors.length; i++){
+ var error = JSLINT.errors[i];
+ if(error.reason != "Unnecessary semicolon."){
+ error.line++;
+ var e = new Error();
+ e.lineNumber = error.line;
+ e.message = error.reason;
+ if(options.view)
+ e.fileName = options.view;
+ throw e;
+ }
+ }
+ }else{
+ throw e;
+ }
+ }
+ }
+};
+
+
+//type, cache, folder
+/**
+ * Sets default options for all views
+ * @param {Object} options Set view with the following options
+ * <table class="options">
+ <tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr>
+ <tr>
+ <td>type</td>
+ <td>'<'</td>
+ <td>type of magic tags. Options are '&lt;' or '['
+ </td>
+ </tr>
+ <tr>
+ <td>cache</td>
+ <td>true in production mode, false in other modes</td>
+ <td>true to cache template.
+ </td>
+ </tr>
+ </tbody></table>
+ *
+ */
+EJS.config = function(options){
+ EJS.cache = options.cache != null ? options.cache : EJS.cache;
+ EJS.type = options.type != null ? options.type : EJS.type;
+ EJS.ext = options.ext != null ? options.ext : EJS.ext;
+
+ var templates_directory = EJS.templates_directory || {}; //nice and private container
+ EJS.templates_directory = templates_directory;
+ EJS.get = function(path, cache){
+ if(cache == false) return null;
+ if(templates_directory[path]) return templates_directory[path];
+ return null;
+ };
+
+ EJS.update = function(path, template) {
+ if(path == null) return;
+ templates_directory[path] = template ;
+ };
+
+ EJS.INVALID_PATH = -1;
+};
+EJS.config( {cache: true, type: '<', ext: '.ejs' } );
+
+
+
+/**
+ * @constructor
+ * By adding functions to EJS.Helpers.prototype, those functions will be available in the
+ * views.
+ * @init Creates a view helper. This function is called internally. You should never call it.
+ * @param {Object} data The data passed to the view. Helpers have access to it through this._data
+ */
+EJS.Helpers = function(data, extras){
+ this._data = data;
+ this._extras = extras;
+ extend(this, extras );
+};
+/* @prototype*/
+EJS.Helpers.prototype = {
+ /**
+ * Renders a new view. If data is passed in, uses that to render the view.
+ * @param {Object} options standard options passed to a new view.
+ * @param {optional:Object} data
+ * @return {String}
+ */
+ view: function(options, data, helpers){
+ if(!helpers) helpers = this._extras
+ if(!data) data = this._data;
+ return new EJS(options).render(data, helpers);
+ },
+ /**
+ * For a given value, tries to create a human representation.
+ * @param {Object} input the value being converted.
+ * @param {Object} null_text what text should be present if input == null or undefined, defaults to ''
+ * @return {String}
+ */
+ to_text: function(input, null_text) {
+ if(input == null || input === undefined) return null_text || '';
+ if(input instanceof Date) return input.toDateString();
+ if(input.toString) return input.toString().replace(/\n/g, '<br />').replace(/''/g, "'");
+ return '';
+ }
+};
+ EJS.newRequest = function(){
+ var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }];
+ for(var i = 0; i < factories.length; i++) {
+ try {
+ var request = factories[i]();
+ if (request != null) return request;
+ }
+ catch(e) { continue;}
+ }
+ }
+
+ EJS.request = function(path){
+ var request = new EJS.newRequest()
+ request.open("GET", path, false);
+
+ try{request.send(null);}
+ catch(e){return null;}
+
+ if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null;
+
+ return request.responseText
+ }
+ EJS.ajax_request = function(params){
+ params.method = ( params.method ? params.method : 'GET')
+
+ var request = new EJS.newRequest();
+ request.onreadystatechange = function(){
+ if(request.readyState == 4){
+ if(request.status == 200){
+ params.onComplete(request)
+ }else
+ {
+ params.onComplete(request)
+ }
+ }
+ }
+ request.open(params.method, params.url)
+ request.send(null)
+ }
+
+
+})(); \ No newline at end of file
diff --git a/deps/rabbitmq_management/priv/www/js/ejs-1.0.min.js b/deps/rabbitmq_management/priv/www/js/ejs-1.0.min.js
new file mode 100644
index 0000000000..ccce9220ee
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/ejs-1.0.min.js
@@ -0,0 +1 @@
+(function(){var rsplit=function(string,regex){var result=regex.exec(string),retArr=new Array(),first_idx,last_idx,first_bit;while(result!=null){first_idx=result.index;last_idx=regex.lastIndex;if((first_idx)!=0){first_bit=string.substring(0,first_idx);retArr.push(string.substring(0,first_idx));string=string.slice(first_idx)}retArr.push(result[0]);string=string.slice(result[0].length);result=regex.exec(string)}if(!string==""){retArr.push(string)}return retArr},chop=function(string){return string.substr(0,string.length-1)},extend=function(d,s){for(var n in s){if(s.hasOwnProperty(n)){d[n]=s[n]}}};EJS=function(options){options=typeof options=="string"?{view:options}:options;this.set_options(options);if(options.precompiled){this.template={};this.template.process=options.precompiled;EJS.update(this.name,this);return }if(options.element){if(typeof options.element=="string"){var name=options.element;options.element=document.getElementById(options.element);if(options.element==null){throw name+"does not exist!"}}if(options.element.value){this.text=options.element.value}else{this.text=options.element.innerHTML}this.name=options.element.id;this.type="["}else{if(options.url){options.url=EJS.endExt(options.url,this.extMatch);this.name=this.name?this.name:options.url;var url=options.url;var template=EJS.get(this.name,this.cache);if(template){return template}if(template==EJS.INVALID_PATH){return null}try{this.text=EJS.request(url+(this.cache?"":"?"+Math.random()))}catch(e){}if(this.text==null){throw ({type:"EJS",message:"There is no template at "+url})}}}var template=new EJS.Compiler(this.text,this.type);template.compile(options,this.name);EJS.update(this.name,this);this.template=template};EJS.prototype={render:function(object,extra_helpers){object=object||{};this._extra_helpers=extra_helpers;var v=new EJS.Helpers(object,extra_helpers||{});return this.template.process.call(object,object,v)},update:function(element,options){if(typeof element=="string"){element=document.getElementById(element)}if(options==null){_template=this;return function(object){EJS.prototype.update.call(_template,element,object)}}if(typeof options=="string"){params={};params.url=options;_template=this;params.onComplete=function(request){var object=eval(request.responseText);EJS.prototype.update.call(_template,element,object)};EJS.ajax_request(params)}else{element.innerHTML=this.render(options)}},out:function(){return this.template.out},set_options:function(options){this.type=options.type||EJS.type;this.cache=options.cache!=null?options.cache:EJS.cache;this.text=options.text||null;this.name=options.name||null;this.ext=options.ext||EJS.ext;this.extMatch=new RegExp(this.ext.replace(/\./,"."))}};EJS.endExt=function(path,match){if(!path){return null}match.lastIndex=0;return path+(match.test(path)?"":this.ext)};EJS.Scanner=function(source,left,right){extend(this,{left_delimiter:left+"%",right_delimiter:"%"+right,double_left:left+"%%",double_right:"%%"+right,left_equal:left+"%=",left_comment:left+"%#"});this.SplitRegexp=left=="["?/(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/:new RegExp("("+this.double_left+")|(%%"+this.double_right+")|("+this.left_equal+")|("+this.left_comment+")|("+this.left_delimiter+")|("+this.right_delimiter+"\n)|("+this.right_delimiter+")|(\n)");this.source=source;this.stag=null;this.lines=0};EJS.Scanner.to_text=function(input){if(input==null||input===undefined){return""}if(input instanceof Date){return input.toDateString()}if(input.toString){return input.toString()}return""};EJS.Scanner.prototype={scan:function(block){scanline=this.scanline;regex=this.SplitRegexp;if(!this.source==""){var source_split=rsplit(this.source,/\n/);for(var i=0;i<source_split.length;i++){var item=source_split[i];this.scanline(item,regex,block)}}},scanline:function(line,regex,block){this.lines++;var line_split=rsplit(line,regex);for(var i=0;i<line_split.length;i++){var token=line_split[i];if(token!=null){try{block(token,this)}catch(e){throw {type:"EJS.Scanner",line:this.lines}}}}}};EJS.Buffer=function(pre_cmd,post_cmd){this.line=new Array();this.script="";this.pre_cmd=pre_cmd;this.post_cmd=post_cmd;for(var i=0;i<this.pre_cmd.length;i++){this.push(pre_cmd[i])}};EJS.Buffer.prototype={push:function(cmd){this.line.push(cmd)},cr:function(){this.script=this.script+this.line.join("; ");this.line=new Array();this.script=this.script+"\n"},close:function(){if(this.line.length>0){for(var i=0;i<this.post_cmd.length;i++){this.push(pre_cmd[i])}this.script=this.script+this.line.join("; ");line=null}}};EJS.Compiler=function(source,left){this.pre_cmd=["var ___ViewO = [];"];this.post_cmd=new Array();this.source=" ";if(source!=null){if(typeof source=="string"){source=source.replace(/\r\n/g,"\n");source=source.replace(/\r/g,"\n");this.source=source}else{if(source.innerHTML){this.source=source.innerHTML}}if(typeof this.source!="string"){this.source=""}}left=left||"<";var right=">";switch(left){case"[":right="]";break;case"<":break;default:throw left+" is not a supported deliminator";break}this.scanner=new EJS.Scanner(this.source,left,right);this.out=""};EJS.Compiler.prototype={compile:function(options,name){options=options||{};this.out="";var put_cmd="___ViewO.push(";var insert_cmd=put_cmd;var buff=new EJS.Buffer(this.pre_cmd,this.post_cmd);var content="";var clean=function(content){content=content.replace(/\\/g,"\\\\");content=content.replace(/\n/g,"\\n");content=content.replace(/"/g,'\\"');return content};this.scanner.scan(function(token,scanner){if(scanner.stag==null){switch(token){case"\n":content=content+"\n";buff.push(put_cmd+'"'+clean(content)+'");');buff.cr();content="";break;case scanner.left_delimiter:case scanner.left_equal:case scanner.left_comment:scanner.stag=token;if(content.length>0){buff.push(put_cmd+'"'+clean(content)+'")')}content="";break;case scanner.double_left:content=content+scanner.left_delimiter;break;default:content=content+token;break}}else{switch(token){case scanner.right_delimiter:switch(scanner.stag){case scanner.left_delimiter:if(content[content.length-1]=="\n"){content=chop(content);buff.push(content);buff.cr()}else{buff.push(content)}break;case scanner.left_equal:buff.push(insert_cmd+"(EJS.Scanner.to_text("+content+")))");break}scanner.stag=null;content="";break;case scanner.double_right:content=content+scanner.right_delimiter;break;default:content=content+token;break}}});if(content.length>0){buff.push(put_cmd+'"'+clean(content)+'")')}buff.close();this.out=buff.script+";";var to_be_evaled="/*"+name+"*/this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {"+this.out+" return ___ViewO.join('');}}}catch(e){e.lineNumber=null;throw e;}};";try{eval(to_be_evaled)}catch(e){if(typeof JSLINT!="undefined"){JSLINT(this.out);for(var i=0;i<JSLINT.errors.length;i++){var error=JSLINT.errors[i];if(error.reason!="Unnecessary semicolon."){error.line++;var e=new Error();e.lineNumber=error.line;e.message=error.reason;if(options.view){e.fileName=options.view}throw e}}}else{throw e}}}};EJS.config=function(options){EJS.cache=options.cache!=null?options.cache:EJS.cache;EJS.type=options.type!=null?options.type:EJS.type;EJS.ext=options.ext!=null?options.ext:EJS.ext;var templates_directory=EJS.templates_directory||{};EJS.templates_directory=templates_directory;EJS.get=function(path,cache){if(cache==false){return null}if(templates_directory[path]){return templates_directory[path]}return null};EJS.update=function(path,template){if(path==null){return }templates_directory[path]=template};EJS.INVALID_PATH=-1};EJS.config({cache:true,type:"<",ext:".ejs"});EJS.Helpers=function(data,extras){this._data=data;this._extras=extras;extend(this,extras)};EJS.Helpers.prototype={view:function(options,data,helpers){if(!helpers){helpers=this._extras}if(!data){data=this._data}return new EJS(options).render(data,helpers)},to_text:function(input,null_text){if(input==null||input===undefined){return null_text||""}if(input instanceof Date){return input.toDateString()}if(input.toString){return input.toString().replace(/\n/g,"<br />").replace(/''/g,"'")}return""}};EJS.newRequest=function(){var factories=[function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new XMLHttpRequest()},function(){return new ActiveXObject("Microsoft.XMLHTTP")}];for(var i=0;i<factories.length;i++){try{var request=factories[i]();if(request!=null){return request}}catch(e){continue}}};EJS.request=function(path){var request=new EJS.newRequest();request.open("GET",path,false);try{request.send(null)}catch(e){return null}if(request.status==404||request.status==2||(request.status==0&&request.responseText=="")){return null}return request.responseText};EJS.ajax_request=function(params){params.method=(params.method?params.method:"GET");var request=new EJS.newRequest();request.onreadystatechange=function(){if(request.readyState==4){if(request.status==200){params.onComplete(request)}else{params.onComplete(request)}}};request.open(params.method,params.url);request.send(null)}})();EJS.Helpers.prototype.date_tag=function(C,O,A){if(!(O instanceof Date)){O=new Date()}var B=["January","February","March","April","May","June","July","August","September","October","November","December"];var G=[],D=[],P=[];var J=O.getFullYear();var H=O.getMonth();var N=O.getDate();for(var M=J-15;M<J+15;M++){G.push({value:M,text:M})}for(var E=0;E<12;E++){D.push({value:(E),text:B[E]})}for(var I=0;I<31;I++){P.push({value:(I+1),text:(I+1)})}var L=this.select_tag(C+"[year]",J,G,{id:C+"[year]"});var F=this.select_tag(C+"[month]",H,D,{id:C+"[month]"});var K=this.select_tag(C+"[day]",N,P,{id:C+"[day]"});return L+F+K};EJS.Helpers.prototype.form_tag=function(B,A){A=A||{};A.action=B;if(A.multipart==true){A.method="post";A.enctype="multipart/form-data"}return this.start_tag_for("form",A)};EJS.Helpers.prototype.form_tag_end=function(){return this.tag_end("form")};EJS.Helpers.prototype.hidden_field_tag=function(A,C,B){return this.input_field_tag(A,C,"hidden",B)};EJS.Helpers.prototype.input_field_tag=function(A,D,C,B){B=B||{};B.id=B.id||A;B.value=D||"";B.type=C||"text";B.name=A;return this.single_tag_for("input",B)};EJS.Helpers.prototype.is_current_page=function(A){return(window.location.href==A||window.location.pathname==A?true:false)};EJS.Helpers.prototype.link_to=function(B,A,C){if(!B){var B="null"}if(!C){var C={}}if(C.confirm){C.onclick=' var ret_confirm = confirm("'+C.confirm+'"); if(!ret_confirm){ return false;} ';C.confirm=null}C.href=A;return this.start_tag_for("a",C)+B+this.tag_end("a")};EJS.Helpers.prototype.submit_link_to=function(B,A,C){if(!B){var B="null"}if(!C){var C={}}C.onclick=C.onclick||"";if(C.confirm){C.onclick=' var ret_confirm = confirm("'+C.confirm+'"); if(!ret_confirm){ return false;} ';C.confirm=null}C.value=B;C.type="submit";C.onclick=C.onclick+(A?this.url_for(A):"")+"return false;";return this.start_tag_for("input",C)};EJS.Helpers.prototype.link_to_if=function(F,B,A,D,C,E){return this.link_to_unless((F==false),B,A,D,C,E)};EJS.Helpers.prototype.link_to_unless=function(E,B,A,C,D){C=C||{};if(E){if(D&&typeof D=="function"){return D(B,A,C,D)}else{return B}}else{return this.link_to(B,A,C)}};EJS.Helpers.prototype.link_to_unless_current=function(B,A,C,D){C=C||{};return this.link_to_unless(this.is_current_page(A),B,A,C,D)};EJS.Helpers.prototype.password_field_tag=function(A,C,B){return this.input_field_tag(A,C,"password",B)};EJS.Helpers.prototype.select_tag=function(D,G,H,F){F=F||{};F.id=F.id||D;F.value=G;F.name=D;var B="";B+=this.start_tag_for("select",F);for(var E=0;E<H.length;E++){var C=H[E];var A={value:C.value};if(C.value==G){A.selected="selected"}B+=this.start_tag_for("option",A)+C.text+this.tag_end("option")}B+=this.tag_end("select");return B};EJS.Helpers.prototype.single_tag_for=function(A,B){return this.tag(A,B,"/>")};EJS.Helpers.prototype.start_tag_for=function(A,B){return this.tag(A,B)};EJS.Helpers.prototype.submit_tag=function(A,B){B=B||{};B.type=B.type||"submit";B.value=A||"Submit";return this.single_tag_for("input",B)};EJS.Helpers.prototype.tag=function(C,E,D){if(!D){var D=">"}var B=" ";for(var A in E){if(E[A]!=null){var F=E[A].toString()}else{var F=""}if(A=="Class"){A="class"}if(F.indexOf("'")!=-1){B+=A+'="'+F+'" '}else{B+=A+"='"+F+"' "}}return"<"+C+B+D};EJS.Helpers.prototype.tag_end=function(A){return"</"+A+">"};EJS.Helpers.prototype.text_area_tag=function(A,C,B){B=B||{};B.id=B.id||A;B.name=B.name||A;C=C||"";if(B.size){B.cols=B.size.split("x")[0];B.rows=B.size.split("x")[1];delete B.size}B.cols=B.cols||50;B.rows=B.rows||4;return this.start_tag_for("textarea",B)+C+this.tag_end("textarea")};EJS.Helpers.prototype.text_tag=EJS.Helpers.prototype.text_area_tag;EJS.Helpers.prototype.text_field_tag=function(A,C,B){return this.input_field_tag(A,C,"text",B)};EJS.Helpers.prototype.url_for=function(A){return'window.location="'+A+'";'};EJS.Helpers.prototype.img_tag=function(B,C,A){A=A||{};A.src=B;A.alt=C;return this.single_tag_for("img",A)} \ No newline at end of file
diff --git a/deps/rabbitmq_management/priv/www/js/excanvas.js b/deps/rabbitmq_management/priv/www/js/excanvas.js
new file mode 100644
index 0000000000..5e1915f68a
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/excanvas.js
@@ -0,0 +1,1428 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns only support repeat.
+// * Radial gradient are not implemented. The VML version of these look very
+// different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+// width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+// Quirks mode will draw the canvas using border-box. Either change your
+// doctype to HTML5
+// (https://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+// or use Box Sizing Behavior from WebFX
+// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Filling very large shapes (above 5000 points) is buggy.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+ // alias some functions to make (compiled) code shorter
+ var m = Math;
+ var mr = m.round;
+ var ms = m.sin;
+ var mc = m.cos;
+ var abs = m.abs;
+ var sqrt = m.sqrt;
+
+ // this is used for sub pixel precision
+ var Z = 10;
+ var Z2 = Z / 2;
+
+ var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
+
+ /**
+ * This funtion is assigned to the <canvas> elements as element.getContext().
+ * @this {HTMLElement}
+ * @return {CanvasRenderingContext2D_}
+ */
+ function getContext() {
+ return this.context_ ||
+ (this.context_ = new CanvasRenderingContext2D_(this));
+ }
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * Binds a function to an object. The returned function will always use the
+ * passed in {@code obj} as {@code this}.
+ *
+ * Example:
+ *
+ * g = bind(f, obj, a, b)
+ * g(c, d) // will do f.call(obj, a, b, c, d)
+ *
+ * @param {Function} f The function to bind the object to
+ * @param {Object} obj The object that should act as this when the function
+ * is called
+ * @param {*} var_args Rest arguments that will be used as the initial
+ * arguments when the function is called
+ * @return {Function} A new function that has bound this
+ */
+ function bind(f, obj, var_args) {
+ var a = slice.call(arguments, 2);
+ return function() {
+ return f.apply(obj, a.concat(slice.call(arguments)));
+ };
+ }
+
+ function encodeHtmlAttribute(s) {
+ return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+ }
+
+ function addNamespace(doc, prefix, urn) {
+ if (!doc.namespaces[prefix]) {
+ doc.namespaces.add(prefix, urn, '#default#VML');
+ }
+ }
+
+ function addNamespacesAndStylesheet(doc) {
+ addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
+ addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
+
+ // Setup default CSS. Only add one style sheet per document
+ if (!doc.styleSheets['ex_canvas_']) {
+ var ss = doc.createStyleSheet();
+ ss.owningElement.id = 'ex_canvas_';
+ ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+ // default size is 300x150 in Gecko and Opera
+ 'text-align:left;width:300px;height:150px}';
+ }
+ }
+
+ // Add namespaces and stylesheet at startup.
+ addNamespacesAndStylesheet(document);
+
+ var G_vmlCanvasManager_ = {
+ init: function(opt_doc) {
+ var doc = opt_doc || document;
+ // Create a dummy element so that IE will allow canvas elements to be
+ // recognized.
+ doc.createElement('canvas');
+ doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+ },
+
+ init_: function(doc) {
+ // find all canvas elements
+ var els = doc.getElementsByTagName('canvas');
+ for (var i = 0; i < els.length; i++) {
+ this.initElement(els[i]);
+ }
+ },
+
+ /**
+ * Public initializes a canvas element so that it can be used as canvas
+ * element from now on. This is called automatically before the page is
+ * loaded but if you are creating elements using createElement you need to
+ * make sure this is called on the element.
+ * @param {HTMLElement} el The canvas element to initialize.
+ * @return {HTMLElement} the element that was created.
+ */
+ initElement: function(el) {
+ if (!el.getContext) {
+ el.getContext = getContext;
+
+ // Add namespaces and stylesheet to document of the element.
+ addNamespacesAndStylesheet(el.ownerDocument);
+
+ // Remove fallback content. There is no way to hide text nodes so we
+ // just remove all childNodes. We could hide all elements and remove
+ // text nodes but who really cares about the fallback content.
+ el.innerHTML = '';
+
+ // do not use inline function because that will leak memory
+ el.attachEvent('onpropertychange', onPropertyChange);
+ el.attachEvent('onresize', onResize);
+
+ var attrs = el.attributes;
+ if (attrs.width && attrs.width.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setWidth_(attrs.width.nodeValue);
+ el.style.width = attrs.width.nodeValue + 'px';
+ } else {
+ el.width = el.clientWidth;
+ }
+ if (attrs.height && attrs.height.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setHeight_(attrs.height.nodeValue);
+ el.style.height = attrs.height.nodeValue + 'px';
+ } else {
+ el.height = el.clientHeight;
+ }
+ //el.getContext().setCoordsize_()
+ }
+ return el;
+ }
+ };
+
+ function onPropertyChange(e) {
+ var el = e.srcElement;
+
+ switch (e.propertyName) {
+ case 'width':
+ el.getContext().clearRect();
+ el.style.width = el.attributes.width.nodeValue + 'px';
+ // In IE8 this does not trigger onresize.
+ el.firstChild.style.width = el.clientWidth + 'px';
+ break;
+ case 'height':
+ el.getContext().clearRect();
+ el.style.height = el.attributes.height.nodeValue + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ break;
+ }
+ }
+
+ function onResize(e) {
+ var el = e.srcElement;
+ if (el.firstChild) {
+ el.firstChild.style.width = el.clientWidth + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ }
+ }
+
+ G_vmlCanvasManager_.init();
+
+ // precompute "00" to "FF"
+ var decToHex = [];
+ for (var i = 0; i < 16; i++) {
+ for (var j = 0; j < 16; j++) {
+ decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
+ }
+ }
+
+ function createMatrixIdentity() {
+ return [
+ [1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1]
+ ];
+ }
+
+ function matrixMultiply(m1, m2) {
+ var result = createMatrixIdentity();
+
+ for (var x = 0; x < 3; x++) {
+ for (var y = 0; y < 3; y++) {
+ var sum = 0;
+
+ for (var z = 0; z < 3; z++) {
+ sum += m1[x][z] * m2[z][y];
+ }
+
+ result[x][y] = sum;
+ }
+ }
+ return result;
+ }
+
+ function copyState(o1, o2) {
+ o2.fillStyle = o1.fillStyle;
+ o2.lineCap = o1.lineCap;
+ o2.lineJoin = o1.lineJoin;
+ o2.lineWidth = o1.lineWidth;
+ o2.miterLimit = o1.miterLimit;
+ o2.shadowBlur = o1.shadowBlur;
+ o2.shadowColor = o1.shadowColor;
+ o2.shadowOffsetX = o1.shadowOffsetX;
+ o2.shadowOffsetY = o1.shadowOffsetY;
+ o2.strokeStyle = o1.strokeStyle;
+ o2.globalAlpha = o1.globalAlpha;
+ o2.font = o1.font;
+ o2.textAlign = o1.textAlign;
+ o2.textBaseline = o1.textBaseline;
+ o2.arcScaleX_ = o1.arcScaleX_;
+ o2.arcScaleY_ = o1.arcScaleY_;
+ o2.lineScale_ = o1.lineScale_;
+ }
+
+ var colorData = {
+ aliceblue: '#F0F8FF',
+ antiquewhite: '#FAEBD7',
+ aquamarine: '#7FFFD4',
+ azure: '#F0FFFF',
+ beige: '#F5F5DC',
+ bisque: '#FFE4C4',
+ black: '#000000',
+ blanchedalmond: '#FFEBCD',
+ blueviolet: '#8A2BE2',
+ brown: '#A52A2A',
+ burlywood: '#DEB887',
+ cadetblue: '#5F9EA0',
+ chartreuse: '#7FFF00',
+ chocolate: '#D2691E',
+ coral: '#FF7F50',
+ cornflowerblue: '#6495ED',
+ cornsilk: '#FFF8DC',
+ crimson: '#DC143C',
+ cyan: '#00FFFF',
+ darkblue: '#00008B',
+ darkcyan: '#008B8B',
+ darkgoldenrod: '#B8860B',
+ darkgray: '#A9A9A9',
+ darkgreen: '#006400',
+ darkgrey: '#A9A9A9',
+ darkkhaki: '#BDB76B',
+ darkmagenta: '#8B008B',
+ darkolivegreen: '#556B2F',
+ darkorange: '#FF8C00',
+ darkorchid: '#9932CC',
+ darkred: '#8B0000',
+ darksalmon: '#E9967A',
+ darkseagreen: '#8FBC8F',
+ darkslateblue: '#483D8B',
+ darkslategray: '#2F4F4F',
+ darkslategrey: '#2F4F4F',
+ darkturquoise: '#00CED1',
+ darkviolet: '#9400D3',
+ deeppink: '#FF1493',
+ deepskyblue: '#00BFFF',
+ dimgray: '#696969',
+ dimgrey: '#696969',
+ dodgerblue: '#1E90FF',
+ firebrick: '#B22222',
+ floralwhite: '#FFFAF0',
+ forestgreen: '#228B22',
+ gainsboro: '#DCDCDC',
+ ghostwhite: '#F8F8FF',
+ gold: '#FFD700',
+ goldenrod: '#DAA520',
+ grey: '#808080',
+ greenyellow: '#ADFF2F',
+ honeydew: '#F0FFF0',
+ hotpink: '#FF69B4',
+ indianred: '#CD5C5C',
+ indigo: '#4B0082',
+ ivory: '#FFFFF0',
+ khaki: '#F0E68C',
+ lavender: '#E6E6FA',
+ lavenderblush: '#FFF0F5',
+ lawngreen: '#7CFC00',
+ lemonchiffon: '#FFFACD',
+ lightblue: '#ADD8E6',
+ lightcoral: '#F08080',
+ lightcyan: '#E0FFFF',
+ lightgoldenrodyellow: '#FAFAD2',
+ lightgreen: '#90EE90',
+ lightgrey: '#D3D3D3',
+ lightpink: '#FFB6C1',
+ lightsalmon: '#FFA07A',
+ lightseagreen: '#20B2AA',
+ lightskyblue: '#87CEFA',
+ lightslategray: '#778899',
+ lightslategrey: '#778899',
+ lightsteelblue: '#B0C4DE',
+ lightyellow: '#FFFFE0',
+ limegreen: '#32CD32',
+ linen: '#FAF0E6',
+ magenta: '#FF00FF',
+ mediumaquamarine: '#66CDAA',
+ mediumblue: '#0000CD',
+ mediumorchid: '#BA55D3',
+ mediumpurple: '#9370DB',
+ mediumseagreen: '#3CB371',
+ mediumslateblue: '#7B68EE',
+ mediumspringgreen: '#00FA9A',
+ mediumturquoise: '#48D1CC',
+ mediumvioletred: '#C71585',
+ midnightblue: '#191970',
+ mintcream: '#F5FFFA',
+ mistyrose: '#FFE4E1',
+ moccasin: '#FFE4B5',
+ navajowhite: '#FFDEAD',
+ oldlace: '#FDF5E6',
+ olivedrab: '#6B8E23',
+ orange: '#FFA500',
+ orangered: '#FF4500',
+ orchid: '#DA70D6',
+ palegoldenrod: '#EEE8AA',
+ palegreen: '#98FB98',
+ paleturquoise: '#AFEEEE',
+ palevioletred: '#DB7093',
+ papayawhip: '#FFEFD5',
+ peachpuff: '#FFDAB9',
+ peru: '#CD853F',
+ pink: '#FFC0CB',
+ plum: '#DDA0DD',
+ powderblue: '#B0E0E6',
+ rosybrown: '#BC8F8F',
+ royalblue: '#4169E1',
+ saddlebrown: '#8B4513',
+ salmon: '#FA8072',
+ sandybrown: '#F4A460',
+ seagreen: '#2E8B57',
+ seashell: '#FFF5EE',
+ sienna: '#A0522D',
+ skyblue: '#87CEEB',
+ slateblue: '#6A5ACD',
+ slategray: '#708090',
+ slategrey: '#708090',
+ snow: '#FFFAFA',
+ springgreen: '#00FF7F',
+ steelblue: '#4682B4',
+ tan: '#D2B48C',
+ thistle: '#D8BFD8',
+ tomato: '#FF6347',
+ turquoise: '#40E0D0',
+ violet: '#EE82EE',
+ wheat: '#F5DEB3',
+ whitesmoke: '#F5F5F5',
+ yellowgreen: '#9ACD32'
+ };
+
+
+ function getRgbHslContent(styleString) {
+ var start = styleString.indexOf('(', 3);
+ var end = styleString.indexOf(')', start + 1);
+ var parts = styleString.substring(start + 1, end).split(',');
+ // add alpha if needed
+ if (parts.length != 4 || styleString.charAt(3) != 'a') {
+ parts[3] = 1;
+ }
+ return parts;
+ }
+
+ function percent(s) {
+ return parseFloat(s) / 100;
+ }
+
+ function clamp(v, min, max) {
+ return Math.min(max, Math.max(min, v));
+ }
+
+ function hslToRgb(parts){
+ var r, g, b, h, s, l;
+ h = parseFloat(parts[0]) / 360 % 360;
+ if (h < 0)
+ h++;
+ s = clamp(percent(parts[1]), 0, 1);
+ l = clamp(percent(parts[2]), 0, 1);
+ if (s == 0) {
+ r = g = b = l; // achromatic
+ } else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hueToRgb(p, q, h + 1 / 3);
+ g = hueToRgb(p, q, h);
+ b = hueToRgb(p, q, h - 1 / 3);
+ }
+
+ return '#' + decToHex[Math.floor(r * 255)] +
+ decToHex[Math.floor(g * 255)] +
+ decToHex[Math.floor(b * 255)];
+ }
+
+ function hueToRgb(m1, m2, h) {
+ if (h < 0)
+ h++;
+ if (h > 1)
+ h--;
+
+ if (6 * h < 1)
+ return m1 + (m2 - m1) * 6 * h;
+ else if (2 * h < 1)
+ return m2;
+ else if (3 * h < 2)
+ return m1 + (m2 - m1) * (2 / 3 - h) * 6;
+ else
+ return m1;
+ }
+
+ var processStyleCache = {};
+
+ function processStyle(styleString) {
+ if (styleString in processStyleCache) {
+ return processStyleCache[styleString];
+ }
+
+ var str, alpha = 1;
+
+ styleString = String(styleString);
+ if (styleString.charAt(0) == '#') {
+ str = styleString;
+ } else if (/^rgb/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ var str = '#', n;
+ for (var i = 0; i < 3; i++) {
+ if (parts[i].indexOf('%') != -1) {
+ n = Math.floor(percent(parts[i]) * 255);
+ } else {
+ n = +parts[i];
+ }
+ str += decToHex[clamp(n, 0, 255)];
+ }
+ alpha = +parts[3];
+ } else if (/^hsl/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ str = hslToRgb(parts);
+ alpha = parts[3];
+ } else {
+ str = colorData[styleString] || styleString;
+ }
+ return processStyleCache[styleString] = {color: str, alpha: alpha};
+ }
+
+ var DEFAULT_STYLE = {
+ style: 'normal',
+ variant: 'normal',
+ weight: 'normal',
+ size: 10,
+ family: 'sans-serif'
+ };
+
+ // Internal text style cache
+ var fontStyleCache = {};
+
+ function processFontStyle(styleString) {
+ if (fontStyleCache[styleString]) {
+ return fontStyleCache[styleString];
+ }
+
+ var el = document.createElement('div');
+ var style = el.style;
+ try {
+ style.font = styleString;
+ } catch (ex) {
+ // Ignore failures to set to invalid font.
+ }
+
+ return fontStyleCache[styleString] = {
+ style: style.fontStyle || DEFAULT_STYLE.style,
+ variant: style.fontVariant || DEFAULT_STYLE.variant,
+ weight: style.fontWeight || DEFAULT_STYLE.weight,
+ size: style.fontSize || DEFAULT_STYLE.size,
+ family: style.fontFamily || DEFAULT_STYLE.family
+ };
+ }
+
+ function getComputedStyle(style, element) {
+ var computedStyle = {};
+
+ for (var p in style) {
+ computedStyle[p] = style[p];
+ }
+
+ // Compute the size
+ var canvasFontSize = parseFloat(element.currentStyle.fontSize),
+ fontSize = parseFloat(style.size);
+
+ if (typeof style.size == 'number') {
+ computedStyle.size = style.size;
+ } else if (style.size.indexOf('px') != -1) {
+ computedStyle.size = fontSize;
+ } else if (style.size.indexOf('em') != -1) {
+ computedStyle.size = canvasFontSize * fontSize;
+ } else if(style.size.indexOf('%') != -1) {
+ computedStyle.size = (canvasFontSize / 100) * fontSize;
+ } else if (style.size.indexOf('pt') != -1) {
+ computedStyle.size = fontSize / .75;
+ } else {
+ computedStyle.size = canvasFontSize;
+ }
+
+ // Different scaling between normal text and VML text. This was found using
+ // trial and error to get the same size as non VML text.
+ computedStyle.size *= 0.981;
+
+ return computedStyle;
+ }
+
+ function buildStyle(style) {
+ return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
+ style.size + 'px ' + style.family;
+ }
+
+ var lineCapMap = {
+ 'butt': 'flat',
+ 'round': 'round'
+ };
+
+ function processLineCap(lineCap) {
+ return lineCapMap[lineCap] || 'square';
+ }
+
+ /**
+ * This class implements CanvasRenderingContext2D interface as described by
+ * the WHATWG.
+ * @param {HTMLElement} canvasElement The element that the 2D context should
+ * be associated with
+ */
+ function CanvasRenderingContext2D_(canvasElement) {
+ this.m_ = createMatrixIdentity();
+
+ this.mStack_ = [];
+ this.aStack_ = [];
+ this.currentPath_ = [];
+
+ // Canvas context properties
+ this.strokeStyle = '#000';
+ this.fillStyle = '#000';
+
+ this.lineWidth = 1;
+ this.lineJoin = 'miter';
+ this.lineCap = 'butt';
+ this.miterLimit = Z * 1;
+ this.globalAlpha = 1;
+ this.font = '10px sans-serif';
+ this.textAlign = 'left';
+ this.textBaseline = 'alphabetic';
+ this.canvas = canvasElement;
+
+ var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
+ canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
+ var el = canvasElement.ownerDocument.createElement('div');
+ el.style.cssText = cssText;
+ canvasElement.appendChild(el);
+
+ var overlayEl = el.cloneNode(false);
+ // Use a non transparent background.
+ overlayEl.style.backgroundColor = 'red';
+ overlayEl.style.filter = 'alpha(opacity=0)';
+ canvasElement.appendChild(overlayEl);
+
+ this.element_ = el;
+ this.arcScaleX_ = 1;
+ this.arcScaleY_ = 1;
+ this.lineScale_ = 1;
+ }
+
+ var contextPrototype = CanvasRenderingContext2D_.prototype;
+ contextPrototype.clearRect = function() {
+ if (this.textMeasureEl_) {
+ this.textMeasureEl_.removeNode(true);
+ this.textMeasureEl_ = null;
+ }
+ this.element_.innerHTML = '';
+ };
+
+ contextPrototype.beginPath = function() {
+ // TODO: Branch current matrix so that save/restore has no effect
+ // as per safari docs.
+ this.currentPath_ = [];
+ };
+
+ contextPrototype.moveTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.lineTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+ aCP2x, aCP2y,
+ aX, aY) {
+ var p = getCoords(this, aX, aY);
+ var cp1 = getCoords(this, aCP1x, aCP1y);
+ var cp2 = getCoords(this, aCP2x, aCP2y);
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ // Helper function that takes the already fixed cordinates.
+ function bezierCurveTo(self, cp1, cp2, p) {
+ self.currentPath_.push({
+ type: 'bezierCurveTo',
+ cp1x: cp1.x,
+ cp1y: cp1.y,
+ cp2x: cp2.x,
+ cp2y: cp2.y,
+ x: p.x,
+ y: p.y
+ });
+ self.currentX_ = p.x;
+ self.currentY_ = p.y;
+ }
+
+ contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+ // the following is lifted almost directly from
+ // https://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+ var cp = getCoords(this, aCPx, aCPy);
+ var p = getCoords(this, aX, aY);
+
+ var cp1 = {
+ x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+ y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+ };
+ var cp2 = {
+ x: cp1.x + (p.x - this.currentX_) / 3.0,
+ y: cp1.y + (p.y - this.currentY_) / 3.0
+ };
+
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ contextPrototype.arc = function(aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise) {
+ aRadius *= Z;
+ var arcType = aClockwise ? 'at' : 'wa';
+
+ var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+ var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+ var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+ var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+ // IE won't render arches drawn counter clockwise if xStart == xEnd.
+ if (xStart == xEnd && !aClockwise) {
+ xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+ // that can be represented in binary
+ }
+
+ var p = getCoords(this, aX, aY);
+ var pStart = getCoords(this, xStart, yStart);
+ var pEnd = getCoords(this, xEnd, yEnd);
+
+ this.currentPath_.push({type: arcType,
+ x: p.x,
+ y: p.y,
+ radius: aRadius,
+ xStart: pStart.x,
+ yStart: pStart.y,
+ xEnd: pEnd.x,
+ yEnd: pEnd.y});
+
+ };
+
+ contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ };
+
+ contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.stroke();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.fill();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+ var gradient = new CanvasGradient_('gradient');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ return gradient;
+ };
+
+ contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+ aX1, aY1, aR1) {
+ var gradient = new CanvasGradient_('gradientradial');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.r0_ = aR0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ gradient.r1_ = aR1;
+ return gradient;
+ };
+
+ contextPrototype.drawImage = function(image, var_args) {
+ var dx, dy, dw, dh, sx, sy, sw, sh;
+
+ // to find the original width we overide the width and height
+ var oldRuntimeWidth = image.runtimeStyle.width;
+ var oldRuntimeHeight = image.runtimeStyle.height;
+ image.runtimeStyle.width = 'auto';
+ image.runtimeStyle.height = 'auto';
+
+ // get the original size
+ var w = image.width;
+ var h = image.height;
+
+ // and remove overides
+ image.runtimeStyle.width = oldRuntimeWidth;
+ image.runtimeStyle.height = oldRuntimeHeight;
+
+ if (arguments.length == 3) {
+ dx = arguments[1];
+ dy = arguments[2];
+ sx = sy = 0;
+ sw = dw = w;
+ sh = dh = h;
+ } else if (arguments.length == 5) {
+ dx = arguments[1];
+ dy = arguments[2];
+ dw = arguments[3];
+ dh = arguments[4];
+ sx = sy = 0;
+ sw = w;
+ sh = h;
+ } else if (arguments.length == 9) {
+ sx = arguments[1];
+ sy = arguments[2];
+ sw = arguments[3];
+ sh = arguments[4];
+ dx = arguments[5];
+ dy = arguments[6];
+ dw = arguments[7];
+ dh = arguments[8];
+ } else {
+ throw Error('Invalid number of arguments');
+ }
+
+ var d = getCoords(this, dx, dy);
+
+ var w2 = sw / 2;
+ var h2 = sh / 2;
+
+ var vmlStr = [];
+
+ var W = 10;
+ var H = 10;
+
+ // For some reason that I've now forgotten, using divs didn't work
+ vmlStr.push(' <g_vml_:group',
+ ' coordsize="', Z * W, ',', Z * H, '"',
+ ' coordorigin="0,0"' ,
+ ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+ // If filters are necessary (rotation exists), create them
+ // filters are bog-slow, so only create them if abbsolutely necessary
+ // The following check doesn't account for skews (which don't exist
+ // in the canvas spec (yet) anyway.
+
+ if (this.m_[0][0] != 1 || this.m_[0][1] ||
+ this.m_[1][1] != 1 || this.m_[1][0]) {
+ var filter = [];
+
+ // Note the 12/21 reversal
+ filter.push('M11=', this.m_[0][0], ',',
+ 'M12=', this.m_[1][0], ',',
+ 'M21=', this.m_[0][1], ',',
+ 'M22=', this.m_[1][1], ',',
+ 'Dx=', mr(d.x / Z), ',',
+ 'Dy=', mr(d.y / Z), '');
+
+ // Bounding box calculation (need to minimize displayed area so that
+ // filters don't waste time on unused pixels.
+ var max = d;
+ var c2 = getCoords(this, dx + dw, dy);
+ var c3 = getCoords(this, dx, dy + dh);
+ var c4 = getCoords(this, dx + dw, dy + dh);
+
+ max.x = m.max(max.x, c2.x, c3.x, c4.x);
+ max.y = m.max(max.y, c2.y, c3.y, c4.y);
+
+ vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
+ 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
+ filter.join(''), ", sizingmethod='clip');");
+
+ } else {
+ vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
+ }
+
+ vmlStr.push(' ">' ,
+ '<g_vml_:image src="', image.src, '"',
+ ' style="width:', Z * dw, 'px;',
+ ' height:', Z * dh, 'px"',
+ ' cropleft="', sx / w, '"',
+ ' croptop="', sy / h, '"',
+ ' cropright="', (w - sx - sw) / w, '"',
+ ' cropbottom="', (h - sy - sh) / h, '"',
+ ' />',
+ '</g_vml_:group>');
+
+ this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
+ };
+
+ contextPrototype.stroke = function(aFill) {
+ var W = 10;
+ var H = 10;
+ // Divide the shape into chunks if it's too long because IE has a limit
+ // somewhere for how long a VML shape can be. This simple division does
+ // not work with fills, only strokes, unfortunately.
+ var chunkSize = 5000;
+
+ var min = {x: null, y: null};
+ var max = {x: null, y: null};
+
+ for (var j = 0; j < this.currentPath_.length; j += chunkSize) {
+ var lineStr = [];
+ var lineOpen = false;
+
+ lineStr.push('<g_vml_:shape',
+ ' filled="', !!aFill, '"',
+ ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
+ ' coordorigin="0,0"',
+ ' coordsize="', Z * W, ',', Z * H, '"',
+ ' stroked="', !aFill, '"',
+ ' path="');
+
+ var newSeq = false;
+
+ for (var i = j; i < Math.min(j + chunkSize, this.currentPath_.length); i++) {
+ if (i % chunkSize == 0 && i > 0) { // move into position for next chunk
+ lineStr.push(' m ', mr(this.currentPath_[i-1].x), ',', mr(this.currentPath_[i-1].y));
+ }
+
+ var p = this.currentPath_[i];
+ var c;
+
+ switch (p.type) {
+ case 'moveTo':
+ c = p;
+ lineStr.push(' m ', mr(p.x), ',', mr(p.y));
+ break;
+ case 'lineTo':
+ lineStr.push(' l ', mr(p.x), ',', mr(p.y));
+ break;
+ case 'close':
+ lineStr.push(' x ');
+ p = null;
+ break;
+ case 'bezierCurveTo':
+ lineStr.push(' c ',
+ mr(p.cp1x), ',', mr(p.cp1y), ',',
+ mr(p.cp2x), ',', mr(p.cp2y), ',',
+ mr(p.x), ',', mr(p.y));
+ break;
+ case 'at':
+ case 'wa':
+ lineStr.push(' ', p.type, ' ',
+ mr(p.x - this.arcScaleX_ * p.radius), ',',
+ mr(p.y - this.arcScaleY_ * p.radius), ' ',
+ mr(p.x + this.arcScaleX_ * p.radius), ',',
+ mr(p.y + this.arcScaleY_ * p.radius), ' ',
+ mr(p.xStart), ',', mr(p.yStart), ' ',
+ mr(p.xEnd), ',', mr(p.yEnd));
+ break;
+ }
+
+
+ // TODO: Following is broken for curves due to
+ // move to proper paths.
+
+ // Figure out dimensions so we can do gradient fills
+ // properly
+ if (p) {
+ if (min.x == null || p.x < min.x) {
+ min.x = p.x;
+ }
+ if (max.x == null || p.x > max.x) {
+ max.x = p.x;
+ }
+ if (min.y == null || p.y < min.y) {
+ min.y = p.y;
+ }
+ if (max.y == null || p.y > max.y) {
+ max.y = p.y;
+ }
+ }
+ }
+ lineStr.push(' ">');
+
+ if (!aFill) {
+ appendStroke(this, lineStr);
+ } else {
+ appendFill(this, lineStr, min, max);
+ }
+
+ lineStr.push('</g_vml_:shape>');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ }
+ };
+
+ function appendStroke(ctx, lineStr) {
+ var a = processStyle(ctx.strokeStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ var lineWidth = ctx.lineScale_ * ctx.lineWidth;
+
+ // VML cannot correctly render a line if the width is less than 1px.
+ // In that case, we dilute the color to make the line look thinner.
+ if (lineWidth < 1) {
+ opacity *= lineWidth;
+ }
+
+ lineStr.push(
+ '<g_vml_:stroke',
+ ' opacity="', opacity, '"',
+ ' joinstyle="', ctx.lineJoin, '"',
+ ' miterlimit="', ctx.miterLimit, '"',
+ ' endcap="', processLineCap(ctx.lineCap), '"',
+ ' weight="', lineWidth, 'px"',
+ ' color="', color, '" />'
+ );
+ }
+
+ function appendFill(ctx, lineStr, min, max) {
+ var fillStyle = ctx.fillStyle;
+ var arcScaleX = ctx.arcScaleX_;
+ var arcScaleY = ctx.arcScaleY_;
+ var width = max.x - min.x;
+ var height = max.y - min.y;
+ if (fillStyle instanceof CanvasGradient_) {
+ // TODO: Gradients transformed with the transformation matrix.
+ var angle = 0;
+ var focus = {x: 0, y: 0};
+
+ // additional offset
+ var shift = 0;
+ // scale factor for offset
+ var expansion = 1;
+
+ if (fillStyle.type_ == 'gradient') {
+ var x0 = fillStyle.x0_ / arcScaleX;
+ var y0 = fillStyle.y0_ / arcScaleY;
+ var x1 = fillStyle.x1_ / arcScaleX;
+ var y1 = fillStyle.y1_ / arcScaleY;
+ var p0 = getCoords(ctx, x0, y0);
+ var p1 = getCoords(ctx, x1, y1);
+ var dx = p1.x - p0.x;
+ var dy = p1.y - p0.y;
+ angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+ // The angle should be a non-negative number.
+ if (angle < 0) {
+ angle += 360;
+ }
+
+ // Very small angles produce an unexpected result because they are
+ // converted to a scientific notation string.
+ if (angle < 1e-6) {
+ angle = 0;
+ }
+ } else {
+ var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
+ focus = {
+ x: (p0.x - min.x) / width,
+ y: (p0.y - min.y) / height
+ };
+
+ width /= arcScaleX * Z;
+ height /= arcScaleY * Z;
+ var dimension = m.max(width, height);
+ shift = 2 * fillStyle.r0_ / dimension;
+ expansion = 2 * fillStyle.r1_ / dimension - shift;
+ }
+
+ // We need to sort the color stops in ascending order by offset,
+ // otherwise IE won't interpret it correctly.
+ var stops = fillStyle.colors_;
+ stops.sort(function(cs1, cs2) {
+ return cs1.offset - cs2.offset;
+ });
+
+ var length = stops.length;
+ var color1 = stops[0].color;
+ var color2 = stops[length - 1].color;
+ var opacity1 = stops[0].alpha * ctx.globalAlpha;
+ var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
+
+ var colors = [];
+ for (var i = 0; i < length; i++) {
+ var stop = stops[i];
+ colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+ }
+
+ // When colors attribute is used, the meanings of opacity and o:opacity2
+ // are reversed.
+ lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
+ ' method="none" focus="100%"',
+ ' color="', color1, '"',
+ ' color2="', color2, '"',
+ ' colors="', colors.join(','), '"',
+ ' opacity="', opacity2, '"',
+ ' g_o_:opacity2="', opacity1, '"',
+ ' angle="', angle, '"',
+ ' focusposition="', focus.x, ',', focus.y, '" />');
+ } else if (fillStyle instanceof CanvasPattern_) {
+ if (width && height) {
+ var deltaLeft = -min.x;
+ var deltaTop = -min.y;
+ lineStr.push('<g_vml_:fill',
+ ' position="',
+ deltaLeft / width * arcScaleX * arcScaleX, ',',
+ deltaTop / height * arcScaleY * arcScaleY, '"',
+ ' type="tile"',
+ // TODO: Figure out the correct size to fit the scale.
+ //' size="', w, 'px ', h, 'px"',
+ ' src="', fillStyle.src_, '" />');
+ }
+ } else {
+ var a = processStyle(ctx.fillStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
+ '" />');
+ }
+ }
+
+ contextPrototype.fill = function() {
+ this.stroke(true);
+ };
+
+ contextPrototype.closePath = function() {
+ this.currentPath_.push({type: 'close'});
+ };
+
+ function getCoords(ctx, aX, aY) {
+ var m = ctx.m_;
+ return {
+ x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+ y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+ };
+ };
+
+ contextPrototype.save = function() {
+ var o = {};
+ copyState(this, o);
+ this.aStack_.push(o);
+ this.mStack_.push(this.m_);
+ this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+ };
+
+ contextPrototype.restore = function() {
+ if (this.aStack_.length) {
+ copyState(this.aStack_.pop(), this);
+ this.m_ = this.mStack_.pop();
+ }
+ };
+
+ function matrixIsFinite(m) {
+ return isFinite(m[0][0]) && isFinite(m[0][1]) &&
+ isFinite(m[1][0]) && isFinite(m[1][1]) &&
+ isFinite(m[2][0]) && isFinite(m[2][1]);
+ }
+
+ function setM(ctx, m, updateLineScale) {
+ if (!matrixIsFinite(m)) {
+ return;
+ }
+ ctx.m_ = m;
+
+ if (updateLineScale) {
+ // Get the line scale.
+ // Determinant of this.m_ means how much the area is enlarged by the
+ // transformation. So its square root can be used as a scale factor
+ // for width.
+ var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+ ctx.lineScale_ = sqrt(abs(det));
+ }
+ }
+
+ contextPrototype.translate = function(aX, aY) {
+ var m1 = [
+ [1, 0, 0],
+ [0, 1, 0],
+ [aX, aY, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.rotate = function(aRot) {
+ var c = mc(aRot);
+ var s = ms(aRot);
+
+ var m1 = [
+ [c, s, 0],
+ [-s, c, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.scale = function(aX, aY) {
+ this.arcScaleX_ *= aX;
+ this.arcScaleY_ *= aY;
+ var m1 = [
+ [aX, 0, 0],
+ [0, aY, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
+ var m1 = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
+ var m = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, m, true);
+ };
+
+ /**
+ * The text drawing function.
+ * The maxWidth argument isn't taken in account, since no browser supports
+ * it yet.
+ */
+ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
+ var m = this.m_,
+ delta = 1000,
+ left = 0,
+ right = delta,
+ offset = {x: 0, y: 0},
+ lineStr = [];
+
+ var fontStyle = getComputedStyle(processFontStyle(this.font),
+ this.element_);
+
+ var fontStyleString = buildStyle(fontStyle);
+
+ var elementStyle = this.element_.currentStyle;
+ var textAlign = this.textAlign.toLowerCase();
+ switch (textAlign) {
+ case 'left':
+ case 'center':
+ case 'right':
+ break;
+ case 'end':
+ textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
+ break;
+ case 'start':
+ textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
+ break;
+ default:
+ textAlign = 'left';
+ }
+
+ // 1.75 is an arbitrary number, as there is no info about the text baseline
+ switch (this.textBaseline) {
+ case 'hanging':
+ case 'top':
+ offset.y = fontStyle.size / 1.75;
+ break;
+ case 'middle':
+ break;
+ default:
+ case null:
+ case 'alphabetic':
+ case 'ideographic':
+ case 'bottom':
+ offset.y = -fontStyle.size / 2.25;
+ break;
+ }
+
+ switch(textAlign) {
+ case 'right':
+ left = delta;
+ right = 0.05;
+ break;
+ case 'center':
+ left = right = delta / 2;
+ break;
+ }
+
+ var d = getCoords(this, x + offset.x, y + offset.y);
+
+ lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ',
+ ' coordsize="100 100" coordorigin="0 0"',
+ ' filled="', !stroke, '" stroked="', !!stroke,
+ '" style="position:absolute;width:1px;height:1px;">');
+
+ if (stroke) {
+ appendStroke(this, lineStr);
+ } else {
+ // TODO: Fix the min and max params.
+ appendFill(this, lineStr, {x: -left, y: 0},
+ {x: right, y: fontStyle.size});
+ }
+
+ var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
+ m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
+
+ var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
+
+ lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ',
+ ' offset="', skewOffset, '" origin="', left ,' 0" />',
+ '<g_vml_:path textpathok="true" />',
+ '<g_vml_:textpath on="true" string="',
+ encodeHtmlAttribute(text),
+ '" style="v-text-align:', textAlign,
+ ';font:', encodeHtmlAttribute(fontStyleString),
+ '" /></g_vml_:line>');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ contextPrototype.fillText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, false);
+ };
+
+ contextPrototype.strokeText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, true);
+ };
+
+ contextPrototype.measureText = function(text) {
+ if (!this.textMeasureEl_) {
+ var s = '<span style="position:absolute;' +
+ 'top:-20000px;left:0;padding:0;margin:0;border:none;' +
+ 'white-space:pre;"></span>';
+ this.element_.insertAdjacentHTML('beforeEnd', s);
+ this.textMeasureEl_ = this.element_.lastChild;
+ }
+ var doc = this.element_.ownerDocument;
+ this.textMeasureEl_.innerHTML = '';
+ this.textMeasureEl_.style.font = this.font;
+ // Don't use innerHTML or innerText because they allow markup/whitespace.
+ this.textMeasureEl_.appendChild(doc.createTextNode(text));
+ return {width: this.textMeasureEl_.offsetWidth};
+ };
+
+ /******** STUBS ********/
+ contextPrototype.clip = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.arcTo = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.createPattern = function(image, repetition) {
+ return new CanvasPattern_(image, repetition);
+ };
+
+ // Gradient / Pattern Stubs
+ function CanvasGradient_(aType) {
+ this.type_ = aType;
+ this.x0_ = 0;
+ this.y0_ = 0;
+ this.r0_ = 0;
+ this.x1_ = 0;
+ this.y1_ = 0;
+ this.r1_ = 0;
+ this.colors_ = [];
+ }
+
+ CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+ aColor = processStyle(aColor);
+ this.colors_.push({offset: aOffset,
+ color: aColor.color,
+ alpha: aColor.alpha});
+ };
+
+ function CanvasPattern_(image, repetition) {
+ assertImageIsValid(image);
+ switch (repetition) {
+ case 'repeat':
+ case null:
+ case '':
+ this.repetition_ = 'repeat';
+ break
+ case 'repeat-x':
+ case 'repeat-y':
+ case 'no-repeat':
+ this.repetition_ = repetition;
+ break;
+ default:
+ throwException('SYNTAX_ERR');
+ }
+
+ this.src_ = image.src;
+ this.width_ = image.width;
+ this.height_ = image.height;
+ }
+
+ function throwException(s) {
+ throw new DOMException_(s);
+ }
+
+ function assertImageIsValid(img) {
+ if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
+ throwException('TYPE_MISMATCH_ERR');
+ }
+ if (img.readyState != 'complete') {
+ throwException('INVALID_STATE_ERR');
+ }
+ }
+
+ function DOMException_(s) {
+ this.code = this[s];
+ this.message = s +': DOM Exception ' + this.code;
+ }
+ var p = DOMException_.prototype = new Error;
+ p.INDEX_SIZE_ERR = 1;
+ p.DOMSTRING_SIZE_ERR = 2;
+ p.HIERARCHY_REQUEST_ERR = 3;
+ p.WRONG_DOCUMENT_ERR = 4;
+ p.INVALID_CHARACTER_ERR = 5;
+ p.NO_DATA_ALLOWED_ERR = 6;
+ p.NO_MODIFICATION_ALLOWED_ERR = 7;
+ p.NOT_FOUND_ERR = 8;
+ p.NOT_SUPPORTED_ERR = 9;
+ p.INUSE_ATTRIBUTE_ERR = 10;
+ p.INVALID_STATE_ERR = 11;
+ p.SYNTAX_ERR = 12;
+ p.INVALID_MODIFICATION_ERR = 13;
+ p.NAMESPACE_ERR = 14;
+ p.INVALID_ACCESS_ERR = 15;
+ p.VALIDATION_ERR = 16;
+ p.TYPE_MISMATCH_ERR = 17;
+
+ // set up externs
+ G_vmlCanvasManager = G_vmlCanvasManager_;
+ CanvasRenderingContext2D = CanvasRenderingContext2D_;
+ CanvasGradient = CanvasGradient_;
+ CanvasPattern = CanvasPattern_;
+ DOMException = DOMException_;
+})();
+
+} // if
diff --git a/deps/rabbitmq_management/priv/www/js/excanvas.min.js b/deps/rabbitmq_management/priv/www/js/excanvas.min.js
new file mode 100644
index 0000000000..988f934a18
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/excanvas.min.js
@@ -0,0 +1 @@
+if(!document.createElement("canvas").getContext){(function(){var z=Math;var K=z.round;var J=z.sin;var U=z.cos;var b=z.abs;var k=z.sqrt;var D=10;var F=D/2;function T(){return this.context_||(this.context_=new W(this))}var O=Array.prototype.slice;function G(i,j,m){var Z=O.call(arguments,2);return function(){return i.apply(j,Z.concat(O.call(arguments)))}}function AD(Z){return String(Z).replace(/&/g,"&amp;").replace(/"/g,"&quot;")}function r(i){if(!i.namespaces.g_vml_){i.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML")}if(!i.namespaces.g_o_){i.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML")}if(!i.styleSheets.ex_canvas_){var Z=i.createStyleSheet();Z.owningElement.id="ex_canvas_";Z.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}r(document);var E={init:function(Z){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var i=Z||document;i.createElement("canvas");i.attachEvent("onreadystatechange",G(this.init_,this,i))}},init_:function(m){var j=m.getElementsByTagName("canvas");for(var Z=0;Z<j.length;Z++){this.initElement(j[Z])}},initElement:function(i){if(!i.getContext){i.getContext=T;r(i.ownerDocument);i.innerHTML="";i.attachEvent("onpropertychange",S);i.attachEvent("onresize",w);var Z=i.attributes;if(Z.width&&Z.width.specified){i.style.width=Z.width.nodeValue+"px"}else{i.width=i.clientWidth}if(Z.height&&Z.height.specified){i.style.height=Z.height.nodeValue+"px"}else{i.height=i.clientHeight}}return i}};function S(i){var Z=i.srcElement;switch(i.propertyName){case"width":Z.getContext().clearRect();Z.style.width=Z.attributes.width.nodeValue+"px";Z.firstChild.style.width=Z.clientWidth+"px";break;case"height":Z.getContext().clearRect();Z.style.height=Z.attributes.height.nodeValue+"px";Z.firstChild.style.height=Z.clientHeight+"px";break}}function w(i){var Z=i.srcElement;if(Z.firstChild){Z.firstChild.style.width=Z.clientWidth+"px";Z.firstChild.style.height=Z.clientHeight+"px"}}E.init();var I=[];for(var AC=0;AC<16;AC++){for(var AB=0;AB<16;AB++){I[AC*16+AB]=AC.toString(16)+AB.toString(16)}}function V(){return[[1,0,0],[0,1,0],[0,0,1]]}function d(m,j){var i=V();for(var Z=0;Z<3;Z++){for(var AF=0;AF<3;AF++){var p=0;for(var AE=0;AE<3;AE++){p+=m[Z][AE]*j[AE][AF]}i[Z][AF]=p}}return i}function Q(i,Z){Z.fillStyle=i.fillStyle;Z.lineCap=i.lineCap;Z.lineJoin=i.lineJoin;Z.lineWidth=i.lineWidth;Z.miterLimit=i.miterLimit;Z.shadowBlur=i.shadowBlur;Z.shadowColor=i.shadowColor;Z.shadowOffsetX=i.shadowOffsetX;Z.shadowOffsetY=i.shadowOffsetY;Z.strokeStyle=i.strokeStyle;Z.globalAlpha=i.globalAlpha;Z.font=i.font;Z.textAlign=i.textAlign;Z.textBaseline=i.textBaseline;Z.arcScaleX_=i.arcScaleX_;Z.arcScaleY_=i.arcScaleY_;Z.lineScale_=i.lineScale_}var B={aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",grey:"#808080",greenyellow:"#ADFF2F",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",oldlace:"#FDF5E6",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",whitesmoke:"#F5F5F5",yellowgreen:"#9ACD32"};function g(i){var m=i.indexOf("(",3);var Z=i.indexOf(")",m+1);var j=i.substring(m+1,Z).split(",");if(j.length==4&&i.substr(3,1)=="a"){alpha=Number(j[3])}else{j[3]=1}return j}function C(Z){return parseFloat(Z)/100}function N(i,j,Z){return Math.min(Z,Math.max(j,i))}function c(AF){var j,i,Z;h=parseFloat(AF[0])/360%360;if(h<0){h++}s=N(C(AF[1]),0,1);l=N(C(AF[2]),0,1);if(s==0){j=i=Z=l}else{var m=l<0.5?l*(1+s):l+s-l*s;var AE=2*l-m;j=A(AE,m,h+1/3);i=A(AE,m,h);Z=A(AE,m,h-1/3)}return"#"+I[Math.floor(j*255)]+I[Math.floor(i*255)]+I[Math.floor(Z*255)]}function A(i,Z,j){if(j<0){j++}if(j>1){j--}if(6*j<1){return i+(Z-i)*6*j}else{if(2*j<1){return Z}else{if(3*j<2){return i+(Z-i)*(2/3-j)*6}else{return i}}}}function Y(Z){var AE,p=1;Z=String(Z);if(Z.charAt(0)=="#"){AE=Z}else{if(/^rgb/.test(Z)){var m=g(Z);var AE="#",AF;for(var j=0;j<3;j++){if(m[j].indexOf("%")!=-1){AF=Math.floor(C(m[j])*255)}else{AF=Number(m[j])}AE+=I[N(AF,0,255)]}p=m[3]}else{if(/^hsl/.test(Z)){var m=g(Z);AE=c(m);p=m[3]}else{AE=B[Z]||Z}}}return{color:AE,alpha:p}}var L={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var f={};function X(Z){if(f[Z]){return f[Z]}var m=document.createElement("div");var j=m.style;try{j.font=Z}catch(i){}return f[Z]={style:j.fontStyle||L.style,variant:j.fontVariant||L.variant,weight:j.fontWeight||L.weight,size:j.fontSize||L.size,family:j.fontFamily||L.family}}function P(j,i){var Z={};for(var AF in j){Z[AF]=j[AF]}var AE=parseFloat(i.currentStyle.fontSize),m=parseFloat(j.size);if(typeof j.size=="number"){Z.size=j.size}else{if(j.size.indexOf("px")!=-1){Z.size=m}else{if(j.size.indexOf("em")!=-1){Z.size=AE*m}else{if(j.size.indexOf("%")!=-1){Z.size=(AE/100)*m}else{if(j.size.indexOf("pt")!=-1){Z.size=m/0.75}else{Z.size=AE}}}}}Z.size*=0.981;return Z}function AA(Z){return Z.style+" "+Z.variant+" "+Z.weight+" "+Z.size+"px "+Z.family}function t(Z){switch(Z){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function W(i){this.m_=V();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=D*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var Z=i.ownerDocument.createElement("div");Z.style.width=i.clientWidth+"px";Z.style.height=i.clientHeight+"px";Z.style.overflow="hidden";Z.style.position="absolute";i.appendChild(Z);this.element_=Z;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var M=W.prototype;M.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};M.beginPath=function(){this.currentPath_=[]};M.moveTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"moveTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.lineTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"lineTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.bezierCurveTo=function(j,i,AI,AH,AG,AE){var Z=this.getCoords_(AG,AE);var AF=this.getCoords_(j,i);var m=this.getCoords_(AI,AH);e(this,AF,m,Z)};function e(Z,m,j,i){Z.currentPath_.push({type:"bezierCurveTo",cp1x:m.x,cp1y:m.y,cp2x:j.x,cp2y:j.y,x:i.x,y:i.y});Z.currentX_=i.x;Z.currentY_=i.y}M.quadraticCurveTo=function(AG,j,i,Z){var AF=this.getCoords_(AG,j);var AE=this.getCoords_(i,Z);var AH={x:this.currentX_+2/3*(AF.x-this.currentX_),y:this.currentY_+2/3*(AF.y-this.currentY_)};var m={x:AH.x+(AE.x-this.currentX_)/3,y:AH.y+(AE.y-this.currentY_)/3};e(this,AH,m,AE)};M.arc=function(AJ,AH,AI,AE,i,j){AI*=D;var AN=j?"at":"wa";var AK=AJ+U(AE)*AI-F;var AM=AH+J(AE)*AI-F;var Z=AJ+U(i)*AI-F;var AL=AH+J(i)*AI-F;if(AK==Z&&!j){AK+=0.125}var m=this.getCoords_(AJ,AH);var AG=this.getCoords_(AK,AM);var AF=this.getCoords_(Z,AL);this.currentPath_.push({type:AN,x:m.x,y:m.y,radius:AI,xStart:AG.x,yStart:AG.y,xEnd:AF.x,yEnd:AF.y})};M.rect=function(j,i,Z,m){this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath()};M.strokeRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.stroke();this.currentPath_=p};M.fillRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.fill();this.currentPath_=p};M.createLinearGradient=function(i,m,Z,j){var p=new v("gradient");p.x0_=i;p.y0_=m;p.x1_=Z;p.y1_=j;return p};M.createRadialGradient=function(m,AE,j,i,p,Z){var AF=new v("gradientradial");AF.x0_=m;AF.y0_=AE;AF.r0_=j;AF.x1_=i;AF.y1_=p;AF.r1_=Z;return AF};M.drawImage=function(AO,j){var AH,AF,AJ,AV,AM,AK,AQ,AX;var AI=AO.runtimeStyle.width;var AN=AO.runtimeStyle.height;AO.runtimeStyle.width="auto";AO.runtimeStyle.height="auto";var AG=AO.width;var AT=AO.height;AO.runtimeStyle.width=AI;AO.runtimeStyle.height=AN;if(arguments.length==3){AH=arguments[1];AF=arguments[2];AM=AK=0;AQ=AJ=AG;AX=AV=AT}else{if(arguments.length==5){AH=arguments[1];AF=arguments[2];AJ=arguments[3];AV=arguments[4];AM=AK=0;AQ=AG;AX=AT}else{if(arguments.length==9){AM=arguments[1];AK=arguments[2];AQ=arguments[3];AX=arguments[4];AH=arguments[5];AF=arguments[6];AJ=arguments[7];AV=arguments[8]}else{throw Error("Invalid number of arguments")}}}var AW=this.getCoords_(AH,AF);var m=AQ/2;var i=AX/2;var AU=[];var Z=10;var AE=10;AU.push(" <g_vml_:group",' coordsize="',D*Z,",",D*AE,'"',' coordorigin="0,0"',' style="width:',Z,"px;height:",AE,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]||this.m_[1][1]!=1||this.m_[1][0]){var p=[];p.push("M11=",this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",K(AW.x/D),",","Dy=",K(AW.y/D),"");var AS=AW;var AR=this.getCoords_(AH+AJ,AF);var AP=this.getCoords_(AH,AF+AV);var AL=this.getCoords_(AH+AJ,AF+AV);AS.x=z.max(AS.x,AR.x,AP.x,AL.x);AS.y=z.max(AS.y,AR.y,AP.y,AL.y);AU.push("padding:0 ",K(AS.x/D),"px ",K(AS.y/D),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",p.join(""),", sizingmethod='clip');")}else{AU.push("top:",K(AW.y/D),"px;left:",K(AW.x/D),"px;")}AU.push(' ">','<g_vml_:image src="',AO.src,'"',' style="width:',D*AJ,"px;"," height:",D*AV,'px"',' cropleft="',AM/AG,'"',' croptop="',AK/AT,'"',' cropright="',(AG-AM-AQ)/AG,'"',' cropbottom="',(AT-AK-AX)/AT,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",AU.join(""))};M.stroke=function(AM){var m=10;var AN=10;var AE=5000;var AG={x:null,y:null};var AL={x:null,y:null};for(var AH=0;AH<this.currentPath_.length;AH+=AE){var AK=[];var AF=false;AK.push("<g_vml_:shape",' filled="',!!AM,'"',' style="position:absolute;width:',m,"px;height:",AN,'px;"',' coordorigin="0,0"',' coordsize="',D*m,",",D*AN,'"',' stroked="',!AM,'"',' path="');var AO=false;for(var AI=AH;AI<Math.min(AH+AE,this.currentPath_.length);AI++){if(AI%AE==0&&AI>0){AK.push(" m ",K(this.currentPath_[AI-1].x),",",K(this.currentPath_[AI-1].y))}var Z=this.currentPath_[AI];var AJ;switch(Z.type){case"moveTo":AJ=Z;AK.push(" m ",K(Z.x),",",K(Z.y));break;case"lineTo":AK.push(" l ",K(Z.x),",",K(Z.y));break;case"close":AK.push(" x ");Z=null;break;case"bezierCurveTo":AK.push(" c ",K(Z.cp1x),",",K(Z.cp1y),",",K(Z.cp2x),",",K(Z.cp2y),",",K(Z.x),",",K(Z.y));break;case"at":case"wa":AK.push(" ",Z.type," ",K(Z.x-this.arcScaleX_*Z.radius),",",K(Z.y-this.arcScaleY_*Z.radius)," ",K(Z.x+this.arcScaleX_*Z.radius),",",K(Z.y+this.arcScaleY_*Z.radius)," ",K(Z.xStart),",",K(Z.yStart)," ",K(Z.xEnd),",",K(Z.yEnd));break}if(Z){if(AG.x==null||Z.x<AG.x){AG.x=Z.x}if(AL.x==null||Z.x>AL.x){AL.x=Z.x}if(AG.y==null||Z.y<AG.y){AG.y=Z.y}if(AL.y==null||Z.y>AL.y){AL.y=Z.y}}}AK.push(' ">');if(!AM){R(this,AK)}else{a(this,AK,AG,AL)}AK.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",AK.join(""))}};function R(j,AE){var i=Y(j.strokeStyle);var m=i.color;var p=i.alpha*j.globalAlpha;var Z=j.lineScale_*j.lineWidth;if(Z<1){p*=Z}AE.push("<g_vml_:stroke",' opacity="',p,'"',' joinstyle="',j.lineJoin,'"',' miterlimit="',j.miterLimit,'"',' endcap="',t(j.lineCap),'"',' weight="',Z,'px"',' color="',m,'" />')}function a(AO,AG,Ah,AP){var AH=AO.fillStyle;var AY=AO.arcScaleX_;var AX=AO.arcScaleY_;var Z=AP.x-Ah.x;var m=AP.y-Ah.y;if(AH instanceof v){var AL=0;var Ac={x:0,y:0};var AU=0;var AK=1;if(AH.type_=="gradient"){var AJ=AH.x0_/AY;var j=AH.y0_/AX;var AI=AH.x1_/AY;var Aj=AH.y1_/AX;var Ag=AO.getCoords_(AJ,j);var Af=AO.getCoords_(AI,Aj);var AE=Af.x-Ag.x;var p=Af.y-Ag.y;AL=Math.atan2(AE,p)*180/Math.PI;if(AL<0){AL+=360}if(AL<0.000001){AL=0}}else{var Ag=AO.getCoords_(AH.x0_,AH.y0_);Ac={x:(Ag.x-Ah.x)/Z,y:(Ag.y-Ah.y)/m};Z/=AY*D;m/=AX*D;var Aa=z.max(Z,m);AU=2*AH.r0_/Aa;AK=2*AH.r1_/Aa-AU}var AS=AH.colors_;AS.sort(function(Ak,i){return Ak.offset-i.offset});var AN=AS.length;var AR=AS[0].color;var AQ=AS[AN-1].color;var AW=AS[0].alpha*AO.globalAlpha;var AV=AS[AN-1].alpha*AO.globalAlpha;var Ab=[];for(var Ae=0;Ae<AN;Ae++){var AM=AS[Ae];Ab.push(AM.offset*AK+AU+" "+AM.color)}AG.push('<g_vml_:fill type="',AH.type_,'"',' method="none" focus="100%"',' color="',AR,'"',' color2="',AQ,'"',' colors="',Ab.join(","),'"',' opacity="',AV,'"',' g_o_:opacity2="',AW,'"',' angle="',AL,'"',' focusposition="',Ac.x,",",Ac.y,'" />')}else{if(AH instanceof u){if(Z&&m){var AF=-Ah.x;var AZ=-Ah.y;AG.push("<g_vml_:fill",' position="',AF/Z*AY*AY,",",AZ/m*AX*AX,'"',' type="tile"',' src="',AH.src_,'" />')}}else{var Ai=Y(AO.fillStyle);var AT=Ai.color;var Ad=Ai.alpha*AO.globalAlpha;AG.push('<g_vml_:fill color="',AT,'" opacity="',Ad,'" />')}}}M.fill=function(){this.stroke(true)};M.closePath=function(){this.currentPath_.push({type:"close"})};M.getCoords_=function(j,i){var Z=this.m_;return{x:D*(j*Z[0][0]+i*Z[1][0]+Z[2][0])-F,y:D*(j*Z[0][1]+i*Z[1][1]+Z[2][1])-F}};M.save=function(){var Z={};Q(this,Z);this.aStack_.push(Z);this.mStack_.push(this.m_);this.m_=d(V(),this.m_)};M.restore=function(){if(this.aStack_.length){Q(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function H(Z){return isFinite(Z[0][0])&&isFinite(Z[0][1])&&isFinite(Z[1][0])&&isFinite(Z[1][1])&&isFinite(Z[2][0])&&isFinite(Z[2][1])}function y(i,Z,j){if(!H(Z)){return }i.m_=Z;if(j){var p=Z[0][0]*Z[1][1]-Z[0][1]*Z[1][0];i.lineScale_=k(b(p))}}M.translate=function(j,i){var Z=[[1,0,0],[0,1,0],[j,i,1]];y(this,d(Z,this.m_),false)};M.rotate=function(i){var m=U(i);var j=J(i);var Z=[[m,j,0],[-j,m,0],[0,0,1]];y(this,d(Z,this.m_),false)};M.scale=function(j,i){this.arcScaleX_*=j;this.arcScaleY_*=i;var Z=[[j,0,0],[0,i,0],[0,0,1]];y(this,d(Z,this.m_),true)};M.transform=function(p,m,AF,AE,i,Z){var j=[[p,m,0],[AF,AE,0],[i,Z,1]];y(this,d(j,this.m_),true)};M.setTransform=function(AE,p,AG,AF,j,i){var Z=[[AE,p,0],[AG,AF,0],[j,i,1]];y(this,Z,true)};M.drawText_=function(AK,AI,AH,AN,AG){var AM=this.m_,AQ=1000,i=0,AP=AQ,AF={x:0,y:0},AE=[];var Z=P(X(this.font),this.element_);var j=AA(Z);var AR=this.element_.currentStyle;var p=this.textAlign.toLowerCase();switch(p){case"left":case"center":case"right":break;case"end":p=AR.direction=="ltr"?"right":"left";break;case"start":p=AR.direction=="rtl"?"right":"left";break;default:p="left"}switch(this.textBaseline){case"hanging":case"top":AF.y=Z.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":AF.y=-Z.size/2.25;break}switch(p){case"right":i=AQ;AP=0.05;break;case"center":i=AP=AQ/2;break}var AO=this.getCoords_(AI+AF.x,AH+AF.y);AE.push('<g_vml_:line from="',-i,' 0" to="',AP,' 0.05" ',' coordsize="100 100" coordorigin="0 0"',' filled="',!AG,'" stroked="',!!AG,'" style="position:absolute;width:1px;height:1px;">');if(AG){R(this,AE)}else{a(this,AE,{x:-i,y:0},{x:AP,y:Z.size})}var AL=AM[0][0].toFixed(3)+","+AM[1][0].toFixed(3)+","+AM[0][1].toFixed(3)+","+AM[1][1].toFixed(3)+",0,0";var AJ=K(AO.x/D)+","+K(AO.y/D);AE.push('<g_vml_:skew on="t" matrix="',AL,'" ',' offset="',AJ,'" origin="',i,' 0" />','<g_vml_:path textpathok="true" />','<g_vml_:textpath on="true" string="',AD(AK),'" style="v-text-align:',p,";font:",AD(j),'" /></g_vml_:line>');this.element_.insertAdjacentHTML("beforeEnd",AE.join(""))};M.fillText=function(j,Z,m,i){this.drawText_(j,Z,m,i,false)};M.strokeText=function(j,Z,m,i){this.drawText_(j,Z,m,i,true)};M.measureText=function(j){if(!this.textMeasureEl_){var Z='<span style="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;"></span>';this.element_.insertAdjacentHTML("beforeEnd",Z);this.textMeasureEl_=this.element_.lastChild}var i=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(i.createTextNode(j));return{width:this.textMeasureEl_.offsetWidth}};M.clip=function(){};M.arcTo=function(){};M.createPattern=function(i,Z){return new u(i,Z)};function v(Z){this.type_=Z;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}v.prototype.addColorStop=function(i,Z){Z=Y(Z);this.colors_.push({offset:i,color:Z.color,alpha:Z.alpha})};function u(i,Z){q(i);switch(Z){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=Z;break;default:n("SYNTAX_ERR")}this.src_=i.src;this.width_=i.width;this.height_=i.height}function n(Z){throw new o(Z)}function q(Z){if(!Z||Z.nodeType!=1||Z.tagName!="IMG"){n("TYPE_MISMATCH_ERR")}if(Z.readyState!="complete"){n("INVALID_STATE_ERR")}}function o(Z){this.code=this[Z];this.message=Z+": DOM Exception "+this.code}var x=o.prototype=new Error;x.INDEX_SIZE_ERR=1;x.DOMSTRING_SIZE_ERR=2;x.HIERARCHY_REQUEST_ERR=3;x.WRONG_DOCUMENT_ERR=4;x.INVALID_CHARACTER_ERR=5;x.NO_DATA_ALLOWED_ERR=6;x.NO_MODIFICATION_ALLOWED_ERR=7;x.NOT_FOUND_ERR=8;x.NOT_SUPPORTED_ERR=9;x.INUSE_ATTRIBUTE_ERR=10;x.INVALID_STATE_ERR=11;x.SYNTAX_ERR=12;x.INVALID_MODIFICATION_ERR=13;x.NAMESPACE_ERR=14;x.INVALID_ACCESS_ERR=15;x.VALIDATION_ERR=16;x.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=E;CanvasRenderingContext2D=W;CanvasGradient=v;CanvasPattern=u;DOMException=o})()};
diff --git a/deps/rabbitmq_management/priv/www/js/formatters.js b/deps/rabbitmq_management/priv/www/js/formatters.js
new file mode 100644
index 0000000000..8c0f55ed93
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/formatters.js
@@ -0,0 +1,1067 @@
+const UNKNOWN_REPR = '<span class="unknown">?</span>';
+const FD_THRESHOLDS = [[0.95, 'red'],
+ [0.8, 'yellow']];
+const SOCKETS_THRESHOLDS = [[1.0, 'red'],
+ [0.8, 'yellow']];
+const PROCESS_THRESHOLDS = [[0.75, 'red'],
+ [0.5, 'yellow']];
+
+const TAB_HIGHLIGHTER = "\u2192";
+const WHITESPACE_HIGHLIGHTER = "\u23B5";
+
+function fmt_string(str, unknown) {
+ if (unknown == undefined) {
+ unknown = UNKNOWN_REPR;
+ }
+ if (str == undefined) {
+ return unknown;
+ }
+ return fmt_escape_html("" + str);
+}
+
+function fmt_si_prefix(num0, max0, thousand, allow_fractions) {
+ if (num == 0) return 0;
+
+ function f(n, m, p) {
+ if (m > thousand) return f(n / thousand, m / thousand, p + 1);
+ else return [n, m, p];
+ }
+
+ var num_power = f(num0, max0, 0);
+ var num = num_power[0];
+ var max = num_power[1];
+ var power = num_power[2];
+ var powers = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
+ return (((power != 0 || allow_fractions) && max <= 10) ? num.toFixed(1) :
+ num.toFixed(0)) + " " + powers[power];
+}
+
+function fmt_boolean(b, unknown) {
+ if (unknown == undefined) unknown = UNKNOWN_REPR;
+ if (b == undefined) return unknown;
+
+ return b ? "&#9679;" : "&#9675;";
+}
+
+function fmt_date(d) {
+ var res = fmt_date0(d);
+ return res[0] + ' ' + res[1];
+}
+
+function fmt_date_mini(d) {
+ var res = fmt_date0(d);
+ return res[1] + '<sub>' + res[0] + '</sub>';
+}
+
+function fmt_date0(d) {
+ function f(i) {
+ return i < 10 ? "0" + i : i;
+ }
+
+ return [d.getFullYear() + "-" + f(d.getMonth() + 1) + "-" +
+ f(d.getDate()), f(d.getHours()) + ":" + f(d.getMinutes()) +
+ ":" + f(d.getSeconds())];
+}
+
+function fmt_timestamp(ts) {
+ return fmt_date(new Date(ts));
+}
+
+function fmt_timestamp_mini(ts) {
+ return fmt_date_mini(new Date(ts));
+}
+
+function fmt_time(t, suffix) {
+ if (typeof t === "undefined") return '';
+ return t + suffix;
+}
+
+function fmt_millis(millis) {
+ return Math.round(millis / 1000) + "s";
+}
+
+function fmt_features(obj) {
+ return fmt_table_short(args_to_features(obj));
+}
+
+function fmt_policy_short(obj) {
+ if (obj.policy != undefined && obj.policy != '') {
+ return '<abbr class="policy" title="Policy: ' +
+ fmt_escape_html(obj.policy) + '">' +
+ link_policy(obj.vhost, obj.policy) + '</abbr> ';
+ } else {
+ return '';
+ }
+}
+
+function fmt_op_policy_short(obj) {
+ if (obj.operator_policy != undefined && obj.operator_policy != '') {
+ return '<abbr class="policy" title="Operator policy: ' +
+ fmt_escape_html(obj.operator_policy) + '">' +
+ fmt_escape_html(obj.operator_policy) + '</abbr> ';
+ } else {
+ return '';
+ }
+}
+
+function fmt_features_short(obj) {
+ var res = '';
+ var features = args_to_features(obj);
+
+ if (obj.owner_pid_details != undefined) {
+ res += '<acronym title="Exclusive queue: click for owning connection">'
+ + link_conn(obj.owner_pid_details.name, "Excl") + '</acronym> ';
+ }
+
+ for (var k in ALL_ARGS) {
+ if (features[k] != undefined) {
+ res += '<abbr title="' + k + ': ' + fmt_string(features[k]) +
+ '">' + ALL_ARGS[k].short + '</abbr> ';
+ }
+ }
+
+ if (features.arguments) {
+ res += '<abbr title="' + fmt_table_flat(features.arguments) +
+ '">Args</abbr> ';
+ }
+ return res;
+}
+
+function fmt_activity_status(obj) {
+ return obj.replace('_', ' ');
+}
+
+function short_conn(name) {
+ var pat = /^(.*)->/;
+ var match = pat.exec(name);
+ return (match != null && match.length == 2) ? match[1] : name;
+}
+
+function short_chan(name) {
+ var pat = /^(.*)->.*( \(.*\))/;
+ var match = pat.exec(name);
+ return (match != null && match.length == 3) ? match[1] + match[2] : name;
+}
+
+function args_to_features(obj) {
+ var res = {};
+ for (var k in obj.arguments) {
+ if (k in KNOWN_ARGS) {
+ res[k] = fmt_escape_feature(obj.arguments[k]);
+ }
+ else {
+ if (res.arguments == undefined) res.arguments = {};
+ res.arguments[fmt_escape_html(k)] = fmt_escape_html(obj.arguments[k]);
+ }
+ }
+ if (obj.exclusive) {
+ res['exclusive'] = true;
+ }
+ if (obj.durable) {
+ res['durable'] = true;
+ }
+ if (obj.auto_delete) {
+ res['auto-delete'] = true;
+ }
+ if (obj.internal != undefined && obj.internal) {
+ res['internal'] = true;
+ }
+ if (obj.messages_delayed != undefined){
+ res['messages delayed'] = obj.messages_delayed;
+ }
+ return res;
+}
+
+function fmt_mirrors(queue) {
+ var synced = queue.synchronised_slave_nodes || [];
+ var unsynced = queue.slave_nodes || [];
+ unsynced = jQuery.grep(unsynced,
+ function (node, i) {
+ return jQuery.inArray(node, synced) == -1;
+ });
+ var res = '';
+ if (synced.length > 0) {
+ res += ' <abbr title="Synchronised mirrors: ' + synced + '">+' +
+ synced.length + '</abbr>';
+ }
+ if (synced.length == 0 && unsynced.length > 0) {
+ res += ' <abbr title="There are no synchronised mirrors">+0</abbr>';
+ }
+ if (unsynced.length > 0) {
+ res += ' <abbr class="warning" title="Unsynchronised mirrors: ' +
+ unsynced + '">+' + unsynced.length + '</abbr>';
+ }
+ return res;
+}
+
+function fmt_sync_state(queue) {
+ var res = '<p><b>Syncing: ';
+ res += (queue.messages == 0) ? 100 : Math.round(100 * queue.sync_messages /
+ queue.messages);
+ res += '%</b></p>';
+ return res;
+}
+
+function fmt_members(queue) {
+ var res = '';
+ var isMajority = (queue.online.length >= (Math.floor(queue.members.length / 2) + 1));
+ var followers = queue.online;
+ followers.splice(followers.indexOf(queue.node), 1);
+ if (isMajority) {
+ res += ' <abbr title="Followers: ' + followers + '">+' +
+ followers.length + '</abbr>';
+ } else {
+ res += ' <abbr class="warning" title="Cluster is in minority">+' + followers.length + '</abbr>';
+ }
+ return res;
+}
+
+function fmt_channel_mode(ch) {
+ if (ch.transactional) {
+ return '<abbr title="Transactional">T</abbr>';
+ }
+ else if (ch.confirm) {
+ return '<abbr title="Confirm">C</abbr>';
+ }
+ else {
+ return '';
+ }
+}
+
+function fmt_color(r, thresholds) {
+ if (r == undefined) return '';
+
+ for (var i in thresholds) {
+ var threshold = thresholds[i][0];
+ var color = thresholds[i][1];
+
+ if (r >= threshold) {
+ return color;
+ }
+ }
+ return 'green';
+}
+
+function fmt_rate_num(num) {
+ if (num == undefined) return UNKNOWN_REPR;
+ else if (num < 1) return num.toFixed(2);
+ else if (num < 10) return num.toFixed(1);
+ else return fmt_num_thousands(num);
+}
+
+function fmt_num_thousands(num) {
+ var conv_num = parseFloat(num); // to avoid errors, if someone calls fmt_num_thousands(someNumber.toFixed(0))
+ return fmt_num_thousands_unfixed(conv_num.toFixed(0));
+}
+
+function fmt_num_thousands_unfixed(num) {
+ if (num == undefined) return UNKNOWN_REPR;
+ num = '' + num;
+ if (num.length < 4) return num;
+ res= fmt_num_thousands_unfixed(num.slice(0, -3)) + ',' + num.slice(-3);
+ return res;
+}
+
+function fmt_percent(num) {
+ if (num === '') {
+ return 'N/A';
+ } else {
+ return Math.round(num * 100) + '%';
+ }
+}
+
+function pick_rate(fmt, obj, name, mode) {
+ if (obj == undefined || obj[name] == undefined ||
+ obj[name + '_details'] == undefined) return '';
+ var details = obj[name + '_details'];
+ return fmt(mode == 'avg' ? details.avg_rate : details.rate);
+}
+
+function pick_abs(fmt, obj, name, mode) {
+ if (obj == undefined || obj[name] == undefined ||
+ obj[name + '_details'] == undefined) return '';
+ var details = obj[name + '_details'];
+ return fmt(mode == 'avg' ? details.avg : obj[name]);
+}
+
+function fmt_detail_rate(obj, name, mode) {
+ return pick_rate(fmt_rate, obj, name, mode);
+}
+
+function fmt_detail_rate_bytes(obj, name, mode) {
+ return pick_rate(fmt_rate_bytes, obj, name, mode);
+}
+
+// ---------------------------------------------------------------------
+
+// These are pluggable for charts etc
+
+function fmt_plain(num) {
+ return num;
+}
+
+function fmt_plain_axis(num, max) {
+ return fmt_si_prefix(num, max, 1000, true);
+}
+
+function fmt_rate(num) {
+ return fmt_rate_num(num) + '/s';
+}
+
+function fmt_rate_axis(num, max) {
+ return fmt_plain_axis(num, max) + '/s';
+}
+
+function fmt_bytes(bytes) {
+ if (bytes == undefined) return UNKNOWN_REPR;
+ var prefix = fmt_si_prefix(bytes, bytes, 1024, false);
+ return prefix + (prefix != bytes ? 'iB' : 'B');
+}
+
+function fmt_bytes_axis(num, max) {
+ num = parseInt(num);
+ return fmt_bytes(isNaN(num) ? 0 : num);
+}
+
+function fmt_rate_bytes(num) {
+ return fmt_bytes(num) + '/s';
+}
+
+function fmt_rate_bytes_axis(num, max) {
+ return fmt_bytes_axis(num, max) + '/s';
+}
+
+function fmt_ms(num) {
+ return fmt_rate_num(num) + 'ms';
+}
+
+// ---------------------------------------------------------------------
+
+function fmt_maybe_vhost(name) {
+ return vhosts_interesting ?
+ ' in virtual host <b>' + fmt_escape_html(name) + '</b>'
+ : '';
+}
+
+function fmt_exchange(name) {
+ return fmt_escape_html(fmt_exchange0(name));
+}
+
+function fmt_exchange0(name) {
+ return name == '' ? '(AMQP default)' : name;
+}
+
+function fmt_exchange_type(type) {
+ for (var i in exchange_types) {
+ if (exchange_types[i].name == type) {
+ return fmt_escape_html(type);
+ }
+ }
+ return '<div class="status-red"><abbr title="Exchange type not found. ' +
+ 'Publishing to this exchange will fail.">' + fmt_escape_html(type) +
+ '</abbr></div>';
+}
+
+function fmt_exchange_url(name) {
+ return name == '' ? 'amq.default' : fmt_escape_html(name);
+}
+
+function fmt_download_filename(host) {
+ var now = new Date();
+ return host.replace('@', '_') + "_" + now.getFullYear() + "-" +
+ (now.getMonth() + 1) + "-" + now.getDate() + ".json";
+}
+
+function fmt_table_short(table) {
+ return '<table class="mini">' + fmt_table_body(table, ':') + '</table>';
+}
+
+function fmt_table_long(table) {
+ return '<table class="facts">' + fmt_table_body(table, '') +
+ '</table>';
+}
+
+function fmt_table_body(table, x) {
+ var res = '';
+ for (k in table) {
+ res += '<tr><th>' + fmt_escape_html(k) + x + '</th>' +
+ '<td>' + fmt_amqp_value(table[k]) + '</td>';
+ }
+ return res;
+}
+
+function fmt_amqp_value(val) {
+ if (val instanceof Array) {
+ var val2 = new Array();
+ for (var i = 0; i < val.length; i++) {
+ val2[i] = fmt_amqp_value(val[i]);
+ }
+ return val2.join("<br/>");
+ } else if (val instanceof Object) {
+ return fmt_table_short(val);
+ } else {
+ var t = typeof(val);
+ if (t == 'string') {
+ return '<abbr class="type" title="string">' +
+ fmt_escape_html(val) + '</abbr>';
+ } else {
+ return '<abbr class="type" title="' + t + '">' + val + '</abbr>';
+ }
+ }
+}
+
+function fmt_table_flat(table) {
+ var res = [];
+ for (k in table) {
+ res.push(fmt_escape_html(k) + ': ' + fmt_amqp_value_flat(table[k]));
+ }
+ return res.join(', ');
+}
+
+function fmt_amqp_value_flat(val) {
+ if (val instanceof Array) {
+ var val2 = new Array();
+ for (var i = 0; i < val.length; i++) {
+ val2[i] = fmt_amqp_value_flat(val[i]);
+ }
+ return '[' + val2.join(",") + ']';
+ } else if (val instanceof Object) {
+ return '(' + fmt_table_flat(val) + ')';
+ } else if (typeof(val) == 'string') {
+ return fmt_escape_html(val);
+ } else {
+ return val;
+ }
+}
+
+function fmt_uptime(u) {
+ var uptime = Math.floor(u / 1000);
+ var sec = uptime % 60;
+ var min = Math.floor(uptime / 60) % 60;
+ var hour = Math.floor(uptime / 3600) % 24;
+ var day = Math.floor(uptime / 86400);
+
+ if (day > 0)
+ return day + 'd ' + hour + 'h';
+ else if (hour > 0)
+ return hour + 'h ' + min + 'm';
+ else
+ return min + 'm ' + sec + 's';
+}
+
+function fmt_plugins_small(node) {
+ if (node.applications === undefined) return '';
+ var plugins = [];
+ for (var i = 0; i < node.applications.length; i++) {
+ var application = node.applications[i];
+ if (jQuery.inArray(application.name, node.enabled_plugins) != -1 ) {
+ plugins.push(application.name);
+ }
+ }
+ return '<abbr title="Enabled plugins: ' + plugins.join(", ") + '">' +
+ plugins.length + '</abbr>';
+}
+
+function get_plugins_list(node) {
+ var result = [];
+ for (var i = 0; i < node.applications.length; i++) {
+ var application = node.applications[i];
+ if (jQuery.inArray(application.name, node.enabled_plugins) != -1 ) {
+ result.push(application);
+ }
+ }
+ return result;
+}
+
+function fmt_rabbit_version(applications) {
+ for (var i in applications) {
+ if (applications[i].name == 'rabbit') {
+ return applications[i].version;
+ }
+ }
+ return 'unknown';
+}
+
+function fmt_strip_tags(txt) {
+ if(txt === null) {
+ return "";
+ }
+ if(txt === undefined) {
+ return "";
+ }
+ return ("" + txt).replace(/<(?:.|\n)*?>/gm, '');
+}
+
+function fmt_escape_feature(txt) {
+ // Exclude SAC which is the only boolean feature
+ if(txt === false) {
+ return undefined;
+ } else {
+ return fmt_escape_html0(txt).replace(/\n/g, '<br/>');
+ }
+}
+
+function fmt_escape_html(txt) {
+ return fmt_escape_html0(txt).replace(/\n/g, '<br/>');
+}
+
+function fmt_escape_html_one_line(txt) {
+ return fmt_escape_html0(txt).replace(/\n/g, '');
+}
+
+function fmt_escape_html0(txt) {
+ if(txt === null) {
+ return "";
+ }
+ if(txt === undefined) {
+ return "";
+ }
+
+ return ("" + txt).replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/\"/g, '&quot;');
+}
+
+function fmt_maybe_wrap(txt, encoding) {
+ if (encoding == 'string') return fmt_escape_html(txt);
+
+ var WRAP = 120;
+ var res = '';
+ while (txt != '') {
+ var i = txt.indexOf('\n');
+ if (i == -1 || i > WRAP) {
+ i = Math.min(WRAP, txt.length);
+ res += txt.substring(0, i) + '\n';
+ txt = txt.substring(i);
+ }
+ else {
+ res += txt.substring(0, i + 1);
+ txt = txt.substring(i + 1);
+ }
+ }
+ return fmt_escape_html(res);
+}
+
+function fmt_node(node_host) {
+ return fmt_string(node_host);
+}
+
+function fmt_object_state(obj) {
+ if (obj.state == undefined) return '';
+
+ var colour = 'green';
+ var text = obj.state;
+ var explanation;
+
+ if (obj.idle_since !== undefined) {
+ colour = 'grey';
+ explanation = 'Idle since ' + obj.idle_since;
+ text = 'idle';
+ }
+ // Only connections can be 'blocked' or 'blocking'
+ else if (obj.state == 'blocked') {
+ colour = 'red';
+ explanation = 'Resource alarm: connection blocked.';
+ }
+ else if (obj.state == 'blocking') {
+ colour = 'yellow';
+ explanation = 'Resource alarm: connection will block on publish.';
+ }
+ else if (obj.state == 'flow') {
+ colour = 'yellow';
+ explanation = 'Publishing rate recently throttled by server.';
+ }
+ else if (obj.state == 'terminated') {
+ colour = 'yellow';
+ var terminated_by = "";
+ if (obj.terminated_by) {
+ terminated_by = " by \"" + String(obj.terminated_by) + "\"";
+ }
+ explanation = 'The queue is being deleted' + terminated_by + ".";
+ }
+ else if (obj.state == 'down') {
+ colour = 'red';
+ explanation = 'The queue is located on a cluster node or nodes that ' +
+ 'are down.';
+ }
+ else if (obj.state == 'crashed') {
+ colour = 'red';
+ explanation = 'The queue has crashed repeatedly and been unable to ' +
+ 'restart.';
+ }
+ else if (obj.state == 'stopped') {
+ colour = 'red';
+ explanation = 'The queue process was stopped by the vhost supervisor.';
+ }
+
+ return fmt_state(colour, text, explanation);
+}
+
+function fmt_state(colour, text, explanation) {
+ var key;
+ if (explanation) {
+ key = '<abbr class="normal" title="' + explanation + '">' +
+ text + '</abbr>';
+ }
+ else {
+ key = text;
+ }
+
+ return '<div class="colour-key status-key-' + colour + '"></div>' + key;
+}
+
+function fmt_shortened_uri(uri) {
+ if (typeof uri == 'object') {
+ var res = '';
+ for (i in uri) {
+ res += fmt_shortened_uri(uri[i]) + '<br/>';
+ }
+ return res;
+ }
+ var uri = fmt_escape_html(uri);
+ if (uri.indexOf('?') == -1) {
+ return uri;
+ }
+ else {
+ return '<abbr title="' + uri + '">' +
+ uri.substr(0, uri.indexOf('?')) + '?...</abbr>';
+ }
+}
+
+function fmt_uri_with_credentials(uri) {
+ if (typeof uri == 'object') {
+ var res = [];
+ for (i in uri) {
+ res.push(fmt_uri_with_credentials(uri[i]));
+ }
+ return res;
+ }
+ else if (typeof uri == 'string') {
+ // mask password
+ var mask = /^([a-zA-Z0-9+-.]+):\/\/(.*):(.*)@/;
+ return uri.replace(mask, "$1://$2:[redacted]@");
+ } else {
+ return UNKNOWN_REPR;
+ }
+}
+
+function fmt_client_name(properties) {
+ var res = [];
+ if (properties.product != undefined) {
+ res.push(fmt_trunc(properties.product, 120));
+ }
+ if (properties.platform != undefined) {
+ res.push(fmt_trunc(properties.platform, 120));
+ }
+ res = res.join(" / ");
+
+ if (properties.version != undefined) {
+ res += '<sub>' + fmt_trunc(properties.version) + '</sub>';
+ }
+
+ if (properties.client_id != undefined) {
+ res += '<sub>' + fmt_trunc(properties.client_id, 120) + '</sub>';
+ }
+
+ return res;
+}
+
+function fmt_trunc(str, max_length) {
+ return str.length > max_length ?
+ ('<abbr class="normal" title="' + fmt_escape_html(str) + '">' +
+ fmt_escape_html(str.substring(0, max_length)) + '...</abbr>') :
+ fmt_escape_html(str);
+}
+
+function alt_rows(i, args) {
+ var css = [(i % 2 == 0) ? 'alt1' : 'alt2'];
+ if (args != undefined && args['x-internal-purpose'] != undefined) {
+ css.push('internal-purpose');
+ }
+ return ' class="' + css.join(' ') + '"';
+}
+
+function esc(str) {
+ return encodeURIComponent(str);
+}
+
+// Replaces a sequence of characters matched by a regular expression
+// group with the given character. Replaced group is combined with the stripped
+// original using the provided combine function.
+function replace_char_sequence_individually(input, regex, replacement, combine) {
+ let ms = input.match(regex);
+ if (ms == null) {
+ return input;
+ } else {
+ let n = ms[0].length;
+ return combine(replacement.repeat(n), input.replace(regex, ""));
+ }
+}
+
+// Replaces a leading sequence of characters matched by a regular expression
+// group with the given character.
+function replace_leading_chars(input, regex, replacement) {
+ return replace_char_sequence_individually(input, regex, replacement, function(patch, stripped_input) {
+ return patch + stripped_input;
+ });
+}
+
+// Replaces a trailing sequence of characters matched by a regular expression
+// group with the given character.
+function replace_trailing_chars(input, regex, replacement) {
+ return replace_char_sequence_individually(input, regex, replacement, function(patch, stripped_input) {
+ return stripped_input + patch;
+ });
+}
+
+// Highlights extra (leading and trailing) whitespace and tab characters
+// with suitable Unicode characters for improved visiblity.
+// Note that mid-word whitespace should not be highighted, which makes
+// the implementation trickier than a simple chain of replace/2 calls.
+function highlight_extra_whitespace(str) {
+ // Highlight leading and trailing whitespace individually but all tabs.
+ // This assumes that spaces are reasonable to use in the middle of a name
+ // but tabs are not used intentionally and must be highlighted even in the middle.
+ return [[replace_trailing_chars, /(\s+)$/g, WHITESPACE_HIGHLIGHTER],
+ [replace_leading_chars, /^(\s+)/g, WHITESPACE_HIGHLIGHTER]].reduce(function(acc, triplet) {
+ return triplet[0](acc, triplet[1], triplet[2]);
+ }, str.replace(/\t/g, TAB_HIGHLIGHTER));
+}
+
+function link_conn(name, desc) {
+ if (desc == undefined) {
+ return _link_to(short_conn(name), '#/connections/' + esc(name));
+ }
+ else {
+ return _link_to(desc, '#/connections/' + esc(name), false);
+ }
+}
+
+function link_channel(name) {
+ return _link_to(short_chan(name), '#/channels/' + esc(name));
+}
+
+function link_exchange(vhost, name, args) {
+ var url = esc(vhost) + '/' + (name == '' ? 'amq.default' : esc(name));
+ return _link_to(fmt_exchange0(highlight_extra_whitespace(name)), '#/exchanges/' + url, true, args);
+}
+
+function link_queue(vhost, name, args) {
+ return _link_to(highlight_extra_whitespace(name), '#/queues/' + esc(vhost) + '/' + esc(name), true, args);
+}
+
+function link_vhost(name) {
+ return _link_to(name, '#/vhosts/' + esc(name));
+}
+
+function link_user(name) {
+ return _link_to(name, '#/users/' + esc(name));
+}
+
+function link_node(name) {
+ return _link_to(fmt_node(name), '#/nodes/' + esc(name));
+}
+
+function link_policy(vhost, name) {
+ return _link_to(name, '#/policies/' + esc(vhost) + '/' + esc(name));
+}
+
+function _link_to(name, url, highlight, args) {
+ if (highlight == undefined) highlight = true;
+ var title = null;
+ if (args != undefined && args['x-internal-purpose'] != undefined) {
+ var purpose = args['x-internal-purpose'];
+ title = 'This is used internally by the ' + purpose + ' mechanism.';
+ }
+ return '<a href="' + url + '"' +
+ (title ? ' title="' + title + '"' : '') + '>' +
+ (highlight ? fmt_highlight_filter(name) : fmt_escape_html(name)) +
+ '</a>';
+}
+
+function fmt_highlight_filter(text) {
+ if (current_filter == '') return fmt_escape_html(text);
+
+ var text_to_match = current_filter.toLowerCase();
+ if (current_filter_regex) {
+ var potential_match = current_filter_regex.exec(text.toLowerCase());
+ if (potential_match) {
+ text_to_match = potential_match[0];
+ }
+ }
+ var ix = text.toLowerCase().indexOf(text_to_match);
+ var l = text_to_match.length;
+ if (ix == -1) {
+ return fmt_escape_html(text);
+ }
+ else {
+ return fmt_escape_html(text.substring(0, ix)) +
+ '<span class="filter-highlight">' +
+ fmt_escape_html(text.substring(ix, ix + l)) + '</span>' +
+ fmt_escape_html(text.substring(ix + l));
+ }
+}
+
+function filter_ui_pg(items, truncate, appendselect) {
+ var total = items.length;
+ if (current_filter != '') {
+ var items2 = [];
+ for (var i in items) {
+ var item = items[i];
+ var item_name = item.name.toLowerCase();
+ if ((current_filter_regex_on &&
+ current_filter_regex &&
+ current_filter_regex.test(item_name)) ||
+ item_name.indexOf(current_filter.toLowerCase()) != -1) {
+ items2.push(item);
+ }
+ }
+ items.length = items2.length;
+ for (var i in items2) items[i] = items2[i];
+ }
+
+ var res = '<div class="filter"><table' +
+ (current_filter == '' ? '' : ' class="filter-active"') +
+ '><tr><th>Filter:</th>' +
+ '<td><input id="filter" type="text" value="' +
+ fmt_escape_html(current_filter) + '"/>' +
+ '<input type="checkbox" name="filter-regex-mode" id="filter-regex-mode"' +
+ (current_filter_regex_on ? ' checked' : '') +
+ '/><label for="filter-regex-mode">Regex</label> <span class="help" id="filter-regex"></span>' +
+ '</td></tr></table>';
+
+ function items_desc(l) {
+ return l == 1 ? (l + ' item') : (l + ' items');
+ }
+
+ var selected = current_filter == '' ? (items_desc(items.length)) :
+ (items.length + ' of ' + items_desc(total) + ' selected');
+
+
+ selected += appendselect;
+
+ res += '<p id="filter-truncate"><span class="updatable">' + selected +
+ '</span>' + truncate + '</p>';
+ res += '</div>';
+
+ return res;
+}
+
+
+function filter_ui(items) {
+ var current_truncate = parseInt(get_pref('truncate'));
+ var truncate_input = '<input type="text" id="truncate" value="' +
+ current_truncate + '">';
+ var selected = '';
+ if (items.length > current_truncate) {
+ selected += '<span id="filter-warning-show"> ' +
+ '(only showing first</span> ';
+ items.length = current_truncate;
+ }
+ else {
+ selected += ', page size up to ';
+ }
+ return filter_ui_pg(items, truncate_input, selected);
+
+}
+
+function paginate_header_ui(pages, context){
+ var res = '<h2 class="updatable">';
+ res += ' All ' + context +' (' + pages.total_count + ((pages.filtered_count != pages.total_count) ? ', filtered down to ' + pages.filtered_count : '') + ')';
+ res += '</h2>';
+ return res;
+}
+
+function paginate_ui(pages, context){
+ var res = paginate_header_ui(pages, context);
+ res += '<div class="hider">';
+ res += '<h3>Pagination</h3>';
+ res += '<div class="filter">';
+ res += '<table class="updatable">';
+ res += '<tr>';
+ res += '<th><label for="'+ context +'-page">Page </label> <select id="'+ context +'-page" class="pagination_class pagination_class_select" >';
+ var page = fmt_page_number_request(context, pages.page);
+ if (pages.page_count > 0 && page > pages.page_count){
+ page = pages.page_count;
+ update_pages(context, page);
+ return;
+ };
+ for (var i = 1; i <= pages.page_count; i++) { ;
+ if (i == page) {;
+ res += ' <option selected="selected" value="'+ i + '">' + i + '</option>';
+ } else { ;
+ res += '<option value="' + i + '"> ' + i + '</option>';
+ } };
+ res += '</select> </th>';
+ res += '<th><label for="' + context +'-pageof">of </label> ' + pages.page_count +'</th>';
+ res += '<th><span><label for="'+ context +'-name"> - Filter: </label> <input id="'+ context +'-name" data-page-start="1" class="pagination_class pagination_class_input" type="text"';
+ res += 'value = ' + fmt_filter_name_request(context, "") + '>';
+ res += '</input></th></span>';
+
+ res += '<th> <input type="checkbox" data-page-start="1" class="pagination_class pagination_class_checkbox" id="'+ context +'-filter-regex-mode"' ;
+
+ res += fmt_regex_request(context, "") + '></input> <label for="filter-regex-mode">Regex</label> <span class="help" id="filter-regex"></span></th>' ;
+
+ res +=' </table>' ;
+ res += '<p id="filter-truncate"><span class="updatable">';
+ res += '<span><label for="'+ context +'-pagesize"> Displaying ' + pages.item_count + ' item'+ ((pages.item_count > 1) ? 's' : '' ) + ' , page size up to: </label> ';
+ res += ' <input id="'+ context +'-pagesize" data-page-start="1" class="pagination_class shortinput pagination_class_input" type="text" ';
+ res += 'value = "' + fmt_page_size_request(context, pages.page_size) +'"';
+ res += 'onkeypress = "return isNumberKey(event)"> </input></span></p>';
+ res += '</tr>';
+ res += '</div>';
+ res += '</div>';
+ return res;
+}
+
+
+function maybe_truncate(items) {
+ var maximum = 500;
+ var str = '';
+
+ if (items.length > maximum) {
+ str = '<p class="warning">Only ' + maximum + ' of ' +
+ items.length + ' items are shown.</p>';
+ items.length = maximum;
+ }
+
+ return str;
+}
+
+function fmt_sort(display, sort) {
+ var prefix = '';
+ if (current_sort == sort) {
+ prefix = '<span class="arrow">' +
+ (current_sort_reverse ? '&#9661; ' : '&#9651; ') +
+ '</span>';
+ }
+ return '<a class="sort" sort="' + sort + '">' + prefix + display + '</a>';
+}
+
+function group_count(mode, group, bools) {
+ var count = 0;
+ for (var i = 0; i < bools.length; i++) {
+ if (bools[i]) count++;
+ }
+
+ var options = COLUMNS[mode][group];
+ for (var i = 0; i < options.length; i++) {
+ var column = options[i][0];
+ if (show_column(mode, column)) count++;
+ }
+ return count;
+}
+
+function group_heading(mode, group, bools) {
+ var count = group_count(mode, group, bools);
+ if (count == 0) {
+ return '';
+ }
+ else {
+ return '<th colspan="' + count + '">' + group + '</th>';
+ }
+}
+
+function fmt_permissions(obj, permissions, lookup, show, warning) {
+ var res = [];
+ for (var i in permissions) {
+ var permission = permissions[i];
+ if (permission[lookup] == obj.name) {
+ res.push(fmt_escape_html(permission[show]));
+ }
+ }
+ return res.length == 0 ? warning : res.join(', ');
+}
+
+var radio_id = 0;
+
+function fmt_radio(name, text, value, current) {
+ radio_id++;
+ return '<label class="radio" for="radio-' + radio_id + '">' +
+ '<input type="radio" id="radio-' + radio_id + '" name="' + name +
+ '" value="' + value + '"' +
+ ((value == current) ? ' checked="checked"' : '') +
+ '>' + text + '</label>';
+}
+
+function fmt_checkbox(name, text, current) {
+ return '<label class="checkbox" for="checkbox-' + name + '">' +
+ '<input type="checkbox" id="checkbox-' + name + '" name="' + name +
+ '"' + (current ? ' checked="checked"' : '') + '>' + text + '</label>';
+}
+
+function properties_size(obj) {
+ var count = 0;
+ for (k in obj) {
+ if (obj.hasOwnProperty(k)) count++;
+ }
+ return count;
+}
+
+function stored_value_or_default(template, defaultValue){
+ var stored_value = get_pref(template);
+ var result = (((stored_value == null)
+ || (stored_value == undefined)
+ || (stored_value == '')) ? defaultValue :
+ stored_value);
+
+ return ((result == undefined) ? defaultValue : result);
+}
+
+function fmt_page_number_request(template, defaultPage){
+ if ((defaultPage == undefined) || (defaultPage <= 0)) {
+ defaultPage = 1;
+ }
+ return stored_value_or_default(template + '_current_page_number', defaultPage);
+}
+function fmt_page_size_request(template, defaultPageSize){
+ if ((defaultPageSize == undefined) || (defaultPageSize < 0)) {
+ defaultPageSize = 100;
+ }
+
+ var result = stored_value_or_default(template + '_current_page_size', defaultPageSize);
+ if (result > 500) {
+ // hard limit
+ result = 500;
+ }
+ return result;
+}
+
+function fmt_filter_name_request(template, defaultName){
+ return fmt_escape_html(stored_value_or_default(template + '_current_filter_name', defaultName));
+}
+
+function fmt_regex_request(template, defaultName){
+ return fmt_escape_html(stored_value_or_default(template + '_current_regex', defaultName));
+}
+
+function fmt_vhost_state(vhost){
+ var cluster_state = vhost.cluster_state;
+ var down_nodes = [];
+ var ok_count = 0;
+ var non_ok_count = 0;
+ for(var node in cluster_state){
+ var node_state = cluster_state[node];
+ if(cluster_state[node] == "stopped" || cluster_state[node] == "nodedown"){
+ non_ok_count++;
+ down_nodes.push(node);
+ } else if(cluster_state[node] == "running"){
+ ok_count++;
+ }
+ }
+ if(non_ok_count == 0 ){
+ return fmt_state('green', 'running', '');
+ } else if(non_ok_count > 0 && ok_count == 0){
+ return fmt_state('red', 'stopped', 'Vhost supervisor is not running.');
+ } else if(non_ok_count > 0 && ok_count > 0){
+ return fmt_state('yellow', 'partial', 'Vhost supervisor is stopped on some cluster nodes: ' + down_nodes.join(', '));
+ }
+}
+
+function isNumberKey(evt){
+ var charCode = (evt.which) ? evt.which : event.keyCode;
+ if (charCode > 31 && (charCode < 48 || charCode > 57))
+ return false;
+ return true;
+}
diff --git a/deps/rabbitmq_management/priv/www/js/global.js b/deps/rabbitmq_management/priv/www/js/global.js
new file mode 100644
index 0000000000..730b003d65
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/global.js
@@ -0,0 +1,798 @@
+///////////////////////
+// //
+// Genuine constants //
+// //
+///////////////////////
+
+// Just used below
+function map(list) {
+ var res = {};
+ for (i in list) {
+ res[list[i]] = '';
+ }
+ return res;
+}
+
+// Extension arguments that we know about and present specially in the UI.
+var KNOWN_ARGS = {'alternate-exchange': {'short': 'AE', 'type': 'string'},
+ 'x-message-ttl': {'short': 'TTL', 'type': 'int'},
+ 'x-expires': {'short': 'Exp', 'type': 'int'},
+ 'x-max-length': {'short': 'Lim', 'type': 'int'},
+ 'x-max-length-bytes': {'short': 'Lim B', 'type': 'int'},
+ 'x-delivery-limit': {'short': 'DlL', 'type': 'int'},
+ 'x-overflow': {'short': 'Ovfl', 'type': 'string'},
+ 'x-dead-letter-exchange': {'short': 'DLX', 'type': 'string'},
+ 'x-dead-letter-routing-key': {'short': 'DLK', 'type': 'string'},
+ 'x-queue-master-locator': {'short': 'ML', 'type': 'string'},
+ 'x-max-priority': {'short': 'Pri', 'type': 'int'},
+ 'x-single-active-consumer': {'short': 'SAC', 'type': 'boolean'}};
+
+// Things that are like arguments that we format the same way in listings.
+var IMPLICIT_ARGS = {'durable': {'short': 'D', 'type': 'boolean'},
+ 'auto-delete': {'short': 'AD', 'type': 'boolean'},
+ 'internal': {'short': 'I', 'type': 'boolean'},
+ 'exclusive': {'short': 'Excl', 'type': 'boolean'},
+ 'messages delayed':{'short': 'DM', 'type': 'int'}};
+
+// Both the above
+var ALL_ARGS = {};
+for (var k in IMPLICIT_ARGS) ALL_ARGS[k] = IMPLICIT_ARGS[k];
+for (var k in KNOWN_ARGS) ALL_ARGS[k] = KNOWN_ARGS[k];
+
+var NAVIGATION = {'Overview': ['#/', "management"],
+ 'Connections': ['#/connections', "management"],
+ 'Channels': ['#/channels', "management"],
+ 'Exchanges': ['#/exchanges', "management"],
+ 'Queues': ['#/queues', "management"],
+ 'Admin':
+ [{'Users': ['#/users', "administrator"],
+ 'Virtual Hosts': ['#/vhosts', "administrator"],
+ 'Feature Flags': ['#/feature-flags', "administrator"],
+ 'Policies': ['#/policies', "management"],
+ 'Limits': ['#/limits', "management"],
+ 'Cluster': ['#/cluster-name', "administrator"]},
+ "management"]
+ };
+
+var CHART_RANGES = {'global': [], 'basic': []};
+var ALL_CHART_RANGES = {};
+
+var ALL_COLUMNS =
+ {'exchanges' :
+ {'Overview': [['type', 'Type', true],
+ ['features', 'Features (with policy)', true],
+ ['features_no_policy', 'Features (no policy)', false],
+ ['policy', 'Policy', false]],
+ 'Message rates': [['rate-in', 'rate in', true],
+ ['rate-out', 'rate out', true]]},
+ 'queues' :
+ {'Overview': [['type', 'Type', true],
+ ['features', 'Features (with policy)', true],
+ ['features_no_policy', 'Features (no policy)', false],
+ ['policy', 'Policy', false],
+ ['consumers', 'Consumer count', false],
+ ['consumer_utilisation', 'Consumer utilisation', false],
+ ['state', 'State', true]],
+ 'Messages': [['msgs-ready', 'Ready', true],
+ ['msgs-unacked', 'Unacknowledged', true],
+ ['msgs-ram', 'In memory', false],
+ ['msgs-persistent', 'Persistent', false],
+ ['msgs-total', 'Total', true]],
+ 'Message bytes': [['msg-bytes-ready', 'Ready', false],
+ ['msg-bytes-unacked', 'Unacknowledged', false],
+ ['msg-bytes-ram', 'In memory', false],
+ ['msg-bytes-persistent', 'Persistent', false],
+ ['msg-bytes-total', 'Total', false]],
+ 'Message rates': [['rate-incoming', 'incoming', true],
+ ['rate-deliver', 'deliver / get', true],
+ ['rate-redeliver', 'redelivered', false],
+ ['rate-ack', 'ack', true]]},
+ 'channels' :
+ {'Overview': [['user', 'User name', true],
+ ['mode', 'Mode', true],
+ ['state', 'State', true]],
+ 'Details': [['msgs-unconfirmed', 'Unconfirmed', true],
+ ['prefetch', 'Prefetch', true],
+ ['msgs-unacked', 'Unacked', true]],
+ 'Transactions': [['msgs-uncommitted', 'Msgs uncommitted', false],
+ ['acks-uncommitted', 'Acks uncommitted', false]],
+ 'Message rates': [['rate-publish', 'publish', true],
+ ['rate-confirm', 'confirm', true],
+ ['rate-unroutable-drop', 'unroutable (drop)', true],
+ ['rate-unroutable-return', 'unroutable (return)', false],
+ ['rate-deliver', 'deliver / get', true],
+ ['rate-redeliver', 'redelivered', false],
+ ['rate-ack', 'ack', true]]},
+ 'connections':
+ {'Overview': [['user', 'User name', true],
+ ['state', 'State', true]],
+ 'Details': [['ssl', 'TLS', true],
+ ['ssl_info', 'TLS details', false],
+ ['protocol', 'Protocol', true],
+ ['channels', 'Channels', true],
+ ['channel_max', 'Channel max', false],
+ ['frame_max', 'Frame max', false],
+ ['auth_mechanism', 'Auth mechanism', false],
+ ['client', 'Client', false]],
+ 'Network': [['from_client', 'From client', true],
+ ['to_client', 'To client', true],
+ ['heartbeat', 'Heartbeat', false],
+ ['connected_at', 'Connected at', false]]},
+
+ 'vhosts':
+ {'Overview': [['cluster-state', 'Cluster state', false],
+ ['description', 'Description', false],
+ ['tags', 'Tags', false]],
+ 'Messages': [['msgs-ready', 'Ready', true],
+ ['msgs-unacked', 'Unacknowledged', true],
+ ['msgs-total', 'Total', true]],
+ 'Network': [['from_client', 'From client', true],
+ ['to_client', 'To client', true]],
+ 'Message rates': [['rate-publish', 'publish', true],
+ ['rate-deliver', 'deliver / get', true]]},
+ 'overview':
+ {'Statistics': [['file_descriptors', 'File descriptors', true],
+ ['socket_descriptors', 'Socket descriptors', true],
+ ['erlang_processes', 'Erlang processes', true],
+ ['memory', 'Memory', true],
+ ['disk_space', 'Disk space', true]],
+ 'General': [['uptime', 'Uptime', true],
+ ['info', 'Info', true],
+ ['reset_stats', 'Reset stats', true]]}};
+
+var DISABLED_STATS_COLUMNS =
+ {'exchanges' :
+ {'Overview': [['type', 'Type', true],
+ ['features', 'Features (with policy)', true],
+ ['features_no_policy', 'Features (no policy)', false],
+ ['policy', 'Policy', false]]},
+ 'queues' :
+ {'Overview': [['type', 'Type', true],
+ ['features', 'Features (with policy)', true],
+ ['features_no_policy', 'Features (no policy)', false],
+ ['policy', 'Policy', false],
+ ['state', 'State', true]],
+ 'Messages': [['msgs-ready', 'Ready', true],
+ ['msgs-unacked', 'Unacknowledged', true],
+ ['msgs-ram', 'In memory', false],
+ ['msgs-persistent', 'Persistent', false],
+ ['msgs-total', 'Total', true]]},
+ 'connections':
+ {'Overview': [['user', 'User name', true],
+ ['state', 'State', true]]},
+
+ 'vhosts':
+ {'Overview': [['cluster-state', 'Cluster state', false]]}};
+
+var COLUMNS;
+
+// All help ? popups
+var HELP = {
+ 'delivery-limit':
+ 'The number of allowed unsuccessful delivery attempts. Once a message has been delivered unsucessfully this many times it will be dropped or dead-lettered, depending on the queue configuration.',
+
+ 'exchange-auto-delete':
+ 'If yes, the exchange will delete itself after at least one queue or exchange has been bound to this one, and then all queues or exchanges have been unbound.',
+
+ 'exchange-internal':
+ 'If yes, clients cannot publish to this exchange directly. It can only be used with exchange to exchange bindings.',
+
+ 'exchange-alternate':
+ 'If messages to this exchange cannot otherwise be routed, send them to the alternate exchange named here.<br/>(Sets the "<a target="_blank" href="https://rabbitmq.com/ae.html">alternate-exchange</a>" argument.)',
+
+ 'queue-message-ttl':
+ 'How long a message published to a queue can live before it is discarded (milliseconds).<br/>(Sets the "<a target="_blank" href="https://rabbitmq.com/ttl.html#per-queue-message-ttl">x-message-ttl</a>" argument.)',
+
+ 'queue-expires':
+ 'How long a queue can be unused for before it is automatically deleted (milliseconds).<br/>(Sets the "<a target="_blank" href="https://rabbitmq.com/ttl.html#queue-ttl">x-expires</a>" argument.)',
+
+ 'queue-max-length':
+ 'How many (ready) messages a queue can contain before it starts to drop them from its head.<br/>(Sets the "<a target="_blank" href="https://rabbitmq.com/maxlength.html">x-max-length</a>" argument.)',
+
+ 'queue-max-length-bytes':
+ 'Total body size for ready messages a queue can contain before it starts to drop them from its head.<br/>(Sets the "<a target="_blank" href="https://rabbitmq.com/maxlength.html">x-max-length-bytes</a>" argument.)',
+
+ 'queue-max-in-memory-length':
+ 'How many (ready) messages a quorum queue can contain in memory before it starts storing them on disk only.<br/>(Sets the x-max-in-memory-length argument.)',
+
+ 'queue-max-in-memory-bytes':
+ 'Total body size for ready messages a quorum queue can contain in memory before it starts storing them on disk only.<br/>(Sets the x-max-in-memory-bytes argument.)',
+
+ 'queue-max-age':
+ 'How long a message published to a stream queue can live before it is discarded.',
+
+ 'queue-max-segment-size':
+ 'Total segment size for stream segments on disk.<br/>(Sets the x-max-segment-size argument.)',
+
+ 'queue-auto-delete':
+ 'If yes, the queue will delete itself after at least one consumer has connected, and then all consumers have disconnected.',
+
+ 'queue-dead-letter-exchange':
+ 'Optional name of an exchange to which messages will be republished if they are rejected or expire.<br/>(Sets the "<a target="_blank" href="https://rabbitmq.com/dlx.html">x-dead-letter-exchange</a>" argument.)',
+
+ 'queue-dead-letter-routing-key':
+ 'Optional replacement routing key to use when a message is dead-lettered. If this is not set, the message\'s original routing key will be used.<br/>(Sets the "<a target="_blank" href="https://rabbitmq.com/dlx.html">x-dead-letter-routing-key</a>" argument.)',
+
+ 'queue-single-active-consumer':
+ 'If set, makes sure only one consumer at a time consumes from the queue and fails over to another registered consumer in case the active one is cancelled or dies.<br/>(Sets the "<a target="_blank" href="https://rabbitmq.com/consumers.html#single-active-consumer">x-single-active-consumer</a>" argument.)',
+
+ 'queue-max-priority':
+ 'Maximum number of priority levels for the queue to support; if not set, the queue will not support message priorities.<br/>(Sets the "<a target="_blank" href="https://rabbitmq.com/priority.html">x-max-priority</a>" argument.)',
+
+ 'queue-max-age':
+ 'Retention policy for stream queues.<br/>(Sets the x-max-age argument.)',
+
+ 'queue-lazy':
+ 'Set the queue into lazy mode, keeping as many messages as possible on disk to reduce RAM usage; if not set, the queue will keep an in-memory cache to deliver messages as fast as possible.<br/>(Sets the "<a target="_blank" href="https://www.rabbitmq.com/lazy-queues.html">x-queue-mode</a>" argument.)',
+
+ 'queue-overflow':
+ 'Sets the <a target="_blank" href="https://www.rabbitmq.com/maxlength.html#overflow-behaviour">queue overflow behaviour</a>. This determines what happens to messages when the maximum length of a queue is reached. Valid values are <code>drop-head</code>, <code>reject-publish</code> or <code>reject-publish-dlx</code>. The quorum queue type only supports <code>drop-head</code> and <code>reject-publish</code>.',
+
+ 'queue-master-locator':
+ 'Set the queue into master location mode, determining the rule by which the queue master is located when declared on a cluster of nodes.<br/>(Sets the "<a target="_blank" href="https://www.rabbitmq.com/ha.html">x-queue-master-locator</a>" argument.)',
+
+ 'queue-leader-locator':
+ 'Set the queue into leader location mode, determining the rule by which the queue leader is located when declared on a cluster of nodes. Valid values are <code>client-local</code>, <code>random</code> and <code>least-leaders</code>.',
+
+ 'queue-initial-cluster-size':
+ 'Set the queue initial cluster size.',
+
+ 'queue-type':
+ 'Set the queue type, determining the type of queue to use: raft-based high availability or classic queue. Valid values are <code>quorum</code> or <code>classic</code>. It defaults to <code>classic<code>. <br/>',
+
+ 'queue-messages':
+ '<p>Message counts.</p><p>Note that "in memory" and "persistent" are not mutually exclusive; persistent messages can be in memory as well as on disc, and transient messages can be paged out if memory is tight. Non-durable queues will consider all messages to be transient.</p>',
+
+ 'queue-message-body-bytes':
+ '<p>The sum total of the sizes of the message bodies in this queue. This only counts message bodies; it does not include message properties (including headers) or metadata used by the queue.</p><p>Note that "in memory" and "persistent" are not mutually exclusive; persistent messages can be in memory as well as on disc, and transient messages can be paged out if memory is tight. Non-durable queues will consider all messages to be transient.</p><p>If a message is routed to multiple queues on publication, its body will be stored only once (in memory and on disk) and shared between queues. The value shown here does not take account of this effect.</p>',
+
+ 'queue-process-memory':
+ 'Total memory used by this queue process. This does not include in-memory message bodies (which may be shared between queues and will appear in the global "binaries" memory) but does include everything else.',
+
+ 'queue-consumer-utilisation':
+ 'Fraction of the time that the queue is able to immediately deliver messages to consumers. If this number is less than 100% you may be able to deliver messages faster if: \
+ <ul> \
+ <li>There were more consumers or</li> \
+ <li>The consumers were faster or</li> \
+ <li>The consumers had a higher prefetch count</li> \
+ </ul>',
+
+ 'internal-users-only':
+ 'Only users within the internal RabbitMQ database are shown here. Other users (e.g. those authenticated over LDAP) will not appear.',
+
+ 'export-definitions':
+ 'The definitions consist of users, virtual hosts, permissions, parameters, exchanges, queues, policies and bindings. They do not include the contents of queues. Exclusive queues will not be exported.',
+
+ 'export-definitions-vhost':
+ 'The definitions exported for a single virtual host consist of exchanges, queues, bindings and policies.',
+
+ 'import-definitions':
+ 'The definitions that are imported will be merged with the current definitions. If an error occurs during import, any changes made will not be rolled back.',
+
+ 'import-definitions-vhost':
+ 'For a single virtual host, only exchanges, queues, bindings and policies are imported.',
+
+ 'exchange-rates-incoming':
+ 'The incoming rate is the rate at which messages are published directly to this exchange.',
+
+ 'exchange-rates-outgoing':
+ 'The outgoing rate is the rate at which messages enter queues, having been published directly to this exchange.',
+
+ 'channel-mode':
+ 'Channel guarantee mode. Can be one of the following, or neither:<br/> \
+ <dl> \
+ <dt><abbr title="Confirm">C</abbr> &ndash; <a target="_blank" href="https://www.rabbitmq.com/confirms.html">confirm</a></dt> \
+ <dd>Channel will send streaming publish confirmations.</dd> \
+ <dt><abbr title="Transactional">T</abbr> &ndash; <a target="_blank" href="https://www.rabbitmq.com/amqp-0-9-1-reference.html#class.tx">transactional</a></dt> \
+ <dd>Channel is transactional.</dd> \
+ </dl>',
+
+ 'channel-prefetch':
+ 'Channel prefetch counts. \
+ <p> \
+ Each channel can have two prefetch counts: A per-consumer count, which \
+ will limit each new consumer created on the channel, and a global \
+ count, which is shared between all consumers on the channel.\
+ </p> \
+ <p> \
+ This column shows one, the other, or both limits if they are set. \
+ </p>',
+
+ 'file-descriptors':
+ '<p>File descriptor count and limit, as reported by the operating \
+ system. The count includes network sockets and file handles.</p> \
+ <p>To optimize disk access RabbitMQ uses as many free descriptors as are \
+ available, so the count may safely approach the limit. \
+ However, if most of the file descriptors are used by sockets then \
+ persister performance will be negatively impacted.</p> \
+ <p>To change the limit on Unix / Linux, use "ulimit -n". To change \
+ the limit on Windows, set the ERL_MAX_PORTS environment variable</p> \
+ <p>To report used file handles on Windows, handle.exe from \
+ sysinternals must be installed in your path. You can download it \
+ <a target="_blank" href="https://technet.microsoft.com/en-us/sysinternals/bb896655">here</a>.</p>',
+
+ 'socket-descriptors':
+ 'The network sockets count and limit managed by RabbitMQ.<br/> \
+ When the limit is exhausted RabbitMQ will stop accepting new \
+ network connections.',
+
+ 'memory-alarm':
+ '<p>The <a target="_blank" href="https://www.rabbitmq.com/memory.html#memsup">memory \
+ alarm</a> for this node has gone off. It will block \
+ incoming network traffic until the memory usage drops below \
+ the watermark.</p>\
+ <p>Note that the pale line in this case indicates the high watermark \
+ in relation to how much memory is used in total. </p>',
+
+ 'disk-free-alarm':
+ 'The <a target="_blank" href="https://www.rabbitmq.com/memory.html#diskfreesup">disk \
+ free space alarm</a> for this node has gone off. It will block \
+ incoming network traffic until the amount of free space exceeds \
+ the limit.',
+
+ 'message-get-requeue':
+ '<p>Clicking "Get Message(s)" will consume messages from the queue. \
+ If requeue is set the message will be put back into the queue in place, \
+ but "redelivered" will be set on the message.</p> \
+ <p>If requeue is not set messages will be removed from the queue.</p> \
+ <p>Furthermore, message payloads will be truncated to 50000 bytes.</p>',
+
+ 'message-publish-headers':
+ 'Headers can have any name. Only long string headers can be set here.',
+
+ 'message-publish-properties':
+ '<p>You can set other message properties here (delivery mode and headers \
+ are pulled out as the most common cases).</p>\
+ <p>Invalid properties will be ignored. Valid properties are:</p>\
+ <ul>\
+ <li>content_type</li>\
+ <li>content_encoding</li>\
+ <li>priority</li>\
+ <li>correlation_id</li>\
+ <li>reply_to</li>\
+ <li>expiration</li>\
+ <li>message_id</li>\
+ <li>timestamp</li>\
+ <li>type</li>\
+ <li>user_id</li>\
+ <li>app_id</li>\
+ <li>cluster_id</li>\
+ </ul>',
+
+ 'string-base64':
+ '<p>AMQP message payloads can contain any binary content. They can \
+ therefore be difficult to display in a browser. The options here \
+ have the following meanings:</p> \
+ <dl> \
+ <dt>Auto string / base64</dt> \
+ <dd>If the message payload can be interpreted as a string in UTF-8 \
+ encoding, do so. Otherwise return the payload encoded as \
+ base64.</dd> \
+ <dt>base64</dt> \
+ <dd>Return the payload encoded as base64 unconditionally.</dd> \
+ </dl>',
+
+ 'user-tags':
+ 'Comma-separated list of tags to apply to the user. Currently \
+ <a target="_blank" href="https://www.rabbitmq.com/management.html#permissions">supported \
+ by the management plugin</a>: \
+ <dl> \
+ <dt>management</dt> \
+ <dd> \
+ User can access the management plugin \
+ </dd> \
+ <dt>policymaker</dt> \
+ <dd> \
+ User can access the management plugin and manage policies and \
+ parameters for the vhosts they have access to. \
+ </dd> \
+ <dt>monitoring</dt> \
+ <dd> \
+ User can access the management plugin and see all connections and \
+ channels as well as node-related information. \
+ </dd> \
+ <dt>administrator</dt> \
+ <dd> \
+ User can do everything monitoring can do, manage users, \
+ vhosts and permissions, close other user\'s connections, and manage \
+ policies and parameters for all vhosts. \
+ </dd> \
+ </dl> \
+ <p> \
+ Note that you can set any tag here; the links for the above four \
+ tags are just for convenience. \
+ </p>',
+
+ 'queued-messages':
+ '<dl> \
+ <dt>Ready</dt>\
+ <dd>Number of messages that are available to be delivered now.</dd>\
+ <dt>Unacknowledged</dt>\
+ <dd>Number of messages for which the server is waiting for acknowledgement.</dd>\
+ <dt>Total</dt>\
+ <dd>The total of these two numbers.</dd>\
+ </dl>',
+
+ 'message-rates':
+ 'Only rates for which some activity is taking place will be shown.\
+ <dl>\
+ <dt>Publish</dt>\
+ <dd>Rate at which messages are entering the server.</dd>\
+ <dt>Publisher confirm</dt>\
+ <dd>Rate at which the server is confirming publishes.</dd>\
+ <dt>Deliver (manual ack)</dt>\
+ <dd>Rate at which messages are delivered to consumers that use manual acknowledgements.</dd>\
+ <dt>Deliver (auto ack)</dt>\
+ <dd>Rate at which messages are delivered to consumers that use automatic acknowledgements.</dd>\
+ <dt>Consumer ack</dt>\
+ <dd>Rate at which messages are being acknowledged by consumers.</dd>\
+ <dt>Redelivered</dt>\
+ <dd>Rate at which messages with the \'redelivered\' flag set are being delivered. Note that these messages will <b>also</b> be counted in one of the delivery rates above.</dd>\
+ <dt>Get (manual ack)</dt>\
+ <dd>Rate at which messages requiring acknowledgement are being delivered in response to basic.get.</dd>\
+ <dt>Get (auto ack)</dt>\
+ <dd>Rate at which messages not requiring acknowledgement are being delivered in response to basic.get.</dd>\
+ <dt>Get (empty)</dt>\
+ <dd>Rate at which empty queues are hit in response to basic.get.</dd>\
+ <dt>Return</dt>\
+ <dd>Rate at which basic.return is sent to publishers for unroutable messages published with the \'mandatory\' flag set.</dd>\
+ <dt>Disk read</dt>\
+ <dd>Rate at which queues read messages from disk.</dd>\
+ <dt>Disk write</dt>\
+ <dd>Rate at which queues write messages to disk.</dd>\
+ </dl>\
+ <p>\
+ Note that the last two items originate in queues rather than \
+ channels; they may therefore be slightly out of sync with other \
+ statistics.\
+ </p>',
+
+ 'disk-monitoring-no-watermark' : 'There is no <a target="_blank" href="https://www.rabbitmq.com/memory.html#diskfreesup">disk space low watermark</a> set. RabbitMQ will not take any action to avoid running out of disk space.',
+
+ 'resource-counts' : 'Shows total number of objects for all virtual hosts the current user has access to.',
+
+ 'memory-use' : '<p>Note that the memory details shown here are only updated on request - they could be too expensive to calculate every few seconds on a busy server.</p><p><a target="_blank" href="https://www.rabbitmq.com/memory-use.html">Read more</a> on memory use.</p>',
+
+ 'memory-calculation-strategy-breakdown' : '<p>The setting <code>vm_memory_calculation_strategy</code> defines which of the below memory values is used to check if the memory usage reaches the watermark or paging to disk is required.</p><p><a target="_blank" href="https://www.rabbitmq.com/memory-use.html">Read more</a> on memory use.</p>',
+
+ 'memory-calculation-strategy' : '<p>This value can be calculated using different strategies the <code>vm_memory_calculation_strategy</code> config setting.</p><p><a target="_blank" href="https://www.rabbitmq.com/memory-use.html">Read more</a> on memory use.</p>',
+
+ 'binary-use' : '<p>Binary accounting is not exact; binaries are shared between processes (and thus the same binary might be counted in more than one section), and the VM does not allow us to track binaries that are not associated with processes (so some binary use might not appear at all).</p>',
+
+ 'policy-ha-mode' : 'One of <code>all</code> (mirror to all nodes in the cluster), <code>exactly</code> (mirror to a set number of nodes) or <code>nodes</code> (mirror to an explicit list of nodes). If you choose one of the latter two, you must also set <code>ha-params</code>.',
+
+ 'policy-ha-params' : 'Absent if <code>ha-mode</code> is <code>all</code>, a number\
+ if <code>ha-mode</code> is <code>exactly</code>, or a list\
+ of strings if <code>ha-mode</code> is <code>nodes</code>.',
+
+ 'policy-ha-sync-mode' : 'One of <code>manual</code> or <code>automatic</code>. <a target="_blank" href="https://www.rabbitmq.com/ha.html#unsynchronised-mirrors">Learn more</a>',
+
+ 'policy-ha-promote-on-shutdown' : 'One of <code>when-synced</code> or <code>always</code>. <a target="_blank" href="https://www.rabbitmq.com/ha.html#unsynchronised-mirrors">Learn more</a>',
+
+ 'policy-ha-promote-on-failure' : 'One of <code>when-synced</code> or <code>always</code>. <a target="_blank" href="https://www.rabbitmq.com/ha.html#unsynchronised-mirrors">Learn more</a>',
+
+ 'policy-federation-upstream-set' :
+ 'A string; only if the federation plugin is enabled. Chooses the name of a set of upstreams to use with federation, or "all" to use all upstreams. Incompatible with <code>federation-upstream</code>.',
+
+ 'policy-federation-upstream' :
+ 'A string; only if the federation plugin is enabled. Chooses a specific upstream set to use for federation. Incompatible with <code>federation-upstream-set</code>.',
+
+ 'handle-exe' : 'In order to monitor the number of file descriptors in use on Windows, RabbitMQ needs the <a href="https://technet.microsoft.com/en-us/sysinternals/bb896655" target="_blank">handle.exe command line tool from Microsoft</a>. Download it and place it in the path (e.g. in C:\Windows).',
+
+ 'filter-regex' :
+ 'Whether to enable regular expression matching. Both string literals \
+ and regular expressions are matched in a case-insensitive manner.<br/><br/> \
+ (<a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions" target="_blank">Regular expression reference</a>)',
+
+ 'consumer-active' :
+ 'Whether the consumer is active or not, i.e. whether the consumer can get messages from the queue. \
+ When single active consumer is enabled for the queue, only one consumer at a time is active. \
+ When single active consumer is disabled for the queue, consumers are active by default. \
+ For a quorum queue, a consumer can be inactive because its owning node is suspected down. <br/><br/> \
+ (<a href="https://www.rabbitmq.com/consumers.html#active-consumer" target="_blank">Documentation</a>)',
+
+ 'plugins' :
+ 'Note that only plugins which are both explicitly enabled and running are shown here.',
+
+ 'io-operations':
+ 'Rate of I/O operations. Only operations performed by the message \
+ persister are shown here (e.g. changes in the schema data store or writes \
+ to the log files are not shown).\
+ <dl>\
+ <dt>Read</dt>\
+ <dd>Rate at which data is read from the disk.</dd>\
+ <dt>Write</dt>\
+ <dd>Rate at which data is written to the disk.</dd>\
+ <dt>Seek</dt>\
+ <dd>Rate at which the broker switches position while reading or \
+ writing to disk.</dd>\
+ <dt>Sync</dt>\
+ <dd>Rate at which the node invokes <code>fsync()</code> to ensure \
+ data is flushed to disk.</dd>\
+ <dt>Reopen</dt>\
+ <dd>Rate at which the node recycles file handles in order to support \
+ more queues than it has file handles. If this operation is occurring \
+ frequently you may get a performance boost from increasing the number \
+ of file handles available.</dd>\
+ </dl>',
+
+ 'mnesia-transactions':
+ 'Rate at which schema data store transactions are initiated on this node (this node \
+ will also take part in the transactions initiated on other nodes).\
+ <dl>\
+ <dt>RAM only</dt>\
+ <dd>Rate at which RAM-only schema data store transactions take place (e.g. creation or \
+ deletion of transient queues).</dd>\
+ <dt>Disk</dt>\
+ <dd>Rate at which all schema data store transactions take place (e.g. \
+ creation or deletion of durable queues).</dd>\
+ </dl>',
+
+ 'persister-operations-msg':
+ 'Rate at which per-message persister operations take place on this node. See \
+ <a href="https://www.rabbitmq.com/persistence-conf.html" target="_blank">here</a> \
+ for more information on the persister. \
+ <dl>\
+ <dt>QI Journal</dt>\
+ <dd>Rate at which message information (publishes, deliveries and \
+ acknowledgements) is written to queue index journals.</dd>\
+ <dt>Store Read</dt>\
+ <dd>Rate at which messages are read from the message store.</dd>\
+ <dt>Store Write</dt>\
+ <dd>Rate at which messages are written to the message store.</dd>\
+ </dl>',
+
+ 'persister-operations-bulk':
+ 'Rate at which whole-file persister operations take place on this node. See \
+ <a href="https://www.rabbitmq.com/persistence-conf.html" target="_blank">here</a> \
+ for more information on the persister. \
+ <dl>\
+ <dt>QI Read</dt>\
+ <dd>Rate at which queue index segment files are read.</dd>\
+ <dt>QI Write</dt>\
+ <dd>Rate at which queue index segment files are written. </dd>\
+ </dl>',
+
+ 'gc-operations':
+ 'Rate at which garbage collection operations take place on this node.',
+
+ 'gc-bytes':
+ 'Rate at which memory is reclaimed by the garbage collector on this node.',
+
+ 'context-switches-operations':
+ 'Rate at which runtime context switching takes place on this node.',
+
+ 'process-reductions':
+ 'Rate at which reductions take place on this process.',
+
+ 'connection-operations':
+ ' <dl>\
+ <dt>Created</dt>\
+ <dd>Rate at which connections are created.</dd>\
+ <dt>Closed</dt>\
+ <dd>Rate at which connections are closed.</dd>\
+ </dl> ',
+
+ 'channel-operations':
+ ' <dl>\
+ <dt>Created</dt>\
+ <dd>Rate at which channels are created.</dd>\
+ <dt>Closed</dt>\
+ <dd>Rate at which channels are closed.</dd>\
+ </dl> ',
+
+ 'queue-operations':
+ ' <dl>\
+ <dt>Declared</dt>\
+ <dd>Rate at which queues are declared by clients.</dd>\
+ <dt>Created</dt>\
+ <dd>Rate at which queues are created. Declaring a queue that already exists counts for a "Declared" event, but not for a "Created" event. </dd>\
+ <dt>Deleted</dt>\
+ <dd>Rate at which queues are deleted.</dd>\
+ </dl> '
+
+};
+
+///////////////////////////////////////////////////////////////////////////
+// //
+// Mostly constant, typically get set once at startup (or rarely anyway) //
+// //
+///////////////////////////////////////////////////////////////////////////
+
+// All these are to do with hiding UI elements if
+var rates_mode; // ...there are no fine stats
+var user_administrator; // ...user is not an admin
+var is_user_policymaker; // ...user is not a policymaker
+var user_monitor; // ...user cannot monitor
+var nodes_interesting; // ...we are not in a cluster
+var vhosts_interesting; // ...there is only one vhost
+var queue_type;
+var rabbit_versions_interesting; // ...all cluster nodes run the same version
+var disable_stats; // ...disable all stats, management only mode
+
+// Extensions write to this, the dispatcher maker reads it
+var dispatcher_modules = [];
+
+// We need to know when all extension script files have loaded
+var extension_count;
+
+// The dispatcher needs access to the Sammy app
+var app;
+
+// Used for the new exchange form, and to display broken exchange types
+var exchange_types;
+
+// Used for access control
+var user_tags;
+var user;
+
+// Set up the above vars
+function setup_global_vars() {
+ var overview = JSON.parse(sync_get('/overview'));
+ rates_mode = overview.rates_mode;
+ user_tags = expand_user_tags(user.tags.split(","));
+ user_administrator = jQuery.inArray("administrator", user_tags) != -1;
+ is_user_policymaker = jQuery.inArray("policymaker", user_tags) != -1;
+ user_monitor = jQuery.inArray("monitoring", user_tags) != -1;
+ exchange_types = overview.exchange_types.map(function(xt) { return xt.name; });
+
+ cluster_name = fmt_escape_html(overview.cluster_name);
+ $('#logout').before(
+ '<li>Cluster ' + (user_administrator ? '<a href="#/cluster-name">' + cluster_name + '</a>' : cluster_name) + '</li>'
+ );
+
+ user_name = fmt_escape_html(user.name);
+ $('#header #logout').prepend(
+ 'User ' + (user_administrator ? '<a href="#/users/' + user_name + '">' + user_name + '</a>' : user_name)
+ );
+
+ var product = overview.rabbitmq_version;
+ if (overview.product_name && overview.product_version) {
+ product = overview.product_name + ' ' + overview.product_version;
+ }
+
+ $('#versions').html(
+ '<abbr title="Available exchange types: ' + exchange_types.join(", ") + '">' + fmt_escape_html(product) + '</abbr>' +
+ '<abbr title="' + fmt_escape_html(overview.erlang_full_version) + '">Erlang ' + fmt_escape_html(overview.erlang_version) + '</abbr>'
+ );
+ nodes_interesting = false;
+ rabbit_versions_interesting = false;
+ if (user_monitor) {
+ var nodes = JSON.parse(sync_get('/nodes'));
+ if (nodes.length > 1) {
+ nodes_interesting = true;
+ var v = '';
+ for (var i = 0; i < nodes.length; i++) {
+ var v1 = fmt_rabbit_version(nodes[i].applications);
+ if (v1 != 'unknown') {
+ if (v != '' && v != v1) rabbit_versions_interesting = true;
+ v = v1;
+ }
+ }
+ }
+ }
+ vhosts_interesting = JSON.parse(sync_get('/vhosts')).length > 1;
+
+ queue_type = "classic";
+ current_vhost = get_pref('vhost');
+ exchange_types = overview.exchange_types;
+
+ disable_stats = overview.disable_stats;
+ enable_queue_totals = overview.enable_queue_totals;
+ COLUMNS = disable_stats?DISABLED_STATS_COLUMNS:ALL_COLUMNS;
+
+ setup_chart_ranges(overview.sample_retention_policies);
+}
+
+function setup_chart_ranges(srp) {
+ var range_types = ['global', 'basic'];
+ var default_ranges = {
+ 60: ['60|5', 'Last minute'],
+ 600: ['600|5', 'Last ten minutes'],
+ 3600: ['3600|60', 'Last hour'],
+ 28800: ['28800|600', 'Last eight hours'],
+ 86400: ['86400|1800', 'Last day']
+ };
+
+ for (var range in default_ranges) {
+ var data = default_ranges[range];
+ var range = data[0];
+ var desc = data[1];
+ ALL_CHART_RANGES[range] = desc;
+ }
+
+ for (var i = 0; i < range_types.length; ++i) {
+ var range_type = range_types[i];
+ if (srp.hasOwnProperty(range_type)) {
+ var srp_range_types = srp[range_type];
+ var last_minute_added = false;
+ for (var j = 0; j < srp_range_types.length; ++j) {
+ var srp_range = srp_range_types[j];
+ if (default_ranges.hasOwnProperty(srp_range)) {
+ if (srp_range === 60) {
+ last_minute_added = true;
+ }
+ var v = default_ranges[srp_range];
+ CHART_RANGES[range_type].push(v);
+ }
+ }
+ if (!last_minute_added) {
+ var last_minute = default_ranges[60];
+ CHART_RANGES[range_type].unshift(last_minute);
+ }
+ }
+ }
+}
+
+function expand_user_tags(tags) {
+ var new_tags = [];
+ for (var i = 0; i < tags.length; i++) {
+ var tag = tags[i];
+ new_tags.push(tag);
+ switch (tag) { // Note deliberate fall-through
+ case "administrator": new_tags.push("monitoring");
+ new_tags.push("policymaker");
+ case "monitoring": new_tags.push("management");
+ break;
+ case "policymaker": new_tags.push("management");
+ default: break;
+ }
+ }
+ return new_tags;
+}
+
+////////////////////////////////////////////////////
+// //
+// Change frequently (typically every "new page") //
+// //
+////////////////////////////////////////////////////
+
+// Which top level template we're showing
+var current_template;
+
+// Which JSON requests do we need to populate it
+var current_reqs;
+
+// And which of those have yet to return (so we can cancel them when
+// changing current_template).
+var outstanding_reqs = [];
+
+// Which tab is highlighted
+var current_highlight;
+
+// Which vhost are we looking at
+var current_vhost = '';
+
+// What is our current sort order
+var current_sort;
+var current_sort_reverse = false;
+
+var current_filter = '';
+var current_filter_regex_on = false;
+
+var current_filter_regex;
+var current_truncate;
+
+// The timer object for auto-updates, and how often it goes off
+var timer;
+var timer_interval;
+
+// When did we last connect successfully (for the "could not connect" error)
+var last_successful_connect;
+
+// Every 200 updates without user interaction we do a full refresh, to
+// work around memory leaks in browser DOM implementations.
+// TODO: maybe we don't need this any more?
+var update_counter = 0;
+
+// Holds chart data in between writing the div in an ejs and rendering
+// the chart.
+var chart_data = {};
+
+// whenever a UI requests a page that doesn't exist
+// because things were deleted between refreshes
+var last_page_out_of_range_error = 0;
+
+var enable_uaa;
+var uaa_client_id;
+var uaa_location;
diff --git a/deps/rabbitmq_management/priv/www/js/jquery-3.5.1.js b/deps/rabbitmq_management/priv/www/js/jquery-3.5.1.js
new file mode 100644
index 0000000000..50937333b9
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/jquery-3.5.1.js
@@ -0,0 +1,10872 @@
+/*!
+ * jQuery JavaScript Library v3.5.1
+ * https://jquery.com/
+ *
+ * Includes Sizzle.js
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2020-05-04T22:49Z
+ */
+( function( global, factory ) {
+
+ "use strict";
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+ // For CommonJS and CommonJS-like environments where a proper `window`
+ // is present, execute the factory and get jQuery.
+ // For environments that do not have a `window` with a `document`
+ // (such as Node.js), expose a factory as module.exports.
+ // This accentuates the need for the creation of a real `window`.
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket #14549 for more info.
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+// Pass this if window is not defined yet
+} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+// enough that all such attempts are guarded in a try block.
+"use strict";
+
+var arr = [];
+
+var getProto = Object.getPrototypeOf;
+
+var slice = arr.slice;
+
+var flat = arr.flat ? function( array ) {
+ return arr.flat.call( array );
+} : function( array ) {
+ return arr.concat.apply( [], array );
+};
+
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var fnToString = hasOwn.toString;
+
+var ObjectFunctionString = fnToString.call( Object );
+
+var support = {};
+
+var isFunction = function isFunction( obj ) {
+
+ // Support: Chrome <=57, Firefox <=52
+ // In some browsers, typeof returns "function" for HTML <object> elements
+ // (i.e., `typeof document.createElement( "object" ) === "function"`).
+ // We don't want to classify *any* DOM node as a function.
+ return typeof obj === "function" && typeof obj.nodeType !== "number";
+ };
+
+
+var isWindow = function isWindow( obj ) {
+ return obj != null && obj === obj.window;
+ };
+
+
+var document = window.document;
+
+
+
+ var preservedScriptAttributes = {
+ type: true,
+ src: true,
+ nonce: true,
+ noModule: true
+ };
+
+ function DOMEval( code, node, doc ) {
+ doc = doc || document;
+
+ var i, val,
+ script = doc.createElement( "script" );
+
+ script.text = code;
+ if ( node ) {
+ for ( i in preservedScriptAttributes ) {
+
+ // Support: Firefox 64+, Edge 18+
+ // Some browsers don't support the "nonce" property on scripts.
+ // On the other hand, just using `getAttribute` is not enough as
+ // the `nonce` attribute is reset to an empty string whenever it
+ // becomes browsing-context connected.
+ // See https://github.com/whatwg/html/issues/2369
+ // See https://html.spec.whatwg.org/#nonce-attributes
+ // The `node.getAttribute` check was added for the sake of
+ // `jQuery.globalEval` so that it can fake a nonce-containing node
+ // via an object.
+ val = node[ i ] || node.getAttribute && node.getAttribute( i );
+ if ( val ) {
+ script.setAttribute( i, val );
+ }
+ }
+ }
+ doc.head.appendChild( script ).parentNode.removeChild( script );
+ }
+
+
+function toType( obj ) {
+ if ( obj == null ) {
+ return obj + "";
+ }
+
+ // Support: Android <=2.3 only (functionish RegExp)
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call( obj ) ] || "object" :
+ typeof obj;
+}
+/* global Symbol */
+// Defining this global in .eslintrc.json would create a danger of using the global
+// unguarded in another place, it seems safer to define global only for this module
+
+
+
+var
+ version = "3.5.1",
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+
+ // The jQuery object is actually just the init constructor 'enhanced'
+ // Need init if jQuery is called (just allow error to be thrown if not included)
+ return new jQuery.fn.init( selector, context );
+ };
+
+jQuery.fn = jQuery.prototype = {
+
+ // The current version of jQuery being used
+ jquery: version,
+
+ constructor: jQuery,
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+
+ // Return all the elements in a clean array
+ if ( num == null ) {
+ return slice.call( this );
+ }
+
+ // Return just the one element from the set
+ return num < 0 ? this[ num + this.length ] : this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ each: function( callback ) {
+ return jQuery.each( this, callback );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map( this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ } ) );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ even: function() {
+ return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+ return ( i + 1 ) % 2;
+ } ) );
+ },
+
+ odd: function() {
+ return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+ return i % 2;
+ } ) );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor();
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: arr.sort,
+ splice: arr.splice
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[ 0 ] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+
+ // Skip the boolean and the target
+ target = arguments[ i ] || {};
+ i++;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !isFunction( target ) ) {
+ target = {};
+ }
+
+ // Extend jQuery itself if only one argument is passed
+ if ( i === length ) {
+ target = this;
+ i--;
+ }
+
+ for ( ; i < length; i++ ) {
+
+ // Only deal with non-null/undefined values
+ if ( ( options = arguments[ i ] ) != null ) {
+
+ // Extend the base object
+ for ( name in options ) {
+ copy = options[ name ];
+
+ // Prevent Object.prototype pollution
+ // Prevent never-ending loop
+ if ( name === "__proto__" || target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
+ ( copyIsArray = Array.isArray( copy ) ) ) ) {
+ src = target[ name ];
+
+ // Ensure proper type for the source value
+ if ( copyIsArray && !Array.isArray( src ) ) {
+ clone = [];
+ } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
+ clone = {};
+ } else {
+ clone = src;
+ }
+ copyIsArray = false;
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend( {
+
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+ // Assume jQuery is ready without the ready module
+ isReady: true,
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ noop: function() {},
+
+ isPlainObject: function( obj ) {
+ var proto, Ctor;
+
+ // Detect obvious negatives
+ // Use toString instead of jQuery.type to catch host objects
+ if ( !obj || toString.call( obj ) !== "[object Object]" ) {
+ return false;
+ }
+
+ proto = getProto( obj );
+
+ // Objects with no prototype (e.g., `Object.create( null )`) are plain
+ if ( !proto ) {
+ return true;
+ }
+
+ // Objects with prototype are plain iff they were constructed by a global Object function
+ Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
+ return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ // Evaluates a script in a provided context; falls back to the global one
+ // if not specified.
+ globalEval: function( code, options, doc ) {
+ DOMEval( code, { nonce: options && options.nonce }, doc );
+ },
+
+ each: function( obj, callback ) {
+ var length, i = 0;
+
+ if ( isArrayLike( obj ) ) {
+ length = obj.length;
+ for ( ; i < length; i++ ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArrayLike( Object( arr ) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ return arr == null ? -1 : indexOf.call( arr, elem, i );
+ },
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ merge: function( first, second ) {
+ var len = +second.length,
+ j = 0,
+ i = first.length;
+
+ for ( ; j < len; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, invert ) {
+ var callbackInverse,
+ matches = [],
+ i = 0,
+ length = elems.length,
+ callbackExpect = !invert;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ callbackInverse = !callback( elems[ i ], i );
+ if ( callbackInverse !== callbackExpect ) {
+ matches.push( elems[ i ] );
+ }
+ }
+
+ return matches;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var length, value,
+ i = 0,
+ ret = [];
+
+ // Go through the array, translating each of the items to their new values
+ if ( isArrayLike( elems ) ) {
+ length = elems.length;
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return flat( ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // jQuery.support is not used in Core but other projects attach their
+ // properties to it so it needs to exist.
+ support: support
+} );
+
+if ( typeof Symbol === "function" ) {
+ jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+}
+
+// Populate the class2type map
+jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
+function( _i, name ) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+} );
+
+function isArrayLike( obj ) {
+
+ // Support: real iOS 8.2 only (not reproducible in simulator)
+ // `in` check used to prevent JIT error (gh-2145)
+ // hasOwn isn't used here due to false negatives
+ // regarding Nodelist length in IE
+ var length = !!obj && "length" in obj && obj.length,
+ type = toType( obj );
+
+ if ( isFunction( obj ) || isWindow( obj ) ) {
+ return false;
+ }
+
+ return type === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v2.3.5
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://js.foundation/
+ *
+ * Date: 2020-03-14
+ */
+( function( window ) {
+var i,
+ support,
+ Expr,
+ getText,
+ isXML,
+ tokenize,
+ compile,
+ select,
+ outermostContext,
+ sortInput,
+ hasDuplicate,
+
+ // Local document vars
+ setDocument,
+ document,
+ docElem,
+ documentIsHTML,
+ rbuggyQSA,
+ rbuggyMatches,
+ matches,
+ contains,
+
+ // Instance-specific data
+ expando = "sizzle" + 1 * new Date(),
+ preferredDoc = window.document,
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ nonnativeSelectorCache = createCache(),
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ },
+
+ // Instance methods
+ hasOwn = ( {} ).hasOwnProperty,
+ arr = [],
+ pop = arr.pop,
+ pushNative = arr.push,
+ push = arr.push,
+ slice = arr.slice,
+
+ // Use a stripped-down indexOf as it's faster than native
+ // https://jsperf.com/thor-indexof-vs-for/5
+ indexOf = function( list, elem ) {
+ var i = 0,
+ len = list.length;
+ for ( ; i < len; i++ ) {
+ if ( list[ i ] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" +
+ "ismap|loop|multiple|open|readonly|required|scoped",
+
+ // Regular expressions
+
+ // http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+
+ // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
+ identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
+ "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",
+
+ // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
+
+ // Operator (capture 2)
+ "*([*^$|!~]?=)" + whitespace +
+
+ // "Attribute values must be CSS identifiers [capture 5]
+ // or strings [capture 3 or capture 4]"
+ "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
+ whitespace + "*\\]",
+
+ pseudos = ":(" + identifier + ")(?:\\((" +
+
+ // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+ // 1. quoted (capture 3; capture 4 or capture 5)
+ "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+
+ // 2. simple (capture 6)
+ "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+
+ // 3. anything else (capture 2)
+ ".*" +
+ ")\\)|)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rwhitespace = new RegExp( whitespace + "+", "g" ),
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" +
+ whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
+ "*" ),
+ rdescend = new RegExp( whitespace + "|>" ),
+
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + identifier + ")" ),
+ "CLASS": new RegExp( "^\\.(" + identifier + ")" ),
+ "TAG": new RegExp( "^(" + identifier + "|[*])" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
+ whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
+ whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ "needsContext": new RegExp( "^" + whitespace +
+ "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
+
+ rhtml = /HTML$/i,
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
+
+ rnative = /^[^{]+\{\s*\[native \w/,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rsibling = /[+~]/,
+
+ // CSS escapes
+ // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ),
+ funescape = function( escape, nonHex ) {
+ var high = "0x" + escape.slice( 1 ) - 0x10000;
+
+ return nonHex ?
+
+ // Strip the backslash prefix from a non-hex escape sequence
+ nonHex :
+
+ // Replace a hexadecimal escape sequence with the encoded Unicode code point
+ // Support: IE <=11+
+ // For values outside the Basic Multilingual Plane (BMP), manually construct a
+ // surrogate pair
+ high < 0 ?
+ String.fromCharCode( high + 0x10000 ) :
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ },
+
+ // CSS string/identifier serialization
+ // https://drafts.csswg.org/cssom/#common-serializing-idioms
+ rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
+ fcssescape = function( ch, asCodePoint ) {
+ if ( asCodePoint ) {
+
+ // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
+ if ( ch === "\0" ) {
+ return "\uFFFD";
+ }
+
+ // Control characters and (dependent upon position) numbers get escaped as code points
+ return ch.slice( 0, -1 ) + "\\" +
+ ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
+ }
+
+ // Other potentially-special ASCII characters get backslash-escaped
+ return "\\" + ch;
+ },
+
+ // Used for iframes
+ // See setDocument()
+ // Removing the function wrapper causes a "Permission Denied"
+ // error in IE
+ unloadHandler = function() {
+ setDocument();
+ },
+
+ inDisabledFieldset = addCombinator(
+ function( elem ) {
+ return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset";
+ },
+ { dir: "parentNode", next: "legend" }
+ );
+
+// Optimize for push.apply( _, NodeList )
+try {
+ push.apply(
+ ( arr = slice.call( preferredDoc.childNodes ) ),
+ preferredDoc.childNodes
+ );
+
+ // Support: Android<4.0
+ // Detect silently failing push.apply
+ // eslint-disable-next-line no-unused-expressions
+ arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+ push = { apply: arr.length ?
+
+ // Leverage slice if possible
+ function( target, els ) {
+ pushNative.apply( target, slice.call( els ) );
+ } :
+
+ // Support: IE<9
+ // Otherwise append directly
+ function( target, els ) {
+ var j = target.length,
+ i = 0;
+
+ // Can't trust NodeList.length
+ while ( ( target[ j++ ] = els[ i++ ] ) ) {}
+ target.length = j - 1;
+ }
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ var m, i, elem, nid, match, groups, newSelector,
+ newContext = context && context.ownerDocument,
+
+ // nodeType defaults to 9, since context defaults to document
+ nodeType = context ? context.nodeType : 9;
+
+ results = results || [];
+
+ // Return early from calls with invalid selector or context
+ if ( typeof selector !== "string" || !selector ||
+ nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+ return results;
+ }
+
+ // Try to shortcut find operations (as opposed to filters) in HTML documents
+ if ( !seed ) {
+ setDocument( context );
+ context = context || document;
+
+ if ( documentIsHTML ) {
+
+ // If the selector is sufficiently simple, try using a "get*By*" DOM method
+ // (excepting DocumentFragment context, where the methods don't exist)
+ if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {
+
+ // ID selector
+ if ( ( m = match[ 1 ] ) ) {
+
+ // Document context
+ if ( nodeType === 9 ) {
+ if ( ( elem = context.getElementById( m ) ) ) {
+
+ // Support: IE, Opera, Webkit
+ // TODO: identify versions
+ // getElementById can match elements by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+
+ // Element context
+ } else {
+
+ // Support: IE, Opera, Webkit
+ // TODO: identify versions
+ // getElementById can match elements by name instead of ID
+ if ( newContext && ( elem = newContext.getElementById( m ) ) &&
+ contains( context, elem ) &&
+ elem.id === m ) {
+
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Type selector
+ } else if ( match[ 2 ] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Class selector
+ } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&
+ context.getElementsByClassName ) {
+
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // Take advantage of querySelectorAll
+ if ( support.qsa &&
+ !nonnativeSelectorCache[ selector + " " ] &&
+ ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&
+
+ // Support: IE 8 only
+ // Exclude object elements
+ ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) {
+
+ newSelector = selector;
+ newContext = context;
+
+ // qSA considers elements outside a scoping root when evaluating child or
+ // descendant combinators, which is not what we want.
+ // In such cases, we work around the behavior by prefixing every selector in the
+ // list with an ID selector referencing the scope context.
+ // The technique has to be used as well when a leading combinator is used
+ // as such selectors are not recognized by querySelectorAll.
+ // Thanks to Andrew Dupont for this technique.
+ if ( nodeType === 1 &&
+ ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {
+
+ // Expand context for sibling selectors
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
+ context;
+
+ // We can use :scope instead of the ID hack if the browser
+ // supports it & if we're not changing the context.
+ if ( newContext !== context || !support.scope ) {
+
+ // Capture the context ID, setting it first if necessary
+ if ( ( nid = context.getAttribute( "id" ) ) ) {
+ nid = nid.replace( rcssescape, fcssescape );
+ } else {
+ context.setAttribute( "id", ( nid = expando ) );
+ }
+ }
+
+ // Prefix every selector in the list
+ groups = tokenize( selector );
+ i = groups.length;
+ while ( i-- ) {
+ groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
+ toSelector( groups[ i ] );
+ }
+ newSelector = groups.join( "," );
+ }
+
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch ( qsaError ) {
+ nonnativeSelectorCache( selector, true );
+ } finally {
+ if ( nid === expando ) {
+ context.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {function(string, object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+function createCache() {
+ var keys = [];
+
+ function cache( key, value ) {
+
+ // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+ if ( keys.push( key + " " ) > Expr.cacheLength ) {
+
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return ( cache[ key + " " ] = value );
+ }
+ return cache;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created element and returns a boolean result
+ */
+function assert( fn ) {
+ var el = document.createElement( "fieldset" );
+
+ try {
+ return !!fn( el );
+ } catch ( e ) {
+ return false;
+ } finally {
+
+ // Remove from its parent by default
+ if ( el.parentNode ) {
+ el.parentNode.removeChild( el );
+ }
+
+ // release memory in IE
+ el = null;
+ }
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+ var arr = attrs.split( "|" ),
+ i = arr.length;
+
+ while ( i-- ) {
+ Expr.attrHandle[ arr[ i ] ] = handler;
+ }
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+ var cur = b && a,
+ diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+ a.sourceIndex - b.sourceIndex;
+
+ // Use IE sourceIndex if available on both nodes
+ if ( diff ) {
+ return diff;
+ }
+
+ // Check if b follows a
+ if ( cur ) {
+ while ( ( cur = cur.nextSibling ) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
+ }
+
+ return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return ( name === "input" || name === "button" ) && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for :enabled/:disabled
+ * @param {Boolean} disabled true for :disabled; false for :enabled
+ */
+function createDisabledPseudo( disabled ) {
+
+ // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
+ return function( elem ) {
+
+ // Only certain elements can match :enabled or :disabled
+ // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
+ // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
+ if ( "form" in elem ) {
+
+ // Check for inherited disabledness on relevant non-disabled elements:
+ // * listed form-associated elements in a disabled fieldset
+ // https://html.spec.whatwg.org/multipage/forms.html#category-listed
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
+ // * option elements in a disabled optgroup
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
+ // All such elements have a "form" property.
+ if ( elem.parentNode && elem.disabled === false ) {
+
+ // Option elements defer to a parent optgroup if present
+ if ( "label" in elem ) {
+ if ( "label" in elem.parentNode ) {
+ return elem.parentNode.disabled === disabled;
+ } else {
+ return elem.disabled === disabled;
+ }
+ }
+
+ // Support: IE 6 - 11
+ // Use the isDisabled shortcut property to check for disabled fieldset ancestors
+ return elem.isDisabled === disabled ||
+
+ // Where there is no isDisabled, check manually
+ /* jshint -W018 */
+ elem.isDisabled !== !disabled &&
+ inDisabledFieldset( elem ) === disabled;
+ }
+
+ return elem.disabled === disabled;
+
+ // Try to winnow out elements that can't be disabled before trusting the disabled property.
+ // Some victims get caught in our net (label, legend, menu, track), but it shouldn't
+ // even exist on them, let alone have a boolean value.
+ } else if ( "label" in elem ) {
+ return elem.disabled === disabled;
+ }
+
+ // Remaining elements are neither :enabled nor :disabled
+ return false;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+ return markFunction( function( argument ) {
+ argument = +argument;
+ return markFunction( function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
+ seed[ j ] = !( matches[ j ] = seed[ j ] );
+ }
+ }
+ } );
+ } );
+}
+
+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+ return context && typeof context.getElementsByTagName !== "undefined" && context;
+}
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = Sizzle.isXML = function( elem ) {
+ var namespace = elem.namespaceURI,
+ docElem = ( elem.ownerDocument || elem ).documentElement;
+
+ // Support: IE <=8
+ // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
+ // https://bugs.jquery.com/ticket/4833
+ return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" );
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+ var hasCompare, subWindow,
+ doc = node ? node.ownerDocument || node : preferredDoc;
+
+ // Return early if doc is invalid or already selected
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Update global variables
+ document = doc;
+ docElem = document.documentElement;
+ documentIsHTML = !isXML( document );
+
+ // Support: IE 9 - 11+, Edge 12 - 18+
+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( preferredDoc != document &&
+ ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
+
+ // Support: IE 11, Edge
+ if ( subWindow.addEventListener ) {
+ subWindow.addEventListener( "unload", unloadHandler, false );
+
+ // Support: IE 9 - 10 only
+ } else if ( subWindow.attachEvent ) {
+ subWindow.attachEvent( "onunload", unloadHandler );
+ }
+ }
+
+ // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,
+ // Safari 4 - 5 only, Opera <=11.6 - 12.x only
+ // IE/Edge & older browsers don't support the :scope pseudo-class.
+ // Support: Safari 6.0 only
+ // Safari 6.0 supports :scope but it's an alias of :root there.
+ support.scope = assert( function( el ) {
+ docElem.appendChild( el ).appendChild( document.createElement( "div" ) );
+ return typeof el.querySelectorAll !== "undefined" &&
+ !el.querySelectorAll( ":scope fieldset div" ).length;
+ } );
+
+ /* Attributes
+ ---------------------------------------------------------------------- */
+
+ // Support: IE<8
+ // Verify that getAttribute really returns attributes and not properties
+ // (excepting IE8 booleans)
+ support.attributes = assert( function( el ) {
+ el.className = "i";
+ return !el.getAttribute( "className" );
+ } );
+
+ /* getElement(s)By*
+ ---------------------------------------------------------------------- */
+
+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert( function( el ) {
+ el.appendChild( document.createComment( "" ) );
+ return !el.getElementsByTagName( "*" ).length;
+ } );
+
+ // Support: IE<9
+ support.getElementsByClassName = rnative.test( document.getElementsByClassName );
+
+ // Support: IE<10
+ // Check if getElementById returns elements by name
+ // The broken getElementById methods don't pick up programmatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert( function( el ) {
+ docElem.appendChild( el ).id = expando;
+ return !document.getElementsByName || !document.getElementsByName( expando ).length;
+ } );
+
+ // ID filter and find
+ if ( support.getById ) {
+ Expr.filter[ "ID" ] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute( "id" ) === attrId;
+ };
+ };
+ Expr.find[ "ID" ] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var elem = context.getElementById( id );
+ return elem ? [ elem ] : [];
+ }
+ };
+ } else {
+ Expr.filter[ "ID" ] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== "undefined" &&
+ elem.getAttributeNode( "id" );
+ return node && node.value === attrId;
+ };
+ };
+
+ // Support: IE 6 - 7 only
+ // getElementById is not reliable as a find shortcut
+ Expr.find[ "ID" ] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var node, i, elems,
+ elem = context.getElementById( id );
+
+ if ( elem ) {
+
+ // Verify the id attribute
+ node = elem.getAttributeNode( "id" );
+ if ( node && node.value === id ) {
+ return [ elem ];
+ }
+
+ // Fall back on getElementsByName
+ elems = context.getElementsByName( id );
+ i = 0;
+ while ( ( elem = elems[ i++ ] ) ) {
+ node = elem.getAttributeNode( "id" );
+ if ( node && node.value === id ) {
+ return [ elem ];
+ }
+ }
+ }
+
+ return [];
+ }
+ };
+ }
+
+ // Tag
+ Expr.find[ "TAG" ] = support.getElementsByTagName ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( tag );
+
+ // DocumentFragment nodes don't have gEBTN
+ } else if ( support.qsa ) {
+ return context.querySelectorAll( tag );
+ }
+ } :
+
+ function( tag, context ) {
+ var elem,
+ tmp = [],
+ i = 0,
+
+ // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+ results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( ( elem = results[ i++ ] ) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ };
+
+ // Class
+ Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
+
+ // QSA and matchesSelector support
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
+
+ // qSa(:focus) reports false when true (Chrome 21)
+ // We allow this because of a bug in IE8/9 that throws an error
+ // whenever `document.activeElement` is accessed on an iframe
+ // So, we allow :focus to pass through QSA all the time to avoid the IE error
+ // See https://bugs.jquery.com/ticket/13378
+ rbuggyQSA = [];
+
+ if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert( function( el ) {
+
+ var input;
+
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explicitly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // https://bugs.jquery.com/ticket/12359
+ docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
+ "<select id='" + expando + "-\r\\' msallowcapture=''>" +
+ "<option selected=''></option></select>";
+
+ // Support: IE8, Opera 11-12.16
+ // Nothing should be selected when empty strings follow ^= or $= or *=
+ // The test attribute must be unknown in Opera but "safe" for WinRT
+ // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+ if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
+
+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !el.querySelectorAll( "[selected]" ).length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+ rbuggyQSA.push( "~=" );
+ }
+
+ // Support: IE 11+, Edge 15 - 18+
+ // IE 11/Edge don't find elements on a `[name='']` query in some cases.
+ // Adding a temporary attribute to the document before the selection works
+ // around the issue.
+ // Interestingly, IE 10 & older don't seem to have the issue.
+ input = document.createElement( "input" );
+ input.setAttribute( "name", "" );
+ el.appendChild( input );
+ if ( !el.querySelectorAll( "[name='']" ).length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
+ whitespace + "*(?:''|\"\")" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here and will not see later tests
+ if ( !el.querySelectorAll( ":checked" ).length ) {
+ rbuggyQSA.push( ":checked" );
+ }
+
+ // Support: Safari 8+, iOS 8+
+ // https://bugs.webkit.org/show_bug.cgi?id=136851
+ // In-page `selector#id sibling-combinator selector` fails
+ if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
+ rbuggyQSA.push( ".#.+[+~]" );
+ }
+
+ // Support: Firefox <=3.6 - 5 only
+ // Old Firefox doesn't throw on a badly-escaped identifier.
+ el.querySelectorAll( "\\\f" );
+ rbuggyQSA.push( "[\\r\\n\\f]" );
+ } );
+
+ assert( function( el ) {
+ el.innerHTML = "<a href='' disabled='disabled'></a>" +
+ "<select disabled='disabled'><option/></select>";
+
+ // Support: Windows 8 Native Apps
+ // The type and name attributes are restricted during .innerHTML assignment
+ var input = document.createElement( "input" );
+ input.setAttribute( "type", "hidden" );
+ el.appendChild( input ).setAttribute( "name", "D" );
+
+ // Support: IE8
+ // Enforce case-sensitivity of name attribute
+ if ( el.querySelectorAll( "[name=d]" ).length ) {
+ rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here and will not see later tests
+ if ( el.querySelectorAll( ":enabled" ).length !== 2 ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Support: IE9-11+
+ // IE's :disabled selector does not pick up the children of disabled fieldsets
+ docElem.appendChild( el ).disabled = true;
+ if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Support: Opera 10 - 11 only
+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ el.querySelectorAll( "*,:x" );
+ rbuggyQSA.push( ",.*:" );
+ } );
+ }
+
+ if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||
+ docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector ) ) ) ) {
+
+ assert( function( el ) {
+
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ support.disconnectedMatch = matches.call( el, "*" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( el, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ } );
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );
+
+ /* Contains
+ ---------------------------------------------------------------------- */
+ hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+ // Element contains another
+ // Purposefully self-exclusive
+ // As in, an element does not contain itself
+ contains = hasCompare || rnative.test( docElem.contains ) ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ) );
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( ( b = b.parentNode ) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /* Sorting
+ ---------------------------------------------------------------------- */
+
+ // Document order sorting
+ sortOrder = hasCompare ?
+ function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ // Sort on method existence if only one input has compareDocumentPosition
+ var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+ if ( compare ) {
+ return compare;
+ }
+
+ // Calculate position if both inputs belong to the same document
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
+ a.compareDocumentPosition( b ) :
+
+ // Otherwise we know they are disconnected
+ 1;
+
+ // Disconnected nodes
+ if ( compare & 1 ||
+ ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {
+
+ // Choose the first element that is related to our preferred document
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( a == document || a.ownerDocument == preferredDoc &&
+ contains( preferredDoc, a ) ) {
+ return -1;
+ }
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( b == document || b.ownerDocument == preferredDoc &&
+ contains( preferredDoc, b ) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ } :
+ function( a, b ) {
+
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
+
+ // Parentless nodes are either documents or disconnected
+ if ( !aup || !bup ) {
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ /* eslint-disable eqeqeq */
+ return a == document ? -1 :
+ b == document ? 1 :
+ /* eslint-enable eqeqeq */
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+
+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
+
+ // Otherwise we need full lists of their ancestors for comparison
+ cur = a;
+ while ( ( cur = cur.parentNode ) ) {
+ ap.unshift( cur );
+ }
+ cur = b;
+ while ( ( cur = cur.parentNode ) ) {
+ bp.unshift( cur );
+ }
+
+ // Walk down the tree looking for a discrepancy
+ while ( ap[ i ] === bp[ i ] ) {
+ i++;
+ }
+
+ return i ?
+
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[ i ], bp[ i ] ) :
+
+ // Otherwise nodes in our document sort first
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ /* eslint-disable eqeqeq */
+ ap[ i ] == preferredDoc ? -1 :
+ bp[ i ] == preferredDoc ? 1 :
+ /* eslint-enable eqeqeq */
+ 0;
+ };
+
+ return document;
+};
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ setDocument( elem );
+
+ if ( support.matchesSelector && documentIsHTML &&
+ !nonnativeSelectorCache[ expr + " " ] &&
+ ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch ( e ) {
+ nonnativeSelectorCache( expr, true );
+ }
+ }
+
+ return Sizzle( expr, document, null, [ elem ] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+
+ // Set document vars if needed
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( ( context.ownerDocument || context ) != document ) {
+ setDocument( context );
+ }
+ return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+
+ // Set document vars if needed
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( ( elem.ownerDocument || elem ) != document ) {
+ setDocument( elem );
+ }
+
+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+
+ // Don't get fooled by Object.prototype properties (jQuery #13807)
+ val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+ fn( elem, name, !documentIsHTML ) :
+ undefined;
+
+ return val !== undefined ?
+ val :
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ ( val = elem.getAttributeNode( name ) ) && val.specified ?
+ val.value :
+ null;
+};
+
+Sizzle.escape = function( sel ) {
+ return ( sel + "" ).replace( rcssescape, fcssescape );
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( ( elem = results[ i++ ] ) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ // Clear input after sorting to release objects
+ // See https://github.com/jquery/sizzle/pull/225
+ sortInput = null;
+
+ return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+
+ // If no nodeType, this is expected to be an array
+ while ( ( node = elem[ i++ ] ) ) {
+
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (jQuery #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[ 1 ] = match[ 1 ].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[ 3 ] = ( match[ 3 ] || match[ 4 ] ||
+ match[ 5 ] || "" ).replace( runescape, funescape );
+
+ if ( match[ 2 ] === "~=" ) {
+ match[ 3 ] = " " + match[ 3 ] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[ 1 ] = match[ 1 ].toLowerCase();
+
+ if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
+
+ // nth-* requires argument
+ if ( !match[ 3 ] ) {
+ Sizzle.error( match[ 0 ] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[ 4 ] = +( match[ 4 ] ?
+ match[ 5 ] + ( match[ 6 ] || 1 ) :
+ 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) );
+ match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[ 3 ] ) {
+ Sizzle.error( match[ 0 ] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[ 6 ] && match[ 2 ];
+
+ if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[ 3 ] ) {
+ match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
+
+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+
+ // Get excess from tokenize (recursively)
+ ( excess = tokenize( unquoted, true ) ) &&
+
+ // advance to the next closing parenthesis
+ ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {
+
+ // excess is a negative index
+ match[ 0 ] = match[ 0 ].slice( 0, excess );
+ match[ 2 ] = unquoted.slice( 0, excess );
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+
+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() {
+ return true;
+ } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ return pattern ||
+ ( pattern = new RegExp( "(^|" + whitespace +
+ ")" + className + "(" + whitespace + "|$)" ) ) && classCache(
+ className, function( elem ) {
+ return pattern.test(
+ typeof elem.className === "string" && elem.className ||
+ typeof elem.getAttribute !== "undefined" &&
+ elem.getAttribute( "class" ) ||
+ ""
+ );
+ } );
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ /* eslint-disable max-len */
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.slice( -check.length ) === check :
+ operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+ false;
+ /* eslint-enable max-len */
+
+ };
+ },
+
+ "CHILD": function( type, what, _argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ function( elem, _context, xml ) {
+ var cache, uniqueCache, outerCache, node, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType,
+ diff = false;
+
+ if ( parent ) {
+
+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( ( node = node[ dir ] ) ) {
+ if ( ofType ?
+ node.nodeName.toLowerCase() === name :
+ node.nodeType === 1 ) {
+
+ return false;
+ }
+ }
+
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+
+ // Seek `elem` from a previously-cached index
+
+ // ...in a gzip-friendly way
+ node = parent;
+ outerCache = node[ expando ] || ( node[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ ( outerCache[ node.uniqueID ] = {} );
+
+ cache = uniqueCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex && cache[ 2 ];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+ while ( ( node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ ( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ } else {
+
+ // Use previously-cached element index if available
+ if ( useCache ) {
+
+ // ...in a gzip-friendly way
+ node = elem;
+ outerCache = node[ expando ] || ( node[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ ( outerCache[ node.uniqueID ] = {} );
+
+ cache = uniqueCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex;
+ }
+
+ // xml :nth-child(...)
+ // or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ if ( diff === false ) {
+
+ // Use the same loop as above to seek `elem` from the start
+ while ( ( node = ++nodeIndex && node && node[ dir ] ||
+ ( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+ if ( ( ofType ?
+ node.nodeName.toLowerCase() === name :
+ node.nodeType === 1 ) &&
+ ++diff ) {
+
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ outerCache = node[ expando ] ||
+ ( node[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ ( outerCache[ node.uniqueID ] = {} );
+
+ uniqueCache[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction( function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf( seed, matched[ i ] );
+ seed[ idx ] = !( matches[ idx ] = matched[ i ] );
+ }
+ } ) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+
+ // Potentially complex pseudos
+ "not": markFunction( function( selector ) {
+
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction( function( seed, matches, _context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( ( elem = unmatched[ i ] ) ) {
+ seed[ i ] = !( matches[ i ] = elem );
+ }
+ }
+ } ) :
+ function( elem, _context, xml ) {
+ input[ 0 ] = elem;
+ matcher( input, null, xml, results );
+
+ // Don't keep the element (issue #299)
+ input[ 0 ] = null;
+ return !results.pop();
+ };
+ } ),
+
+ "has": markFunction( function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ } ),
+
+ "contains": markFunction( function( text ) {
+ text = text.replace( runescape, funescape );
+ return function( elem ) {
+ return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;
+ };
+ } ),
+
+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // http://www.w3.org/TR/selectors/#lang-pseudo
+ "lang": markFunction( function( lang ) {
+
+ // lang value must be a valid identifier
+ if ( !ridentifier.test( lang || "" ) ) {
+ Sizzle.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( ( elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
+ return false;
+ };
+ } ),
+
+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ "root": function( elem ) {
+ return elem === docElem;
+ },
+
+ "focus": function( elem ) {
+ return elem === document.activeElement &&
+ ( !document.hasFocus || document.hasFocus() ) &&
+ !!( elem.type || elem.href || ~elem.tabIndex );
+ },
+
+ // Boolean properties
+ "enabled": createDisabledPseudo( false ),
+ "disabled": createDisabledPseudo( true ),
+
+ "checked": function( elem ) {
+
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return ( nodeName === "input" && !!elem.checked ) ||
+ ( nodeName === "option" && !!elem.selected );
+ },
+
+ "selected": function( elem ) {
+
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ // eslint-disable-next-line no-unused-expressions
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ // Contents
+ "empty": function( elem ) {
+
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+ // but not by others (comment: 8; processing instruction: 7; etc.)
+ // nodeType < 6 works because attributes (2) do not appear as children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeType < 6 ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos[ "empty" ]( elem );
+ },
+
+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "text": function( elem ) {
+ var attr;
+ return elem.nodeName.toLowerCase() === "input" &&
+ elem.type === "text" &&
+
+ // Support: IE<8
+ // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+ ( ( attr = elem.getAttribute( "type" ) ) == null ||
+ attr.toLowerCase() === "text" );
+ },
+
+ // Position-in-collection
+ "first": createPositionalPseudo( function() {
+ return [ 0 ];
+ } ),
+
+ "last": createPositionalPseudo( function( _matchIndexes, length ) {
+ return [ length - 1 ];
+ } ),
+
+ "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ } ),
+
+ "even": createPositionalPseudo( function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ "odd": createPositionalPseudo( function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ "lt": createPositionalPseudo( function( matchIndexes, length, argument ) {
+ var i = argument < 0 ?
+ argument + length :
+ argument > length ?
+ length :
+ argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ "gt": createPositionalPseudo( function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } )
+ }
+};
+
+Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
+ if ( match ) {
+
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[ 0 ].length ) || soFar;
+ }
+ groups.push( ( tokens = [] ) );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( ( match = rcombinators.exec( soFar ) ) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+
+ // Cast descendant combinators to space
+ type: match[ 0 ].replace( rtrim, " " )
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
+ ( match = preFilters[ type ]( match ) ) ) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+ type: type,
+ matches: match
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+};
+
+function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[ i ].value;
+ }
+ return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ skip = combinator.next,
+ key = skip || dir,
+ checkNonElements = base && key === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ return false;
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var oldCache, uniqueCache, outerCache,
+ newCache = [ dirruns, doneName ];
+
+ // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
+ if ( xml ) {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || ( elem[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ elem.uniqueID ] ||
+ ( outerCache[ elem.uniqueID ] = {} );
+
+ if ( skip && skip === elem.nodeName.toLowerCase() ) {
+ elem = elem[ dir ] || elem;
+ } else if ( ( oldCache = uniqueCache[ key ] ) &&
+ oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+ // Assign to newCache so results back-propagate to previous elements
+ return ( newCache[ 2 ] = oldCache[ 2 ] );
+ } else {
+
+ // Reuse newcache so results back-propagate to previous elements
+ uniqueCache[ key ] = newCache;
+
+ // A match means we're done; a fail means we have to keep checking
+ if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[ i ]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[ 0 ];
+}
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[ i ], results );
+ }
+ return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( ( elem = unmatched[ i ] ) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction( function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts(
+ selector || "*",
+ context.nodeType ? [ context ] : context,
+ []
+ ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( ( elem = temp[ i ] ) ) {
+ matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( ( elem = matcherOut[ i ] ) ) {
+
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( ( matcherIn[ i ] = elem ) );
+ }
+ }
+ postFinder( null, ( matcherOut = [] ), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( ( elem = matcherOut[ i ] ) &&
+ ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {
+
+ seed[ temp ] = !( results[ temp ] = elem );
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ } );
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[ 0 ].type ],
+ implicitRelative = leadingRelative || Expr.relative[ " " ],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ ( checkContext = context ).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+
+ // Avoid hanging onto element (issue #299)
+ checkContext = null;
+ return ret;
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+ } else {
+ matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[ j ].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector(
+
+ // If the preceding token was a descendant combinator, insert an implicit any-element `*`
+ tokens
+ .slice( 0, i - 1 )
+ .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
+ ).replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, outermost ) {
+ var elem, j, matcher,
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ setMatched = [],
+ contextBackup = outermostContext,
+
+ // We must always have either seed elements or outermost context
+ elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ),
+
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
+ len = elems.length;
+
+ if ( outermost ) {
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ outermostContext = context == document || context || outermost;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ // Support: IE<9, Safari
+ // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
+ for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( !context && elem.ownerDocument != document ) {
+ setDocument( elem );
+ xml = !documentIsHTML;
+ }
+ while ( ( matcher = elementMatchers[ j++ ] ) ) {
+ if ( matcher( elem, context || document, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+
+ // They will have gone through all possible matchers
+ if ( ( elem = !matcher && elem ) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // `i` is now the count of elements visited above, and adding it to `matchedCount`
+ // makes the latter nonnegative.
+ matchedCount += i;
+
+ // Apply set filters to unmatched elements
+ // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
+ // equals `i`), unless we didn't visit _any_ elements in the above loop because we have
+ // no element matchers and no seed.
+ // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
+ // case, which will result in a "00" `matchedCount` that differs from `i` but is also
+ // numerically zero.
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( ( matcher = setMatchers[ j++ ] ) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
+ setMatched[ i ] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ if ( !cached ) {
+
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !match ) {
+ match = tokenize( selector );
+ }
+ i = match.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( match[ i ] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache(
+ selector,
+ matcherFromGroupMatchers( elementMatchers, setMatchers )
+ );
+
+ // Save selector and tokenization
+ cached.selector = selector;
+ }
+ return cached;
+};
+
+/**
+ * A low-level selection function that works with Sizzle's compiled
+ * selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ * selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+select = Sizzle.select = function( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ compiled = typeof selector === "function" && selector,
+ match = !seed && tokenize( ( selector = compiled.selector || selector ) );
+
+ results = results || [];
+
+ // Try to minimize operations if there is only one selector in the list and no seed
+ // (the latter of which guarantees us context)
+ if ( match.length === 1 ) {
+
+ // Reduce context if the leading compound selector is an ID
+ tokens = match[ 0 ] = match[ 0 ].slice( 0 );
+ if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
+ context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {
+
+ context = ( Expr.find[ "ID" ]( token.matches[ 0 ]
+ .replace( runescape, funescape ), context ) || [] )[ 0 ];
+ if ( !context ) {
+ return results;
+
+ // Precompiled matchers will still verify ancestry, so step up a level
+ } else if ( compiled ) {
+ context = context.parentNode;
+ }
+
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[ i ];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ ( type = token.type ) ] ) {
+ break;
+ }
+ if ( ( find = Expr.find[ type ] ) ) {
+
+ // Search, expanding context for leading sibling combinators
+ if ( ( seed = find(
+ token.matches[ 0 ].replace( runescape, funescape ),
+ rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||
+ context
+ ) ) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function if one is not provided
+ // Provide `match` to avoid retokenization if we modified the selector above
+ ( compiled || compile( selector, match ) )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ !context || rsibling.test( selector ) && testContext( context.parentNode ) || context
+ );
+ return results;
+};
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;
+
+// Support: Chrome 14-35+
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert( function( el ) {
+
+ // Should return 1, but returns 4 (following)
+ return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
+} );
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert( function( el ) {
+ el.innerHTML = "<a href='#'></a>";
+ return el.firstChild.getAttribute( "href" ) === "#";
+} ) ) {
+ addHandle( "type|href|height|width", function( elem, name, isXML ) {
+ if ( !isXML ) {
+ return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+ }
+ } );
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert( function( el ) {
+ el.innerHTML = "<input/>";
+ el.firstChild.setAttribute( "value", "" );
+ return el.firstChild.getAttribute( "value" ) === "";
+} ) ) {
+ addHandle( "value", function( elem, _name, isXML ) {
+ if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+ return elem.defaultValue;
+ }
+ } );
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert( function( el ) {
+ return el.getAttribute( "disabled" ) == null;
+} ) ) {
+ addHandle( booleans, function( elem, name, isXML ) {
+ var val;
+ if ( !isXML ) {
+ return elem[ name ] === true ? name.toLowerCase() :
+ ( val = elem.getAttributeNode( name ) ) && val.specified ?
+ val.value :
+ null;
+ }
+ } );
+}
+
+return Sizzle;
+
+} )( window );
+
+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+
+// Deprecated
+jQuery.expr[ ":" ] = jQuery.expr.pseudos;
+jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+jQuery.escapeSelector = Sizzle.escape;
+
+
+
+
+var dir = function( elem, dir, until ) {
+ var matched = [],
+ truncate = until !== undefined;
+
+ while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
+ if ( elem.nodeType === 1 ) {
+ if ( truncate && jQuery( elem ).is( until ) ) {
+ break;
+ }
+ matched.push( elem );
+ }
+ }
+ return matched;
+};
+
+
+var siblings = function( n, elem ) {
+ var matched = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ matched.push( n );
+ }
+ }
+
+ return matched;
+};
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+
+
+function nodeName( elem, name ) {
+
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+
+};
+var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
+
+
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+ if ( isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ return !!qualifier.call( elem, i, elem ) !== not;
+ } );
+ }
+
+ // Single element
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ } );
+ }
+
+ // Arraylike of elements (jQuery, arguments, Array)
+ if ( typeof qualifier !== "string" ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
+ } );
+ }
+
+ // Filtered directly for both simple and complex selectors
+ return jQuery.filter( qualifier, elements, not );
+}
+
+jQuery.filter = function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ if ( elems.length === 1 && elem.nodeType === 1 ) {
+ return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
+ }
+
+ return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ } ) );
+};
+
+jQuery.fn.extend( {
+ find: function( selector ) {
+ var i, ret,
+ len = this.length,
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter( function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ } ) );
+ }
+
+ ret = this.pushStack( [] );
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ return len > 1 ? jQuery.uniqueSort( ret ) : ret;
+ },
+ filter: function( selector ) {
+ return this.pushStack( winnow( this, selector || [], false ) );
+ },
+ not: function( selector ) {
+ return this.pushStack( winnow( this, selector || [], true ) );
+ },
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
+ }
+} );
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ // Shortcut simple #id case for speed
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
+
+ init = jQuery.fn.init = function( selector, context, root ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Method init() accepts an alternate rootjQuery
+ // so migrate can support jQuery.sub (gh-2101)
+ root = root || rootjQuery;
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector[ 0 ] === "<" &&
+ selector[ selector.length - 1 ] === ">" &&
+ selector.length >= 3 ) {
+
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && ( match[ 1 ] || !context ) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[ 1 ] ) {
+ context = context instanceof jQuery ? context[ 0 ] : context;
+
+ // Option to run scripts is true for back-compat
+ // Intentionally let the error be thrown if parseHTML is not present
+ jQuery.merge( this, jQuery.parseHTML(
+ match[ 1 ],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+
+ // Properties of context are called as methods if possible
+ if ( isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[ 2 ] );
+
+ if ( elem ) {
+
+ // Inject the element directly into the jQuery object
+ this[ 0 ] = elem;
+ this.length = 1;
+ }
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || root ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this[ 0 ] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( isFunction( selector ) ) {
+ return root.ready !== undefined ?
+ root.ready( selector ) :
+
+ // Execute immediately if ready is not present
+ selector( jQuery );
+ }
+
+ return jQuery.makeArray( selector, this );
+ };
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+
+ // Methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend( {
+ has: function( target ) {
+ var targets = jQuery( target, this ),
+ l = targets.length;
+
+ return this.filter( function() {
+ var i = 0;
+ for ( ; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[ i ] ) ) {
+ return true;
+ }
+ }
+ } );
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ targets = typeof selectors !== "string" && jQuery( selectors );
+
+ // Positional selectors never match, since there's no _selection_ context
+ if ( !rneedsContext.test( selectors ) ) {
+ for ( ; i < l; i++ ) {
+ for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && ( targets ?
+ targets.index( cur ) > -1 :
+
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector( cur, selectors ) ) ) {
+
+ matched.push( cur );
+ break;
+ }
+ }
+ }
+ }
+
+ return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
+ },
+
+ // Determine the position of an element within the set
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+ }
+
+ // Index in selector
+ if ( typeof elem === "string" ) {
+ return indexOf.call( jQuery( elem ), this[ 0 ] );
+ }
+
+ // Locate the position of the desired element
+ return indexOf.call( this,
+
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[ 0 ] : elem
+ );
+ },
+
+ add: function( selector, context ) {
+ return this.pushStack(
+ jQuery.uniqueSort(
+ jQuery.merge( this.get(), jQuery( selector, context ) )
+ )
+ );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter( selector )
+ );
+ }
+} );
+
+function sibling( cur, dir ) {
+ while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
+ return cur;
+}
+
+jQuery.each( {
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, _i, until ) {
+ return dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, _i, until ) {
+ return dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, _i, until ) {
+ return dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return siblings( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return siblings( elem.firstChild );
+ },
+ contents: function( elem ) {
+ if ( elem.contentDocument != null &&
+
+ // Support: IE 11+
+ // <object> elements with no `data` attribute has an object
+ // `contentDocument` with a `null` prototype.
+ getProto( elem.contentDocument ) ) {
+
+ return elem.contentDocument;
+ }
+
+ // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
+ // Treat the template element as a regular one in browsers that
+ // don't support it.
+ if ( nodeName( elem, "template" ) ) {
+ elem = elem.content || elem;
+ }
+
+ return jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var matched = jQuery.map( this, fn, until );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ matched = jQuery.filter( selector, matched );
+ }
+
+ if ( this.length > 1 ) {
+
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ jQuery.uniqueSort( matched );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ matched.reverse();
+ }
+ }
+
+ return this.pushStack( matched );
+ };
+} );
+var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
+
+
+
+// Convert String-formatted options into Object-formatted ones
+function createOptions( options ) {
+ var object = {};
+ jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ } );
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ createOptions( options ) :
+ jQuery.extend( {}, options );
+
+ var // Flag to know if list is currently firing
+ firing,
+
+ // Last fire value for non-forgettable lists
+ memory,
+
+ // Flag to know if list was already fired
+ fired,
+
+ // Flag to prevent firing
+ locked,
+
+ // Actual callback list
+ list = [],
+
+ // Queue of execution data for repeatable lists
+ queue = [],
+
+ // Index of currently firing callback (modified by add/remove as needed)
+ firingIndex = -1,
+
+ // Fire callbacks
+ fire = function() {
+
+ // Enforce single-firing
+ locked = locked || options.once;
+
+ // Execute callbacks for all pending executions,
+ // respecting firingIndex overrides and runtime changes
+ fired = firing = true;
+ for ( ; queue.length; firingIndex = -1 ) {
+ memory = queue.shift();
+ while ( ++firingIndex < list.length ) {
+
+ // Run callback and check for early termination
+ if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
+ options.stopOnFalse ) {
+
+ // Jump to end and forget the data so .add doesn't re-fire
+ firingIndex = list.length;
+ memory = false;
+ }
+ }
+ }
+
+ // Forget the data if we're done with it
+ if ( !options.memory ) {
+ memory = false;
+ }
+
+ firing = false;
+
+ // Clean up if we're done firing for good
+ if ( locked ) {
+
+ // Keep an empty list if we have data for future add calls
+ if ( memory ) {
+ list = [];
+
+ // Otherwise, this object is spent
+ } else {
+ list = "";
+ }
+ }
+ },
+
+ // Actual Callbacks object
+ self = {
+
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+
+ // If we have memory from a past run, we should fire after adding
+ if ( memory && !firing ) {
+ firingIndex = list.length - 1;
+ queue.push( memory );
+ }
+
+ ( function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ if ( isFunction( arg ) ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && toType( arg ) !== "string" ) {
+
+ // Inspect recursively
+ add( arg );
+ }
+ } );
+ } )( arguments );
+
+ if ( memory && !firing ) {
+ fire();
+ }
+ }
+ return this;
+ },
+
+ // Remove a callback from the list
+ remove: function() {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+
+ // Handle firing indexes
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ } );
+ return this;
+ },
+
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ?
+ jQuery.inArray( fn, list ) > -1 :
+ list.length > 0;
+ },
+
+ // Remove all callbacks from the list
+ empty: function() {
+ if ( list ) {
+ list = [];
+ }
+ return this;
+ },
+
+ // Disable .fire and .add
+ // Abort any current/pending executions
+ // Clear all callbacks and values
+ disable: function() {
+ locked = queue = [];
+ list = memory = "";
+ return this;
+ },
+ disabled: function() {
+ return !list;
+ },
+
+ // Disable .fire
+ // Also disable .add unless we have memory (since it would have no effect)
+ // Abort any pending executions
+ lock: function() {
+ locked = queue = [];
+ if ( !memory && !firing ) {
+ list = memory = "";
+ }
+ return this;
+ },
+ locked: function() {
+ return !!locked;
+ },
+
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( !locked ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ queue.push( args );
+ if ( !firing ) {
+ fire();
+ }
+ }
+ return this;
+ },
+
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+
+
+function Identity( v ) {
+ return v;
+}
+function Thrower( ex ) {
+ throw ex;
+}
+
+function adoptValue( value, resolve, reject, noValue ) {
+ var method;
+
+ try {
+
+ // Check for promise aspect first to privilege synchronous behavior
+ if ( value && isFunction( ( method = value.promise ) ) ) {
+ method.call( value ).done( resolve ).fail( reject );
+
+ // Other thenables
+ } else if ( value && isFunction( ( method = value.then ) ) ) {
+ method.call( value, resolve, reject );
+
+ // Other non-thenables
+ } else {
+
+ // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
+ // * false: [ value ].slice( 0 ) => resolve( value )
+ // * true: [ value ].slice( 1 ) => resolve()
+ resolve.apply( undefined, [ value ].slice( noValue ) );
+ }
+
+ // For Promises/A+, convert exceptions into rejections
+ // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
+ // Deferred#then to conditionally suppress rejection.
+ } catch ( value ) {
+
+ // Support: Android 4.0 only
+ // Strict mode functions invoked without .call/.apply get global-object context
+ reject.apply( undefined, [ value ] );
+ }
+}
+
+jQuery.extend( {
+
+ Deferred: function( func ) {
+ var tuples = [
+
+ // action, add listener, callbacks,
+ // ... .then handlers, argument index, [final state]
+ [ "notify", "progress", jQuery.Callbacks( "memory" ),
+ jQuery.Callbacks( "memory" ), 2 ],
+ [ "resolve", "done", jQuery.Callbacks( "once memory" ),
+ jQuery.Callbacks( "once memory" ), 0, "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks( "once memory" ),
+ jQuery.Callbacks( "once memory" ), 1, "rejected" ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ "catch": function( fn ) {
+ return promise.then( null, fn );
+ },
+
+ // Keep pipe for back-compat
+ pipe: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+
+ return jQuery.Deferred( function( newDefer ) {
+ jQuery.each( tuples, function( _i, tuple ) {
+
+ // Map tuples (progress, done, fail) to arguments (done, fail, progress)
+ var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
+
+ // deferred.progress(function() { bind to newDefer or newDefer.notify })
+ // deferred.done(function() { bind to newDefer or newDefer.resolve })
+ // deferred.fail(function() { bind to newDefer or newDefer.reject })
+ deferred[ tuple[ 1 ] ]( function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && isFunction( returned.promise ) ) {
+ returned.promise()
+ .progress( newDefer.notify )
+ .done( newDefer.resolve )
+ .fail( newDefer.reject );
+ } else {
+ newDefer[ tuple[ 0 ] + "With" ](
+ this,
+ fn ? [ returned ] : arguments
+ );
+ }
+ } );
+ } );
+ fns = null;
+ } ).promise();
+ },
+ then: function( onFulfilled, onRejected, onProgress ) {
+ var maxDepth = 0;
+ function resolve( depth, deferred, handler, special ) {
+ return function() {
+ var that = this,
+ args = arguments,
+ mightThrow = function() {
+ var returned, then;
+
+ // Support: Promises/A+ section 2.3.3.3.3
+ // https://promisesaplus.com/#point-59
+ // Ignore double-resolution attempts
+ if ( depth < maxDepth ) {
+ return;
+ }
+
+ returned = handler.apply( that, args );
+
+ // Support: Promises/A+ section 2.3.1
+ // https://promisesaplus.com/#point-48
+ if ( returned === deferred.promise() ) {
+ throw new TypeError( "Thenable self-resolution" );
+ }
+
+ // Support: Promises/A+ sections 2.3.3.1, 3.5
+ // https://promisesaplus.com/#point-54
+ // https://promisesaplus.com/#point-75
+ // Retrieve `then` only once
+ then = returned &&
+
+ // Support: Promises/A+ section 2.3.4
+ // https://promisesaplus.com/#point-64
+ // Only check objects and functions for thenability
+ ( typeof returned === "object" ||
+ typeof returned === "function" ) &&
+ returned.then;
+
+ // Handle a returned thenable
+ if ( isFunction( then ) ) {
+
+ // Special processors (notify) just wait for resolution
+ if ( special ) {
+ then.call(
+ returned,
+ resolve( maxDepth, deferred, Identity, special ),
+ resolve( maxDepth, deferred, Thrower, special )
+ );
+
+ // Normal processors (resolve) also hook into progress
+ } else {
+
+ // ...and disregard older resolution values
+ maxDepth++;
+
+ then.call(
+ returned,
+ resolve( maxDepth, deferred, Identity, special ),
+ resolve( maxDepth, deferred, Thrower, special ),
+ resolve( maxDepth, deferred, Identity,
+ deferred.notifyWith )
+ );
+ }
+
+ // Handle all other returned values
+ } else {
+
+ // Only substitute handlers pass on context
+ // and multiple values (non-spec behavior)
+ if ( handler !== Identity ) {
+ that = undefined;
+ args = [ returned ];
+ }
+
+ // Process the value(s)
+ // Default process is resolve
+ ( special || deferred.resolveWith )( that, args );
+ }
+ },
+
+ // Only normal processors (resolve) catch and reject exceptions
+ process = special ?
+ mightThrow :
+ function() {
+ try {
+ mightThrow();
+ } catch ( e ) {
+
+ if ( jQuery.Deferred.exceptionHook ) {
+ jQuery.Deferred.exceptionHook( e,
+ process.stackTrace );
+ }
+
+ // Support: Promises/A+ section 2.3.3.3.4.1
+ // https://promisesaplus.com/#point-61
+ // Ignore post-resolution exceptions
+ if ( depth + 1 >= maxDepth ) {
+
+ // Only substitute handlers pass on context
+ // and multiple values (non-spec behavior)
+ if ( handler !== Thrower ) {
+ that = undefined;
+ args = [ e ];
+ }
+
+ deferred.rejectWith( that, args );
+ }
+ }
+ };
+
+ // Support: Promises/A+ section 2.3.3.3.1
+ // https://promisesaplus.com/#point-57
+ // Re-resolve promises immediately to dodge false rejection from
+ // subsequent errors
+ if ( depth ) {
+ process();
+ } else {
+
+ // Call an optional hook to record the stack, in case of exception
+ // since it's otherwise lost when execution goes async
+ if ( jQuery.Deferred.getStackHook ) {
+ process.stackTrace = jQuery.Deferred.getStackHook();
+ }
+ window.setTimeout( process );
+ }
+ };
+ }
+
+ return jQuery.Deferred( function( newDefer ) {
+
+ // progress_handlers.add( ... )
+ tuples[ 0 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onProgress ) ?
+ onProgress :
+ Identity,
+ newDefer.notifyWith
+ )
+ );
+
+ // fulfilled_handlers.add( ... )
+ tuples[ 1 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onFulfilled ) ?
+ onFulfilled :
+ Identity
+ )
+ );
+
+ // rejected_handlers.add( ... )
+ tuples[ 2 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onRejected ) ?
+ onRejected :
+ Thrower
+ )
+ );
+ } ).promise();
+ },
+
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 5 ];
+
+ // promise.progress = list.add
+ // promise.done = list.add
+ // promise.fail = list.add
+ promise[ tuple[ 1 ] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(
+ function() {
+
+ // state = "resolved" (i.e., fulfilled)
+ // state = "rejected"
+ state = stateString;
+ },
+
+ // rejected_callbacks.disable
+ // fulfilled_callbacks.disable
+ tuples[ 3 - i ][ 2 ].disable,
+
+ // rejected_handlers.disable
+ // fulfilled_handlers.disable
+ tuples[ 3 - i ][ 3 ].disable,
+
+ // progress_callbacks.lock
+ tuples[ 0 ][ 2 ].lock,
+
+ // progress_handlers.lock
+ tuples[ 0 ][ 3 ].lock
+ );
+ }
+
+ // progress_handlers.fire
+ // fulfilled_handlers.fire
+ // rejected_handlers.fire
+ list.add( tuple[ 3 ].fire );
+
+ // deferred.notify = function() { deferred.notifyWith(...) }
+ // deferred.resolve = function() { deferred.resolveWith(...) }
+ // deferred.reject = function() { deferred.rejectWith(...) }
+ deferred[ tuple[ 0 ] ] = function() {
+ deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
+ return this;
+ };
+
+ // deferred.notifyWith = list.fireWith
+ // deferred.resolveWith = list.fireWith
+ // deferred.rejectWith = list.fireWith
+ deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
+ } );
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( singleValue ) {
+ var
+
+ // count of uncompleted subordinates
+ remaining = arguments.length,
+
+ // count of unprocessed arguments
+ i = remaining,
+
+ // subordinate fulfillment data
+ resolveContexts = Array( i ),
+ resolveValues = slice.call( arguments ),
+
+ // the master Deferred
+ master = jQuery.Deferred(),
+
+ // subordinate callback factory
+ updateFunc = function( i ) {
+ return function( value ) {
+ resolveContexts[ i ] = this;
+ resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( !( --remaining ) ) {
+ master.resolveWith( resolveContexts, resolveValues );
+ }
+ };
+ };
+
+ // Single- and empty arguments are adopted like Promise.resolve
+ if ( remaining <= 1 ) {
+ adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
+ !remaining );
+
+ // Use .then() to unwrap secondary thenables (cf. gh-3000)
+ if ( master.state() === "pending" ||
+ isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
+
+ return master.then();
+ }
+ }
+
+ // Multiple arguments are aggregated like Promise.all array elements
+ while ( i-- ) {
+ adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
+ }
+
+ return master.promise();
+ }
+} );
+
+
+// These usually indicate a programmer mistake during development,
+// warn about them ASAP rather than swallowing them by default.
+var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
+
+jQuery.Deferred.exceptionHook = function( error, stack ) {
+
+ // Support: IE 8 - 9 only
+ // Console exists when dev tools are open, which can happen at any time
+ if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
+ window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
+ }
+};
+
+
+
+
+jQuery.readyException = function( error ) {
+ window.setTimeout( function() {
+ throw error;
+ } );
+};
+
+
+
+
+// The deferred used on DOM ready
+var readyList = jQuery.Deferred();
+
+jQuery.fn.ready = function( fn ) {
+
+ readyList
+ .then( fn )
+
+ // Wrap jQuery.readyException in a function so that the lookup
+ // happens at the time of error handling instead of callback
+ // registration.
+ .catch( function( error ) {
+ jQuery.readyException( error );
+ } );
+
+ return this;
+};
+
+jQuery.extend( {
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+ }
+} );
+
+jQuery.ready.then = readyList.then;
+
+// The ready event handler and self cleanup method
+function completed() {
+ document.removeEventListener( "DOMContentLoaded", completed );
+ window.removeEventListener( "load", completed );
+ jQuery.ready();
+}
+
+// Catch cases where $(document).ready() is called
+// after the browser event has already occurred.
+// Support: IE <=9 - 10 only
+// Older IE sometimes signals "interactive" too soon
+if ( document.readyState === "complete" ||
+ ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ window.setTimeout( jQuery.ready );
+
+} else {
+
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed );
+}
+
+
+
+
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ len = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( toType( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ access( elems, fn, i, key[ i ], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, _key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < len; i++ ) {
+ fn(
+ elems[ i ], key, raw ?
+ value :
+ value.call( elems[ i ], i, fn( elems[ i ], key ) )
+ );
+ }
+ }
+ }
+
+ if ( chainable ) {
+ return elems;
+ }
+
+ // Gets
+ if ( bulk ) {
+ return fn.call( elems );
+ }
+
+ return len ? fn( elems[ 0 ], key ) : emptyGet;
+};
+
+
+// Matches dashed string for camelizing
+var rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([a-z])/g;
+
+// Used by camelCase as callback to replace()
+function fcamelCase( _all, letter ) {
+ return letter.toUpperCase();
+}
+
+// Convert dashed to camelCase; used by the css and data modules
+// Support: IE <=9 - 11, Edge 12 - 15
+// Microsoft forgot to hump their vendor prefix (#9572)
+function camelCase( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+}
+var acceptData = function( owner ) {
+
+ // Accepts only:
+ // - Node
+ // - Node.ELEMENT_NODE
+ // - Node.DOCUMENT_NODE
+ // - Object
+ // - Any
+ return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+};
+
+
+
+
+function Data() {
+ this.expando = jQuery.expando + Data.uid++;
+}
+
+Data.uid = 1;
+
+Data.prototype = {
+
+ cache: function( owner ) {
+
+ // Check if the owner object already has a cache
+ var value = owner[ this.expando ];
+
+ // If not, create one
+ if ( !value ) {
+ value = {};
+
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see #8335.
+ // Always return an empty object.
+ if ( acceptData( owner ) ) {
+
+ // If it is a node unlikely to be stringify-ed or looped over
+ // use plain assignment
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = value;
+
+ // Otherwise secure it in a non-enumerable property
+ // configurable must be true to allow the property to be
+ // deleted when data is removed
+ } else {
+ Object.defineProperty( owner, this.expando, {
+ value: value,
+ configurable: true
+ } );
+ }
+ }
+ }
+
+ return value;
+ },
+ set: function( owner, data, value ) {
+ var prop,
+ cache = this.cache( owner );
+
+ // Handle: [ owner, key, value ] args
+ // Always use camelCase key (gh-2257)
+ if ( typeof data === "string" ) {
+ cache[ camelCase( data ) ] = value;
+
+ // Handle: [ owner, { properties } ] args
+ } else {
+
+ // Copy the properties one-by-one to the cache object
+ for ( prop in data ) {
+ cache[ camelCase( prop ) ] = data[ prop ];
+ }
+ }
+ return cache;
+ },
+ get: function( owner, key ) {
+ return key === undefined ?
+ this.cache( owner ) :
+
+ // Always use camelCase key (gh-2257)
+ owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
+ },
+ access: function( owner, key, value ) {
+
+ // In cases where either:
+ //
+ // 1. No key was specified
+ // 2. A string key was specified, but no value provided
+ //
+ // Take the "read" path and allow the get method to determine
+ // which value to return, respectively either:
+ //
+ // 1. The entire cache object
+ // 2. The data stored at the key
+ //
+ if ( key === undefined ||
+ ( ( key && typeof key === "string" ) && value === undefined ) ) {
+
+ return this.get( owner, key );
+ }
+
+ // When the key is not a string, or both a key and value
+ // are specified, set or extend (existing objects) with either:
+ //
+ // 1. An object of properties
+ // 2. A key and value
+ //
+ this.set( owner, key, value );
+
+ // Since the "set" path can have two possible entry points
+ // return the expected data based on which path was taken[*]
+ return value !== undefined ? value : key;
+ },
+ remove: function( owner, key ) {
+ var i,
+ cache = owner[ this.expando ];
+
+ if ( cache === undefined ) {
+ return;
+ }
+
+ if ( key !== undefined ) {
+
+ // Support array or space separated string of keys
+ if ( Array.isArray( key ) ) {
+
+ // If key is an array of keys...
+ // We always set camelCase keys, so remove that.
+ key = key.map( camelCase );
+ } else {
+ key = camelCase( key );
+
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ key = key in cache ?
+ [ key ] :
+ ( key.match( rnothtmlwhite ) || [] );
+ }
+
+ i = key.length;
+
+ while ( i-- ) {
+ delete cache[ key[ i ] ];
+ }
+ }
+
+ // Remove the expando if there's no more data
+ if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+
+ // Support: Chrome <=35 - 45
+ // Webkit & Blink performance suffers when deleting properties
+ // from DOM nodes, so set to undefined instead
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = undefined;
+ } else {
+ delete owner[ this.expando ];
+ }
+ }
+ },
+ hasData: function( owner ) {
+ var cache = owner[ this.expando ];
+ return cache !== undefined && !jQuery.isEmptyObject( cache );
+ }
+};
+var dataPriv = new Data();
+
+var dataUser = new Data();
+
+
+
+// Implementation Summary
+//
+// 1. Enforce API surface and semantic compatibility with 1.9.x branch
+// 2. Improve the module's maintainability by reducing the storage
+// paths to a single mechanism.
+// 3. Use the same single mechanism to support "private" and "user" data.
+// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+// 5. Avoid exposing implementation details on user objects (eg. expando properties)
+// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /[A-Z]/g;
+
+function getData( data ) {
+ if ( data === "true" ) {
+ return true;
+ }
+
+ if ( data === "false" ) {
+ return false;
+ }
+
+ if ( data === "null" ) {
+ return null;
+ }
+
+ // Only convert to a number if it doesn't change the string
+ if ( data === +data + "" ) {
+ return +data;
+ }
+
+ if ( rbrace.test( data ) ) {
+ return JSON.parse( data );
+ }
+
+ return data;
+}
+
+function dataAttr( elem, key, data ) {
+ var name;
+
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = getData( data );
+ } catch ( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ dataUser.set( elem, key, data );
+ } else {
+ data = undefined;
+ }
+ }
+ return data;
+}
+
+jQuery.extend( {
+ hasData: function( elem ) {
+ return dataUser.hasData( elem ) || dataPriv.hasData( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return dataUser.access( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ dataUser.remove( elem, name );
+ },
+
+ // TODO: Now that all calls to _data and _removeData have been replaced
+ // with direct calls to dataPriv methods, these can be deprecated.
+ _data: function( elem, name, data ) {
+ return dataPriv.access( elem, name, data );
+ },
+
+ _removeData: function( elem, name ) {
+ dataPriv.remove( elem, name );
+ }
+} );
+
+jQuery.fn.extend( {
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[ 0 ],
+ attrs = elem && elem.attributes;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = dataUser.get( elem );
+
+ if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
+ i = attrs.length;
+ while ( i-- ) {
+
+ // Support: IE 11 only
+ // The attrs elements can be null (#14894)
+ if ( attrs[ i ] ) {
+ name = attrs[ i ].name;
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = camelCase( name.slice( 5 ) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ }
+ dataPriv.set( elem, "hasDataAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each( function() {
+ dataUser.set( this, key );
+ } );
+ }
+
+ return access( this, function( value ) {
+ var data;
+
+ // The calling jQuery object (element matches) is not empty
+ // (and therefore has an element appears at this[ 0 ]) and the
+ // `value` parameter was not undefined. An empty jQuery object
+ // will result in `undefined` for elem = this[ 0 ] which will
+ // throw an exception if an attempt to read a data cache is made.
+ if ( elem && value === undefined ) {
+
+ // Attempt to get data from the cache
+ // The key will always be camelCased in Data
+ data = dataUser.get( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to "discover" the data in
+ // HTML5 custom data-* attrs
+ data = dataAttr( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // We tried really hard, but the data doesn't exist.
+ return;
+ }
+
+ // Set the data...
+ this.each( function() {
+
+ // We always store the camelCased key
+ dataUser.set( this, key, value );
+ } );
+ }, null, value, arguments.length > 1, null, true );
+ },
+
+ removeData: function( key ) {
+ return this.each( function() {
+ dataUser.remove( this, key );
+ } );
+ }
+} );
+
+
+jQuery.extend( {
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = dataPriv.get( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || Array.isArray( data ) ) {
+ queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // Clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // Not public - generate a queueHooks object, or return the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+ empty: jQuery.Callbacks( "once memory" ).add( function() {
+ dataPriv.remove( elem, [ type + "queue", key ] );
+ } )
+ } );
+ }
+} );
+
+jQuery.fn.extend( {
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[ 0 ], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each( function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // Ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ } );
+ },
+ dequeue: function( type ) {
+ return this.each( function() {
+ jQuery.dequeue( this, type );
+ } );
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while ( i-- ) {
+ tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+} );
+var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
+
+var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+
+
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+var documentElement = document.documentElement;
+
+
+
+ var isAttached = function( elem ) {
+ return jQuery.contains( elem.ownerDocument, elem );
+ },
+ composed = { composed: true };
+
+ // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
+ // Check attachment across shadow DOM boundaries when possible (gh-3504)
+ // Support: iOS 10.0-10.2 only
+ // Early iOS 10 versions support `attachShadow` but not `getRootNode`,
+ // leading to errors. We need to check for `getRootNode`.
+ if ( documentElement.getRootNode ) {
+ isAttached = function( elem ) {
+ return jQuery.contains( elem.ownerDocument, elem ) ||
+ elem.getRootNode( composed ) === elem.ownerDocument;
+ };
+ }
+var isHiddenWithinTree = function( elem, el ) {
+
+ // isHiddenWithinTree might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+
+ // Inline style trumps all
+ return elem.style.display === "none" ||
+ elem.style.display === "" &&
+
+ // Otherwise, check computed style
+ // Support: Firefox <=43 - 45
+ // Disconnected elements can have computed display: none, so first confirm that elem is
+ // in the document.
+ isAttached( elem ) &&
+
+ jQuery.css( elem, "display" ) === "none";
+ };
+
+
+
+function adjustCSS( elem, prop, valueParts, tween ) {
+ var adjusted, scale,
+ maxIterations = 20,
+ currentValue = tween ?
+ function() {
+ return tween.cur();
+ } :
+ function() {
+ return jQuery.css( elem, prop, "" );
+ },
+ initial = currentValue(),
+ unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+ // Starting value computation is required for potential unit mismatches
+ initialInUnit = elem.nodeType &&
+ ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
+ rcssNum.exec( jQuery.css( elem, prop ) );
+
+ if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
+
+ // Support: Firefox <=54
+ // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
+ initial = initial / 2;
+
+ // Trust units reported by jQuery.css
+ unit = unit || initialInUnit[ 3 ];
+
+ // Iteratively approximate from a nonzero starting point
+ initialInUnit = +initial || 1;
+
+ while ( maxIterations-- ) {
+
+ // Evaluate and update our best guess (doubling guesses that zero out).
+ // Finish if the scale equals or crosses 1 (making the old*new product non-positive).
+ jQuery.style( elem, prop, initialInUnit + unit );
+ if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
+ maxIterations = 0;
+ }
+ initialInUnit = initialInUnit / scale;
+
+ }
+
+ initialInUnit = initialInUnit * 2;
+ jQuery.style( elem, prop, initialInUnit + unit );
+
+ // Make sure we update the tween properties later on
+ valueParts = valueParts || [];
+ }
+
+ if ( valueParts ) {
+ initialInUnit = +initialInUnit || +initial || 0;
+
+ // Apply relative offset (+=/-=) if specified
+ adjusted = valueParts[ 1 ] ?
+ initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+ +valueParts[ 2 ];
+ if ( tween ) {
+ tween.unit = unit;
+ tween.start = initialInUnit;
+ tween.end = adjusted;
+ }
+ }
+ return adjusted;
+}
+
+
+var defaultDisplayMap = {};
+
+function getDefaultDisplay( elem ) {
+ var temp,
+ doc = elem.ownerDocument,
+ nodeName = elem.nodeName,
+ display = defaultDisplayMap[ nodeName ];
+
+ if ( display ) {
+ return display;
+ }
+
+ temp = doc.body.appendChild( doc.createElement( nodeName ) );
+ display = jQuery.css( temp, "display" );
+
+ temp.parentNode.removeChild( temp );
+
+ if ( display === "none" ) {
+ display = "block";
+ }
+ defaultDisplayMap[ nodeName ] = display;
+
+ return display;
+}
+
+function showHide( elements, show ) {
+ var display, elem,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ // Determine new display value for elements that need to change
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+
+ display = elem.style.display;
+ if ( show ) {
+
+ // Since we force visibility upon cascade-hidden elements, an immediate (and slow)
+ // check is required in this first loop unless we have a nonempty display value (either
+ // inline or about-to-be-restored)
+ if ( display === "none" ) {
+ values[ index ] = dataPriv.get( elem, "display" ) || null;
+ if ( !values[ index ] ) {
+ elem.style.display = "";
+ }
+ }
+ if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
+ values[ index ] = getDefaultDisplay( elem );
+ }
+ } else {
+ if ( display !== "none" ) {
+ values[ index ] = "none";
+
+ // Remember what we're overwriting
+ dataPriv.set( elem, "display", display );
+ }
+ }
+ }
+
+ // Set the display of the elements in a second loop to avoid constant reflow
+ for ( index = 0; index < length; index++ ) {
+ if ( values[ index ] != null ) {
+ elements[ index ].style.display = values[ index ];
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend( {
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state ) {
+ if ( typeof state === "boolean" ) {
+ return state ? this.show() : this.hide();
+ }
+
+ return this.each( function() {
+ if ( isHiddenWithinTree( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ } );
+ }
+} );
+var rcheckableType = ( /^(?:checkbox|radio)$/i );
+
+var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
+
+var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
+
+
+
+( function() {
+ var fragment = document.createDocumentFragment(),
+ div = fragment.appendChild( document.createElement( "div" ) ),
+ input = document.createElement( "input" );
+
+ // Support: Android 4.0 - 4.3 only
+ // Check state lost if the name is set (#11217)
+ // Support: Windows Web Apps (WWA)
+ // `name` and `type` must use .setAttribute for WWA (#14901)
+ input.setAttribute( "type", "radio" );
+ input.setAttribute( "checked", "checked" );
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+
+ // Support: Android <=4.1 only
+ // Older WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: IE <=11 only
+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ div.innerHTML = "<textarea>x</textarea>";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+
+ // Support: IE <=9 only
+ // IE <=9 replaces <option> tags with their contents when inserted outside of
+ // the select element.
+ div.innerHTML = "<option></option>";
+ support.option = !!div.lastChild;
+} )();
+
+
+// We have to close these tags to support XHTML (#13200)
+var wrapMap = {
+
+ // XHTML parsers do not magically insert elements in the
+ // same way that tag soup parsers do. So we cannot shorten
+ // this by omitting <tbody> or other required elements.
+ thead: [ 1, "<table>", "</table>" ],
+ col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+ _default: [ 0, "", "" ]
+};
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// Support: IE <=9 only
+if ( !support.option ) {
+ wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ];
+}
+
+
+function getAll( context, tag ) {
+
+ // Support: IE <=9 - 11 only
+ // Use typeof to avoid zero-argument method invocation on host objects (#15151)
+ var ret;
+
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ ret = context.getElementsByTagName( tag || "*" );
+
+ } else if ( typeof context.querySelectorAll !== "undefined" ) {
+ ret = context.querySelectorAll( tag || "*" );
+
+ } else {
+ ret = [];
+ }
+
+ if ( tag === undefined || tag && nodeName( context, tag ) ) {
+ return jQuery.merge( [ context ], ret );
+ }
+
+ return ret;
+}
+
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+ var i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ dataPriv.set(
+ elems[ i ],
+ "globalEval",
+ !refElements || dataPriv.get( refElements[ i ], "globalEval" )
+ );
+ }
+}
+
+
+var rhtml = /<|&#?\w+;/;
+
+function buildFragment( elems, context, scripts, selection, ignored ) {
+ var elem, tmp, tag, wrap, attached, j,
+ fragment = context.createDocumentFragment(),
+ nodes = [],
+ i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
+
+ if ( elem || elem === 0 ) {
+
+ // Add nodes directly
+ if ( toType( elem ) === "object" ) {
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
+
+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
+
+ // Deserialize a standard representation
+ tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
+
+ // Descend through wrappers to the right content
+ j = wrap[ 0 ];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, tmp.childNodes );
+
+ // Remember the top-level container
+ tmp = fragment.firstChild;
+
+ // Ensure the created nodes are orphaned (#12392)
+ tmp.textContent = "";
+ }
+ }
+ }
+
+ // Remove wrapper from fragment
+ fragment.textContent = "";
+
+ i = 0;
+ while ( ( elem = nodes[ i++ ] ) ) {
+
+ // Skip elements already in the context collection (trac-4087)
+ if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
+ if ( ignored ) {
+ ignored.push( elem );
+ }
+ continue;
+ }
+
+ attached = isAttached( elem );
+
+ // Append to fragment
+ tmp = getAll( fragment.appendChild( elem ), "script" );
+
+ // Preserve script evaluation history
+ if ( attached ) {
+ setGlobalEval( tmp );
+ }
+
+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( ( elem = tmp[ j++ ] ) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
+
+ return fragment;
+}
+
+
+var
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+// Support: IE <=9 - 11+
+// focus() and blur() are asynchronous, except when they are no-op.
+// So expect focus to be synchronous when the element is already active,
+// and blur to be synchronous when the element is not already active.
+// (focus and blur are always synchronous in other supported browsers,
+// this just defines when we can count on it).
+function expectSync( elem, type ) {
+ return ( elem === safeActiveElement() ) === ( type === "focus" );
+}
+
+// Support: IE <=9 only
+// Accessing document.activeElement can throw unexpectedly
+// https://bugs.jquery.com/ticket/13393
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
+
+function on( elem, types, selector, data, fn, one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ on( elem, type, selector, data, types[ type ], one );
+ }
+ return elem;
+ }
+
+ if ( data == null && fn == null ) {
+
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return elem;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return elem.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ } );
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = dataPriv.get( elem );
+
+ // Only attach events to objects that accept data
+ if ( !acceptData( elem ) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Ensure that invalid selectors throw exceptions at attach time
+ // Evaluate against documentElement in case elem is a non-element node (e.g., document)
+ if ( selector ) {
+ jQuery.find.matchesSelector( documentElement, selector );
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !( events = elemData.events ) ) {
+ events = elemData.events = Object.create( null );
+ }
+ if ( !( eventHandle = elemData.handle ) ) {
+ eventHandle = elemData.handle = function( e ) {
+
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
+ jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+ };
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend( {
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join( "." )
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !( handlers = events[ type ] ) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener if the special events handler returns false
+ if ( !special.setup ||
+ special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
+
+ if ( !elemData || !( events = elemData.events ) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[ 2 ] &&
+ new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector ||
+ selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown ||
+ special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove data and the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ dataPriv.remove( elem, "handle events" );
+ }
+ },
+
+ dispatch: function( nativeEvent ) {
+
+ var i, j, ret, matched, handleObj, handlerQueue,
+ args = new Array( arguments.length ),
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( nativeEvent ),
+
+ handlers = (
+ dataPriv.get( this, "events" ) || Object.create( null )
+ )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[ 0 ] = event;
+
+ for ( i = 1; i < arguments.length; i++ ) {
+ args[ i ] = arguments[ i ];
+ }
+
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( ( handleObj = matched.handlers[ j++ ] ) &&
+ !event.isImmediatePropagationStopped() ) {
+
+ // If the event is namespaced, then each handler is only invoked if it is
+ // specially universal or its namespaces are a superset of the event's.
+ if ( !event.rnamespace || handleObj.namespace === false ||
+ event.rnamespace.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
+ handleObj.handler ).apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( ( event.result = ret ) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var i, handleObj, sel, matchedHandlers, matchedSelectors,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ if ( delegateCount &&
+
+ // Support: IE <=9
+ // Black-hole SVG <use> instance trees (trac-13180)
+ cur.nodeType &&
+
+ // Support: Firefox <=42
+ // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
+ // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
+ // Support: IE 11 only
+ // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
+ !( event.type === "click" && event.button >= 1 ) ) {
+
+ for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+ // Don't check non-elements (#13208)
+ // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
+ matchedHandlers = [];
+ matchedSelectors = {};
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+
+ // Don't conflict with Object.prototype properties (#13203)
+ sel = handleObj.selector + " ";
+
+ if ( matchedSelectors[ sel ] === undefined ) {
+ matchedSelectors[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) > -1 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( matchedSelectors[ sel ] ) {
+ matchedHandlers.push( handleObj );
+ }
+ }
+ if ( matchedHandlers.length ) {
+ handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ cur = this;
+ if ( delegateCount < handlers.length ) {
+ handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
+ }
+
+ return handlerQueue;
+ },
+
+ addProp: function( name, hook ) {
+ Object.defineProperty( jQuery.Event.prototype, name, {
+ enumerable: true,
+ configurable: true,
+
+ get: isFunction( hook ) ?
+ function() {
+ if ( this.originalEvent ) {
+ return hook( this.originalEvent );
+ }
+ } :
+ function() {
+ if ( this.originalEvent ) {
+ return this.originalEvent[ name ];
+ }
+ },
+
+ set: function( value ) {
+ Object.defineProperty( this, name, {
+ enumerable: true,
+ configurable: true,
+ writable: true,
+ value: value
+ } );
+ }
+ } );
+ },
+
+ fix: function( originalEvent ) {
+ return originalEvent[ jQuery.expando ] ?
+ originalEvent :
+ new jQuery.Event( originalEvent );
+ },
+
+ special: {
+ load: {
+
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+ click: {
+
+ // Utilize native event to ensure correct state for checkable inputs
+ setup: function( data ) {
+
+ // For mutual compressibility with _default, replace `this` access with a local var.
+ // `|| data` is dead code meant only to preserve the variable through minification.
+ var el = this || data;
+
+ // Claim the first handler
+ if ( rcheckableType.test( el.type ) &&
+ el.click && nodeName( el, "input" ) ) {
+
+ // dataPriv.set( el, "click", ... )
+ leverageNative( el, "click", returnTrue );
+ }
+
+ // Return false to allow normal processing in the caller
+ return false;
+ },
+ trigger: function( data ) {
+
+ // For mutual compressibility with _default, replace `this` access with a local var.
+ // `|| data` is dead code meant only to preserve the variable through minification.
+ var el = this || data;
+
+ // Force setup before triggering a click
+ if ( rcheckableType.test( el.type ) &&
+ el.click && nodeName( el, "input" ) ) {
+
+ leverageNative( el, "click" );
+ }
+
+ // Return non-false to allow normal event-path propagation
+ return true;
+ },
+
+ // For cross-browser consistency, suppress native .click() on links
+ // Also prevent it if we're currently inside a leveraged native-event stack
+ _default: function( event ) {
+ var target = event.target;
+ return rcheckableType.test( target.type ) &&
+ target.click && nodeName( target, "input" ) &&
+ dataPriv.get( target, "click" ) ||
+ nodeName( target, "a" );
+ }
+ },
+
+ beforeunload: {
+ postDispatch: function( event ) {
+
+ // Support: Firefox 20+
+ // Firefox doesn't alert if the returnValue field is not set.
+ if ( event.result !== undefined && event.originalEvent ) {
+ event.originalEvent.returnValue = event.result;
+ }
+ }
+ }
+ }
+};
+
+// Ensure the presence of an event listener that handles manually-triggered
+// synthetic events by interrupting progress until reinvoked in response to
+// *native* events that it fires directly, ensuring that state changes have
+// already occurred before other listeners are invoked.
+function leverageNative( el, type, expectSync ) {
+
+ // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
+ if ( !expectSync ) {
+ if ( dataPriv.get( el, type ) === undefined ) {
+ jQuery.event.add( el, type, returnTrue );
+ }
+ return;
+ }
+
+ // Register the controller as a special universal handler for all event namespaces
+ dataPriv.set( el, type, false );
+ jQuery.event.add( el, type, {
+ namespace: false,
+ handler: function( event ) {
+ var notAsync, result,
+ saved = dataPriv.get( this, type );
+
+ if ( ( event.isTrigger & 1 ) && this[ type ] ) {
+
+ // Interrupt processing of the outer synthetic .trigger()ed event
+ // Saved data should be false in such cases, but might be a leftover capture object
+ // from an async native handler (gh-4350)
+ if ( !saved.length ) {
+
+ // Store arguments for use when handling the inner native event
+ // There will always be at least one argument (an event object), so this array
+ // will not be confused with a leftover capture object.
+ saved = slice.call( arguments );
+ dataPriv.set( this, type, saved );
+
+ // Trigger the native event and capture its result
+ // Support: IE <=9 - 11+
+ // focus() and blur() are asynchronous
+ notAsync = expectSync( this, type );
+ this[ type ]();
+ result = dataPriv.get( this, type );
+ if ( saved !== result || notAsync ) {
+ dataPriv.set( this, type, false );
+ } else {
+ result = {};
+ }
+ if ( saved !== result ) {
+
+ // Cancel the outer synthetic event
+ event.stopImmediatePropagation();
+ event.preventDefault();
+ return result.value;
+ }
+
+ // If this is an inner synthetic event for an event with a bubbling surrogate
+ // (focus or blur), assume that the surrogate already propagated from triggering the
+ // native event and prevent that from happening again here.
+ // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
+ // bubbling surrogate propagates *after* the non-bubbling base), but that seems
+ // less bad than duplication.
+ } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
+ event.stopPropagation();
+ }
+
+ // If this is a native event triggered above, everything is now in order
+ // Fire an inner synthetic event with the original arguments
+ } else if ( saved.length ) {
+
+ // ...and capture the result
+ dataPriv.set( this, type, {
+ value: jQuery.event.trigger(
+
+ // Support: IE <=9 - 11+
+ // Extend with the prototype to reset the above stopImmediatePropagation()
+ jQuery.extend( saved[ 0 ], jQuery.Event.prototype ),
+ saved.slice( 1 ),
+ this
+ )
+ } );
+
+ // Abort handling of the native event
+ event.stopImmediatePropagation();
+ }
+ }
+ } );
+}
+
+jQuery.removeEvent = function( elem, type, handle ) {
+
+ // This "if" is needed for plain objects
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle );
+ }
+};
+
+jQuery.Event = function( src, props ) {
+
+ // Allow instantiation without the 'new' keyword
+ if ( !( this instanceof jQuery.Event ) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = src.defaultPrevented ||
+ src.defaultPrevented === undefined &&
+
+ // Support: Android <=2.3 only
+ src.returnValue === false ?
+ returnTrue :
+ returnFalse;
+
+ // Create target properties
+ // Support: Safari <=6 - 7 only
+ // Target should not be a text node (#504, #13143)
+ this.target = ( src.target && src.target.nodeType === 3 ) ?
+ src.target.parentNode :
+ src.target;
+
+ this.currentTarget = src.currentTarget;
+ this.relatedTarget = src.relatedTarget;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || Date.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ constructor: jQuery.Event,
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse,
+ isSimulated: false,
+
+ preventDefault: function() {
+ var e = this.originalEvent;
+
+ this.isDefaultPrevented = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.preventDefault();
+ }
+ },
+ stopPropagation: function() {
+ var e = this.originalEvent;
+
+ this.isPropagationStopped = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.stopPropagation();
+ }
+ },
+ stopImmediatePropagation: function() {
+ var e = this.originalEvent;
+
+ this.isImmediatePropagationStopped = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.stopImmediatePropagation();
+ }
+
+ this.stopPropagation();
+ }
+};
+
+// Includes all common event props including KeyEvent and MouseEvent specific props
+jQuery.each( {
+ altKey: true,
+ bubbles: true,
+ cancelable: true,
+ changedTouches: true,
+ ctrlKey: true,
+ detail: true,
+ eventPhase: true,
+ metaKey: true,
+ pageX: true,
+ pageY: true,
+ shiftKey: true,
+ view: true,
+ "char": true,
+ code: true,
+ charCode: true,
+ key: true,
+ keyCode: true,
+ button: true,
+ buttons: true,
+ clientX: true,
+ clientY: true,
+ offsetX: true,
+ offsetY: true,
+ pointerId: true,
+ pointerType: true,
+ screenX: true,
+ screenY: true,
+ targetTouches: true,
+ toElement: true,
+ touches: true,
+
+ which: function( event ) {
+ var button = event.button;
+
+ // Add which for key events
+ if ( event.which == null && rkeyEvent.test( event.type ) ) {
+ return event.charCode != null ? event.charCode : event.keyCode;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
+ if ( button & 1 ) {
+ return 1;
+ }
+
+ if ( button & 2 ) {
+ return 3;
+ }
+
+ if ( button & 4 ) {
+ return 2;
+ }
+
+ return 0;
+ }
+
+ return event.which;
+ }
+}, jQuery.event.addProp );
+
+jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
+ jQuery.event.special[ type ] = {
+
+ // Utilize native event if possible so blur/focus sequence is correct
+ setup: function() {
+
+ // Claim the first handler
+ // dataPriv.set( this, "focus", ... )
+ // dataPriv.set( this, "blur", ... )
+ leverageNative( this, type, expectSync );
+
+ // Return false to allow normal processing in the caller
+ return false;
+ },
+ trigger: function() {
+
+ // Force setup before trigger
+ leverageNative( this, type );
+
+ // Return non-false to allow normal event-path propagation
+ return true;
+ },
+
+ delegateType: delegateType
+ };
+} );
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// so that event delegation works in jQuery.
+// Do the same for pointerenter/pointerleave and pointerover/pointerout
+//
+// Support: Safari 7 only
+// Safari sends mouseenter too often; see:
+// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
+// for the description of the bug (it existed in older Chrome versions as well).
+jQuery.each( {
+ mouseenter: "mouseover",
+ mouseleave: "mouseout",
+ pointerenter: "pointerover",
+ pointerleave: "pointerout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj;
+
+ // For mouseenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+} );
+
+jQuery.fn.extend( {
+
+ on: function( types, selector, data, fn ) {
+ return on( this, types, selector, data, fn );
+ },
+ one: function( types, selector, data, fn ) {
+ return on( this, types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ?
+ handleObj.origType + "." + handleObj.namespace :
+ handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each( function() {
+ jQuery.event.remove( this, types, fn, selector );
+ } );
+ }
+} );
+
+
+var
+
+ // Support: IE <=10 - 11, Edge 12 - 13 only
+ // In IE/Edge using regex groups here causes severe slowdowns.
+ // See https://connect.microsoft.com/IE/feedback/details/1736512/
+ rnoInnerhtml = /<script|<style|<link/i,
+
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
+
+// Prefer a tbody over its parent table for containing new rows
+function manipulationTarget( elem, content ) {
+ if ( nodeName( elem, "table" ) &&
+ nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
+
+ return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
+ }
+
+ return elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+ elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
+ return elem;
+}
+function restoreScript( elem ) {
+ if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
+ elem.type = elem.type.slice( 5 );
+ } else {
+ elem.removeAttribute( "type" );
+ }
+
+ return elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+ var i, l, type, pdataOld, udataOld, udataCur, events;
+
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // 1. Copy private data: events, handlers, etc.
+ if ( dataPriv.hasData( src ) ) {
+ pdataOld = dataPriv.get( src );
+ events = pdataOld.events;
+
+ if ( events ) {
+ dataPriv.remove( dest, "handle events" );
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+ }
+
+ // 2. Copy user data
+ if ( dataUser.hasData( src ) ) {
+ udataOld = dataUser.access( src );
+ udataCur = jQuery.extend( {}, udataOld );
+
+ dataUser.set( dest, udataCur );
+ }
+}
+
+// Fix IE bugs, see support tests
+function fixInput( src, dest ) {
+ var nodeName = dest.nodeName.toLowerCase();
+
+ // Fails to persist the checked state of a cloned checkbox or radio button.
+ if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ dest.checked = src.checked;
+
+ // Fails to return the selected option to the default selected state when cloning options
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+ }
+}
+
+function domManip( collection, args, callback, ignored ) {
+
+ // Flatten any nested arrays
+ args = flat( args );
+
+ var fragment, first, scripts, hasScripts, node, doc,
+ i = 0,
+ l = collection.length,
+ iNoClone = l - 1,
+ value = args[ 0 ],
+ valueIsFunction = isFunction( value );
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( valueIsFunction ||
+ ( l > 1 && typeof value === "string" &&
+ !support.checkClone && rchecked.test( value ) ) ) {
+ return collection.each( function( index ) {
+ var self = collection.eq( index );
+ if ( valueIsFunction ) {
+ args[ 0 ] = value.call( this, index, self.html() );
+ }
+ domManip( self, args, callback, ignored );
+ } );
+ }
+
+ if ( l ) {
+ fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ // Require either new content or an interest in ignored elements to invoke the callback
+ if ( first || ignored ) {
+ scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+ hasScripts = scripts.length;
+
+ // Use the original fragment for the last item
+ // instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ for ( ; i < l; i++ ) {
+ node = fragment;
+
+ if ( i !== iNoClone ) {
+ node = jQuery.clone( node, true, true );
+
+ // Keep references to cloned scripts for later restoration
+ if ( hasScripts ) {
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( scripts, getAll( node, "script" ) );
+ }
+ }
+
+ callback.call( collection[ i ], node, i );
+ }
+
+ if ( hasScripts ) {
+ doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+ // Reenable scripts
+ jQuery.map( scripts, restoreScript );
+
+ // Evaluate executable scripts on first document insertion
+ for ( i = 0; i < hasScripts; i++ ) {
+ node = scripts[ i ];
+ if ( rscriptType.test( node.type || "" ) &&
+ !dataPriv.access( node, "globalEval" ) &&
+ jQuery.contains( doc, node ) ) {
+
+ if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) {
+
+ // Optional AJAX dependency, but won't run scripts if not present
+ if ( jQuery._evalUrl && !node.noModule ) {
+ jQuery._evalUrl( node.src, {
+ nonce: node.nonce || node.getAttribute( "nonce" )
+ }, doc );
+ }
+ } else {
+ DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return collection;
+}
+
+function remove( elem, selector, keepData ) {
+ var node,
+ nodes = selector ? jQuery.filter( selector, elem ) : elem,
+ i = 0;
+
+ for ( ; ( node = nodes[ i ] ) != null; i++ ) {
+ if ( !keepData && node.nodeType === 1 ) {
+ jQuery.cleanData( getAll( node ) );
+ }
+
+ if ( node.parentNode ) {
+ if ( keepData && isAttached( node ) ) {
+ setGlobalEval( getAll( node, "script" ) );
+ }
+ node.parentNode.removeChild( node );
+ }
+ }
+
+ return elem;
+}
+
+jQuery.extend( {
+ htmlPrefilter: function( html ) {
+ return html;
+ },
+
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var i, l, srcElements, destElements,
+ clone = elem.cloneNode( true ),
+ inPage = isAttached( elem );
+
+ // Fix IE cloning issues
+ if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
+ !jQuery.isXMLDoc( elem ) ) {
+
+ // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
+ destElements = getAll( clone );
+ srcElements = getAll( elem );
+
+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ fixInput( srcElements[ i ], destElements[ i ] );
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ if ( deepDataAndEvents ) {
+ srcElements = srcElements || getAll( elem );
+ destElements = destElements || getAll( clone );
+
+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+ }
+ } else {
+ cloneCopyEvent( elem, clone );
+ }
+ }
+
+ // Preserve script evaluation history
+ destElements = getAll( clone, "script" );
+ if ( destElements.length > 0 ) {
+ setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+ }
+
+ // Return the cloned set
+ return clone;
+ },
+
+ cleanData: function( elems ) {
+ var data, elem, type,
+ special = jQuery.event.special,
+ i = 0;
+
+ for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
+ if ( acceptData( elem ) ) {
+ if ( ( data = elem[ dataPriv.expando ] ) ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ // Support: Chrome <=35 - 45+
+ // Assign undefined instead of using delete, see Data#remove
+ elem[ dataPriv.expando ] = undefined;
+ }
+ if ( elem[ dataUser.expando ] ) {
+
+ // Support: Chrome <=35 - 45+
+ // Assign undefined instead of using delete, see Data#remove
+ elem[ dataUser.expando ] = undefined;
+ }
+ }
+ }
+ }
+} );
+
+jQuery.fn.extend( {
+ detach: function( selector ) {
+ return remove( this, selector, true );
+ },
+
+ remove: function( selector ) {
+ return remove( this, selector );
+ },
+
+ text: function( value ) {
+ return access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().each( function() {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ this.textContent = value;
+ }
+ } );
+ }, null, value, arguments.length );
+ },
+
+ append: function() {
+ return domManip( this, arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.appendChild( elem );
+ }
+ } );
+ },
+
+ prepend: function() {
+ return domManip( this, arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.insertBefore( elem, target.firstChild );
+ }
+ } );
+ },
+
+ before: function() {
+ return domManip( this, arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this );
+ }
+ } );
+ },
+
+ after: function() {
+ return domManip( this, arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ }
+ } );
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; ( elem = this[ i ] ) != null; i++ ) {
+ if ( elem.nodeType === 1 ) {
+
+ // Prevent memory leaks
+ jQuery.cleanData( getAll( elem, false ) );
+
+ // Remove any remaining nodes
+ elem.textContent = "";
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function() {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ } );
+ },
+
+ html: function( value ) {
+ return access( this, function( value ) {
+ var elem = this[ 0 ] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined && elem.nodeType === 1 ) {
+ return elem.innerHTML;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+ value = jQuery.htmlPrefilter( value );
+
+ try {
+ for ( ; i < l; i++ ) {
+ elem = this[ i ] || {};
+
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem, false ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch ( e ) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function() {
+ var ignored = [];
+
+ // Make the changes, replacing each non-ignored context element with the new content
+ return domManip( this, arguments, function( elem ) {
+ var parent = this.parentNode;
+
+ if ( jQuery.inArray( this, ignored ) < 0 ) {
+ jQuery.cleanData( getAll( this ) );
+ if ( parent ) {
+ parent.replaceChild( elem, this );
+ }
+ }
+
+ // Force callback invocation
+ }, ignored );
+ }
+} );
+
+jQuery.each( {
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ ret = [],
+ insert = jQuery( selector ),
+ last = insert.length - 1,
+ i = 0;
+
+ for ( ; i <= last; i++ ) {
+ elems = i === last ? this : this.clone( true );
+ jQuery( insert[ i ] )[ original ]( elems );
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // .get() because push.apply(_, arraylike) throws on ancient WebKit
+ push.apply( ret, elems.get() );
+ }
+
+ return this.pushStack( ret );
+ };
+} );
+var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
+
+var getStyles = function( elem ) {
+
+ // Support: IE <=11 only, Firefox <=30 (#15098, #14150)
+ // IE throws on elements created in popups
+ // FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
+ var view = elem.ownerDocument.defaultView;
+
+ if ( !view || !view.opener ) {
+ view = window;
+ }
+
+ return view.getComputedStyle( elem );
+ };
+
+var swap = function( elem, options, callback ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+};
+
+
+var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
+
+
+
+( function() {
+
+ // Executing both pixelPosition & boxSizingReliable tests require only one layout
+ // so they're executed at the same time to save the second computation.
+ function computeStyleTests() {
+
+ // This is a singleton, we need to execute it only once
+ if ( !div ) {
+ return;
+ }
+
+ container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
+ "margin-top:1px;padding:0;border:0";
+ div.style.cssText =
+ "position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
+ "margin:auto;border:1px;padding:1px;" +
+ "width:60%;top:1%";
+ documentElement.appendChild( container ).appendChild( div );
+
+ var divStyle = window.getComputedStyle( div );
+ pixelPositionVal = divStyle.top !== "1%";
+
+ // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
+ reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;
+
+ // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
+ // Some styles come back with percentage values, even though they shouldn't
+ div.style.right = "60%";
+ pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;
+
+ // Support: IE 9 - 11 only
+ // Detect misreporting of content dimensions for box-sizing:border-box elements
+ boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;
+
+ // Support: IE 9 only
+ // Detect overflow:scroll screwiness (gh-3699)
+ // Support: Chrome <=64
+ // Don't get tricked when zoom affects offsetWidth (gh-4029)
+ div.style.position = "absolute";
+ scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;
+
+ documentElement.removeChild( container );
+
+ // Nullify the div so it wouldn't be stored in the memory and
+ // it will also be a sign that checks already performed
+ div = null;
+ }
+
+ function roundPixelMeasures( measure ) {
+ return Math.round( parseFloat( measure ) );
+ }
+
+ var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
+ reliableTrDimensionsVal, reliableMarginLeftVal,
+ container = document.createElement( "div" ),
+ div = document.createElement( "div" );
+
+ // Finish early in limited (non-browser) environments
+ if ( !div.style ) {
+ return;
+ }
+
+ // Support: IE <=9 - 11 only
+ // Style of cloned element affects source element cloned (#8908)
+ div.style.backgroundClip = "content-box";
+ div.cloneNode( true ).style.backgroundClip = "";
+ support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+ jQuery.extend( support, {
+ boxSizingReliable: function() {
+ computeStyleTests();
+ return boxSizingReliableVal;
+ },
+ pixelBoxStyles: function() {
+ computeStyleTests();
+ return pixelBoxStylesVal;
+ },
+ pixelPosition: function() {
+ computeStyleTests();
+ return pixelPositionVal;
+ },
+ reliableMarginLeft: function() {
+ computeStyleTests();
+ return reliableMarginLeftVal;
+ },
+ scrollboxSize: function() {
+ computeStyleTests();
+ return scrollboxSizeVal;
+ },
+
+ // Support: IE 9 - 11+, Edge 15 - 18+
+ // IE/Edge misreport `getComputedStyle` of table rows with width/height
+ // set in CSS while `offset*` properties report correct values.
+ // Behavior in IE 9 is more subtle than in newer versions & it passes
+ // some versions of this test; make sure not to make it pass there!
+ reliableTrDimensions: function() {
+ var table, tr, trChild, trStyle;
+ if ( reliableTrDimensionsVal == null ) {
+ table = document.createElement( "table" );
+ tr = document.createElement( "tr" );
+ trChild = document.createElement( "div" );
+
+ table.style.cssText = "position:absolute;left:-11111px";
+ tr.style.height = "1px";
+ trChild.style.height = "9px";
+
+ documentElement
+ .appendChild( table )
+ .appendChild( tr )
+ .appendChild( trChild );
+
+ trStyle = window.getComputedStyle( tr );
+ reliableTrDimensionsVal = parseInt( trStyle.height ) > 3;
+
+ documentElement.removeChild( table );
+ }
+ return reliableTrDimensionsVal;
+ }
+ } );
+} )();
+
+
+function curCSS( elem, name, computed ) {
+ var width, minWidth, maxWidth, ret,
+
+ // Support: Firefox 51+
+ // Retrieving style before computed somehow
+ // fixes an issue with getting wrong values
+ // on detached elements
+ style = elem.style;
+
+ computed = computed || getStyles( elem );
+
+ // getPropertyValue is needed for:
+ // .css('filter') (IE 9 only, #12537)
+ // .css('--customProperty) (#3144)
+ if ( computed ) {
+ ret = computed.getPropertyValue( name ) || computed[ name ];
+
+ if ( ret === "" && !isAttached( elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // Android Browser returns percentage for some values,
+ // but width seems to be reliably pixels.
+ // This is against the CSSOM draft spec:
+ // https://drafts.csswg.org/cssom/#resolved-values
+ if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
+
+ // Remember the original values
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ // Put in the new values to get a computed value out
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ // Revert the changed values
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret !== undefined ?
+
+ // Support: IE <=9 - 11 only
+ // IE returns zIndex value as an integer.
+ ret + "" :
+ ret;
+}
+
+
+function addGetHookIf( conditionFn, hookFn ) {
+
+ // Define the hook, we'll check on the first run if it's really needed.
+ return {
+ get: function() {
+ if ( conditionFn() ) {
+
+ // Hook not needed (or it's not possible to use it due
+ // to missing dependency), remove it.
+ delete this.get;
+ return;
+ }
+
+ // Hook needed; redefine it so that the support test is not executed again.
+ return ( this.get = hookFn ).apply( this, arguments );
+ }
+ };
+}
+
+
+var cssPrefixes = [ "Webkit", "Moz", "ms" ],
+ emptyStyle = document.createElement( "div" ).style,
+ vendorProps = {};
+
+// Return a vendor-prefixed property or undefined
+function vendorPropName( name ) {
+
+ // Check for vendor prefixed names
+ var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in emptyStyle ) {
+ return name;
+ }
+ }
+}
+
+// Return a potentially-mapped jQuery.cssProps or vendor prefixed property
+function finalPropName( name ) {
+ var final = jQuery.cssProps[ name ] || vendorProps[ name ];
+
+ if ( final ) {
+ return final;
+ }
+ if ( name in emptyStyle ) {
+ return name;
+ }
+ return vendorProps[ name ] = vendorPropName( name ) || name;
+}
+
+
+var
+
+ // Swappable if display is none or starts with table
+ // except "table", "table-cell", or "table-caption"
+ // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ rcustomProp = /^--/,
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: "0",
+ fontWeight: "400"
+ };
+
+function setPositiveNumber( _elem, value, subtract ) {
+
+ // Any relative (+/-) values have already been
+ // normalized at this point
+ var matches = rcssNum.exec( value );
+ return matches ?
+
+ // Guard against undefined "subtract", e.g., when used as in cssHooks
+ Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
+ value;
+}
+
+function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
+ var i = dimension === "width" ? 1 : 0,
+ extra = 0,
+ delta = 0;
+
+ // Adjustment may not be necessary
+ if ( box === ( isBorderBox ? "border" : "content" ) ) {
+ return 0;
+ }
+
+ for ( ; i < 4; i += 2 ) {
+
+ // Both box models exclude margin
+ if ( box === "margin" ) {
+ delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
+ }
+
+ // If we get here with a content-box, we're seeking "padding" or "border" or "margin"
+ if ( !isBorderBox ) {
+
+ // Add padding
+ delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+ // For "border" or "margin", add border
+ if ( box !== "padding" ) {
+ delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+
+ // But still keep track of it otherwise
+ } else {
+ extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+
+ // If we get here with a border-box (content + padding + border), we're seeking "content" or
+ // "padding" or "margin"
+ } else {
+
+ // For "content", subtract padding
+ if ( box === "content" ) {
+ delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+ }
+
+ // For "content" or "padding", subtract border
+ if ( box !== "margin" ) {
+ delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ }
+ }
+
+ // Account for positive content-box scroll gutter when requested by providing computedVal
+ if ( !isBorderBox && computedVal >= 0 ) {
+
+ // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
+ // Assuming integer scroll gutter, subtract the rest and round down
+ delta += Math.max( 0, Math.ceil(
+ elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
+ computedVal -
+ delta -
+ extra -
+ 0.5
+
+ // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
+ // Use an explicit zero to avoid NaN (gh-3964)
+ ) ) || 0;
+ }
+
+ return delta;
+}
+
+function getWidthOrHeight( elem, dimension, extra ) {
+
+ // Start with computed style
+ var styles = getStyles( elem ),
+
+ // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
+ // Fake content-box until we know it's needed to know the true value.
+ boxSizingNeeded = !support.boxSizingReliable() || extra,
+ isBorderBox = boxSizingNeeded &&
+ jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+ valueIsBorderBox = isBorderBox,
+
+ val = curCSS( elem, dimension, styles ),
+ offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );
+
+ // Support: Firefox <=54
+ // Return a confounding non-pixel value or feign ignorance, as appropriate.
+ if ( rnumnonpx.test( val ) ) {
+ if ( !extra ) {
+ return val;
+ }
+ val = "auto";
+ }
+
+
+ // Support: IE 9 - 11 only
+ // Use offsetWidth/offsetHeight for when box sizing is unreliable.
+ // In those cases, the computed value can be trusted to be border-box.
+ if ( ( !support.boxSizingReliable() && isBorderBox ||
+
+ // Support: IE 10 - 11+, Edge 15 - 18+
+ // IE/Edge misreport `getComputedStyle` of table rows with width/height
+ // set in CSS while `offset*` properties report correct values.
+ // Interestingly, in some cases IE 9 doesn't suffer from this issue.
+ !support.reliableTrDimensions() && nodeName( elem, "tr" ) ||
+
+ // Fall back to offsetWidth/offsetHeight when value is "auto"
+ // This happens for inline elements with no explicit setting (gh-3571)
+ val === "auto" ||
+
+ // Support: Android <=4.1 - 4.3 only
+ // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
+ !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&
+
+ // Make sure the element is visible & connected
+ elem.getClientRects().length ) {
+
+ isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+ // Where available, offsetWidth/offsetHeight approximate border box dimensions.
+ // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
+ // retrieved value as a content box dimension.
+ valueIsBorderBox = offsetProp in elem;
+ if ( valueIsBorderBox ) {
+ val = elem[ offsetProp ];
+ }
+ }
+
+ // Normalize "" and auto
+ val = parseFloat( val ) || 0;
+
+ // Adjust for the element's box model
+ return ( val +
+ boxModelAdjustment(
+ elem,
+ dimension,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox,
+ styles,
+
+ // Provide the current computed size to request scroll gutter calculation (gh-3589)
+ val
+ )
+ ) + "px";
+}
+
+jQuery.extend( {
+
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+ }
+ }
+ }
+ },
+
+ // Don't automatically add "px" to these possibly-unitless properties
+ cssNumber: {
+ "animationIterationCount": true,
+ "columnCount": true,
+ "fillOpacity": true,
+ "flexGrow": true,
+ "flexShrink": true,
+ "fontWeight": true,
+ "gridArea": true,
+ "gridColumn": true,
+ "gridColumnEnd": true,
+ "gridColumnStart": true,
+ "gridRow": true,
+ "gridRowEnd": true,
+ "gridRowStart": true,
+ "lineHeight": true,
+ "opacity": true,
+ "order": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {},
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = camelCase( name ),
+ isCustomProp = rcustomProp.test( name ),
+ style = elem.style;
+
+ // Make sure that we're working with the right name. We don't
+ // want to query the value if it is a CSS custom property
+ // since they are user-defined.
+ if ( !isCustomProp ) {
+ name = finalPropName( origName );
+ }
+
+ // Gets hook for the prefixed version, then unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // Convert "+=" or "-=" to relative numbers (#7345)
+ if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
+ value = adjustCSS( elem, name, ret );
+
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that null and NaN values aren't set (#7116)
+ if ( value == null || value !== value ) {
+ return;
+ }
+
+ // If a number was passed in, add the unit (except for certain CSS properties)
+ // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append
+ // "px" to a few hardcoded values.
+ if ( type === "number" && !isCustomProp ) {
+ value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
+ }
+
+ // background-* props affect original clone's values
+ if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
+ style[ name ] = "inherit";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !( "set" in hooks ) ||
+ ( value = hooks.set( elem, value, extra ) ) !== undefined ) {
+
+ if ( isCustomProp ) {
+ style.setProperty( name, value );
+ } else {
+ style[ name ] = value;
+ }
+ }
+
+ } else {
+
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks &&
+ ( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
+
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra, styles ) {
+ var val, num, hooks,
+ origName = camelCase( name ),
+ isCustomProp = rcustomProp.test( name );
+
+ // Make sure that we're working with the right name. We don't
+ // want to modify the value if it is a CSS custom property
+ // since they are user-defined.
+ if ( !isCustomProp ) {
+ name = finalPropName( origName );
+ }
+
+ // Try prefixed name followed by the unprefixed name
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name, styles );
+ }
+
+ // Convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Make numeric if forced or a qualifier was provided and val looks numeric
+ if ( extra === "" || extra ) {
+ num = parseFloat( val );
+ return extra === true || isFinite( num ) ? num || 0 : val;
+ }
+
+ return val;
+ }
+} );
+
+jQuery.each( [ "height", "width" ], function( _i, dimension ) {
+ jQuery.cssHooks[ dimension ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+
+ // Certain elements can have dimension info if we invisibly show them
+ // but it must have a current display style that would benefit
+ return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
+
+ // Support: Safari 8+
+ // Table columns in Safari have non-zero offsetWidth & zero
+ // getBoundingClientRect().width unless display is changed.
+ // Support: IE <=11 only
+ // Running getBoundingClientRect on a disconnected node
+ // in IE throws an error.
+ ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
+ swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, dimension, extra );
+ } ) :
+ getWidthOrHeight( elem, dimension, extra );
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ var matches,
+ styles = getStyles( elem ),
+
+ // Only read styles.position if the test has a chance to fail
+ // to avoid forcing a reflow.
+ scrollboxSizeBuggy = !support.scrollboxSize() &&
+ styles.position === "absolute",
+
+ // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
+ boxSizingNeeded = scrollboxSizeBuggy || extra,
+ isBorderBox = boxSizingNeeded &&
+ jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+ subtract = extra ?
+ boxModelAdjustment(
+ elem,
+ dimension,
+ extra,
+ isBorderBox,
+ styles
+ ) :
+ 0;
+
+ // Account for unreliable border-box dimensions by comparing offset* to computed and
+ // faking a content-box to get border and padding (gh-3699)
+ if ( isBorderBox && scrollboxSizeBuggy ) {
+ subtract -= Math.ceil(
+ elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
+ parseFloat( styles[ dimension ] ) -
+ boxModelAdjustment( elem, dimension, "border", false, styles ) -
+ 0.5
+ );
+ }
+
+ // Convert to pixels if value adjustment is needed
+ if ( subtract && ( matches = rcssNum.exec( value ) ) &&
+ ( matches[ 3 ] || "px" ) !== "px" ) {
+
+ elem.style[ dimension ] = value;
+ value = jQuery.css( elem, dimension );
+ }
+
+ return setPositiveNumber( elem, value, subtract );
+ }
+ };
+} );
+
+jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
+ function( elem, computed ) {
+ if ( computed ) {
+ return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
+ elem.getBoundingClientRect().left -
+ swap( elem, { marginLeft: 0 }, function() {
+ return elem.getBoundingClientRect().left;
+ } )
+ ) + "px";
+ }
+ }
+);
+
+// These hooks are used by animate to expand properties
+jQuery.each( {
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i = 0,
+ expanded = {},
+
+ // Assumes a single number if not a string
+ parts = typeof value === "string" ? value.split( " " ) : [ value ];
+
+ for ( ; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( prefix !== "margin" ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+} );
+
+jQuery.fn.extend( {
+ css: function( name, value ) {
+ return access( this, function( elem, name, value ) {
+ var styles, len,
+ map = {},
+ i = 0;
+
+ if ( Array.isArray( name ) ) {
+ styles = getStyles( elem );
+ len = name.length;
+
+ for ( ; i < len; i++ ) {
+ map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+ }
+
+ return map;
+ }
+
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ }
+} );
+
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || jQuery.easing._default;
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ if ( this.options.duration ) {
+ this.pos = eased = jQuery.easing[ this.easing ](
+ percent, this.options.duration * percent, 0, 1, this.options.duration
+ );
+ } else {
+ this.pos = eased = percent;
+ }
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ // Use a property on the element directly when it is not a DOM element,
+ // or when there is no matching style property that exists.
+ if ( tween.elem.nodeType !== 1 ||
+ tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // Passing an empty string as a 3rd parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails.
+ // Simple values such as "10px" are parsed to Float;
+ // complex values such as "rotate(1rad)" are returned as-is.
+ result = jQuery.css( tween.elem, tween.prop, "" );
+
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+
+ // Use step hook for back compat.
+ // Use cssHook if its there.
+ // Use .style if available and use plain properties where available.
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.nodeType === 1 && (
+ jQuery.cssHooks[ tween.prop ] ||
+ tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+ }
+};
+
+// Support: IE <=9 only
+// Panic based approach to setting things on disconnected nodes
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p * Math.PI ) / 2;
+ },
+ _default: "swing"
+};
+
+jQuery.fx = Tween.prototype.init;
+
+// Back compat <1.8 extension point
+jQuery.fx.step = {};
+
+
+
+
+var
+ fxNow, inProgress,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rrun = /queueHooks$/;
+
+function schedule() {
+ if ( inProgress ) {
+ if ( document.hidden === false && window.requestAnimationFrame ) {
+ window.requestAnimationFrame( schedule );
+ } else {
+ window.setTimeout( schedule, jQuery.fx.interval );
+ }
+
+ jQuery.fx.tick();
+ }
+}
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ window.setTimeout( function() {
+ fxNow = undefined;
+ } );
+ return ( fxNow = Date.now() );
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ i = 0,
+ attrs = { height: type };
+
+ // If we include width, step value is 1 to do all cssExpand values,
+ // otherwise step value is 2 to skip over Left and Right
+ includeWidth = includeWidth ? 1 : 0;
+ for ( ; i < 4; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+function createTween( value, prop, animation ) {
+ var tween,
+ collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
+
+ // We're done with this property
+ return tween;
+ }
+ }
+}
+
+function defaultPrefilter( elem, props, opts ) {
+ var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
+ isBox = "width" in props || "height" in props,
+ anim = this,
+ orig = {},
+ style = elem.style,
+ hidden = elem.nodeType && isHiddenWithinTree( elem ),
+ dataShow = dataPriv.get( elem, "fxshow" );
+
+ // Queue-skipping animations hijack the fx hooks
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+
+ anim.always( function() {
+
+ // Ensure the complete handler is called before this completes
+ anim.always( function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ } );
+ } );
+ }
+
+ // Detect show/hide animations
+ for ( prop in props ) {
+ value = props[ prop ];
+ if ( rfxtypes.test( value ) ) {
+ delete props[ prop ];
+ toggle = toggle || value === "toggle";
+ if ( value === ( hidden ? "hide" : "show" ) ) {
+
+ // Pretend to be hidden if this is a "show" and
+ // there is still data from a stopped show/hide
+ if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+ hidden = true;
+
+ // Ignore all other no-op show/hide data
+ } else {
+ continue;
+ }
+ }
+ orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+ }
+ }
+
+ // Bail out if this is a no-op like .hide().hide()
+ propTween = !jQuery.isEmptyObject( props );
+ if ( !propTween && jQuery.isEmptyObject( orig ) ) {
+ return;
+ }
+
+ // Restrict "overflow" and "display" styles during box animations
+ if ( isBox && elem.nodeType === 1 ) {
+
+ // Support: IE <=9 - 11, Edge 12 - 15
+ // Record all 3 overflow attributes because IE does not infer the shorthand
+ // from identically-valued overflowX and overflowY and Edge just mirrors
+ // the overflowX value there.
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+ // Identify a display type, preferring old show/hide data over the CSS cascade
+ restoreDisplay = dataShow && dataShow.display;
+ if ( restoreDisplay == null ) {
+ restoreDisplay = dataPriv.get( elem, "display" );
+ }
+ display = jQuery.css( elem, "display" );
+ if ( display === "none" ) {
+ if ( restoreDisplay ) {
+ display = restoreDisplay;
+ } else {
+
+ // Get nonempty value(s) by temporarily forcing visibility
+ showHide( [ elem ], true );
+ restoreDisplay = elem.style.display || restoreDisplay;
+ display = jQuery.css( elem, "display" );
+ showHide( [ elem ] );
+ }
+ }
+
+ // Animate inline elements as inline-block
+ if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
+ if ( jQuery.css( elem, "float" ) === "none" ) {
+
+ // Restore the original display value at the end of pure show/hide animations
+ if ( !propTween ) {
+ anim.done( function() {
+ style.display = restoreDisplay;
+ } );
+ if ( restoreDisplay == null ) {
+ display = style.display;
+ restoreDisplay = display === "none" ? "" : display;
+ }
+ }
+ style.display = "inline-block";
+ }
+ }
+ }
+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ anim.always( function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ } );
+ }
+
+ // Implement show/hide animations
+ propTween = false;
+ for ( prop in orig ) {
+
+ // General show/hide setup for this element animation
+ if ( !propTween ) {
+ if ( dataShow ) {
+ if ( "hidden" in dataShow ) {
+ hidden = dataShow.hidden;
+ }
+ } else {
+ dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
+ }
+
+ // Store hidden/visible for toggle so `.stop().toggle()` "reverses"
+ if ( toggle ) {
+ dataShow.hidden = !hidden;
+ }
+
+ // Show elements before animating them
+ if ( hidden ) {
+ showHide( [ elem ], true );
+ }
+
+ /* eslint-disable no-loop-func */
+
+ anim.done( function() {
+
+ /* eslint-enable no-loop-func */
+
+ // The final step of a "hide" animation is actually hiding the element
+ if ( !hidden ) {
+ showHide( [ elem ] );
+ }
+ dataPriv.remove( elem, "fxshow" );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ } );
+ }
+
+ // Per-property setup
+ propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = propTween.start;
+ if ( hidden ) {
+ propTween.end = propTween.start;
+ propTween.start = 0;
+ }
+ }
+ }
+}
+
+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
+
+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( Array.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // Not quite $.extend, this won't overwrite existing keys.
+ // Reusing 'index' because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
+
+function Animation( elem, properties, options ) {
+ var result,
+ stopped,
+ index = 0,
+ length = Animation.prefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+
+ // Don't match elem in the :animated selector
+ delete tick.elem;
+ } ),
+ tick = function() {
+ if ( stopped ) {
+ return false;
+ }
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+
+ // Support: Android 2.3 only
+ // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
+ temp = remaining / animation.duration || 0,
+ percent = 1 - temp,
+ index = 0,
+ length = animation.tweens.length;
+
+ for ( ; index < length; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ] );
+
+ // If there's more to do, yield
+ if ( percent < 1 && length ) {
+ return remaining;
+ }
+
+ // If this was an empty animation, synthesize a final progress notification
+ if ( !length ) {
+ deferred.notifyWith( elem, [ animation, 1, 0 ] );
+ }
+
+ // Resolve the animation and report its conclusion
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ },
+ animation = deferred.promise( {
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, {
+ specialEasing: {},
+ easing: jQuery.easing._default
+ }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+
+ // If we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+ if ( stopped ) {
+ return this;
+ }
+ stopped = true;
+ for ( ; index < length; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
+
+ // Resolve when we played the last frame; otherwise, reject
+ if ( gotoEnd ) {
+ deferred.notifyWith( elem, [ animation, 1, 0 ] );
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ } ),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length; index++ ) {
+ result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ if ( isFunction( result.stop ) ) {
+ jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
+ result.stop.bind( result );
+ }
+ return result;
+ }
+ }
+
+ jQuery.map( props, createTween, animation );
+
+ if ( isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ // Attach callbacks from options
+ animation
+ .progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ elem: elem,
+ anim: animation,
+ queue: animation.opts.queue
+ } )
+ );
+
+ return animation;
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+ tweeners: {
+ "*": [ function( prop, value ) {
+ var tween = this.createTween( prop, value );
+ adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
+ return tween;
+ } ]
+ },
+
+ tweener: function( props, callback ) {
+ if ( isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.match( rnothtmlwhite );
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length; index++ ) {
+ prop = props[ index ];
+ Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
+ Animation.tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilters: [ defaultPrefilter ],
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ Animation.prefilters.unshift( callback );
+ } else {
+ Animation.prefilters.push( callback );
+ }
+ }
+} );
+
+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !isFunction( easing ) && easing
+ };
+
+ // Go to the end state if fx are off
+ if ( jQuery.fx.off ) {
+ opt.duration = 0;
+
+ } else {
+ if ( typeof opt.duration !== "number" ) {
+ if ( opt.duration in jQuery.fx.speeds ) {
+ opt.duration = jQuery.fx.speeds[ opt.duration ];
+
+ } else {
+ opt.duration = jQuery.fx.speeds._default;
+ }
+ }
+ }
+
+ // Normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function() {
+ if ( isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
+
+ return opt;
+};
+
+jQuery.fn.extend( {
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // Show any hidden elements after setting opacity to 0
+ return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
+
+ // Animate to the value specified
+ .end().animate( { opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+ // Empty animations, or finishing resolves immediately
+ if ( empty || dataPriv.get( this, "finish" ) ) {
+ anim.stop( true );
+ }
+ };
+ doAnimation.finish = doAnimation;
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each( function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = dataPriv.get( this );
+
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this &&
+ ( type == null || timers[ index ].queue === type ) ) {
+
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // Start the next in the queue if the last step wasn't forced.
+ // Timers currently will call their complete callbacks, which
+ // will dequeue but only if they were gotoEnd.
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ } );
+ },
+ finish: function( type ) {
+ if ( type !== false ) {
+ type = type || "fx";
+ }
+ return this.each( function() {
+ var index,
+ data = dataPriv.get( this ),
+ queue = data[ type + "queue" ],
+ hooks = data[ type + "queueHooks" ],
+ timers = jQuery.timers,
+ length = queue ? queue.length : 0;
+
+ // Enable finishing flag on private data
+ data.finish = true;
+
+ // Empty the queue first
+ jQuery.queue( this, type, [] );
+
+ if ( hooks && hooks.stop ) {
+ hooks.stop.call( this, true );
+ }
+
+ // Look for any active animations, and finish them
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+ timers[ index ].anim.stop( true );
+ timers.splice( index, 1 );
+ }
+ }
+
+ // Look for any animations in the old queue and finish them
+ for ( index = 0; index < length; index++ ) {
+ if ( queue[ index ] && queue[ index ].finish ) {
+ queue[ index ].finish.call( this );
+ }
+ }
+
+ // Turn off finishing flag
+ delete data.finish;
+ } );
+ }
+} );
+
+jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
+} );
+
+// Generate shortcuts for custom animations
+jQuery.each( {
+ slideDown: genFx( "show" ),
+ slideUp: genFx( "hide" ),
+ slideToggle: genFx( "toggle" ),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+} );
+
+jQuery.timers = [];
+jQuery.fx.tick = function() {
+ var timer,
+ i = 0,
+ timers = jQuery.timers;
+
+ fxNow = Date.now();
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+
+ // Run the timer and safely remove it when done (allowing for external removal)
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+ jQuery.timers.push( timer );
+ jQuery.fx.start();
+};
+
+jQuery.fx.interval = 13;
+jQuery.fx.start = function() {
+ if ( inProgress ) {
+ return;
+ }
+
+ inProgress = true;
+ schedule();
+};
+
+jQuery.fx.stop = function() {
+ inProgress = null;
+};
+
+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+
+ // Default speed
+ _default: 400
+};
+
+
+// Based off of the plugin by Clint Helfers, with permission.
+// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
+jQuery.fn.delay = function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = window.setTimeout( next, time );
+ hooks.stop = function() {
+ window.clearTimeout( timeout );
+ };
+ } );
+};
+
+
+( function() {
+ var input = document.createElement( "input" ),
+ select = document.createElement( "select" ),
+ opt = select.appendChild( document.createElement( "option" ) );
+
+ input.type = "checkbox";
+
+ // Support: Android <=4.3 only
+ // Default value for a checkbox should be "on"
+ support.checkOn = input.value !== "";
+
+ // Support: IE <=11 only
+ // Must access selectedIndex to make default options select
+ support.optSelected = opt.selected;
+
+ // Support: IE <=11 only
+ // An input loses its value after becoming a radio
+ input = document.createElement( "input" );
+ input.value = "t";
+ input.type = "radio";
+ support.radioValue = input.value === "t";
+} )();
+
+
+var boolHook,
+ attrHandle = jQuery.expr.attrHandle;
+
+jQuery.fn.extend( {
+ attr: function( name, value ) {
+ return access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each( function() {
+ jQuery.removeAttr( this, name );
+ } );
+ }
+} );
+
+jQuery.extend( {
+ attr: function( elem, name, value ) {
+ var ret, hooks,
+ nType = elem.nodeType;
+
+ // Don't get/set attributes on text, comment and attribute nodes
+ if ( nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ // Attribute hooks are determined by the lowercase version
+ // Grab necessary hook if one is defined
+ if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+ hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
+ ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
+ }
+
+ if ( value !== undefined ) {
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+ }
+
+ if ( hooks && "set" in hooks &&
+ ( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+ return ret;
+ }
+
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+ return ret;
+ }
+
+ ret = jQuery.find.attr( elem, name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret == null ? undefined : ret;
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ if ( !support.radioValue && value === "radio" &&
+ nodeName( elem, "input" ) ) {
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var name,
+ i = 0,
+
+ // Attribute names can contain non-HTML whitespace characters
+ // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
+ attrNames = value && value.match( rnothtmlwhite );
+
+ if ( attrNames && elem.nodeType === 1 ) {
+ while ( ( name = attrNames[ i++ ] ) ) {
+ elem.removeAttribute( name );
+ }
+ }
+ }
+} );
+
+// Hooks for boolean attributes
+boolHook = {
+ set: function( elem, value, name ) {
+ if ( value === false ) {
+
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ elem.setAttribute( name, name );
+ }
+ return name;
+ }
+};
+
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) {
+ var getter = attrHandle[ name ] || jQuery.find.attr;
+
+ attrHandle[ name ] = function( elem, name, isXML ) {
+ var ret, handle,
+ lowercaseName = name.toLowerCase();
+
+ if ( !isXML ) {
+
+ // Avoid an infinite loop by temporarily removing this function from the getter
+ handle = attrHandle[ lowercaseName ];
+ attrHandle[ lowercaseName ] = ret;
+ ret = getter( elem, name, isXML ) != null ?
+ lowercaseName :
+ null;
+ attrHandle[ lowercaseName ] = handle;
+ }
+ return ret;
+ };
+} );
+
+
+
+
+var rfocusable = /^(?:input|select|textarea|button)$/i,
+ rclickable = /^(?:a|area)$/i;
+
+jQuery.fn.extend( {
+ prop: function( name, value ) {
+ return access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ return this.each( function() {
+ delete this[ jQuery.propFix[ name ] || name ];
+ } );
+ }
+} );
+
+jQuery.extend( {
+ prop: function( elem, name, value ) {
+ var ret, hooks,
+ nType = elem.nodeType;
+
+ // Don't get/set properties on text, comment and attribute nodes
+ if ( nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks &&
+ ( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+ return ret;
+ }
+
+ return ( elem[ name ] = value );
+ }
+
+ if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+ return ret;
+ }
+
+ return elem[ name ];
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+
+ // Support: IE <=9 - 11 only
+ // elem.tabIndex doesn't always return the
+ // correct value when it hasn't been explicitly set
+ // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ // Use proper attribute retrieval(#12072)
+ var tabindex = jQuery.find.attr( elem, "tabindex" );
+
+ if ( tabindex ) {
+ return parseInt( tabindex, 10 );
+ }
+
+ if (
+ rfocusable.test( elem.nodeName ) ||
+ rclickable.test( elem.nodeName ) &&
+ elem.href
+ ) {
+ return 0;
+ }
+
+ return -1;
+ }
+ }
+ },
+
+ propFix: {
+ "for": "htmlFor",
+ "class": "className"
+ }
+} );
+
+// Support: IE <=11 only
+// Accessing the selectedIndex property
+// forces the browser to respect setting selected
+// on the option
+// The getter ensures a default option is selected
+// when in an optgroup
+// eslint rule "no-unused-expressions" is disabled for this code
+// since it considers such accessions noop
+if ( !support.optSelected ) {
+ jQuery.propHooks.selected = {
+ get: function( elem ) {
+
+ /* eslint no-unused-expressions: "off" */
+
+ var parent = elem.parentNode;
+ if ( parent && parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ return null;
+ },
+ set: function( elem ) {
+
+ /* eslint no-unused-expressions: "off" */
+
+ var parent = elem.parentNode;
+ if ( parent ) {
+ parent.selectedIndex;
+
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ }
+ };
+}
+
+jQuery.each( [
+ "tabIndex",
+ "readOnly",
+ "maxLength",
+ "cellSpacing",
+ "cellPadding",
+ "rowSpan",
+ "colSpan",
+ "useMap",
+ "frameBorder",
+ "contentEditable"
+], function() {
+ jQuery.propFix[ this.toLowerCase() ] = this;
+} );
+
+
+
+
+ // Strip and collapse whitespace according to HTML spec
+ // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
+ function stripAndCollapse( value ) {
+ var tokens = value.match( rnothtmlwhite ) || [];
+ return tokens.join( " " );
+ }
+
+
+function getClass( elem ) {
+ return elem.getAttribute && elem.getAttribute( "class" ) || "";
+}
+
+function classesToArray( value ) {
+ if ( Array.isArray( value ) ) {
+ return value;
+ }
+ if ( typeof value === "string" ) {
+ return value.match( rnothtmlwhite ) || [];
+ }
+ return [];
+}
+
+jQuery.fn.extend( {
+ addClass: function( value ) {
+ var classes, elem, cur, curValue, clazz, j, finalValue,
+ i = 0;
+
+ if ( isFunction( value ) ) {
+ return this.each( function( j ) {
+ jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
+ } );
+ }
+
+ classes = classesToArray( value );
+
+ if ( classes.length ) {
+ while ( ( elem = this[ i++ ] ) ) {
+ curValue = getClass( elem );
+ cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
+
+ if ( cur ) {
+ j = 0;
+ while ( ( clazz = classes[ j++ ] ) ) {
+ if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+ cur += clazz + " ";
+ }
+ }
+
+ // Only assign if different to avoid unneeded rendering.
+ finalValue = stripAndCollapse( cur );
+ if ( curValue !== finalValue ) {
+ elem.setAttribute( "class", finalValue );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classes, elem, cur, curValue, clazz, j, finalValue,
+ i = 0;
+
+ if ( isFunction( value ) ) {
+ return this.each( function( j ) {
+ jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
+ } );
+ }
+
+ if ( !arguments.length ) {
+ return this.attr( "class", "" );
+ }
+
+ classes = classesToArray( value );
+
+ if ( classes.length ) {
+ while ( ( elem = this[ i++ ] ) ) {
+ curValue = getClass( elem );
+
+ // This expression is here for better compressibility (see addClass)
+ cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
+
+ if ( cur ) {
+ j = 0;
+ while ( ( clazz = classes[ j++ ] ) ) {
+
+ // Remove *all* instances
+ while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
+ cur = cur.replace( " " + clazz + " ", " " );
+ }
+ }
+
+ // Only assign if different to avoid unneeded rendering.
+ finalValue = stripAndCollapse( cur );
+ if ( curValue !== finalValue ) {
+ elem.setAttribute( "class", finalValue );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isValidValue = type === "string" || Array.isArray( value );
+
+ if ( typeof stateVal === "boolean" && isValidValue ) {
+ return stateVal ? this.addClass( value ) : this.removeClass( value );
+ }
+
+ if ( isFunction( value ) ) {
+ return this.each( function( i ) {
+ jQuery( this ).toggleClass(
+ value.call( this, i, getClass( this ), stateVal ),
+ stateVal
+ );
+ } );
+ }
+
+ return this.each( function() {
+ var className, i, self, classNames;
+
+ if ( isValidValue ) {
+
+ // Toggle individual class names
+ i = 0;
+ self = jQuery( this );
+ classNames = classesToArray( value );
+
+ while ( ( className = classNames[ i++ ] ) ) {
+
+ // Check each className given, space separated list
+ if ( self.hasClass( className ) ) {
+ self.removeClass( className );
+ } else {
+ self.addClass( className );
+ }
+ }
+
+ // Toggle whole class name
+ } else if ( value === undefined || type === "boolean" ) {
+ className = getClass( this );
+ if ( className ) {
+
+ // Store className if set
+ dataPriv.set( this, "__className__", className );
+ }
+
+ // If the element has a class name or if we're passed `false`,
+ // then remove the whole classname (if there was one, the above saved it).
+ // Otherwise bring back whatever was previously saved (if anything),
+ // falling back to the empty string if nothing was stored.
+ if ( this.setAttribute ) {
+ this.setAttribute( "class",
+ className || value === false ?
+ "" :
+ dataPriv.get( this, "__className__" ) || ""
+ );
+ }
+ }
+ } );
+ },
+
+ hasClass: function( selector ) {
+ var className, elem,
+ i = 0;
+
+ className = " " + selector + " ";
+ while ( ( elem = this[ i++ ] ) ) {
+ if ( elem.nodeType === 1 &&
+ ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+} );
+
+
+
+
+var rreturn = /\r/g;
+
+jQuery.fn.extend( {
+ val: function( value ) {
+ var hooks, ret, valueIsFunction,
+ elem = this[ 0 ];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] ||
+ jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks &&
+ "get" in hooks &&
+ ( ret = hooks.get( elem, "value" ) ) !== undefined
+ ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ // Handle most common string cases
+ if ( typeof ret === "string" ) {
+ return ret.replace( rreturn, "" );
+ }
+
+ // Handle cases where value is null/undef or number
+ return ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ valueIsFunction = isFunction( value );
+
+ return this.each( function( i ) {
+ var val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( valueIsFunction ) {
+ val = value.call( this, i, jQuery( this ).val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+
+ } else if ( typeof val === "number" ) {
+ val += "";
+
+ } else if ( Array.isArray( val ) ) {
+ val = jQuery.map( val, function( value ) {
+ return value == null ? "" : value + "";
+ } );
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ } );
+ }
+} );
+
+jQuery.extend( {
+ valHooks: {
+ option: {
+ get: function( elem ) {
+
+ var val = jQuery.find.attr( elem, "value" );
+ return val != null ?
+ val :
+
+ // Support: IE <=10 - 11 only
+ // option.text throws exceptions (#14686, #14858)
+ // Strip and collapse whitespace
+ // https://html.spec.whatwg.org/#strip-and-collapse-whitespace
+ stripAndCollapse( jQuery.text( elem ) );
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option, i,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one",
+ values = one ? null : [],
+ max = one ? index + 1 : options.length;
+
+ if ( index < 0 ) {
+ i = max;
+
+ } else {
+ i = one ? index : 0;
+ }
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Support: IE <=9 only
+ // IE8-9 doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+
+ // Don't return options that are disabled or in a disabled optgroup
+ !option.disabled &&
+ ( !option.parentNode.disabled ||
+ !nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var optionSet, option,
+ options = elem.options,
+ values = jQuery.makeArray( value ),
+ i = options.length;
+
+ while ( i-- ) {
+ option = options[ i ];
+
+ /* eslint-disable no-cond-assign */
+
+ if ( option.selected =
+ jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
+ ) {
+ optionSet = true;
+ }
+
+ /* eslint-enable no-cond-assign */
+ }
+
+ // Force browsers to behave consistently when non-matching value is set
+ if ( !optionSet ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ }
+} );
+
+// Radios and checkboxes getter/setter
+jQuery.each( [ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ set: function( elem, value ) {
+ if ( Array.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
+ }
+ }
+ };
+ if ( !support.checkOn ) {
+ jQuery.valHooks[ this ].get = function( elem ) {
+ return elem.getAttribute( "value" ) === null ? "on" : elem.value;
+ };
+ }
+} );
+
+
+
+
+// Return jQuery for attributes-only inclusion
+
+
+support.focusin = "onfocusin" in window;
+
+
+var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ stopPropagationCallback = function( e ) {
+ e.stopPropagation();
+ };
+
+jQuery.extend( jQuery.event, {
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+
+ var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
+ eventPath = [ elem || document ],
+ type = hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
+
+ cur = lastElement = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "." ) > -1 ) {
+
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split( "." );
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf( ":" ) < 0 && "on" + type;
+
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
+
+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join( "." );
+ event.rnamespace = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
+ null;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === ( elem.ownerDocument || document ) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
+ lastElement = cur;
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = (
+ dataPriv.get( cur, "events" ) || Object.create( null )
+ )[ event.type ] &&
+ dataPriv.get( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && handle.apply && acceptData( cur ) ) {
+ event.result = handle.apply( cur, data );
+ if ( event.result === false ) {
+ event.preventDefault();
+ }
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( ( !special._default ||
+ special._default.apply( eventPath.pop(), data ) === false ) &&
+ acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name as the event.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+
+ if ( event.isPropagationStopped() ) {
+ lastElement.addEventListener( type, stopPropagationCallback );
+ }
+
+ elem[ type ]();
+
+ if ( event.isPropagationStopped() ) {
+ lastElement.removeEventListener( type, stopPropagationCallback );
+ }
+
+ jQuery.event.triggered = undefined;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ // Piggyback on a donor event to simulate a different one
+ // Used only for `focus(in | out)` events
+ simulate: function( type, elem, event ) {
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ {
+ type: type,
+ isSimulated: true
+ }
+ );
+
+ jQuery.event.trigger( e, null, elem );
+ }
+
+} );
+
+jQuery.fn.extend( {
+
+ trigger: function( type, data ) {
+ return this.each( function() {
+ jQuery.event.trigger( type, data, this );
+ } );
+ },
+ triggerHandler: function( type, data ) {
+ var elem = this[ 0 ];
+ if ( elem ) {
+ return jQuery.event.trigger( type, data, elem, true );
+ }
+ }
+} );
+
+
+// Support: Firefox <=44
+// Firefox doesn't have focus(in | out) events
+// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
+//
+// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
+// focus(in | out) events fire after focus & blur events,
+// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
+// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
+if ( !support.focusin ) {
+ jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler on the document while someone wants focusin/focusout
+ var handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+
+ // Handle: regular nodes (via `this.ownerDocument`), window
+ // (via `this.document`) & document (via `this`).
+ var doc = this.ownerDocument || this.document || this,
+ attaches = dataPriv.access( doc, fix );
+
+ if ( !attaches ) {
+ doc.addEventListener( orig, handler, true );
+ }
+ dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
+ },
+ teardown: function() {
+ var doc = this.ownerDocument || this.document || this,
+ attaches = dataPriv.access( doc, fix ) - 1;
+
+ if ( !attaches ) {
+ doc.removeEventListener( orig, handler, true );
+ dataPriv.remove( doc, fix );
+
+ } else {
+ dataPriv.access( doc, fix, attaches );
+ }
+ }
+ };
+ } );
+}
+var location = window.location;
+
+var nonce = { guid: Date.now() };
+
+var rquery = ( /\?/ );
+
+
+
+// Cross-browser xml parsing
+jQuery.parseXML = function( data ) {
+ var xml;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+
+ // Support: IE 9 - 11 only
+ // IE throws on parseFromString with invalid input.
+ try {
+ xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
+ } catch ( e ) {
+ xml = undefined;
+ }
+
+ if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+};
+
+
+var
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+ rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( Array.isArray( obj ) ) {
+
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+
+ // Item is non-scalar (array or object), encode its numeric index.
+ buildParams(
+ prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
+ v,
+ traditional,
+ add
+ );
+ }
+ } );
+
+ } else if ( !traditional && toType( obj ) === "object" ) {
+
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+
+// Serialize an array of form elements or a set of
+// key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, valueOrFunction ) {
+
+ // If value is a function, invoke it and use its return value
+ var value = isFunction( valueOrFunction ) ?
+ valueOrFunction() :
+ valueOrFunction;
+
+ s[ s.length ] = encodeURIComponent( key ) + "=" +
+ encodeURIComponent( value == null ? "" : value );
+ };
+
+ if ( a == null ) {
+ return "";
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ } );
+
+ } else {
+
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" );
+};
+
+jQuery.fn.extend( {
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map( function() {
+
+ // Can add propHook for "elements" to filter or add form elements
+ var elements = jQuery.prop( this, "elements" );
+ return elements ? jQuery.makeArray( elements ) : this;
+ } )
+ .filter( function() {
+ var type = this.type;
+
+ // Use .is( ":disabled" ) so that fieldset[disabled] works
+ return this.name && !jQuery( this ).is( ":disabled" ) &&
+ rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+ ( this.checked || !rcheckableType.test( type ) );
+ } )
+ .map( function( _i, elem ) {
+ var val = jQuery( this ).val();
+
+ if ( val == null ) {
+ return null;
+ }
+
+ if ( Array.isArray( val ) ) {
+ return jQuery.map( val, function( val ) {
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ } );
+ }
+
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ } ).get();
+ }
+} );
+
+
+var
+ r20 = /%20/g,
+ rhash = /#.*$/,
+ rantiCache = /([?&])_=[^&]*/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
+
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = "*/".concat( "*" ),
+
+ // Anchor tag for parsing the document origin
+ originAnchor = document.createElement( "a" );
+ originAnchor.href = location.href;
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ var dataType,
+ i = 0,
+ dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];
+
+ if ( isFunction( func ) ) {
+
+ // For each dataType in the dataTypeExpression
+ while ( ( dataType = dataTypes[ i++ ] ) ) {
+
+ // Prepend if requested
+ if ( dataType[ 0 ] === "+" ) {
+ dataType = dataType.slice( 1 ) || "*";
+ ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
+
+ // Otherwise append
+ } else {
+ ( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
+ }
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+ var inspected = {},
+ seekingTransport = ( structure === transports );
+
+ function inspect( dataType ) {
+ var selected;
+ inspected[ dataType ] = true;
+ jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+ var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+ if ( typeof dataTypeOrTransport === "string" &&
+ !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+
+ options.dataTypes.unshift( dataTypeOrTransport );
+ inspect( dataTypeOrTransport );
+ return false;
+ } else if ( seekingTransport ) {
+ return !( selected = dataTypeOrTransport );
+ }
+ } );
+ return selected;
+ }
+
+ return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+
+ return target;
+}
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var ct, type, finalDataType, firstDataType,
+ contents = s.contents,
+ dataTypes = s.dataTypes;
+
+ // Remove auto dataType and get content-type in the process
+ while ( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+ var conv2, current, conv, tmp, prev,
+ converters = {},
+
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice();
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ current = dataTypes.shift();
+
+ // Convert to each sequential dataType
+ while ( current ) {
+
+ if ( s.responseFields[ current ] ) {
+ jqXHR[ s.responseFields[ current ] ] = response;
+ }
+
+ // Apply the dataFilter if provided
+ if ( !prev && isSuccess && s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ prev = current;
+ current = dataTypes.shift();
+
+ if ( current ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current === "*" ) {
+
+ current = prev;
+
+ // Convert response if prev dataType is non-auto and differs from current
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split( " " );
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.unshift( tmp[ 1 ] );
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s.throws ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return {
+ state: "parsererror",
+ error: conv ? e : "No conversion from " + prev + " to " + current
+ };
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return { state: "success", data: response };
+}
+
+jQuery.extend( {
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajaxSettings: {
+ url: location.href,
+ type: "GET",
+ isLocal: rlocalProtocol.test( location.protocol ),
+ global: true,
+ processData: true,
+ async: true,
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ "*": allTypes,
+ text: "text/plain",
+ html: "text/html",
+ xml: "application/xml, text/xml",
+ json: "application/json, text/javascript"
+ },
+
+ contents: {
+ xml: /\bxml\b/,
+ html: /\bhtml/,
+ json: /\bjson\b/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText",
+ json: "responseJSON"
+ },
+
+ // Data converters
+ // Keys separate source (or catchall "*") and destination types with a single space
+ converters: {
+
+ // Convert anything to text
+ "* text": String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": JSON.parse,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ url: true,
+ context: true
+ }
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ return settings ?
+
+ // Building a settings object
+ ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+ // Extending ajaxSettings
+ ajaxExtend( jQuery.ajaxSettings, target );
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var transport,
+
+ // URL without anti-cache param
+ cacheURL,
+
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+
+ // timeout handle
+ timeoutTimer,
+
+ // Url cleanup var
+ urlAnchor,
+
+ // Request state (becomes false upon send and true upon completion)
+ completed,
+
+ // To know if global events are to be dispatched
+ fireGlobals,
+
+ // Loop variable
+ i,
+
+ // uncached part of the url
+ uncached,
+
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+
+ // Callbacks context
+ callbackContext = s.context || s,
+
+ // Context for global events is callbackContext if it is a DOM node or jQuery collection
+ globalEventContext = s.context &&
+ ( callbackContext.nodeType || callbackContext.jquery ) ?
+ jQuery( callbackContext ) :
+ jQuery.event,
+
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks( "once memory" ),
+
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+
+ // Default abort message
+ strAbort = "canceled",
+
+ // Fake xhr
+ jqXHR = {
+ readyState: 0,
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( completed ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
+ responseHeaders[ match[ 1 ].toLowerCase() + " " ] =
+ ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] )
+ .concat( match[ 2 ] );
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() + " " ];
+ }
+ return match == null ? null : match.join( ", " );
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return completed ? responseHeadersString : null;
+ },
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ if ( completed == null ) {
+ name = requestHeadersNames[ name.toLowerCase() ] =
+ requestHeadersNames[ name.toLowerCase() ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( completed == null ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Status-dependent callbacks
+ statusCode: function( map ) {
+ var code;
+ if ( map ) {
+ if ( completed ) {
+
+ // Execute the appropriate callbacks
+ jqXHR.always( map[ jqXHR.status ] );
+ } else {
+
+ // Lazy-add the new callbacks in a way that preserves old ones
+ for ( code in map ) {
+ statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+ }
+ }
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ var finalText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( finalText );
+ }
+ done( 0, finalText );
+ return this;
+ }
+ };
+
+ // Attach deferreds
+ deferred.promise( jqXHR );
+
+ // Add protocol if not provided (prefilters might expect it)
+ // Handle falsy url in the settings object (#10093: consistency with old signature)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url || location.href ) + "" )
+ .replace( rprotocol, location.protocol + "//" );
+
+ // Alias method option to type as per ticket #12004
+ s.type = options.method || options.type || s.method || s.type;
+
+ // Extract dataTypes list
+ s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];
+
+ // A cross-domain request is in order when the origin doesn't match the current origin.
+ if ( s.crossDomain == null ) {
+ urlAnchor = document.createElement( "a" );
+
+ // Support: IE <=8 - 11, Edge 12 - 15
+ // IE throws exception on accessing the href property if url is malformed,
+ // e.g. http://example.com:80x/
+ try {
+ urlAnchor.href = s.url;
+
+ // Support: IE <=8 - 11 only
+ // Anchor's host property isn't correctly set when s.url is relative
+ urlAnchor.href = urlAnchor.href;
+ s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
+ urlAnchor.protocol + "//" + urlAnchor.host;
+ } catch ( e ) {
+
+ // If there is an error parsing the URL, assume it is crossDomain,
+ // it can be rejected by the transport if it is invalid
+ s.crossDomain = true;
+ }
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( completed ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
+ fireGlobals = jQuery.event && s.global;
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Save the URL in case we're toying with the If-Modified-Since
+ // and/or If-None-Match header later on
+ // Remove hash to simplify url manipulation
+ cacheURL = s.url.replace( rhash, "" );
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // Remember the hash so we can put it back
+ uncached = s.url.slice( cacheURL.length );
+
+ // If data is available and should be processed, append data to url
+ if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
+ cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
+
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Add or update anti-cache param if needed
+ if ( s.cache === false ) {
+ cacheURL = cacheURL.replace( rantiCache, "$1" );
+ uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) +
+ uncached;
+ }
+
+ // Put hash and anti-cache on the URL that will be requested (gh-1732)
+ s.url = cacheURL + uncached;
+
+ // Change '%20' to '+' if this is encoded form body content (gh-2658)
+ } else if ( s.data && s.processData &&
+ ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
+ s.data = s.data.replace( r20, "+" );
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+ }
+ if ( jQuery.etag[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
+ s.accepts[ s.dataTypes[ 0 ] ] +
+ ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend &&
+ ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
+
+ // Abort if not done already and return
+ return jqXHR.abort();
+ }
+
+ // Aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ completeDeferred.add( s.complete );
+ jqXHR.done( s.success );
+ jqXHR.fail( s.error );
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+
+ // If request was aborted inside ajaxSend, stop there
+ if ( completed ) {
+ return jqXHR;
+ }
+
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = window.setTimeout( function() {
+ jqXHR.abort( "timeout" );
+ }, s.timeout );
+ }
+
+ try {
+ completed = false;
+ transport.send( requestHeaders, done );
+ } catch ( e ) {
+
+ // Rethrow post-completion exceptions
+ if ( completed ) {
+ throw e;
+ }
+
+ // Propagate others as results
+ done( -1, e );
+ }
+ }
+
+ // Callback for when everything is done
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Ignore repeat invocations
+ if ( completed ) {
+ return;
+ }
+
+ completed = true;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ window.clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Determine if successful
+ isSuccess = status >= 200 && status < 300 || status === 304;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // Use a noop converter for missing script
+ if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) {
+ s.converters[ "text script" ] = function() {};
+ }
+
+ // Convert no matter what (that way responseXXX fields are always set)
+ response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+ // If successful, handle type chaining
+ if ( isSuccess ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ modified = jqXHR.getResponseHeader( "Last-Modified" );
+ if ( modified ) {
+ jQuery.lastModified[ cacheURL ] = modified;
+ }
+ modified = jqXHR.getResponseHeader( "etag" );
+ if ( modified ) {
+ jQuery.etag[ cacheURL ] = modified;
+ }
+ }
+
+ // if no content
+ if ( status === 204 || s.type === "HEAD" ) {
+ statusText = "nocontent";
+
+ // if not modified
+ } else if ( status === 304 ) {
+ statusText = "notmodified";
+
+ // If we have data, let's convert it
+ } else {
+ statusText = response.state;
+ success = response.data;
+ error = response.error;
+ isSuccess = !error;
+ }
+ } else {
+
+ // Extract error from statusText and normalize for non-aborts
+ error = statusText;
+ if ( status || !statusText ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ }
+} );
+
+jQuery.each( [ "get", "post" ], function( _i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+
+ // Shift arguments if data argument was omitted
+ if ( isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ // The url can be an options object (which then must have .url)
+ return jQuery.ajax( jQuery.extend( {
+ url: url,
+ type: method,
+ dataType: type,
+ data: data,
+ success: callback
+ }, jQuery.isPlainObject( url ) && url ) );
+ };
+} );
+
+jQuery.ajaxPrefilter( function( s ) {
+ var i;
+ for ( i in s.headers ) {
+ if ( i.toLowerCase() === "content-type" ) {
+ s.contentType = s.headers[ i ] || "";
+ }
+ }
+} );
+
+
+jQuery._evalUrl = function( url, options, doc ) {
+ return jQuery.ajax( {
+ url: url,
+
+ // Make this explicit, since user can override this through ajaxSetup (#11264)
+ type: "GET",
+ dataType: "script",
+ cache: true,
+ async: false,
+ global: false,
+
+ // Only evaluate the response if it is successful (gh-4126)
+ // dataFilter is not invoked for failure responses, so using it instead
+ // of the default converter is kludgy but it works.
+ converters: {
+ "text script": function() {}
+ },
+ dataFilter: function( response ) {
+ jQuery.globalEval( response, options, doc );
+ }
+ } );
+};
+
+
+jQuery.fn.extend( {
+ wrapAll: function( html ) {
+ var wrap;
+
+ if ( this[ 0 ] ) {
+ if ( isFunction( html ) ) {
+ html = html.call( this[ 0 ] );
+ }
+
+ // The elements to wrap the target around
+ wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+ if ( this[ 0 ].parentNode ) {
+ wrap.insertBefore( this[ 0 ] );
+ }
+
+ wrap.map( function() {
+ var elem = this;
+
+ while ( elem.firstElementChild ) {
+ elem = elem.firstElementChild;
+ }
+
+ return elem;
+ } ).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( isFunction( html ) ) {
+ return this.each( function( i ) {
+ jQuery( this ).wrapInner( html.call( this, i ) );
+ } );
+ }
+
+ return this.each( function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ } );
+ },
+
+ wrap: function( html ) {
+ var htmlIsFunction = isFunction( html );
+
+ return this.each( function( i ) {
+ jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
+ } );
+ },
+
+ unwrap: function( selector ) {
+ this.parent( selector ).not( "body" ).each( function() {
+ jQuery( this ).replaceWith( this.childNodes );
+ } );
+ return this;
+ }
+} );
+
+
+jQuery.expr.pseudos.hidden = function( elem ) {
+ return !jQuery.expr.pseudos.visible( elem );
+};
+jQuery.expr.pseudos.visible = function( elem ) {
+ return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
+};
+
+
+
+
+jQuery.ajaxSettings.xhr = function() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch ( e ) {}
+};
+
+var xhrSuccessStatus = {
+
+ // File protocol always yields status code 0, assume 200
+ 0: 200,
+
+ // Support: IE <=9 only
+ // #1450: sometimes IE returns 1223 when it should be 204
+ 1223: 204
+ },
+ xhrSupported = jQuery.ajaxSettings.xhr();
+
+support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+support.ajax = xhrSupported = !!xhrSupported;
+
+jQuery.ajaxTransport( function( options ) {
+ var callback, errorCallback;
+
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( support.cors || xhrSupported && !options.crossDomain ) {
+ return {
+ send: function( headers, complete ) {
+ var i,
+ xhr = options.xhr();
+
+ xhr.open(
+ options.type,
+ options.url,
+ options.async,
+ options.username,
+ options.password
+ );
+
+ // Apply custom fields if provided
+ if ( options.xhrFields ) {
+ for ( i in options.xhrFields ) {
+ xhr[ i ] = options.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( options.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( options.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
+ headers[ "X-Requested-With" ] = "XMLHttpRequest";
+ }
+
+ // Set headers
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+
+ // Callback
+ callback = function( type ) {
+ return function() {
+ if ( callback ) {
+ callback = errorCallback = xhr.onload =
+ xhr.onerror = xhr.onabort = xhr.ontimeout =
+ xhr.onreadystatechange = null;
+
+ if ( type === "abort" ) {
+ xhr.abort();
+ } else if ( type === "error" ) {
+
+ // Support: IE <=9 only
+ // On a manual native abort, IE9 throws
+ // errors on any property access that is not readyState
+ if ( typeof xhr.status !== "number" ) {
+ complete( 0, "error" );
+ } else {
+ complete(
+
+ // File: protocol always yields status 0; see #8605, #14207
+ xhr.status,
+ xhr.statusText
+ );
+ }
+ } else {
+ complete(
+ xhrSuccessStatus[ xhr.status ] || xhr.status,
+ xhr.statusText,
+
+ // Support: IE <=9 only
+ // IE9 has no XHR2 but throws on binary (trac-11426)
+ // For XHR2 non-text, let the caller handle it (gh-2498)
+ ( xhr.responseType || "text" ) !== "text" ||
+ typeof xhr.responseText !== "string" ?
+ { binary: xhr.response } :
+ { text: xhr.responseText },
+ xhr.getAllResponseHeaders()
+ );
+ }
+ }
+ };
+ };
+
+ // Listen to events
+ xhr.onload = callback();
+ errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" );
+
+ // Support: IE 9 only
+ // Use onreadystatechange to replace onabort
+ // to handle uncaught aborts
+ if ( xhr.onabort !== undefined ) {
+ xhr.onabort = errorCallback;
+ } else {
+ xhr.onreadystatechange = function() {
+
+ // Check readyState before timeout as it changes
+ if ( xhr.readyState === 4 ) {
+
+ // Allow onerror to be called first,
+ // but that will not handle a native abort
+ // Also, save errorCallback to a variable
+ // as xhr.onerror cannot be accessed
+ window.setTimeout( function() {
+ if ( callback ) {
+ errorCallback();
+ }
+ } );
+ }
+ };
+ }
+
+ // Create the abort callback
+ callback = callback( "abort" );
+
+ try {
+
+ // Do send the request (this may raise an exception)
+ xhr.send( options.hasContent && options.data || null );
+ } catch ( e ) {
+
+ // #14683: Only rethrow if this hasn't been notified as an error yet
+ if ( callback ) {
+ throw e;
+ }
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback();
+ }
+ }
+ };
+ }
+} );
+
+
+
+
+// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
+jQuery.ajaxPrefilter( function( s ) {
+ if ( s.crossDomain ) {
+ s.contents.script = false;
+ }
+} );
+
+// Install script dataType
+jQuery.ajaxSetup( {
+ accepts: {
+ script: "text/javascript, application/javascript, " +
+ "application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /\b(?:java|ecma)script\b/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+} );
+
+// Handle cache's special case and crossDomain
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ }
+} );
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function( s ) {
+
+ // This transport only deals with cross domain or forced-by-attrs requests
+ if ( s.crossDomain || s.scriptAttrs ) {
+ var script, callback;
+ return {
+ send: function( _, complete ) {
+ script = jQuery( "<script>" )
+ .attr( s.scriptAttrs || {} )
+ .prop( { charset: s.scriptCharset, src: s.url } )
+ .on( "load error", callback = function( evt ) {
+ script.remove();
+ callback = null;
+ if ( evt ) {
+ complete( evt.type === "error" ? 404 : 200, evt.type );
+ }
+ } );
+
+ // Use native DOM manipulation to avoid our domManip AJAX trickery
+ document.head.appendChild( script[ 0 ] );
+ },
+ abort: function() {
+ if ( callback ) {
+ callback();
+ }
+ }
+ };
+ }
+} );
+
+
+
+
+var oldCallbacks = [],
+ rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup( {
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) );
+ this[ callback ] = true;
+ return callback;
+ }
+} );
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var callbackName, overwritten, responseContainer,
+ jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+ "url" :
+ typeof s.data === "string" &&
+ ( s.contentType || "" )
+ .indexOf( "application/x-www-form-urlencoded" ) === 0 &&
+ rjsonp.test( s.data ) && "data"
+ );
+
+ // Handle iff the expected data type is "jsonp" or we have a parameter to set
+ if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+ // Get callback name, remembering preexisting value associated with it
+ callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?
+ s.jsonpCallback() :
+ s.jsonpCallback;
+
+ // Insert callback into url or form data
+ if ( jsonProp ) {
+ s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+ } else if ( s.jsonp !== false ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+ }
+
+ // Use data converter to retrieve json after script execution
+ s.converters[ "script json" ] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( callbackName + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // Force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Install callback
+ overwritten = window[ callbackName ];
+ window[ callbackName ] = function() {
+ responseContainer = arguments;
+ };
+
+ // Clean-up function (fires after converters)
+ jqXHR.always( function() {
+
+ // If previous value didn't exist - remove it
+ if ( overwritten === undefined ) {
+ jQuery( window ).removeProp( callbackName );
+
+ // Otherwise restore preexisting value
+ } else {
+ window[ callbackName ] = overwritten;
+ }
+
+ // Save back as free
+ if ( s[ callbackName ] ) {
+
+ // Make sure that re-using the options doesn't screw things around
+ s.jsonpCallback = originalSettings.jsonpCallback;
+
+ // Save the callback name for future use
+ oldCallbacks.push( callbackName );
+ }
+
+ // Call if it was a function and we have a response
+ if ( responseContainer && isFunction( overwritten ) ) {
+ overwritten( responseContainer[ 0 ] );
+ }
+
+ responseContainer = overwritten = undefined;
+ } );
+
+ // Delegate to script
+ return "script";
+ }
+} );
+
+
+
+
+// Support: Safari 8 only
+// In Safari 8 documents created via document.implementation.createHTMLDocument
+// collapse sibling forms: the second one becomes a child of the first one.
+// Because of that, this security measure has to be disabled in Safari 8.
+// https://bugs.webkit.org/show_bug.cgi?id=137337
+support.createHTMLDocument = ( function() {
+ var body = document.implementation.createHTMLDocument( "" ).body;
+ body.innerHTML = "<form></form><form></form>";
+ return body.childNodes.length === 2;
+} )();
+
+
+// Argument "data" should be string of html
+// context (optional): If specified, the fragment will be created in this context,
+// defaults to document
+// keepScripts (optional): If true, will include scripts passed in the html string
+jQuery.parseHTML = function( data, context, keepScripts ) {
+ if ( typeof data !== "string" ) {
+ return [];
+ }
+ if ( typeof context === "boolean" ) {
+ keepScripts = context;
+ context = false;
+ }
+
+ var base, parsed, scripts;
+
+ if ( !context ) {
+
+ // Stop scripts or inline event handlers from being executed immediately
+ // by using document.implementation
+ if ( support.createHTMLDocument ) {
+ context = document.implementation.createHTMLDocument( "" );
+
+ // Set the base href for the created document
+ // so any parsed elements with URLs
+ // are based on the document's URL (gh-2965)
+ base = context.createElement( "base" );
+ base.href = document.location.href;
+ context.head.appendChild( base );
+ } else {
+ context = document;
+ }
+ }
+
+ parsed = rsingleTag.exec( data );
+ scripts = !keepScripts && [];
+
+ // Single tag
+ if ( parsed ) {
+ return [ context.createElement( parsed[ 1 ] ) ];
+ }
+
+ parsed = buildFragment( [ data ], context, scripts );
+
+ if ( scripts && scripts.length ) {
+ jQuery( scripts ).remove();
+ }
+
+ return jQuery.merge( [], parsed.childNodes );
+};
+
+
+/**
+ * Load a url into a page
+ */
+jQuery.fn.load = function( url, params, callback ) {
+ var selector, type, response,
+ self = this,
+ off = url.indexOf( " " );
+
+ if ( off > -1 ) {
+ selector = stripAndCollapse( url.slice( off ) );
+ url = url.slice( 0, off );
+ }
+
+ // If it's a function
+ if ( isFunction( params ) ) {
+
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( params && typeof params === "object" ) {
+ type = "POST";
+ }
+
+ // If we have elements to modify, make the request
+ if ( self.length > 0 ) {
+ jQuery.ajax( {
+ url: url,
+
+ // If "type" variable is undefined, then "GET" method will be used.
+ // Make value of this field explicit since
+ // user can override it through ajaxSetup method
+ type: type || "GET",
+ dataType: "html",
+ data: params
+ } ).done( function( responseText ) {
+
+ // Save response for use in complete callback
+ response = arguments;
+
+ self.html( selector ?
+
+ // If a selector was specified, locate the right elements in a dummy div
+ // Exclude scripts to avoid IE 'Permission Denied' errors
+ jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+ // Otherwise use the full result
+ responseText );
+
+ // If the request succeeds, this function gets "data", "status", "jqXHR"
+ // but they are ignored because response was set above.
+ // If it fails, this function gets "jqXHR", "status", "error"
+ } ).always( callback && function( jqXHR, status ) {
+ self.each( function() {
+ callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
+ } );
+ } );
+ }
+
+ return this;
+};
+
+
+
+
+jQuery.expr.pseudos.animated = function( elem ) {
+ return jQuery.grep( jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ } ).length;
+};
+
+
+
+
+jQuery.offset = {
+ setOffset: function( elem, options, i ) {
+ var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+ position = jQuery.css( elem, "position" ),
+ curElem = jQuery( elem ),
+ props = {};
+
+ // Set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ curOffset = curElem.offset();
+ curCSSTop = jQuery.css( elem, "top" );
+ curCSSLeft = jQuery.css( elem, "left" );
+ calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+ ( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
+
+ // Need to be able to calculate position if either
+ // top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( isFunction( options ) ) {
+
+ // Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
+ options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+
+ } else {
+ if ( typeof props.top === "number" ) {
+ props.top += "px";
+ }
+ if ( typeof props.left === "number" ) {
+ props.left += "px";
+ }
+ curElem.css( props );
+ }
+ }
+};
+
+jQuery.fn.extend( {
+
+ // offset() relates an element's border box to the document origin
+ offset: function( options ) {
+
+ // Preserve chaining for setter
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each( function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ } );
+ }
+
+ var rect, win,
+ elem = this[ 0 ];
+
+ if ( !elem ) {
+ return;
+ }
+
+ // Return zeros for disconnected and hidden (display: none) elements (gh-2310)
+ // Support: IE <=11 only
+ // Running getBoundingClientRect on a
+ // disconnected node in IE throws an error
+ if ( !elem.getClientRects().length ) {
+ return { top: 0, left: 0 };
+ }
+
+ // Get document-relative position by adding viewport scroll to viewport-relative gBCR
+ rect = elem.getBoundingClientRect();
+ win = elem.ownerDocument.defaultView;
+ return {
+ top: rect.top + win.pageYOffset,
+ left: rect.left + win.pageXOffset
+ };
+ },
+
+ // position() relates an element's margin box to its offset parent's padding box
+ // This corresponds to the behavior of CSS absolute positioning
+ position: function() {
+ if ( !this[ 0 ] ) {
+ return;
+ }
+
+ var offsetParent, offset, doc,
+ elem = this[ 0 ],
+ parentOffset = { top: 0, left: 0 };
+
+ // position:fixed elements are offset from the viewport, which itself always has zero offset
+ if ( jQuery.css( elem, "position" ) === "fixed" ) {
+
+ // Assume position:fixed implies availability of getBoundingClientRect
+ offset = elem.getBoundingClientRect();
+
+ } else {
+ offset = this.offset();
+
+ // Account for the *real* offset parent, which can be the document or its root element
+ // when a statically positioned element is identified
+ doc = elem.ownerDocument;
+ offsetParent = elem.offsetParent || doc.documentElement;
+ while ( offsetParent &&
+ ( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
+ jQuery.css( offsetParent, "position" ) === "static" ) {
+
+ offsetParent = offsetParent.parentNode;
+ }
+ if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
+
+ // Incorporate borders into its offset, since they are outside its content origin
+ parentOffset = jQuery( offsetParent ).offset();
+ parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
+ parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
+ }
+ }
+
+ // Subtract parent offsets and element margins
+ return {
+ top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+ left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+ };
+ },
+
+ // This method will return documentElement in the following cases:
+ // 1) For the element inside the iframe without offsetParent, this method will return
+ // documentElement of the parent window
+ // 2) For the hidden or detached element
+ // 3) For body or html element, i.e. in case of the html node - it will return itself
+ //
+ // but those exceptions were never presented as a real life use-cases
+ // and might be considered as more preferable results.
+ //
+ // This logic, however, is not guaranteed and can change at any point in the future
+ offsetParent: function() {
+ return this.map( function() {
+ var offsetParent = this.offsetParent;
+
+ while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+
+ return offsetParent || documentElement;
+ } );
+ }
+} );
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+ var top = "pageYOffset" === prop;
+
+ jQuery.fn[ method ] = function( val ) {
+ return access( this, function( elem, method, val ) {
+
+ // Coalesce documents and windows
+ var win;
+ if ( isWindow( elem ) ) {
+ win = elem;
+ } else if ( elem.nodeType === 9 ) {
+ win = elem.defaultView;
+ }
+
+ if ( val === undefined ) {
+ return win ? win[ prop ] : elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : win.pageXOffset,
+ top ? val : win.pageYOffset
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length );
+ };
+} );
+
+// Support: Safari <=7 - 9.1, Chrome <=37 - 49
+// Add the top/left cssHooks using jQuery.fn.position
+// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
+// getComputedStyle returns percent when specified for top/left/bottom/right;
+// rather than make the css module depend on the offset module, just check for it here
+jQuery.each( [ "top", "left" ], function( _i, prop ) {
+ jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+ function( elem, computed ) {
+ if ( computed ) {
+ computed = curCSS( elem, prop );
+
+ // If curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( computed ) ?
+ jQuery( elem ).position()[ prop ] + "px" :
+ computed;
+ }
+ }
+ );
+} );
+
+
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
+ function( defaultExtra, funcName ) {
+
+ // Margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( isWindow( elem ) ) {
+
+ // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
+ return funcName.indexOf( "outer" ) === 0 ?
+ elem[ "inner" + name ] :
+ elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+ // whichever is greatest
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable );
+ };
+ } );
+} );
+
+
+jQuery.each( [
+ "ajaxStart",
+ "ajaxStop",
+ "ajaxComplete",
+ "ajaxError",
+ "ajaxSuccess",
+ "ajaxSend"
+], function( _i, type ) {
+ jQuery.fn[ type ] = function( fn ) {
+ return this.on( type, fn );
+ };
+} );
+
+
+
+
+jQuery.fn.extend( {
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ?
+ this.off( selector, "**" ) :
+ this.off( types, selector || "**", fn );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+} );
+
+jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup contextmenu" ).split( " " ),
+ function( _i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+ } );
+
+
+
+
+// Support: Android <=4.0 only
+// Make sure we trim BOM and NBSP
+var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
+
+// Bind a function to a context, optionally partially applying any
+// arguments.
+// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
+// However, it is not slated for removal any time soon
+jQuery.proxy = function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+};
+
+jQuery.holdReady = function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+};
+jQuery.isArray = Array.isArray;
+jQuery.parseJSON = JSON.parse;
+jQuery.nodeName = nodeName;
+jQuery.isFunction = isFunction;
+jQuery.isWindow = isWindow;
+jQuery.camelCase = camelCase;
+jQuery.type = toType;
+
+jQuery.now = Date.now;
+
+jQuery.isNumeric = function( obj ) {
+
+ // As of jQuery 3.0, isNumeric is limited to
+ // strings and numbers (primitives or objects)
+ // that can be coerced to finite numbers (gh-2662)
+ var type = jQuery.type( obj );
+ return ( type === "number" || type === "string" ) &&
+
+ // parseFloat NaNs numeric-cast false positives ("")
+ // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+ // subtraction forces infinities to NaN
+ !isNaN( obj - parseFloat( obj ) );
+};
+
+jQuery.trim = function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+};
+
+
+
+// Register as a named AMD module, since jQuery can be concatenated with other
+// files that may use define, but not via a proper concatenation script that
+// understands anonymous AMD modules. A named AMD is safest and most robust
+// way to register. Lowercase jquery is used because AMD module names are
+// derived from file names, and jQuery is normally delivered in a lowercase
+// file name. Do this after creating the global so that if an AMD module wants
+// to call noConflict to hide this version of jQuery, it will work.
+
+// Note that for maximum portability, libraries that are not jQuery should
+// declare themselves as anonymous modules, and avoid setting a global if an
+// AMD loader is present. jQuery is a special case. For more information, see
+// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+if ( typeof define === "function" && define.amd ) {
+ define( "jquery", [], function() {
+ return jQuery;
+ } );
+}
+
+
+
+
+var
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$;
+
+jQuery.noConflict = function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+};
+
+// Expose jQuery and $ identifiers, even in AMD
+// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+// and CommonJS for browser emulators (#13566)
+if ( typeof noGlobal === "undefined" ) {
+ window.jQuery = window.$ = jQuery;
+}
+
+
+
+
+return jQuery;
+} );
diff --git a/deps/rabbitmq_management/priv/www/js/jquery-3.5.1.min.js b/deps/rabbitmq_management/priv/www/js/jquery-3.5.1.min.js
new file mode 100644
index 0000000000..b0614034ad
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/jquery-3.5.1.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",F=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",B=new RegExp(M+"+","g"),$=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="<a id='"+S+"'></a><select id='"+S+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&j.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+"").replace(re,ie)},se.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(D),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(B," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(h,e,t,g,v){var y="nth"!==h.slice(0,3),m="last"!==h.slice(-4),x="of-type"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?"nextSibling":"previousSibling",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l="only"===h&&!u&&"nextSibling"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if(1===a.nodeType&&++d&&a===e){i[h]=[k,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1]),!1===d)while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e))break;return(d-=v)===g||d%g==0&&0<=d/g}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error("unsupported pseudo: "+e);return a[S]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,"$1"));return s[S]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||"")||se.error("unsupported lang: "+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);function me(){}function xe(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&"parentNode"===c,p=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[k,p];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[S]||(e[S]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===k&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[S]&&(v=Ce(v)),y&&!y[S]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v){i=Te(p,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a))}if(e){if(y||d){if(y){i=[],o=p.length;while(o--)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}o=p.length;while(o--)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return Ce(1<s&&we(c),1<s&&xe(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&xe(e))}c.push(t)}return we(c)}return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($," ")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=A[e+" "];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[S]?i.push(a):o.push(a);(a=A(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=k+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==C||(T(o),n=!E);while(s=v[a++])if(s(o,t||C,n)){r.push(o);break}i&&(k=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(k=h,w=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=G.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=S.split("").sort(D).join("")===S,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement("fieldset"))}),ce(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||fe("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||fe(R,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);S.find=d,S.expr=d.selectors,S.expr[":"]=S.expr.pseudos,S.uniqueSort=S.unique=d.uniqueSort,S.text=d.getText,S.isXMLDoc=d.isXML,S.contains=d.contains,S.escapeSelector=d.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&S(e).is(n))break;r.push(e)}return r},T=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},k=S.expr.match.needsContext;function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var N=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1<i.call(n,e)!==r}):S.filter(n,e,r)}S.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?S.find.matchesSelector(r,e)?[r]:[]:S.find.matches(e,S.grep(t,function(e){return 1===e.nodeType}))},S.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(S(e).filter(function(){for(t=0;t<r;t++)if(S.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)S.find(e,i[t],n);return 1<r?S.uniqueSort(n):n},filter:function(e){return this.pushStack(D(this,e||[],!1))},not:function(e){return this.pushStack(D(this,e||[],!0))},is:function(e){return!!D(this,"string"==typeof e&&k.test(e)?S(e):e||[],!1).length}});var j,q=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(S.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&S(e);if(!k.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&S.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?S.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?i.call(S(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(S.uniqueSort(S.merge(this.get(),S(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),S.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,"parentNode")},parentsUntil:function(e,t,n){return h(e,"parentNode",n)},next:function(e){return O(e,"nextSibling")},prev:function(e){return O(e,"previousSibling")},nextAll:function(e){return h(e,"nextSibling")},prevAll:function(e){return h(e,"previousSibling")},nextUntil:function(e,t,n){return h(e,"nextSibling",n)},prevUntil:function(e,t,n){return h(e,"previousSibling",n)},siblings:function(e){return T((e.parentNode||{}).firstChild,e)},children:function(e){return T(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(A(e,"template")&&(e=e.content||e),S.merge([],e.childNodes))}},function(r,i){S.fn[r]=function(e,t){var n=S.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=S.filter(t,n)),1<this.length&&(H[r]||S.uniqueSort(n),L.test(r)&&n.reverse()),this.pushStack(n)}});var P=/[^\x20\t\r\n\f]+/g;function R(e){return e}function M(e){throw e}function I(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}S.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},S.each(e.match(P)||[],function(e,t){n[t]=!0}),n):S.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){S.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return S.each(arguments,function(e,t){var n;while(-1<(n=S.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<S.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},S.extend({Deferred:function(e){var o=[["notify","progress",S.Callbacks("memory"),S.Callbacks("memory"),2],["resolve","done",S.Callbacks("once memory"),S.Callbacks("once memory"),0,"resolved"],["reject","fail",S.Callbacks("once memory"),S.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return S.Deferred(function(r){S.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,R,s),l(u,o,M,s)):(u++,t.call(e,l(u,o,R,s),l(u,o,M,s),l(u,o,R,o.notifyWith))):(a!==R&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){S.Deferred.exceptionHook&&S.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==M&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(S.Deferred.getStackHook&&(t.stackTrace=S.Deferred.getStackHook()),C.setTimeout(t))}}return S.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:R,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:R)),o[2][3].add(l(0,e,m(n)?n:M))}).promise()},promise:function(e){return null!=e?S.extend(e,a):a}},s={};return S.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=S.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(I(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||m(i[t]&&i[t].then)))return o.then();while(t--)I(i[t],a(t),o.reject);return o.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;S.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&W.test(e.name)&&C.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},S.readyException=function(e){C.setTimeout(function(){throw e})};var F=S.Deferred();function B(){E.removeEventListener("DOMContentLoaded",B),C.removeEventListener("load",B),S.ready()}S.fn.ready=function(e){return F.then(e)["catch"](function(e){S.readyException(e)}),this},S.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--S.readyWait:S.isReady)||(S.isReady=!0)!==e&&0<--S.readyWait||F.resolveWith(E,[S])}}),S.ready.then=F.then,"complete"===E.readyState||"loading"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(S.ready):(E.addEventListener("DOMContentLoaded",B),C.addEventListener("load",B));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===w(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(S(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},_=/^-ms-/,z=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function X(e){return e.replace(_,"ms-").replace(z,U)}var V=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function G(){this.expando=S.expando+G.uid++}G.uid=1,G.prototype={cache:function(e){var t=e[this.expando];return t||(t={},V(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[X(t)]=n;else for(r in t)i[X(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][X(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(X):(t=X(t))in r?[t]:t.match(P)||[]).length;while(n--)delete r[t[n]]}(void 0===t||S.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!S.isEmptyObject(t)}};var Y=new G,Q=new G,J=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,K=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(K,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:J.test(i)?JSON.parse(i):i)}catch(e){}Q.set(e,t,n)}else n=void 0;return n}S.extend({hasData:function(e){return Q.hasData(e)||Y.hasData(e)},data:function(e,t,n){return Q.access(e,t,n)},removeData:function(e,t){Q.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),S.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=Q.get(o),1===o.nodeType&&!Y.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=X(r.slice(5)),Z(o,r,i[r]));Y.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){Q.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=Q.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){Q.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){Q.remove(this,e)})}}),S.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,S.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=S.queue(e,t),r=n.length,i=n.shift(),o=S._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){S.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Y.get(e,n)||Y.access(e,n,{empty:S.Callbacks("once memory").add(function(){Y.remove(e,[t+"queue",n])})})}}),S.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?S.queue(this[0],t):void 0===n?this:this.each(function(){var e=S.queue(this,t,n);S._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&S.dequeue(this,t)})},dequeue:function(e){return this.each(function(){S.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=S.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=Y.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,te=new RegExp("^(?:([+-])=|)("+ee+")([a-z%]*)$","i"),ne=["Top","Right","Bottom","Left"],re=E.documentElement,ie=function(e){return S.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return S.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&ie(e)&&"none"===S.css(e,"display")};function se(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return S.css(e,t,"")},u=s(),l=n&&n[3]||(S.cssNumber[t]?"":"px"),c=e.nodeType&&(S.cssNumber[t]||"px"!==l&&+u)&&te.exec(S.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)S.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,S.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ue={};function le(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=Y.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ue[s])||(o=a.body.appendChild(a.createElement(s)),u=S.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ue[s]=u)))):"none"!==n&&(l[c]="none",Y.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}S.fn.extend({show:function(){return le(this,!0)},hide:function(){return le(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?S(this).show():S(this).hide()})}});var ce,fe,pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="<textarea>x</textarea>",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="<option></option>",y.option=!!ce.lastChild;var ge={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],"globalEval",!t||Y.get(t[n],"globalEval"))}ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td,y.option||(ge.optgroup=ge.option=[1,"<select multiple='multiple'>","</select>"]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===w(o))S.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+S.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;S.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<S.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}var be=/^key/,we=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ce(){return!0}function Ee(){return!1}function Se(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function ke(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)ke(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ee;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return S().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=S.guid++)),e.each(function(){S.event.add(this,t,i,r,n)})}function Ae(e,i,o){o?(Y.set(e,i,!1),S.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(S.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Y.set(this,i,{value:S.event.trigger(S.extend(r[0],S.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&S.event.add(e,i,Ce)}S.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.get(t);if(V(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&S.find.matchesSelector(re,i),n.guid||(n.guid=S.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof S&&S.event.triggered!==e.type?S.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(P)||[""]).length;while(l--)d=g=(s=Te.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=S.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=S.event.special[d]||{},c=S.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&S.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),S.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(P)||[""]).length;while(l--)if(d=g=(s=Te.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=S.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||S.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)S.event.remove(e,d+t[l],n,r,!0);S.isEmptyObject(u)&&Y.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=S.event.fix(e),l=(Y.get(this,"events")||Object.create(null))[u.type]||[],c=S.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=S.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((S.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<S(i,this).index(l):S.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(S.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[S.expando]?e:new S.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click",Ce),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,"input")&&Y.get(t,"click")||A(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},S.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},S.Event=function(e,t){if(!(this instanceof S.Event))return new S.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ce:Ee,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&S.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[S.expando]=!0},S.Event.prototype={constructor:S.Event,isDefaultPrevented:Ee,isPropagationStopped:Ee,isImmediatePropagationStopped:Ee,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ce,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ce,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ce,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},S.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&be.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&we.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},S.event.addProp),S.each({focus:"focusin",blur:"focusout"},function(e,t){S.event.special[e]={setup:function(){return Ae(this,e,Se),!1},trigger:function(){return Ae(this,e),!0},delegateType:t}}),S.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){S.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||S.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),S.fn.extend({on:function(e,t,n,r){return ke(this,e,t,n,r)},one:function(e,t,n,r){return ke(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,S(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=Ee),this.each(function(){S.event.remove(this,e,n,t)})}});var Ne=/<script|<style|<link/i,De=/checked\s*(?:[^=]|=\s*.checked.)/i,je=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)S.event.add(t,i,s[i][n]);Q.hasData(e)&&(o=Q.access(e),a=S.extend({},o),Q.set(t,a))}}function Pe(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||1<f&&"string"==typeof d&&!y.checkClone&&De.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),Pe(t,r,i,o)});if(f&&(t=(e=xe(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=S.map(ve(e,"script"),Le)).length;c<f;c++)u=e,c!==p&&(u=S.clone(u,!0,!0),s&&S.merge(a,ve(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,S.map(a,He),c=0;c<s;c++)u=a[c],he.test(u.type||"")&&!Y.access(u,"globalEval")&&S.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?S._evalUrl&&!u.noModule&&S._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):b(u.textContent.replace(je,""),u,l))}return n}function Re(e,t,n){for(var r,i=t?S.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||S.cleanData(ve(r)),r.parentNode&&(n&&ie(r)&&ye(ve(r,"script")),r.parentNode.removeChild(r));return e}S.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||S.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;r<i;r++)Oe(o[r],a[r]);else Oe(e,c);return 0<(a=ve(c,"script")).length&&ye(a,!f&&ve(e,"script")),c},cleanData:function(e){for(var t,n,r,i=S.event.special,o=0;void 0!==(n=e[o]);o++)if(V(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?S.event.remove(n,r):S.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),S.fn.extend({detach:function(e){return Re(this,e,!0)},remove:function(e){return Re(this,e)},text:function(e){return $(this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Pe(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||qe(this,e).appendChild(e)})},prepend:function(){return Pe(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=qe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(S.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return S.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ne.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(S.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return Pe(this,arguments,function(e){var t=this.parentNode;S.inArray(this,n)<0&&(S.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),S.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){S.fn[e]=function(e){for(var t,n=[],r=S(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),S(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Me=new RegExp("^("+ee+")(?!px)[a-z%]+$","i"),Ie=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},We=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Fe=new RegExp(ne.join("|"),"i");function Be(e,t,n){var r,i,o,a,s=e.style;return(n=n||Ie(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=S.style(e,t)),!y.pixelBoxStyles()&&Me.test(a)&&Fe.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function $e(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",re.appendChild(u).appendChild(l);var e=C.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=E.createElement("div"),l=E.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",y.clearCloneStyle="content-box"===l.style.backgroundClip,S.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=E.createElement("table"),t=E.createElement("tr"),n=E.createElement("div"),e.style.cssText="position:absolute;left:-11111px",t.style.height="1px",n.style.height="9px",re.appendChild(e).appendChild(t).appendChild(n),r=C.getComputedStyle(t),a=3<parseInt(r.height),re.removeChild(e)),a}}))}();var _e=["Webkit","Moz","ms"],ze=E.createElement("div").style,Ue={};function Xe(e){var t=S.cssProps[e]||Ue[e];return t||(e in ze?e:Ue[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=_e.length;while(n--)if((e=_e[n]+t)in ze)return e}(e)||e)}var Ve=/^(none|table(?!-c[ea]).+)/,Ge=/^--/,Ye={position:"absolute",visibility:"hidden",display:"block"},Qe={letterSpacing:"0",fontWeight:"400"};function Je(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Ke(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=S.css(e,n+ne[a],!0,i)),r?("content"===n&&(u-=S.css(e,"padding"+ne[a],!0,i)),"margin"!==n&&(u-=S.css(e,"border"+ne[a]+"Width",!0,i))):(u+=S.css(e,"padding"+ne[a],!0,i),"padding"!==n?u+=S.css(e,"border"+ne[a]+"Width",!0,i):s+=S.css(e,"border"+ne[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function Ze(e,t,n){var r=Ie(e),i=(!y.boxSizingReliable()||n)&&"border-box"===S.css(e,"boxSizing",!1,r),o=i,a=Be(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Me.test(a)){if(!n)return a;a="auto"}return(!y.boxSizingReliable()&&i||!y.reliableTrDimensions()&&A(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===S.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===S.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Ke(e,t,n||(i?"border":"content"),o,r,a)+"px"}function et(e,t,n,r,i){return new et.prototype.init(e,t,n,r,i)}S.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Be(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=X(t),u=Ge.test(t),l=e.style;if(u||(t=Xe(s)),a=S.cssHooks[t]||S.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(S.cssNumber[s]?"":"px")),y.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=X(t);return Ge.test(t)||(t=Xe(s)),(a=S.cssHooks[t]||S.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Be(e,t,r)),"normal"===i&&t in Qe&&(i=Qe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),S.each(["height","width"],function(e,u){S.cssHooks[u]={get:function(e,t,n){if(t)return!Ve.test(S.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?Ze(e,u,n):We(e,Ye,function(){return Ze(e,u,n)})},set:function(e,t,n){var r,i=Ie(e),o=!y.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===S.css(e,"boxSizing",!1,i),s=n?Ke(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Ke(e,u,"border",!1,i)-.5)),s&&(r=te.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=S.css(e,u)),Je(0,t,s)}}}),S.cssHooks.marginLeft=$e(y.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Be(e,"marginLeft"))||e.getBoundingClientRect().left-We(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),S.each({margin:"",padding:"",border:"Width"},function(i,o){S.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(S.cssHooks[i+o].set=Je)}),S.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Ie(e),i=t.length;a<i;a++)o[t[a]]=S.css(e,t[a],!1,r);return o}return void 0!==n?S.style(e,t,n):S.css(e,t)},e,t,1<arguments.length)}}),((S.Tween=et).prototype={constructor:et,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||S.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(S.cssNumber[n]?"":"px")},cur:function(){var e=et.propHooks[this.prop];return e&&e.get?e.get(this):et.propHooks._default.get(this)},run:function(e){var t,n=et.propHooks[this.prop];return this.options.duration?this.pos=t=S.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):et.propHooks._default.set(this),this}}).init.prototype=et.prototype,(et.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=S.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){S.fx.step[e.prop]?S.fx.step[e.prop](e):1!==e.elem.nodeType||!S.cssHooks[e.prop]&&null==e.elem.style[Xe(e.prop)]?e.elem[e.prop]=e.now:S.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=et.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},S.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},S.fx=et.prototype.init,S.fx.step={};var tt,nt,rt,it,ot=/^(?:toggle|show|hide)$/,at=/queueHooks$/;function st(){nt&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(st):C.setTimeout(st,S.fx.interval),S.fx.tick())}function ut(){return C.setTimeout(function(){tt=void 0}),tt=Date.now()}function lt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=ne[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ct(e,t,n){for(var r,i=(ft.tweeners[t]||[]).concat(ft.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function ft(o,e,t){var n,a,r=0,i=ft.prefilters.length,s=S.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=tt||ut(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:S.extend({},e),opts:S.extend(!0,{specialEasing:{},easing:S.easing._default},t),originalProperties:e,originalOptions:t,startTime:tt||ut(),duration:t.duration,tweens:[],createTween:function(e,t){var n=S.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=X(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=S.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=ft.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(S._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return S.map(c,ct,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),S.fx.timer(S.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}S.Animation=S.extend(ft,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return se(n.elem,e,te.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=["*"]):e=e.match(P);for(var n,r=0,i=e.length;r<i;r++)n=e[r],ft.tweeners[n]=ft.tweeners[n]||[],ft.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),v=Y.get(e,"fxshow");for(r in n.queue||(null==(a=S._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,S.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],ot.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||S.style(e,r)}if((u=!S.isEmptyObject(t))||!S.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Y.get(e,"display")),"none"===(c=S.css(e,"display"))&&(l?c=l:(le([e],!0),l=e.style.display||l,c=S.css(e,"display"),le([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===S.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=Y.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&le([e],!0),p.done(function(){for(r in g||le([e]),Y.remove(e,"fxshow"),d)S.style(e,r,d[r])})),u=ct(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?ft.prefilters.unshift(e):ft.prefilters.push(e)}}),S.speed=function(e,t,n){var r=e&&"object"==typeof e?S.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return S.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in S.fx.speeds?r.duration=S.fx.speeds[r.duration]:r.duration=S.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&S.dequeue(this,r.queue)},r},S.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=S.isEmptyObject(t),o=S.speed(e,n,r),a=function(){var e=ft(this,S.extend({},t),o);(i||Y.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=S.timers,r=Y.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&at.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||S.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=Y.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=S.timers,o=n?n.length:0;for(t.finish=!0,S.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),S.each(["toggle","show","hide"],function(e,r){var i=S.fn[r];S.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(lt(r,!0),e,t,n)}}),S.each({slideDown:lt("show"),slideUp:lt("hide"),slideToggle:lt("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){S.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),S.timers=[],S.fx.tick=function(){var e,t=0,n=S.timers;for(tt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||S.fx.stop(),tt=void 0},S.fx.timer=function(e){S.timers.push(e),S.fx.start()},S.fx.interval=13,S.fx.start=function(){nt||(nt=!0,st())},S.fx.stop=function(){nt=null},S.fx.speeds={slow:600,fast:200,_default:400},S.fn.delay=function(r,e){return r=S.fx&&S.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},rt=E.createElement("input"),it=E.createElement("select").appendChild(E.createElement("option")),rt.type="checkbox",y.checkOn=""!==rt.value,y.optSelected=it.selected,(rt=E.createElement("input")).value="t",rt.type="radio",y.radioValue="t"===rt.value;var pt,dt=S.expr.attrHandle;S.fn.extend({attr:function(e,t){return $(this,S.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){S.removeAttr(this,e)})}}),S.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?S.prop(e,t,n):(1===o&&S.isXMLDoc(e)||(i=S.attrHooks[t.toLowerCase()]||(S.expr.match.bool.test(t)?pt:void 0)),void 0!==n?null===n?void S.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=S.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&"radio"===t&&A(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),pt={set:function(e,t,n){return!1===t?S.removeAttr(e,n):e.setAttribute(n,n),n}},S.each(S.expr.match.bool.source.match(/\w+/g),function(e,t){var a=dt[t]||S.find.attr;dt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=dt[o],dt[o]=r,r=null!=a(e,t,n)?o:null,dt[o]=i),r}});var ht=/^(?:input|select|textarea|button)$/i,gt=/^(?:a|area)$/i;function vt(e){return(e.match(P)||[]).join(" ")}function yt(e){return e.getAttribute&&e.getAttribute("class")||""}function mt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(P)||[]}S.fn.extend({prop:function(e,t){return $(this,S.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[S.propFix[e]||e]})}}),S.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&S.isXMLDoc(e)||(t=S.propFix[t]||t,i=S.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=S.find.attr(e,"tabindex");return t?parseInt(t,10):ht.test(e.nodeName)||gt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),y.optSelected||(S.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),S.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){S.propFix[this.toLowerCase()]=this}),S.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).addClass(t.call(this,e,yt(this)))});if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).removeClass(t.call(this,e,yt(this)))});if(!arguments.length)return this.attr("class","");if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])while(-1<r.indexOf(" "+o+" "))r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(i,t){var o=typeof i,a="string"===o||Array.isArray(i);return"boolean"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){S(this).toggleClass(i.call(this,e,yt(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=S(this),r=mt(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&"boolean"!==o||((e=yt(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===i?"":Y.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+vt(yt(n))+" ").indexOf(t))return!0;return!1}});var xt=/\r/g;S.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,S(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=S.map(t,function(e){return null==e?"":e+""})),(r=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=S.valHooks[t.type]||S.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(xt,""):null==e?"":e:void 0}}),S.extend({valHooks:{option:{get:function(e){var t=S.find.attr(e,"value");return null!=t?t:vt(S.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,"optgroup"))){if(t=S(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=S.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<S.inArray(S.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),S.each(["radio","checkbox"],function(){S.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<S.inArray(S(e).val(),t)}},y.checkOn||(S.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),y.focusin="onfocusin"in C;var bt=/^(?:focusinfocus|focusoutblur)$/,wt=function(e){e.stopPropagation()};S.extend(S.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,"type")?e.type:e,h=v.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!bt.test(d+S.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[S.expando]?e:new S.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:S.makeArray(t,[e]),c=S.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,bt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(Y.get(o,"events")||Object.create(null))[e.type]&&Y.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&V(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!V(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),S.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,wt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,wt),S.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=S.extend(new S.Event,n,{type:e,isSimulated:!0});S.event.trigger(r,null,t)}}),S.fn.extend({trigger:function(e,t){return this.each(function(){S.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return S.event.trigger(e,t,n,!0)}}),y.focusin||S.each({focus:"focusin",blur:"focusout"},function(n,r){var i=function(e){S.event.simulate(r,e.target,S.event.fix(e))};S.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}});var Tt=C.location,Ct={guid:Date.now()},Et=/\?/;S.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||S.error("Invalid XML: "+e),t};var St=/\[\]$/,kt=/\r?\n/g,At=/^(?:submit|button|image|reset|file)$/i,Nt=/^(?:input|select|textarea|keygen)/i;function Dt(n,e,r,i){var t;if(Array.isArray(e))S.each(e,function(e,t){r||St.test(n)?i(n,t):Dt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==w(e))i(n,e);else for(t in e)Dt(n+"["+t+"]",e[t],r,i)}S.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!S.isPlainObject(e))S.each(e,function(){i(this.name,this.value)});else for(n in e)Dt(n,e[n],t,i);return r.join("&")},S.fn.extend({serialize:function(){return S.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=S.prop(this,"elements");return e?S.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!S(this).is(":disabled")&&Nt.test(this.nodeName)&&!At.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=S(this).val();return null==n?null:Array.isArray(n)?S.map(n,function(e){return{name:t.name,value:e.replace(kt,"\r\n")}}):{name:t.name,value:n.replace(kt,"\r\n")}}).get()}});var jt=/%20/g,qt=/#.*$/,Lt=/([?&])_=[^&]*/,Ht=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ot=/^(?:GET|HEAD)$/,Pt=/^\/\//,Rt={},Mt={},It="*/".concat("*"),Wt=E.createElement("a");function Ft(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(P)||[];if(m(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Bt(t,i,o,a){var s={},u=t===Mt;function l(e){var r;return s[e]=!0,S.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function $t(e,t){var n,r,i=S.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&S.extend(!0,e,r),e}Wt.href=Tt.href,S.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Tt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Tt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":It,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":S.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?$t($t(e,S.ajaxSettings),t):$t(S.ajaxSettings,e)},ajaxPrefilter:Ft(Rt),ajaxTransport:Ft(Mt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=S.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?S(y):S.event,x=S.Deferred(),b=S.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=Ht.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Tt.href)+"").replace(Pt,Tt.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(P)||[""],null==v.crossDomain){r=E.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Wt.protocol+"//"+Wt.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=S.param(v.data,v.traditional)),Bt(Rt,v,t,T),h)return T;for(i in(g=S.event&&v.global)&&0==S.active++&&S.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Ot.test(v.type),f=v.url.replace(qt,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(jt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(Et.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Lt,"$1"),o=(Et.test(f)?"&":"?")+"_="+Ct.guid+++o),v.url=f+o),v.ifModified&&(S.lastModified[f]&&T.setRequestHeader("If-Modified-Since",S.lastModified[f]),S.etag[f]&&T.setRequestHeader("If-None-Match",S.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+It+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Bt(Mt,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<S.inArray("script",v.dataTypes)&&(v.converters["text script"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(S.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(S.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--S.active||S.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return S.get(e,t,n,"json")},getScript:function(e,t){return S.get(e,void 0,t,"script")}}),S.each(["get","post"],function(e,i){S[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),S.ajax(S.extend({url:e,type:i,dataType:r,data:t,success:n},S.isPlainObject(e)&&e))}}),S.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),S._evalUrl=function(e,t,n){return S.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){S.globalEval(e,t,n)}})},S.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=S(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){S(this).wrapInner(n.call(this,e))}):this.each(function(){var e=S(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){S(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){S(this).replaceWith(this.childNodes)}),this}}),S.expr.pseudos.hidden=function(e){return!S.expr.pseudos.visible(e)},S.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},S.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var _t={0:200,1223:204},zt=S.ajaxSettings.xhr();y.cors=!!zt&&"withCredentials"in zt,y.ajax=zt=!!zt,S.ajaxTransport(function(i){var o,a;if(y.cors||zt&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(_t[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),S.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),S.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return S.globalEval(e),e}}}),S.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),S.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=S("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=vt(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&S.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?S("<div>").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var Gt=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;S.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),m(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||S.guid++,i},S.holdReady=function(e){e?S.readyWait++:S.ready(!0)},S.isArray=Array.isArray,S.parseJSON=JSON.parse,S.nodeName=A,S.isFunction=m,S.isWindow=x,S.camelCase=X,S.type=w,S.now=Date.now,S.isNumeric=function(e){var t=S.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},S.trim=function(e){return null==e?"":(e+"").replace(Gt,"")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return S});var Yt=C.jQuery,Qt=C.$;return S.noConflict=function(e){return C.$===S&&(C.$=Qt),e&&C.jQuery===S&&(C.jQuery=Yt),S},"undefined"==typeof e&&(C.jQuery=C.$=S),S});
diff --git a/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.js b/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.js
new file mode 100644
index 0000000000..4b34fd68eb
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.js
@@ -0,0 +1,3061 @@
+/* Javascript plotting library for jQuery, version 0.8.1.
+
+Copyright (c) 2007-2013 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+
+// first an inline dependency, jquery.colorhelpers.js, we inline it here
+// for convenience
+
+/* Plugin for jQuery for working with colors.
+ *
+ * Version 1.1.
+ *
+ * Inspiration from jQuery color animation plugin by John Resig.
+ *
+ * Released under the MIT license by Ole Laursen, October 2009.
+ *
+ * Examples:
+ *
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
+ * var c = $.color.extract($("#mydiv"), 'background-color');
+ * console.log(c.r, c.g, c.b, c.a);
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
+ *
+ * Note that .scale() and .add() return the same modified object
+ * instead of making a new one.
+ *
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
+ * produce a color rather than just crashing.
+ */
+(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
+
+// the actual Flot code
+(function($) {
+
+ // Cache the prototype hasOwnProperty for faster access
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // The Canvas object is a wrapper around an HTML5 <canvas> tag.
+ //
+ // @constructor
+ // @param {string} cls List of classes to apply to the canvas.
+ // @param {element} container Element onto which to append the canvas.
+ //
+ // Requiring a container is a little iffy, but unfortunately canvas
+ // operations don't work unless the canvas is attached to the DOM.
+
+ function Canvas(cls, container) {
+
+ var element = container.children("." + cls)[0];
+
+ if (element == null) {
+
+ element = document.createElement("canvas");
+ element.className = cls;
+
+ $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
+ .appendTo(container);
+
+ // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
+
+ if (!element.getContext) {
+ if (window.G_vmlCanvasManager) {
+ element = window.G_vmlCanvasManager.initElement(element);
+ } else {
+ throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
+ }
+ }
+ }
+
+ this.element = element;
+
+ var context = this.context = element.getContext("2d");
+
+ // Determine the screen's ratio of physical to device-independent
+ // pixels. This is the ratio between the canvas width that the browser
+ // advertises and the number of pixels actually present in that space.
+
+ // The iPhone 4, for example, has a device-independent width of 320px,
+ // but its screen is actually 640px wide. It therefore has a pixel
+ // ratio of 2, while most normal devices have a ratio of 1.
+
+ var devicePixelRatio = window.devicePixelRatio || 1,
+ backingStoreRatio =
+ context.webkitBackingStorePixelRatio ||
+ context.mozBackingStorePixelRatio ||
+ context.msBackingStorePixelRatio ||
+ context.oBackingStorePixelRatio ||
+ context.backingStorePixelRatio || 1;
+
+ this.pixelRatio = devicePixelRatio / backingStoreRatio;
+
+ // Size the canvas to match the internal dimensions of its container
+
+ this.resize(container.width(), container.height());
+
+ // Collection of HTML div layers for text overlaid onto the canvas
+
+ this.textContainer = null;
+ this.text = {};
+
+ // Cache of text fragments and metrics, so we can avoid expensively
+ // re-calculating them when the plot is re-rendered in a loop.
+
+ this._textCache = {};
+ }
+
+ // Resizes the canvas to the given dimensions.
+ //
+ // @param {number} width New width of the canvas, in pixels.
+ // @param {number} width New height of the canvas, in pixels.
+
+ Canvas.prototype.resize = function(width, height) {
+
+ if (width <= 0 || height <= 0) {
+ throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
+ }
+
+ var element = this.element,
+ context = this.context,
+ pixelRatio = this.pixelRatio;
+
+ // Resize the canvas, increasing its density based on the display's
+ // pixel ratio; basically giving it more pixels without increasing the
+ // size of its element, to take advantage of the fact that retina
+ // displays have that many more pixels in the same advertised space.
+
+ // Resizing should reset the state (excanvas seems to be buggy though)
+
+ if (this.width != width) {
+ element.width = width * pixelRatio;
+ element.style.width = width + "px";
+ this.width = width;
+ }
+
+ if (this.height != height) {
+ element.height = height * pixelRatio;
+ element.style.height = height + "px";
+ this.height = height;
+ }
+
+ // Save the context, so we can reset in case we get replotted. The
+ // restore ensure that we're really back at the initial state, and
+ // should be safe even if we haven't saved the initial state yet.
+
+ context.restore();
+ context.save();
+
+ // Scale the coordinate space to match the display density; so even though we
+ // may have twice as many pixels, we still want lines and other drawing to
+ // appear at the same size; the extra pixels will just make them crisper.
+
+ context.scale(pixelRatio, pixelRatio);
+ };
+
+ // Clears the entire canvas area, not including any overlaid HTML text
+
+ Canvas.prototype.clear = function() {
+ this.context.clearRect(0, 0, this.width, this.height);
+ };
+
+ // Finishes rendering the canvas, including managing the text overlay.
+
+ Canvas.prototype.render = function() {
+
+ var cache = this._textCache;
+
+ // For each text layer, add elements marked as active that haven't
+ // already been rendered, and remove those that are no longer active.
+
+ for (var layerKey in cache) {
+ if (hasOwnProperty.call(cache, layerKey)) {
+
+ var layer = this.getTextLayer(layerKey),
+ layerCache = cache[layerKey];
+
+ layer.hide();
+
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey];
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+
+ var positions = styleCache[key].positions;
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.active) {
+ if (!position.rendered) {
+ layer.append(position.element);
+ position.rendered = true;
+ }
+ } else {
+ positions.splice(i--, 1);
+ if (position.rendered) {
+ position.element.detach();
+ }
+ }
+ }
+
+ if (positions.length == 0) {
+ delete styleCache[key];
+ }
+ }
+ }
+ }
+ }
+
+ layer.show();
+ }
+ }
+ };
+
+ // Creates (if necessary) and returns the text overlay container.
+ //
+ // @param {string} classes String of space-separated CSS classes used to
+ // uniquely identify the text layer.
+ // @return {object} The jQuery-wrapped text-layer div.
+
+ Canvas.prototype.getTextLayer = function(classes) {
+
+ var layer = this.text[classes];
+
+ // Create the text layer if it doesn't exist
+
+ if (layer == null) {
+
+ // Create the text layer container, if it doesn't exist
+
+ if (this.textContainer == null) {
+ this.textContainer = $("<div class='flot-text'></div>")
+ .css({
+ position: "absolute",
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ 'font-size': "smaller",
+ color: "#545454"
+ })
+ .insertAfter(this.element);
+ }
+
+ layer = this.text[classes] = $("<div></div>")
+ .addClass(classes)
+ .css({
+ position: "absolute",
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0
+ })
+ .appendTo(this.textContainer);
+ }
+
+ return layer;
+ };
+
+ // Creates (if necessary) and returns a text info object.
+ //
+ // The object looks like this:
+ //
+ // {
+ // width: Width of the text's wrapper div.
+ // height: Height of the text's wrapper div.
+ // element: The jQuery-wrapped HTML div containing the text.
+ // positions: Array of positions at which this text is drawn.
+ // }
+ //
+ // The positions array contains objects that look like this:
+ //
+ // {
+ // active: Flag indicating whether the text should be visible.
+ // rendered: Flag indicating whether the text is currently visible.
+ // element: The jQuery-wrapped HTML div containing the text.
+ // x: X coordinate at which to draw the text.
+ // y: Y coordinate at which to draw the text.
+ // }
+ //
+ // Each position after the first receives a clone of the original element.
+ //
+ // The idea is that that the width, height, and general 'identity' of the
+ // text is constant no matter where it is placed; the placements are a
+ // secondary property.
+ //
+ // Canvas maintains a cache of recently-used text info objects; getTextInfo
+ // either returns the cached element or creates a new entry.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {string} text Text string to retrieve info for.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+ // @param {number=} width Maximum width of the text before it wraps.
+ // @return {object} a text info object.
+
+ Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
+
+ var textStyle, layerCache, styleCache, info;
+
+ // Cast the value to a string, in case we were given a number or such
+
+ text = "" + text;
+
+ // If the font is a font-spec object, generate a CSS font definition
+
+ if (typeof font === "object") {
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
+ } else {
+ textStyle = font;
+ }
+
+ // Retrieve (or create) the cache for the text's layer and styles
+
+ layerCache = this._textCache[layer];
+
+ if (layerCache == null) {
+ layerCache = this._textCache[layer] = {};
+ }
+
+ styleCache = layerCache[textStyle];
+
+ if (styleCache == null) {
+ styleCache = layerCache[textStyle] = {};
+ }
+
+ info = styleCache[text];
+
+ // If we can't find a matching element in our cache, create a new one
+
+ if (info == null) {
+
+ var element = $("<div></div>").html(text)
+ .css({
+ position: "absolute",
+ 'max-width': width,
+ top: -9999
+ })
+ .appendTo(this.getTextLayer(layer));
+
+ if (typeof font === "object") {
+ element.css({
+ font: textStyle,
+ color: font.color
+ });
+ } else if (typeof font === "string") {
+ element.addClass(font);
+ }
+
+ info = styleCache[text] = {
+ width: element.outerWidth(true),
+ height: element.outerHeight(true),
+ element: element,
+ positions: []
+ };
+
+ element.detach();
+ }
+
+ return info;
+ };
+
+ // Adds a text string to the canvas text overlay.
+ //
+ // The text isn't drawn immediately; it is marked as rendering, which will
+ // result in its addition to the canvas on the next render pass.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {number} x X coordinate at which to draw the text.
+ // @param {number} y Y coordinate at which to draw the text.
+ // @param {string} text Text string to draw.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+ // @param {number=} width Maximum width of the text before it wraps.
+ // @param {string=} halign Horizontal alignment of the text; either "left",
+ // "center" or "right".
+ // @param {string=} valign Vertical alignment of the text; either "top",
+ // "middle" or "bottom".
+
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
+
+ var info = this.getTextInfo(layer, text, font, angle, width),
+ positions = info.positions;
+
+ // Tweak the div's position to match the text's alignment
+
+ if (halign == "center") {
+ x -= info.width / 2;
+ } else if (halign == "right") {
+ x -= info.width;
+ }
+
+ if (valign == "middle") {
+ y -= info.height / 2;
+ } else if (valign == "bottom") {
+ y -= info.height;
+ }
+
+ // Determine whether this text already exists at this position.
+ // If so, mark it for inclusion in the next render pass.
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = true;
+ return;
+ }
+ }
+
+ // If the text doesn't exist at this position, create a new entry
+
+ // For the very first position we'll re-use the original element,
+ // while for subsequent ones we'll clone it.
+
+ position = {
+ active: true,
+ rendered: false,
+ element: positions.length ? info.element.clone() : info.element,
+ x: x,
+ y: y
+ }
+
+ positions.push(position);
+
+ // Move the element to its final position within the container
+
+ position.element.css({
+ top: Math.round(y),
+ left: Math.round(x),
+ 'text-align': halign // In case the text wraps
+ });
+ };
+
+ // Removes one or more text strings from the canvas text overlay.
+ //
+ // If no parameters are given, all text within the layer is removed.
+ //
+ // Note that the text is not immediately removed; it is simply marked as
+ // inactive, which will result in its removal on the next render pass.
+ // This avoids the performance penalty for 'clear and redraw' behavior,
+ // where we potentially get rid of all text on a layer, but will likely
+ // add back most or all of it later, as when redrawing axes, for example.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {number=} x X coordinate of the text.
+ // @param {number=} y Y coordinate of the text.
+ // @param {string=} text Text string to remove.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which the text is rotated, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+
+ Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
+ if (text == null) {
+ var layerCache = this._textCache[layer];
+ if (layerCache != null) {
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey];
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+ var positions = styleCache[key].positions;
+ for (var i = 0, position; position = positions[i]; i++) {
+ position.active = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ var positions = this.getTextInfo(layer, text, font, angle).positions;
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = false;
+ }
+ }
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // The top-level container for the entire plot.
+
+ function Plot(placeholder, data_, options_, plugins) {
+ // data is on the form:
+ // [ series1, series2 ... ]
+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
+
+ var series = [],
+ options = {
+ // the color theme used for graphs
+ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
+ legend: {
+ show: true,
+ noColumns: 1, // number of colums in legend table
+ labelFormatter: null, // fn: string -> string
+ labelBoxBorderColor: "#ccc", // border color for the little label boxes
+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
+ position: "ne", // position of default legend container within plot
+ margin: 5, // distance from grid edge to default legend container within plot
+ backgroundColor: null, // null means auto-detect
+ backgroundOpacity: 0.85, // set to 0 to avoid background
+ sorted: null // default to no legend sorting
+ },
+ xaxis: {
+ show: null, // null = auto-detect, true = always, false = never
+ position: "bottom", // or "top"
+ mode: null, // null or "time"
+ font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
+ color: null, // base color, labels, ticks
+ tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
+ transform: null, // null or f: number -> number to transform axis
+ inverseTransform: null, // if transform is set, this should be the inverse function
+ min: null, // min. value to show, null means set automatically
+ max: null, // max. value to show, null means set automatically
+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
+ tickFormatter: null, // fn: number -> string
+ labelWidth: null, // size of tick labels in pixels
+ labelHeight: null,
+ reserveSpace: null, // whether to reserve space even if axis isn't shown
+ tickLength: null, // size in pixels of ticks, or "full" for whole line
+ alignTicksWithAxis: null, // axis number or null for no sync
+ tickDecimals: null, // no. of decimals, null means auto
+ tickSize: null, // number or [number, "unit"]
+ minTickSize: null // number or [number, "unit"]
+ },
+ yaxis: {
+ autoscaleMargin: 0.02,
+ position: "left" // or "right"
+ },
+ xaxes: [],
+ yaxes: [],
+ series: {
+ points: {
+ show: false,
+ radius: 3,
+ lineWidth: 2, // in pixels
+ fill: true,
+ fillColor: "#ffffff",
+ symbol: "circle" // or callback
+ },
+ lines: {
+ // we don't put in show: false so we can see
+ // whether lines were actively disabled
+ lineWidth: 2, // in pixels
+ fill: false,
+ fillColor: null,
+ steps: false
+ // Omit 'zero', so we can later default its value to
+ // match that of the 'fill' option.
+ },
+ bars: {
+ show: false,
+ lineWidth: 2, // in pixels
+ barWidth: 1, // in units of the x axis
+ fill: true,
+ fillColor: null,
+ align: "left", // "left", "right", or "center"
+ horizontal: false,
+ zero: true
+ },
+ shadowSize: 3,
+ highlightColor: null
+ },
+ grid: {
+ show: true,
+ aboveData: false,
+ color: "#545454", // primary color used for outline and labels
+ backgroundColor: null, // null for transparent, else color
+ borderColor: null, // set if different from the grid color
+ tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
+ margin: 0, // distance from the canvas edge to the grid
+ labelMargin: 5, // in pixels
+ axisMargin: 8, // in pixels
+ borderWidth: 2, // in pixels
+ minBorderMargin: null, // in pixels, null means taken from points radius
+ markings: null, // array of ranges or fn: axes -> array of ranges
+ markingsColor: "#f4f4f4",
+ markingsLineWidth: 2,
+ // interactive stuff
+ clickable: false,
+ hoverable: false,
+ autoHighlight: true, // highlight in case mouse is near
+ mouseActiveRadius: 10 // how far the mouse can be away to activate an item
+ },
+ interaction: {
+ redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
+ },
+ hooks: {}
+ },
+ surface = null, // the canvas for the plot itself
+ overlay = null, // canvas for interactive stuff on top of plot
+ eventHolder = null, // jQuery object that events should be bound to
+ ctx = null, octx = null,
+ xaxes = [], yaxes = [],
+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
+ plotWidth = 0, plotHeight = 0,
+ hooks = {
+ processOptions: [],
+ processRawData: [],
+ processDatapoints: [],
+ processOffset: [],
+ drawBackground: [],
+ drawSeries: [],
+ draw: [],
+ bindEvents: [],
+ drawOverlay: [],
+ shutdown: []
+ },
+ plot = this;
+
+ // public functions
+ plot.setData = setData;
+ plot.setupGrid = setupGrid;
+ plot.draw = draw;
+ plot.getPlaceholder = function() { return placeholder; };
+ plot.getCanvas = function() { return surface.element; };
+ plot.getPlotOffset = function() { return plotOffset; };
+ plot.width = function () { return plotWidth; };
+ plot.height = function () { return plotHeight; };
+ plot.offset = function () {
+ var o = eventHolder.offset();
+ o.left += plotOffset.left;
+ o.top += plotOffset.top;
+ return o;
+ };
+ plot.getData = function () { return series; };
+ plot.getAxes = function () {
+ var res = {}, i;
+ $.each(xaxes.concat(yaxes), function (_, axis) {
+ if (axis)
+ res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
+ });
+ return res;
+ };
+ plot.getXAxes = function () { return xaxes; };
+ plot.getYAxes = function () { return yaxes; };
+ plot.c2p = canvasToAxisCoords;
+ plot.p2c = axisToCanvasCoords;
+ plot.getOptions = function () { return options; };
+ plot.highlight = highlight;
+ plot.unhighlight = unhighlight;
+ plot.triggerRedrawOverlay = triggerRedrawOverlay;
+ plot.pointOffset = function(point) {
+ return {
+ left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
+ top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
+ };
+ };
+ plot.shutdown = shutdown;
+ plot.resize = function () {
+ var width = placeholder.width(),
+ height = placeholder.height();
+ surface.resize(width, height);
+ overlay.resize(width, height);
+ };
+
+ // public attributes
+ plot.hooks = hooks;
+
+ // initialize
+ initPlugins(plot);
+ parseOptions(options_);
+ setupCanvases();
+ setData(data_);
+ setupGrid();
+ draw();
+ bindEvents();
+
+
+ function executeHooks(hook, args) {
+ args = [plot].concat(args);
+ for (var i = 0; i < hook.length; ++i)
+ hook[i].apply(this, args);
+ }
+
+ function initPlugins() {
+
+ // References to key classes, allowing plugins to modify them
+
+ var classes = {
+ Canvas: Canvas
+ };
+
+ for (var i = 0; i < plugins.length; ++i) {
+ var p = plugins[i];
+ p.init(plot, classes);
+ if (p.options)
+ $.extend(true, options, p.options);
+ }
+ }
+
+ function parseOptions(opts) {
+
+ $.extend(true, options, opts);
+
+ // $.extend merges arrays, rather than replacing them. When less
+ // colors are provided than the size of the default palette, we
+ // end up with those colors plus the remaining defaults, which is
+ // not expected behavior; avoid it by replacing them here.
+
+ if (opts && opts.colors) {
+ options.colors = opts.colors;
+ }
+
+ if (options.xaxis.color == null)
+ options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+ if (options.yaxis.color == null)
+ options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+
+ if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
+ options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
+ if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
+ options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
+
+ if (options.grid.borderColor == null)
+ options.grid.borderColor = options.grid.color;
+ if (options.grid.tickColor == null)
+ options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+
+ // Fill in defaults for axis options, including any unspecified
+ // font-spec fields, if a font-spec was provided.
+
+ // If no x/y axis options were provided, create one of each anyway,
+ // since the rest of the code assumes that they exist.
+
+ var i, axisOptions, axisCount,
+ fontDefaults = {
+ style: placeholder.css("font-style"),
+ size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)),
+ variant: placeholder.css("font-variant"),
+ weight: placeholder.css("font-weight"),
+ family: placeholder.css("font-family")
+ };
+
+ fontDefaults.lineHeight = fontDefaults.size * 1.15;
+
+ axisCount = options.xaxes.length || 1;
+ for (i = 0; i < axisCount; ++i) {
+
+ axisOptions = options.xaxes[i];
+ if (axisOptions && !axisOptions.tickColor) {
+ axisOptions.tickColor = axisOptions.color;
+ }
+
+ axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
+ options.xaxes[i] = axisOptions;
+
+ if (axisOptions.font) {
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
+ if (!axisOptions.font.color) {
+ axisOptions.font.color = axisOptions.color;
+ }
+ }
+ }
+
+ axisCount = options.yaxes.length || 1;
+ for (i = 0; i < axisCount; ++i) {
+
+ axisOptions = options.yaxes[i];
+ if (axisOptions && !axisOptions.tickColor) {
+ axisOptions.tickColor = axisOptions.color;
+ }
+
+ axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
+ options.yaxes[i] = axisOptions;
+
+ if (axisOptions.font) {
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
+ if (!axisOptions.font.color) {
+ axisOptions.font.color = axisOptions.color;
+ }
+ }
+ }
+
+ // backwards compatibility, to be removed in future
+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
+ options.xaxis.ticks = options.xaxis.noTicks;
+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
+ options.yaxis.ticks = options.yaxis.noTicks;
+ if (options.x2axis) {
+ options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
+ options.xaxes[1].position = "top";
+ }
+ if (options.y2axis) {
+ options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
+ options.yaxes[1].position = "right";
+ }
+ if (options.grid.coloredAreas)
+ options.grid.markings = options.grid.coloredAreas;
+ if (options.grid.coloredAreasColor)
+ options.grid.markingsColor = options.grid.coloredAreasColor;
+ if (options.lines)
+ $.extend(true, options.series.lines, options.lines);
+ if (options.points)
+ $.extend(true, options.series.points, options.points);
+ if (options.bars)
+ $.extend(true, options.series.bars, options.bars);
+ if (options.shadowSize != null)
+ options.series.shadowSize = options.shadowSize;
+ if (options.highlightColor != null)
+ options.series.highlightColor = options.highlightColor;
+
+ // save options on axes for future reference
+ for (i = 0; i < options.xaxes.length; ++i)
+ getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
+ for (i = 0; i < options.yaxes.length; ++i)
+ getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
+
+ // add hooks from options
+ for (var n in hooks)
+ if (options.hooks[n] && options.hooks[n].length)
+ hooks[n] = hooks[n].concat(options.hooks[n]);
+
+ executeHooks(hooks.processOptions, [options]);
+ }
+
+ function setData(d) {
+ series = parseData(d);
+ fillInSeriesOptions();
+ processData();
+ }
+
+ function parseData(d) {
+ var res = [];
+ for (var i = 0; i < d.length; ++i) {
+ var s = $.extend(true, {}, options.series);
+
+ if (d[i].data != null) {
+ s.data = d[i].data; // move the data instead of deep-copy
+ delete d[i].data;
+
+ $.extend(true, s, d[i]);
+
+ d[i].data = s.data;
+ }
+ else
+ s.data = d[i];
+ res.push(s);
+ }
+
+ return res;
+ }
+
+ function axisNumber(obj, coord) {
+ var a = obj[coord + "axis"];
+ if (typeof a == "object") // if we got a real axis, extract number
+ a = a.n;
+ if (typeof a != "number")
+ a = 1; // default to first axis
+ return a;
+ }
+
+ function allAxes() {
+ // return flat array without annoying null entries
+ return $.grep(xaxes.concat(yaxes), function (a) { return a; });
+ }
+
+ function canvasToAxisCoords(pos) {
+ // return an object with x/y corresponding to all used axes
+ var res = {}, i, axis;
+ for (i = 0; i < xaxes.length; ++i) {
+ axis = xaxes[i];
+ if (axis && axis.used)
+ res["x" + axis.n] = axis.c2p(pos.left);
+ }
+
+ for (i = 0; i < yaxes.length; ++i) {
+ axis = yaxes[i];
+ if (axis && axis.used)
+ res["y" + axis.n] = axis.c2p(pos.top);
+ }
+
+ if (res.x1 !== undefined)
+ res.x = res.x1;
+ if (res.y1 !== undefined)
+ res.y = res.y1;
+
+ return res;
+ }
+
+ function axisToCanvasCoords(pos) {
+ // get canvas coords from the first pair of x/y found in pos
+ var res = {}, i, axis, key;
+
+ for (i = 0; i < xaxes.length; ++i) {
+ axis = xaxes[i];
+ if (axis && axis.used) {
+ key = "x" + axis.n;
+ if (pos[key] == null && axis.n == 1)
+ key = "x";
+
+ if (pos[key] != null) {
+ res.left = axis.p2c(pos[key]);
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < yaxes.length; ++i) {
+ axis = yaxes[i];
+ if (axis && axis.used) {
+ key = "y" + axis.n;
+ if (pos[key] == null && axis.n == 1)
+ key = "y";
+
+ if (pos[key] != null) {
+ res.top = axis.p2c(pos[key]);
+ break;
+ }
+ }
+ }
+
+ return res;
+ }
+
+ function getOrCreateAxis(axes, number) {
+ if (!axes[number - 1])
+ axes[number - 1] = {
+ n: number, // save the number for future reference
+ direction: axes == xaxes ? "x" : "y",
+ options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
+ };
+
+ return axes[number - 1];
+ }
+
+ function fillInSeriesOptions() {
+
+ var neededColors = series.length, maxIndex = -1, i;
+
+ // Subtract the number of series that already have fixed colors or
+ // color indexes from the number that we still need to generate.
+
+ for (i = 0; i < series.length; ++i) {
+ var sc = series[i].color;
+ if (sc != null) {
+ neededColors--;
+ if (typeof sc == "number" && sc > maxIndex) {
+ maxIndex = sc;
+ }
+ }
+ }
+
+ // If any of the series have fixed color indexes, then we need to
+ // generate at least as many colors as the highest index.
+
+ if (neededColors <= maxIndex) {
+ neededColors = maxIndex + 1;
+ }
+
+ // Generate all the colors, using first the option colors and then
+ // variations on those colors once they're exhausted.
+
+ var c, colors = [], colorPool = options.colors,
+ colorPoolSize = colorPool.length, variation = 0;
+
+ for (i = 0; i < neededColors; i++) {
+
+ c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
+
+ // Each time we exhaust the colors in the pool we adjust
+ // a scaling factor used to produce more variations on
+ // those colors. The factor alternates negative/positive
+ // to produce lighter/darker colors.
+
+ // Reset the variation after every few cycles, or else
+ // it will end up producing only white or black colors.
+
+ if (i % colorPoolSize == 0 && i) {
+ if (variation >= 0) {
+ if (variation < 0.5) {
+ variation = -variation - 0.2;
+ } else variation = 0;
+ } else variation = -variation;
+ }
+
+ colors[i] = c.scale('rgb', 1 + variation);
+ }
+
+ // Finalize the series options, filling in their colors
+
+ var colori = 0, s;
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ // assign colors
+ if (s.color == null) {
+ s.color = colors[colori].toString();
+ ++colori;
+ }
+ else if (typeof s.color == "number")
+ s.color = colors[s.color].toString();
+
+ // turn on lines automatically in case nothing is set
+ if (s.lines.show == null) {
+ var v, show = true;
+ for (v in s)
+ if (s[v] && s[v].show) {
+ show = false;
+ break;
+ }
+ if (show)
+ s.lines.show = true;
+ }
+
+ // If nothing was provided for lines.zero, default it to match
+ // lines.fill, since areas by default should extend to zero.
+
+ if (s.lines.zero == null) {
+ s.lines.zero = !!s.lines.fill;
+ }
+
+ // setup axes
+ s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
+ s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
+ }
+ }
+
+ function processData() {
+ var topSentry = Number.POSITIVE_INFINITY,
+ bottomSentry = Number.NEGATIVE_INFINITY,
+ fakeInfinity = Number.MAX_VALUE,
+ i, j, k, m, length,
+ s, points, ps, x, y, axis, val, f, p,
+ data, format;
+
+ function updateAxis(axis, min, max) {
+ if (min < axis.datamin && min != -fakeInfinity)
+ axis.datamin = min;
+ if (max > axis.datamax && max != fakeInfinity)
+ axis.datamax = max;
+ }
+
+ $.each(allAxes(), function (_, axis) {
+ // init axis
+ axis.datamin = topSentry;
+ axis.datamax = bottomSentry;
+ axis.used = false;
+ });
+
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ s.datapoints = { points: [] };
+
+ executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
+ }
+
+ // first pass: clean and copy data
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ data = s.data;
+ format = s.datapoints.format;
+
+ if (!format) {
+ format = [];
+ // find out how to copy
+ format.push({ x: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
+ var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
+ format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
+ if (s.bars.horizontal) {
+ delete format[format.length - 1].y;
+ format[format.length - 1].x = true;
+ }
+ }
+
+ s.datapoints.format = format;
+ }
+
+ if (s.datapoints.pointsize != null)
+ continue; // already filled in
+
+ s.datapoints.pointsize = format.length;
+
+ ps = s.datapoints.pointsize;
+ points = s.datapoints.points;
+
+ var insertSteps = s.lines.show && s.lines.steps;
+ s.xaxis.used = s.yaxis.used = true;
+
+ for (j = k = 0; j < data.length; ++j, k += ps) {
+ p = data[j];
+
+ var nullify = p == null;
+ if (!nullify) {
+ for (m = 0; m < ps; ++m) {
+ val = p[m];
+ f = format[m];
+
+ if (f) {
+ if (f.number && val != null) {
+ val = +val; // convert to number
+ if (isNaN(val))
+ val = null;
+ else if (val == Infinity)
+ val = fakeInfinity;
+ else if (val == -Infinity)
+ val = -fakeInfinity;
+ }
+
+ if (val == null) {
+ if (f.required)
+ nullify = true;
+
+ if (f.defaultValue != null)
+ val = f.defaultValue;
+ }
+ }
+
+ points[k + m] = val;
+ }
+ }
+
+ if (nullify) {
+ for (m = 0; m < ps; ++m) {
+ val = points[k + m];
+ if (val != null) {
+ f = format[m];
+ // extract min/max info
+ if (f.autoscale) {
+ if (f.x) {
+ updateAxis(s.xaxis, val, val);
+ }
+ if (f.y) {
+ updateAxis(s.yaxis, val, val);
+ }
+ }
+ }
+ points[k + m] = null;
+ }
+ }
+ else {
+ // a little bit of line specific stuff that
+ // perhaps shouldn't be here, but lacking
+ // better means...
+ if (insertSteps && k > 0
+ && points[k - ps] != null
+ && points[k - ps] != points[k]
+ && points[k - ps + 1] != points[k + 1]) {
+ // copy the point to make room for a middle point
+ for (m = 0; m < ps; ++m)
+ points[k + ps + m] = points[k + m];
+
+ // middle point has same y
+ points[k + 1] = points[k - ps + 1];
+
+ // we've added a point, better reflect that
+ k += ps;
+ }
+ }
+ }
+ }
+
+ // give the hooks a chance to run
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
+ }
+
+ // second pass: find datamax/datamin for auto-scaling
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ points = s.datapoints.points;
+ ps = s.datapoints.pointsize;
+ format = s.datapoints.format;
+
+ var xmin = topSentry, ymin = topSentry,
+ xmax = bottomSentry, ymax = bottomSentry;
+
+ for (j = 0; j < points.length; j += ps) {
+ if (points[j] == null)
+ continue;
+
+ for (m = 0; m < ps; ++m) {
+ val = points[j + m];
+ f = format[m];
+ if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
+ continue;
+
+ if (f.x) {
+ if (val < xmin)
+ xmin = val;
+ if (val > xmax)
+ xmax = val;
+ }
+ if (f.y) {
+ if (val < ymin)
+ ymin = val;
+ if (val > ymax)
+ ymax = val;
+ }
+ }
+ }
+
+ if (s.bars.show) {
+ // make sure we got room for the bar on the dancing floor
+ var delta;
+
+ switch (s.bars.align) {
+ case "left":
+ delta = 0;
+ break;
+ case "right":
+ delta = -s.bars.barWidth;
+ break;
+ case "center":
+ delta = -s.bars.barWidth / 2;
+ break;
+ default:
+ throw new Error("Invalid bar alignment: " + s.bars.align);
+ }
+
+ if (s.bars.horizontal) {
+ ymin += delta;
+ ymax += delta + s.bars.barWidth;
+ }
+ else {
+ xmin += delta;
+ xmax += delta + s.bars.barWidth;
+ }
+ }
+
+ updateAxis(s.xaxis, xmin, xmax);
+ updateAxis(s.yaxis, ymin, ymax);
+ }
+
+ $.each(allAxes(), function (_, axis) {
+ if (axis.datamin == topSentry)
+ axis.datamin = null;
+ if (axis.datamax == bottomSentry)
+ axis.datamax = null;
+ });
+ }
+
+ function setupCanvases() {
+
+ // Make sure the placeholder is clear of everything except canvases
+ // from a previous plot in this container that we'll try to re-use.
+
+ placeholder.css("padding", 0) // padding messes up the positioning
+ .children(":not(.flot-base,.flot-overlay)").remove();
+
+ if (placeholder.css("position") == 'static')
+ placeholder.css("position", "relative"); // for positioning labels and overlay
+
+ surface = new Canvas("flot-base", placeholder);
+ overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
+
+ ctx = surface.context;
+ octx = overlay.context;
+
+ // define which element we're listening for events on
+ eventHolder = $(overlay.element).unbind();
+
+ // If we're re-using a plot object, shut down the old one
+
+ var existing = placeholder.data("plot");
+
+ if (existing) {
+ existing.shutdown();
+ overlay.clear();
+ }
+
+ // save in case we get replotted
+ placeholder.data("plot", plot);
+ }
+
+ function bindEvents() {
+ // bind events
+ if (options.grid.hoverable) {
+ eventHolder.mousemove(onMouseMove);
+
+ // Use bind, rather than .mouseleave, because we officially
+ // still support jQuery 1.2.6, which doesn't define a shortcut
+ // for mouseenter or mouseleave. This was a bug/oversight that
+ // was fixed somewhere around 1.3.x. We can return to using
+ // .mouseleave when we drop support for 1.2.6.
+
+ eventHolder.bind("mouseleave", onMouseLeave);
+ }
+
+ if (options.grid.clickable)
+ eventHolder.click(onClick);
+
+ executeHooks(hooks.bindEvents, [eventHolder]);
+ }
+
+ function shutdown() {
+ if (redrawTimeout)
+ clearTimeout(redrawTimeout);
+
+ eventHolder.unbind("mousemove", onMouseMove);
+ eventHolder.unbind("mouseleave", onMouseLeave);
+ eventHolder.unbind("click", onClick);
+
+ executeHooks(hooks.shutdown, [eventHolder]);
+ }
+
+ function setTransformationHelpers(axis) {
+ // set helper functions on the axis, assumes plot area
+ // has been computed already
+
+ function identity(x) { return x; }
+
+ var s, m, t = axis.options.transform || identity,
+ it = axis.options.inverseTransform;
+
+ // precompute how much the axis is scaling a point
+ // in canvas space
+ if (axis.direction == "x") {
+ s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
+ m = Math.min(t(axis.max), t(axis.min));
+ }
+ else {
+ s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
+ s = -s;
+ m = Math.max(t(axis.max), t(axis.min));
+ }
+
+ // data point to canvas coordinate
+ if (t == identity) // slight optimization
+ axis.p2c = function (p) { return (p - m) * s; };
+ else
+ axis.p2c = function (p) { return (t(p) - m) * s; };
+ // canvas coordinate to data point
+ if (!it)
+ axis.c2p = function (c) { return m + c / s; };
+ else
+ axis.c2p = function (c) { return it(m + c / s); };
+ }
+
+ function measureTickLabels(axis) {
+
+ var opts = axis.options,
+ ticks = axis.ticks || [],
+ labelWidth = opts.labelWidth || 0,
+ labelHeight = opts.labelHeight || 0,
+ maxWidth = labelWidth || axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null;
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
+ font = opts.font || "flot-tick-label tickLabel";
+
+ for (var i = 0; i < ticks.length; ++i) {
+
+ var t = ticks[i];
+
+ if (!t.label)
+ continue;
+
+ var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
+
+ labelWidth = Math.max(labelWidth, info.width);
+ labelHeight = Math.max(labelHeight, info.height);
+ }
+
+ axis.labelWidth = opts.labelWidth || labelWidth;
+ axis.labelHeight = opts.labelHeight || labelHeight;
+ }
+
+ function allocateAxisBoxFirstPhase(axis) {
+ // find the bounding box of the axis by looking at label
+ // widths/heights and ticks, make room by diminishing the
+ // plotOffset; this first phase only looks at one
+ // dimension per axis, the other dimension depends on the
+ // other axes so will have to wait
+
+ var lw = axis.labelWidth,
+ lh = axis.labelHeight,
+ pos = axis.options.position,
+ tickLength = axis.options.tickLength,
+ axisMargin = options.grid.axisMargin,
+ padding = options.grid.labelMargin,
+ all = axis.direction == "x" ? xaxes : yaxes,
+ index, innermost;
+
+ // determine axis margin
+ var samePosition = $.grep(all, function (a) {
+ return a && a.options.position == pos && a.reserveSpace;
+ });
+ if ($.inArray(axis, samePosition) == samePosition.length - 1)
+ axisMargin = 0; // outermost
+
+ // determine tick length - if we're innermost, we can use "full"
+ if (tickLength == null) {
+ var sameDirection = $.grep(all, function (a) {
+ return a && a.reserveSpace;
+ });
+
+ innermost = $.inArray(axis, sameDirection) == 0;
+ if (innermost)
+ tickLength = "full";
+ else
+ tickLength = 5;
+ }
+
+ if (!isNaN(+tickLength))
+ padding += +tickLength;
+
+ // compute box
+ if (axis.direction == "x") {
+ lh += padding;
+
+ if (pos == "bottom") {
+ plotOffset.bottom += lh + axisMargin;
+ axis.box = { top: surface.height - plotOffset.bottom, height: lh };
+ }
+ else {
+ axis.box = { top: plotOffset.top + axisMargin, height: lh };
+ plotOffset.top += lh + axisMargin;
+ }
+ }
+ else {
+ lw += padding;
+
+ if (pos == "left") {
+ axis.box = { left: plotOffset.left + axisMargin, width: lw };
+ plotOffset.left += lw + axisMargin;
+ }
+ else {
+ plotOffset.right += lw + axisMargin;
+ axis.box = { left: surface.width - plotOffset.right, width: lw };
+ }
+ }
+
+ // save for future reference
+ axis.position = pos;
+ axis.tickLength = tickLength;
+ axis.box.padding = padding;
+ axis.innermost = innermost;
+ }
+
+ function allocateAxisBoxSecondPhase(axis) {
+ // now that all axis boxes have been placed in one
+ // dimension, we can set the remaining dimension coordinates
+ if (axis.direction == "x") {
+ axis.box.left = plotOffset.left - axis.labelWidth / 2;
+ axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
+ }
+ else {
+ axis.box.top = plotOffset.top - axis.labelHeight / 2;
+ axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
+ }
+ }
+
+ function adjustLayoutForThingsStickingOut() {
+ // possibly adjust plot offset to ensure everything stays
+ // inside the canvas and isn't clipped off
+
+ var minMargin = options.grid.minBorderMargin,
+ margins = { x: 0, y: 0 }, i, axis;
+
+ // check stuff from the plot (FIXME: this should just read
+ // a value from the series, otherwise it's impossible to
+ // customize)
+ if (minMargin == null) {
+ minMargin = 0;
+ for (i = 0; i < series.length; ++i)
+ minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
+ }
+
+ margins.x = margins.y = Math.ceil(minMargin);
+
+ // check axis labels, note we don't check the actual
+ // labels but instead use the overall width/height to not
+ // jump as much around with replots
+ $.each(allAxes(), function (_, axis) {
+ var dir = axis.direction;
+ if (axis.reserveSpace)
+ margins[dir] = Math.ceil(Math.max(margins[dir], (dir == "x" ? axis.labelWidth : axis.labelHeight) / 2));
+ });
+
+ plotOffset.left = Math.max(margins.x, plotOffset.left);
+ plotOffset.right = Math.max(margins.x, plotOffset.right);
+ plotOffset.top = Math.max(margins.y, plotOffset.top);
+ plotOffset.bottom = Math.max(margins.y, plotOffset.bottom);
+ }
+
+ function setupGrid() {
+ var i, axes = allAxes(), showGrid = options.grid.show;
+
+ // Initialize the plot's offset from the edge of the canvas
+
+ for (var a in plotOffset) {
+ var margin = options.grid.margin || 0;
+ plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
+ }
+
+ executeHooks(hooks.processOffset, [plotOffset]);
+
+ // If the grid is visible, add its border width to the offset
+
+ for (var a in plotOffset) {
+ if(typeof(options.grid.borderWidth) == "object") {
+ plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
+ }
+ else {
+ plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
+ }
+ }
+
+ // init axes
+ $.each(axes, function (_, axis) {
+ axis.show = axis.options.show;
+ if (axis.show == null)
+ axis.show = axis.used; // by default an axis is visible if it's got data
+
+ axis.reserveSpace = axis.show || axis.options.reserveSpace;
+
+ setRange(axis);
+ });
+
+ if (showGrid) {
+
+ var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
+
+ $.each(allocatedAxes, function (_, axis) {
+ // make the ticks
+ setupTickGeneration(axis);
+ setTicks(axis);
+ snapRangeToTicks(axis, axis.ticks);
+ // find labelWidth/Height for axis
+ measureTickLabels(axis);
+ });
+
+ // with all dimensions calculated, we can compute the
+ // axis bounding boxes, start from the outside
+ // (reverse order)
+ for (i = allocatedAxes.length - 1; i >= 0; --i)
+ allocateAxisBoxFirstPhase(allocatedAxes[i]);
+
+ // make sure we've got enough space for things that
+ // might stick out
+ adjustLayoutForThingsStickingOut();
+
+ $.each(allocatedAxes, function (_, axis) {
+ allocateAxisBoxSecondPhase(axis);
+ });
+ }
+
+ plotWidth = surface.width - plotOffset.left - plotOffset.right;
+ plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
+
+ // now we got the proper plot dimensions, we can compute the scaling
+ $.each(axes, function (_, axis) {
+ setTransformationHelpers(axis);
+ });
+
+ if (showGrid) {
+ drawAxisLabels();
+ }
+
+ insertLegend();
+ }
+
+ function setRange(axis) {
+ var opts = axis.options,
+ min = +(opts.min != null ? opts.min : axis.datamin),
+ max = +(opts.max != null ? opts.max : axis.datamax),
+ delta = max - min;
+
+ if (delta == 0.0) {
+ // degenerate case
+ var widen = max == 0 ? 1 : 0.01;
+
+ if (opts.min == null)
+ min -= widen;
+ // always widen max if we couldn't widen min to ensure we
+ // don't fall into min == max which doesn't work
+ if (opts.max == null || opts.min != null)
+ max += widen;
+ }
+ else {
+ // consider autoscaling
+ var margin = opts.autoscaleMargin;
+ if (margin != null) {
+ if (opts.min == null) {
+ min -= delta * margin;
+ // make sure we don't go below zero if all values
+ // are positive
+ if (min < 0 && axis.datamin != null && axis.datamin >= 0)
+ min = 0;
+ }
+ if (opts.max == null) {
+ max += delta * margin;
+ if (max > 0 && axis.datamax != null && axis.datamax <= 0)
+ max = 0;
+ }
+ }
+ }
+ axis.min = min;
+ axis.max = max;
+ }
+
+ function setupTickGeneration(axis) {
+ var opts = axis.options;
+
+ // estimate number of ticks
+ var noTicks;
+ if (typeof opts.ticks == "number" && opts.ticks > 0)
+ noTicks = opts.ticks;
+ else
+ // heuristic based on the model a*sqrt(x) fitted to
+ // some data points that seemed reasonable
+ noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
+
+ var delta = (axis.max - axis.min) / noTicks,
+ dec = -Math.floor(Math.log(delta) / Math.LN10),
+ maxDec = opts.tickDecimals;
+
+ if (maxDec != null && dec > maxDec) {
+ dec = maxDec;
+ }
+
+ var magn = Math.pow(10, -dec),
+ norm = delta / magn, // norm is between 1.0 and 10.0
+ size;
+
+ if (norm < 1.5) {
+ size = 1;
+ } else if (norm < 3) {
+ size = 2;
+ // special case for 2.5, requires an extra decimal
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
+ size = 2.5;
+ ++dec;
+ }
+ } else if (norm < 7.5) {
+ size = 5;
+ } else {
+ size = 10;
+ }
+
+ size *= magn;
+
+ if (opts.minTickSize != null && size < opts.minTickSize) {
+ size = opts.minTickSize;
+ }
+
+ axis.delta = delta;
+ axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
+ axis.tickSize = opts.tickSize || size;
+
+ // Time mode was moved to a plug-in in 0.8, but since so many people use this
+ // we'll add an especially friendly make sure they remembered to include it.
+
+ if (opts.mode == "time" && !axis.tickGenerator) {
+ throw new Error("Time mode requires the flot.time plugin.");
+ }
+
+ // Flot supports base-10 axes; any other mode else is handled by a plug-in,
+ // like flot.time.js.
+
+ if (!axis.tickGenerator) {
+
+ axis.tickGenerator = function (axis) {
+
+ var ticks = [],
+ start = floorInBase(axis.min, axis.tickSize),
+ i = 0,
+ v = Number.NaN,
+ prev;
+
+ do {
+ prev = v;
+ v = start + i * axis.tickSize;
+ ticks.push(v);
+ ++i;
+ } while (v < axis.max && v != prev);
+ return ticks;
+ };
+
+ axis.tickFormatter = function (value, axis) {
+
+ var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
+ var formatted = "" + Math.round(value * factor) / factor;
+
+ // If tickDecimals was specified, ensure that we have exactly that
+ // much precision; otherwise default to the value's own precision.
+
+ if (axis.tickDecimals != null) {
+ var decimal = formatted.indexOf(".");
+ var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
+ if (precision < axis.tickDecimals) {
+ return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
+ }
+ }
+
+ return formatted;
+ };
+ }
+
+ if ($.isFunction(opts.tickFormatter))
+ axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
+
+ if (opts.alignTicksWithAxis != null) {
+ var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
+ if (otherAxis && otherAxis.used && otherAxis != axis) {
+ // consider snapping min/max to outermost nice ticks
+ var niceTicks = axis.tickGenerator(axis);
+ if (niceTicks.length > 0) {
+ if (opts.min == null)
+ axis.min = Math.min(axis.min, niceTicks[0]);
+ if (opts.max == null && niceTicks.length > 1)
+ axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
+ }
+
+ axis.tickGenerator = function (axis) {
+ // copy ticks, scaled to this axis
+ var ticks = [], v, i;
+ for (i = 0; i < otherAxis.ticks.length; ++i) {
+ v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
+ v = axis.min + v * (axis.max - axis.min);
+ ticks.push(v);
+ }
+ return ticks;
+ };
+
+ // we might need an extra decimal since forced
+ // ticks don't necessarily fit naturally
+ if (!axis.mode && opts.tickDecimals == null) {
+ var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
+ ts = axis.tickGenerator(axis);
+
+ // only proceed if the tick interval rounded
+ // with an extra decimal doesn't give us a
+ // zero at end
+ if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
+ axis.tickDecimals = extraDec;
+ }
+ }
+ }
+ }
+
+ function setTicks(axis) {
+ var oticks = axis.options.ticks, ticks = [];
+ if (oticks == null || (typeof oticks == "number" && oticks > 0))
+ ticks = axis.tickGenerator(axis);
+ else if (oticks) {
+ if ($.isFunction(oticks))
+ // generate the ticks
+ ticks = oticks(axis);
+ else
+ ticks = oticks;
+ }
+
+ // clean up/labelify the supplied ticks, copy them over
+ var i, v;
+ axis.ticks = [];
+ for (i = 0; i < ticks.length; ++i) {
+ var label = null;
+ var t = ticks[i];
+ if (typeof t == "object") {
+ v = +t[0];
+ if (t.length > 1)
+ label = t[1];
+ }
+ else
+ v = +t;
+ if (label == null)
+ label = axis.tickFormatter(v, axis);
+ if (!isNaN(v))
+ axis.ticks.push({ v: v, label: label });
+ }
+ }
+
+ function snapRangeToTicks(axis, ticks) {
+ if (axis.options.autoscaleMargin && ticks.length > 0) {
+ // snap to ticks
+ if (axis.options.min == null)
+ axis.min = Math.min(axis.min, ticks[0].v);
+ if (axis.options.max == null && ticks.length > 1)
+ axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
+ }
+ }
+
+ function draw() {
+
+ surface.clear();
+
+ executeHooks(hooks.drawBackground, [ctx]);
+
+ var grid = options.grid;
+
+ // draw background, if any
+ if (grid.show && grid.backgroundColor)
+ drawBackground();
+
+ if (grid.show && !grid.aboveData) {
+ drawGrid();
+ }
+
+ for (var i = 0; i < series.length; ++i) {
+ executeHooks(hooks.drawSeries, [ctx, series[i]]);
+ drawSeries(series[i]);
+ }
+
+ executeHooks(hooks.draw, [ctx]);
+
+ if (grid.show && grid.aboveData) {
+ drawGrid();
+ }
+
+ surface.render();
+
+ // A draw implies that either the axes or data have changed, so we
+ // should probably update the overlay highlights as well.
+
+ triggerRedrawOverlay();
+ }
+
+ function extractRange(ranges, coord) {
+ var axis, from, to, key, axes = allAxes();
+
+ for (var i = 0; i < axes.length; ++i) {
+ axis = axes[i];
+ if (axis.direction == coord) {
+ key = coord + axis.n + "axis";
+ if (!ranges[key] && axis.n == 1)
+ key = coord + "axis"; // support x1axis as xaxis
+ if (ranges[key]) {
+ from = ranges[key].from;
+ to = ranges[key].to;
+ break;
+ }
+ }
+ }
+
+ // backwards-compat stuff - to be removed in future
+ if (!ranges[key]) {
+ axis = coord == "x" ? xaxes[0] : yaxes[0];
+ from = ranges[coord + "1"];
+ to = ranges[coord + "2"];
+ }
+
+ // auto-reverse as an added bonus
+ if (from != null && to != null && from > to) {
+ var tmp = from;
+ from = to;
+ to = tmp;
+ }
+
+ return { from: from, to: to, axis: axis };
+ }
+
+ function drawBackground() {
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
+ ctx.restore();
+ }
+
+ function drawGrid() {
+ var i, axes, bw, bc;
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // draw markings
+ var markings = options.grid.markings;
+ if (markings) {
+ if ($.isFunction(markings)) {
+ axes = plot.getAxes();
+ // xmin etc. is backwards compatibility, to be
+ // removed in the future
+ axes.xmin = axes.xaxis.min;
+ axes.xmax = axes.xaxis.max;
+ axes.ymin = axes.yaxis.min;
+ axes.ymax = axes.yaxis.max;
+
+ markings = markings(axes);
+ }
+
+ for (i = 0; i < markings.length; ++i) {
+ var m = markings[i],
+ xrange = extractRange(m, "x"),
+ yrange = extractRange(m, "y");
+
+ // fill in missing
+ if (xrange.from == null)
+ xrange.from = xrange.axis.min;
+ if (xrange.to == null)
+ xrange.to = xrange.axis.max;
+ if (yrange.from == null)
+ yrange.from = yrange.axis.min;
+ if (yrange.to == null)
+ yrange.to = yrange.axis.max;
+
+ // clip
+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
+ continue;
+
+ xrange.from = Math.max(xrange.from, xrange.axis.min);
+ xrange.to = Math.min(xrange.to, xrange.axis.max);
+ yrange.from = Math.max(yrange.from, yrange.axis.min);
+ yrange.to = Math.min(yrange.to, yrange.axis.max);
+
+ if (xrange.from == xrange.to && yrange.from == yrange.to)
+ continue;
+
+ // then draw
+ xrange.from = xrange.axis.p2c(xrange.from);
+ xrange.to = xrange.axis.p2c(xrange.to);
+ yrange.from = yrange.axis.p2c(yrange.from);
+ yrange.to = yrange.axis.p2c(yrange.to);
+
+ if (xrange.from == xrange.to || yrange.from == yrange.to) {
+ // draw line
+ ctx.beginPath();
+ ctx.strokeStyle = m.color || options.grid.markingsColor;
+ ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
+ ctx.moveTo(xrange.from, yrange.from);
+ ctx.lineTo(xrange.to, yrange.to);
+ ctx.stroke();
+ }
+ else {
+ // fill area
+ ctx.fillStyle = m.color || options.grid.markingsColor;
+ ctx.fillRect(xrange.from, yrange.to,
+ xrange.to - xrange.from,
+ yrange.from - yrange.to);
+ }
+ }
+ }
+
+ // draw the ticks
+ axes = allAxes();
+ bw = options.grid.borderWidth;
+
+ for (var j = 0; j < axes.length; ++j) {
+ var axis = axes[j], box = axis.box,
+ t = axis.tickLength, x, y, xoff, yoff;
+ if (!axis.show || axis.ticks.length == 0)
+ continue;
+
+ ctx.lineWidth = 1;
+
+ // find the edges
+ if (axis.direction == "x") {
+ x = 0;
+ if (t == "full")
+ y = (axis.position == "top" ? 0 : plotHeight);
+ else
+ y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
+ }
+ else {
+ y = 0;
+ if (t == "full")
+ x = (axis.position == "left" ? 0 : plotWidth);
+ else
+ x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
+ }
+
+ // draw tick bar
+ if (!axis.innermost) {
+ ctx.strokeStyle = axis.options.color;
+ ctx.beginPath();
+ xoff = yoff = 0;
+ if (axis.direction == "x")
+ xoff = plotWidth + 1;
+ else
+ yoff = plotHeight + 1;
+
+ if (ctx.lineWidth == 1) {
+ if (axis.direction == "x") {
+ y = Math.floor(y) + 0.5;
+ } else {
+ x = Math.floor(x) + 0.5;
+ }
+ }
+
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + xoff, y + yoff);
+ ctx.stroke();
+ }
+
+ // draw ticks
+
+ ctx.strokeStyle = axis.options.tickColor;
+
+ ctx.beginPath();
+ for (i = 0; i < axis.ticks.length; ++i) {
+ var v = axis.ticks[i].v;
+
+ xoff = yoff = 0;
+
+ if (isNaN(v) || v < axis.min || v > axis.max
+ // skip those lying on the axes if we got a border
+ || (t == "full"
+ && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
+ && (v == axis.min || v == axis.max)))
+ continue;
+
+ if (axis.direction == "x") {
+ x = axis.p2c(v);
+ yoff = t == "full" ? -plotHeight : t;
+
+ if (axis.position == "top")
+ yoff = -yoff;
+ }
+ else {
+ y = axis.p2c(v);
+ xoff = t == "full" ? -plotWidth : t;
+
+ if (axis.position == "left")
+ xoff = -xoff;
+ }
+
+ if (ctx.lineWidth == 1) {
+ if (axis.direction == "x")
+ x = Math.floor(x) + 0.5;
+ else
+ y = Math.floor(y) + 0.5;
+ }
+
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + xoff, y + yoff);
+ }
+
+ ctx.stroke();
+ }
+
+
+ // draw border
+ if (bw) {
+ // If either borderWidth or borderColor is an object, then draw the border
+ // line by line instead of as one rectangle
+ bc = options.grid.borderColor;
+ if(typeof bw == "object" || typeof bc == "object") {
+ if (typeof bw !== "object") {
+ bw = {top: bw, right: bw, bottom: bw, left: bw};
+ }
+ if (typeof bc !== "object") {
+ bc = {top: bc, right: bc, bottom: bc, left: bc};
+ }
+
+ if (bw.top > 0) {
+ ctx.strokeStyle = bc.top;
+ ctx.lineWidth = bw.top;
+ ctx.beginPath();
+ ctx.moveTo(0 - bw.left, 0 - bw.top/2);
+ ctx.lineTo(plotWidth, 0 - bw.top/2);
+ ctx.stroke();
+ }
+
+ if (bw.right > 0) {
+ ctx.strokeStyle = bc.right;
+ ctx.lineWidth = bw.right;
+ ctx.beginPath();
+ ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
+ ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
+ ctx.stroke();
+ }
+
+ if (bw.bottom > 0) {
+ ctx.strokeStyle = bc.bottom;
+ ctx.lineWidth = bw.bottom;
+ ctx.beginPath();
+ ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
+ ctx.lineTo(0, plotHeight + bw.bottom / 2);
+ ctx.stroke();
+ }
+
+ if (bw.left > 0) {
+ ctx.strokeStyle = bc.left;
+ ctx.lineWidth = bw.left;
+ ctx.beginPath();
+ ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
+ ctx.lineTo(0- bw.left/2, 0);
+ ctx.stroke();
+ }
+ }
+ else {
+ ctx.lineWidth = bw;
+ ctx.strokeStyle = options.grid.borderColor;
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
+ }
+ }
+
+ ctx.restore();
+ }
+
+ function drawAxisLabels() {
+
+ $.each(allAxes(), function (_, axis) {
+ if (!axis.show || axis.ticks.length == 0)
+ return;
+
+ var box = axis.box,
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
+ font = axis.options.font || "flot-tick-label tickLabel",
+ tick, x, y, halign, valign;
+
+ surface.removeText(layer);
+
+ for (var i = 0; i < axis.ticks.length; ++i) {
+
+ tick = axis.ticks[i];
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
+ continue;
+
+ if (axis.direction == "x") {
+ halign = "center";
+ x = plotOffset.left + axis.p2c(tick.v);
+ if (axis.position == "bottom") {
+ y = box.top + box.padding;
+ } else {
+ y = box.top + box.height - box.padding;
+ valign = "bottom";
+ }
+ } else {
+ valign = "middle";
+ y = plotOffset.top + axis.p2c(tick.v);
+ if (axis.position == "left") {
+ x = box.left + box.width - box.padding;
+ halign = "right";
+ } else {
+ x = box.left + box.padding;
+ }
+ }
+
+ surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
+ }
+ });
+ }
+
+ function drawSeries(series) {
+ if (series.lines.show)
+ drawSeriesLines(series);
+ if (series.bars.show)
+ drawSeriesBars(series);
+ if (series.points.show)
+ drawSeriesPoints(series);
+ }
+
+ function drawSeriesLines(series) {
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ prevx = null, prevy = null;
+
+ ctx.beginPath();
+ for (var i = ps; i < points.length; i += ps) {
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
+ x2 = points[i], y2 = points[i + 1];
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min) {
+ if (y2 < axisy.min)
+ continue; // line segment is outside
+ // compute new intersection point
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min) {
+ if (y1 < axisy.min)
+ continue;
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max) {
+ if (y2 > axisy.max)
+ continue;
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max) {
+ if (y1 > axisy.max)
+ continue;
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (x1 != prevx || y1 != prevy)
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
+
+ prevx = x2;
+ prevy = y2;
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
+ }
+ ctx.stroke();
+ }
+
+ function plotLineArea(datapoints, axisx, axisy) {
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
+ i = 0, top, areaOpen = false,
+ ypos = 1, segmentStart = 0, segmentEnd = 0;
+
+ // we process each segment in two turns, first forward
+ // direction to sketch out top, then once we hit the
+ // end we go backwards to sketch the bottom
+ while (true) {
+ if (ps > 0 && i > points.length + ps)
+ break;
+
+ i += ps; // ps is negative if going backwards
+
+ var x1 = points[i - ps],
+ y1 = points[i - ps + ypos],
+ x2 = points[i], y2 = points[i + ypos];
+
+ if (areaOpen) {
+ if (ps > 0 && x1 != null && x2 == null) {
+ // at turning point
+ segmentEnd = i;
+ ps = -ps;
+ ypos = 2;
+ continue;
+ }
+
+ if (ps < 0 && i == segmentStart + ps) {
+ // done with the reverse sweep
+ ctx.fill();
+ areaOpen = false;
+ ps = -ps;
+ ypos = 1;
+ i = segmentStart = segmentEnd + ps;
+ continue;
+ }
+ }
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip x values
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (!areaOpen) {
+ // open area
+ ctx.beginPath();
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
+ areaOpen = true;
+ }
+
+ // now first check the case where both is outside
+ if (y1 >= axisy.max && y2 >= axisy.max) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
+ continue;
+ }
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
+ continue;
+ }
+
+ // else it's a bit more complicated, there might
+ // be a flat maxed out rectangle first, then a
+ // triangular cutout or reverse; to find these
+ // keep track of the current x values
+ var x1old = x1, x2old = x2;
+
+ // clip the y values, without shortcutting, we
+ // go through all cases in turn
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+ // if the x value was changed we got a rectangle
+ // to fill
+ if (x1 != x1old) {
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
+ // it goes to (x1, y1), but we fill that below
+ }
+
+ // fill triangular section, this sometimes result
+ // in redundant points if (x1, y1) hasn't changed
+ // from previous line to, but we just ignore that
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+
+ // fill the other rectangle if it's there
+ if (x2 != x2old) {
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
+ }
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+ ctx.lineJoin = "round";
+
+ var lw = series.lines.lineWidth,
+ sw = series.shadowSize;
+ // FIXME: consider another form of shadow when filling is turned on
+ if (lw > 0 && sw > 0) {
+ // draw shadow as a thick and thin line with transparency
+ ctx.lineWidth = sw;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ // position shadow at angle from the mid of line
+ var angle = Math.PI/18;
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
+ ctx.lineWidth = sw/2;
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
+ }
+
+ if (lw > 0)
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function drawSeriesPoints(series) {
+ function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ var x = points[i], y = points[i + 1];
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ continue;
+
+ ctx.beginPath();
+ x = axisx.p2c(x);
+ y = axisy.p2c(y) + offset;
+ if (symbol == "circle")
+ ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
+ else
+ symbol(ctx, x, y, radius, shadow);
+ ctx.closePath();
+
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ ctx.fill();
+ }
+ ctx.stroke();
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ var lw = series.points.lineWidth,
+ sw = series.shadowSize,
+ radius = series.points.radius,
+ symbol = series.points.symbol;
+
+ // If the user sets the line width to 0, we change it to a very
+ // small value. A line width of 0 seems to force the default of 1.
+ // Doing the conditional here allows the shadow setting to still be
+ // optional even with a lineWidth of 0.
+
+ if( lw == 0 )
+ lw = 0.0001;
+
+ if (lw > 0 && sw > 0) {
+ // draw shadow in two steps
+ var w = sw / 2;
+ ctx.lineWidth = w;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ plotPoints(series.datapoints, radius, null, w + w/2, true,
+ series.xaxis, series.yaxis, symbol);
+
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
+ plotPoints(series.datapoints, radius, null, w/2, true,
+ series.xaxis, series.yaxis, symbol);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ plotPoints(series.datapoints, radius,
+ getFillStyle(series.points, series.color), 0, false,
+ series.xaxis, series.yaxis, symbol);
+ ctx.restore();
+ }
+
+ function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
+ var left, right, bottom, top,
+ drawLeft, drawRight, drawTop, drawBottom,
+ tmp;
+
+ // in horizontal mode, we start the bar from the left
+ // instead of from the bottom so it appears to be
+ // horizontal rather than vertical
+ if (horizontal) {
+ drawBottom = drawRight = drawTop = true;
+ drawLeft = false;
+ left = b;
+ right = x;
+ top = y + barLeft;
+ bottom = y + barRight;
+
+ // account for negative bars
+ if (right < left) {
+ tmp = right;
+ right = left;
+ left = tmp;
+ drawLeft = true;
+ drawRight = false;
+ }
+ }
+ else {
+ drawLeft = drawRight = drawTop = true;
+ drawBottom = false;
+ left = x + barLeft;
+ right = x + barRight;
+ bottom = b;
+ top = y;
+
+ // account for negative bars
+ if (top < bottom) {
+ tmp = top;
+ top = bottom;
+ bottom = tmp;
+ drawBottom = true;
+ drawTop = false;
+ }
+ }
+
+ // clip
+ if (right < axisx.min || left > axisx.max ||
+ top < axisy.min || bottom > axisy.max)
+ return;
+
+ if (left < axisx.min) {
+ left = axisx.min;
+ drawLeft = false;
+ }
+
+ if (right > axisx.max) {
+ right = axisx.max;
+ drawRight = false;
+ }
+
+ if (bottom < axisy.min) {
+ bottom = axisy.min;
+ drawBottom = false;
+ }
+
+ if (top > axisy.max) {
+ top = axisy.max;
+ drawTop = false;
+ }
+
+ left = axisx.p2c(left);
+ bottom = axisy.p2c(bottom);
+ right = axisx.p2c(right);
+ top = axisy.p2c(top);
+
+ // fill the bar
+ if (fillStyleCallback) {
+ c.beginPath();
+ c.moveTo(left, bottom);
+ c.lineTo(left, top);
+ c.lineTo(right, top);
+ c.lineTo(right, bottom);
+ c.fillStyle = fillStyleCallback(bottom, top);
+ c.fill();
+ }
+
+ // draw outline
+ if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
+ c.beginPath();
+
+ // FIXME: inline moveTo is buggy with excanvas
+ c.moveTo(left, bottom + offset);
+ if (drawLeft)
+ c.lineTo(left, top + offset);
+ else
+ c.moveTo(left, top + offset);
+ if (drawTop)
+ c.lineTo(right, top + offset);
+ else
+ c.moveTo(right, top + offset);
+ if (drawRight)
+ c.lineTo(right, bottom + offset);
+ else
+ c.moveTo(right, bottom + offset);
+ if (drawBottom)
+ c.lineTo(left, bottom + offset);
+ else
+ c.moveTo(left, bottom + offset);
+ c.stroke();
+ }
+ }
+
+ function drawSeriesBars(series) {
+ function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ if (points[i] == null)
+ continue;
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
+ ctx.lineWidth = series.bars.lineWidth;
+ ctx.strokeStyle = series.color;
+
+ var barLeft;
+
+ switch (series.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -series.bars.barWidth;
+ break;
+ case "center":
+ barLeft = -series.bars.barWidth / 2;
+ break;
+ default:
+ throw new Error("Invalid bar alignment: " + series.bars.align);
+ }
+
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
+ var fill = filloptions.fill;
+ if (!fill)
+ return null;
+
+ if (filloptions.fillColor)
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
+
+ var c = $.color.parse(seriesColor);
+ c.a = typeof fill == "number" ? fill : 0.4;
+ c.normalize();
+ return c.toString();
+ }
+
+ function insertLegend() {
+
+ placeholder.find(".legend").remove();
+
+ if (!options.legend.show)
+ return;
+
+ var fragments = [], entries = [], rowStarted = false,
+ lf = options.legend.labelFormatter, s, label;
+
+ // Build a list of legend entries, with each having a label and a color
+
+ for (var i = 0; i < series.length; ++i) {
+ s = series[i];
+ if (s.label) {
+ label = lf ? lf(s.label, s) : s.label;
+ if (label) {
+ entries.push({
+ label: label,
+ color: s.color
+ });
+ }
+ }
+ }
+
+ // Sort the legend using either the default or a custom comparator
+
+ if (options.legend.sorted) {
+ if ($.isFunction(options.legend.sorted)) {
+ entries.sort(options.legend.sorted);
+ } else if (options.legend.sorted == "reverse") {
+ entries.reverse();
+ } else {
+ var ascending = options.legend.sorted != "descending";
+ entries.sort(function(a, b) {
+ return a.label == b.label ? 0 : (
+ (a.label < b.label) != ascending ? 1 : -1 // Logical XOR
+ );
+ });
+ }
+ }
+
+ // Generate markup for the list of entries, in their final order
+
+ for (var i = 0; i < entries.length; ++i) {
+
+ var entry = entries[i];
+
+ if (i % options.legend.noColumns == 0) {
+ if (rowStarted)
+ fragments.push('</tr>');
+ fragments.push('<tr>');
+ rowStarted = true;
+ }
+
+ fragments.push(
+ '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' +
+ '<td class="legendLabel">' + entry.label + '</td>'
+ );
+ }
+
+ if (rowStarted)
+ fragments.push('</tr>');
+
+ if (fragments.length == 0)
+ return;
+
+ var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
+ if (options.legend.container != null)
+ $(options.legend.container).html(table);
+ else {
+ var pos = "",
+ p = options.legend.position,
+ m = options.legend.margin;
+ if (m[0] == null)
+ m = [m, m];
+ if (p.charAt(0) == "n")
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
+ else if (p.charAt(0) == "s")
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
+ if (p.charAt(1) == "e")
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
+ else if (p.charAt(1) == "w")
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
+ var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
+ if (options.legend.backgroundOpacity != 0.0) {
+ // put in the transparent background
+ // separately to avoid blended labels and
+ // label boxes
+ var c = options.legend.backgroundColor;
+ if (c == null) {
+ c = options.grid.backgroundColor;
+ if (c && typeof c == "string")
+ c = $.color.parse(c);
+ else
+ c = $.color.extract(legend, 'background-color');
+ c.a = 1;
+ c = c.toString();
+ }
+ var div = legend.children();
+ $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
+ }
+ }
+ }
+
+
+ // interactive features
+
+ var highlights = [],
+ redrawTimeout = null;
+
+ // returns the data item the mouse is over, or null if none is found
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
+ var maxDistance = options.grid.mouseActiveRadius,
+ smallestDistance = maxDistance * maxDistance + 1,
+ item = null, foundPoint = false, i, j, ps;
+
+ for (i = series.length - 1; i >= 0; --i) {
+ if (!seriesFilter(series[i]))
+ continue;
+
+ var s = series[i],
+ axisx = s.xaxis,
+ axisy = s.yaxis,
+ points = s.datapoints.points,
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
+ my = axisy.c2p(mouseY),
+ maxx = maxDistance / axisx.scale,
+ maxy = maxDistance / axisy.scale;
+
+ ps = s.datapoints.pointsize;
+ // with inverse transforms, we can't use the maxx/maxy
+ // optimization, sadly
+ if (axisx.options.inverseTransform)
+ maxx = Number.MAX_VALUE;
+ if (axisy.options.inverseTransform)
+ maxy = Number.MAX_VALUE;
+
+ if (s.lines.show || s.points.show) {
+ for (j = 0; j < points.length; j += ps) {
+ var x = points[j], y = points[j + 1];
+ if (x == null)
+ continue;
+
+ // For points and lines, the cursor must be within a
+ // certain distance to the data point
+ if (x - mx > maxx || x - mx < -maxx ||
+ y - my > maxy || y - my < -maxy)
+ continue;
+
+ // We have to calculate distances in pixels, not in
+ // data units, because the scales of the axes may be different
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
+ dy = Math.abs(axisy.p2c(y) - mouseY),
+ dist = dx * dx + dy * dy; // we save the sqrt
+
+ // use <= to ensure last point takes precedence
+ // (last generally means on top of)
+ if (dist < smallestDistance) {
+ smallestDistance = dist;
+ item = [i, j / ps];
+ }
+ }
+ }
+
+ if (s.bars.show && !item) { // no other point can be nearby
+ var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
+ barRight = barLeft + s.bars.barWidth;
+
+ for (j = 0; j < points.length; j += ps) {
+ var x = points[j], y = points[j + 1], b = points[j + 2];
+ if (x == null)
+ continue;
+
+ // for a bar graph, the cursor must be inside the bar
+ if (series[i].bars.horizontal ?
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
+ my >= y + barLeft && my <= y + barRight) :
+ (mx >= x + barLeft && mx <= x + barRight &&
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
+ item = [i, j / ps];
+ }
+ }
+ }
+
+ if (item) {
+ i = item[0];
+ j = item[1];
+ ps = series[i].datapoints.pointsize;
+
+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
+ dataIndex: j,
+ series: series[i],
+ seriesIndex: i };
+ }
+
+ return null;
+ }
+
+ function onMouseMove(e) {
+ if (options.grid.hoverable)
+ triggerClickHoverEvent("plothover", e,
+ function (s) { return s["hoverable"] != false; });
+ }
+
+ function onMouseLeave(e) {
+ if (options.grid.hoverable)
+ triggerClickHoverEvent("plothover", e,
+ function (s) { return false; });
+ }
+
+ function onClick(e) {
+ triggerClickHoverEvent("plotclick", e,
+ function (s) { return s["clickable"] != false; });
+ }
+
+ // trigger click or hover event (they send the same parameters
+ // so we share their code)
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
+ var offset = eventHolder.offset(),
+ canvasX = event.pageX - offset.left - plotOffset.left,
+ canvasY = event.pageY - offset.top - plotOffset.top,
+ pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
+
+ pos.pageX = event.pageX;
+ pos.pageY = event.pageY;
+
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
+
+ if (item) {
+ // fill in mouse pos for any listeners out there
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
+ }
+
+ if (options.grid.autoHighlight) {
+ // clear auto-highlights
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.auto == eventname &&
+ !(item && h.series == item.series &&
+ h.point[0] == item.datapoint[0] &&
+ h.point[1] == item.datapoint[1]))
+ unhighlight(h.series, h.point);
+ }
+
+ if (item)
+ highlight(item.series, item.datapoint, eventname);
+ }
+
+ placeholder.trigger(eventname, [ pos, item ]);
+ }
+
+ function triggerRedrawOverlay() {
+ var t = options.interaction.redrawOverlayInterval;
+ if (t == -1) { // skip event queue
+ drawOverlay();
+ return;
+ }
+
+ if (!redrawTimeout)
+ redrawTimeout = setTimeout(drawOverlay, t);
+ }
+
+ function drawOverlay() {
+ redrawTimeout = null;
+
+ // draw highlights
+ octx.save();
+ overlay.clear();
+ octx.translate(plotOffset.left, plotOffset.top);
+
+ var i, hi;
+ for (i = 0; i < highlights.length; ++i) {
+ hi = highlights[i];
+
+ if (hi.series.bars.show)
+ drawBarHighlight(hi.series, hi.point);
+ else
+ drawPointHighlight(hi.series, hi.point);
+ }
+ octx.restore();
+
+ executeHooks(hooks.drawOverlay, [octx]);
+ }
+
+ function highlight(s, point, auto) {
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number") {
+ var ps = s.datapoints.pointsize;
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
+ }
+
+ var i = indexOfHighlight(s, point);
+ if (i == -1) {
+ highlights.push({ series: s, point: point, auto: auto });
+
+ triggerRedrawOverlay();
+ }
+ else if (!auto)
+ highlights[i].auto = false;
+ }
+
+ function unhighlight(s, point) {
+ if (s == null && point == null) {
+ highlights = [];
+ triggerRedrawOverlay();
+ return;
+ }
+
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number") {
+ var ps = s.datapoints.pointsize;
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
+ }
+
+ var i = indexOfHighlight(s, point);
+ if (i != -1) {
+ highlights.splice(i, 1);
+
+ triggerRedrawOverlay();
+ }
+ }
+
+ function indexOfHighlight(s, p) {
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.series == s && h.point[0] == p[0]
+ && h.point[1] == p[1])
+ return i;
+ }
+ return -1;
+ }
+
+ function drawPointHighlight(series, point) {
+ var x = point[0], y = point[1],
+ axisx = series.xaxis, axisy = series.yaxis,
+ highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
+
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ return;
+
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
+ octx.lineWidth = pointRadius;
+ octx.strokeStyle = highlightColor;
+ var radius = 1.5 * pointRadius;
+ x = axisx.p2c(x);
+ y = axisy.p2c(y);
+
+ octx.beginPath();
+ if (series.points.symbol == "circle")
+ octx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ else
+ series.points.symbol(octx, x, y, radius, false);
+ octx.closePath();
+ octx.stroke();
+ }
+
+ function drawBarHighlight(series, point) {
+ var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
+ fillStyle = highlightColor,
+ barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
+
+ octx.lineWidth = series.bars.lineWidth;
+ octx.strokeStyle = highlightColor;
+
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
+ 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
+ }
+
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
+ if (typeof spec == "string")
+ return spec;
+ else {
+ // assume this is a gradient spec; IE currently only
+ // supports a simple vertical gradient properly, so that's
+ // what we support too
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
+
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
+ var c = spec.colors[i];
+ if (typeof c != "string") {
+ var co = $.color.parse(defaultColor);
+ if (c.brightness != null)
+ co = co.scale('rgb', c.brightness);
+ if (c.opacity != null)
+ co.a *= c.opacity;
+ c = co.toString();
+ }
+ gradient.addColorStop(i / (l - 1), c);
+ }
+
+ return gradient;
+ }
+ }
+ }
+
+ // Add the plot function to the top level of the jQuery object
+
+ $.plot = function(placeholder, data, options) {
+ //var t0 = new Date();
+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
+ //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
+ return plot;
+ };
+
+ $.plot.version = "0.8.1";
+
+ $.plot.plugins = [];
+
+ // Also add the plot function as a chainable property
+
+ $.fn.plot = function(data, options) {
+ return this.each(function() {
+ $.plot(this, data, options);
+ });
+ };
+
+ // round to nearby lower multiple of base
+ function floorInBase(n, base) {
+ return base * Math.floor(n / base);
+ }
+
+})(jQuery);
diff --git a/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.min.js b/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.min.js
new file mode 100644
index 0000000000..3706512c48
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.min.js
@@ -0,0 +1,29 @@
+/* Javascript plotting library for jQuery, version 0.8.1.
+
+Copyright (c) 2007-2013 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/// first an inline dependency, jquery.colorhelpers.js, we inline it here
+// for convenience
+/* Plugin for jQuery for working with colors.
+ *
+ * Version 1.1.
+ *
+ * Inspiration from jQuery color animation plugin by John Resig.
+ *
+ * Released under the MIT license by Ole Laursen, October 2009.
+ *
+ * Examples:
+ *
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
+ * var c = $.color.extract($("#mydiv"), 'background-color');
+ * console.log(c.r, c.g, c.b, c.a);
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
+ *
+ * Note that .scale() and .add() return the same modified object
+ * instead of making a new one.
+ *
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
+ * produce a color rather than just crashing.
+ */(function(e){e.color={},e.color.make=function(t,n,r,i){var s={};return s.r=t||0,s.g=n||0,s.b=r||0,s.a=i!=null?i:1,s.add=function(e,t){for(var n=0;n<e.length;++n)s[e.charAt(n)]+=t;return s.normalize()},s.scale=function(e,t){for(var n=0;n<e.length;++n)s[e.charAt(n)]*=t;return s.normalize()},s.toString=function(){return s.a>=1?"rgb("+[s.r,s.g,s.b].join(",")+")":"rgba("+[s.r,s.g,s.b,s.a].join(",")+")"},s.normalize=function(){function e(e,t,n){return t<e?e:t>n?n:t}return s.r=e(0,parseInt(s.r),255),s.g=e(0,parseInt(s.g),255),s.b=e(0,parseInt(s.b),255),s.a=e(0,s.a,1),s},s.clone=function(){return e.color.make(s.r,s.b,s.g,s.a)},s.normalize()},e.color.extract=function(t,n){var r;do{r=t.css(n).toLowerCase();if(r!=""&&r!="transparent")break;t=t.parent()}while(!e.nodeName(t.get(0),"body"));return r=="rgba(0, 0, 0, 0)"&&(r="transparent"),e.color.parse(r)},e.color.parse=function(n){var r,i=e.color.make;if(r=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(n))return i(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10));if(r=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(n))return i(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10),parseFloat(r[4]));if(r=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(n))return i(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55);if(r=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(n))return i(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55,parseFloat(r[4]));if(r=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(n))return i(parseInt(r[1],16),parseInt(r[2],16),parseInt(r[3],16));if(r=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(n))return i(parseInt(r[1]+r[1],16),parseInt(r[2]+r[2],16),parseInt(r[3]+r[3],16));var s=e.trim(n).toLowerCase();return s=="transparent"?i(255,255,255,0):(r=t[s]||[0,0,0],i(r[0],r[1],r[2]))};var t={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery),function(e){function n(t,n){var r=n.children("."+t)[0];if(r==null){r=document.createElement("canvas"),r.className=t,e(r).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(n);if(!r.getContext){if(!window.G_vmlCanvasManager)throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");r=window.G_vmlCanvasManager.initElement(r)}}this.element=r;var i=this.context=r.getContext("2d"),s=window.devicePixelRatio||1,o=i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1;this.pixelRatio=s/o,this.resize(n.width(),n.height()),this.textContainer=null,this.text={},this._textCache={}}function r(t,r,s,o){function E(e,t){t=[w].concat(t);for(var n=0;n<e.length;++n)e[n].apply(this,t)}function S(){var t={Canvas:n};for(var r=0;r<o.length;++r){var i=o[r];i.init(w,t),i.options&&e.extend(!0,a,i.options)}}function x(n){e.extend(!0,a,n),n&&n.colors&&(a.colors=n.colors),a.xaxis.color==null&&(a.xaxis.color=e.color.parse(a.grid.color).scale("a",.22).toString()),a.yaxis.color==null&&(a.yaxis.color=e.color.parse(a.grid.color).scale("a",.22).toString()),a.xaxis.tickColor==null&&(a.xaxis.tickColor=a.grid.tickColor||a.xaxis.color),a.yaxis.tickColor==null&&(a.yaxis.tickColor=a.grid.tickColor||a.yaxis.color),a.grid.borderColor==null&&(a.grid.borderColor=a.grid.color),a.grid.tickColor==null&&(a.grid.tickColor=e.color.parse(a.grid.color).scale("a",.22).toString());var r,i,s,o={style:t.css("font-style"),size:Math.round(.8*(+t.css("font-size").replace("px","")||13)),variant:t.css("font-variant"),weight:t.css("font-weight"),family:t.css("font-family")};o.lineHeight=o.size*1.15,s=a.xaxes.length||1;for(r=0;r<s;++r)i=a.xaxes[r],i&&!i.tickColor&&(i.tickColor=i.color),i=e.extend(!0,{},a.xaxis,i),a.xaxes[r]=i,i.font&&(i.font=e.extend({},o,i.font),i.font.color||(i.font.color=i.color));s=a.yaxes.length||1;for(r=0;r<s;++r)i=a.yaxes[r],i&&!i.tickColor&&(i.tickColor=i.color),i=e.extend(!0,{},a.yaxis,i),a.yaxes[r]=i,i.font&&(i.font=e.extend({},o,i.font),i.font.color||(i.font.color=i.color));a.xaxis.noTicks&&a.xaxis.ticks==null&&(a.xaxis.ticks=a.xaxis.noTicks),a.yaxis.noTicks&&a.yaxis.ticks==null&&(a.yaxis.ticks=a.yaxis.noTicks),a.x2axis&&(a.xaxes[1]=e.extend(!0,{},a.xaxis,a.x2axis),a.xaxes[1].position="top"),a.y2axis&&(a.yaxes[1]=e.extend(!0,{},a.yaxis,a.y2axis),a.yaxes[1].position="right"),a.grid.coloredAreas&&(a.grid.markings=a.grid.coloredAreas),a.grid.coloredAreasColor&&(a.grid.markingsColor=a.grid.coloredAreasColor),a.lines&&e.extend(!0,a.series.lines,a.lines),a.points&&e.extend(!0,a.series.points,a.points),a.bars&&e.extend(!0,a.series.bars,a.bars),a.shadowSize!=null&&(a.series.shadowSize=a.shadowSize),a.highlightColor!=null&&(a.series.highlightColor=a.highlightColor);for(r=0;r<a.xaxes.length;++r)O(d,r+1).options=a.xaxes[r];for(r=0;r<a.yaxes.length;++r)O(v,r+1).options=a.yaxes[r];for(var u in b)a.hooks[u]&&a.hooks[u].length&&(b[u]=b[u].concat(a.hooks[u]));E(b.processOptions,[a])}function T(e){u=N(e),M(),_()}function N(t){var n=[];for(var r=0;r<t.length;++r){var i=e.extend(!0,{},a.series);t[r].data!=null?(i.data=t[r].data,delete t[r].data,e.extend(!0,i,t[r]),t[r].data=i.data):i.data=t[r],n.push(i)}return n}function C(e,t){var n=e[t+"axis"];return typeof n=="object"&&(n=n.n),typeof n!="number"&&(n=1),n}function k(){return e.grep(d.concat(v),function(e){return e})}function L(e){var t={},n,r;for(n=0;n<d.length;++n)r=d[n],r&&r.used&&(t["x"+r.n]=r.c2p(e.left));for(n=0;n<v.length;++n)r=v[n],r&&r.used&&(t["y"+r.n]=r.c2p(e.top));return t.x1!==undefined&&(t.x=t.x1),t.y1!==undefined&&(t.y=t.y1),t}function A(e){var t={},n,r,i;for(n=0;n<d.length;++n){r=d[n];if(r&&r.used){i="x"+r.n,e[i]==null&&r.n==1&&(i="x");if(e[i]!=null){t.left=r.p2c(e[i]);break}}}for(n=0;n<v.length;++n){r=v[n];if(r&&r.used){i="y"+r.n,e[i]==null&&r.n==1&&(i="y");if(e[i]!=null){t.top=r.p2c(e[i]);break}}}return t}function O(t,n){return t[n-1]||(t[n-1]={n:n,direction:t==d?"x":"y",options:e.extend(!0,{},t==d?a.xaxis:a.yaxis)}),t[n-1]}function M(){var t=u.length,n=-1,r;for(r=0;r<u.length;++r){var i=u[r].color;i!=null&&(t--,typeof i=="number"&&i>n&&(n=i))}t<=n&&(t=n+1);var s,o=[],f=a.colors,l=f.length,c=0;for(r=0;r<t;r++)s=e.color.parse(f[r%l]||"#666"),r%l==0&&r&&(c>=0?c<.5?c=-c-.2:c=0:c=-c),o[r]=s.scale("rgb",1+c);var h=0,p;for(r=0;r<u.length;++r){p=u[r],p.color==null?(p.color=o[h].toString(),++h):typeof p.color=="number"&&(p.color=o[p.color].toString());if(p.lines.show==null){var m,g=!0;for(m in p)if(p[m]&&p[m].show){g=!1;break}g&&(p.lines.show=!0)}p.lines.zero==null&&(p.lines.zero=!!p.lines.fill),p.xaxis=O(d,C(p,"x")),p.yaxis=O(v,C(p,"y"))}}function _(){function x(e,t,n){t<e.datamin&&t!=-r&&(e.datamin=t),n>e.datamax&&n!=r&&(e.datamax=n)}var t=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY,r=Number.MAX_VALUE,i,s,o,a,f,l,c,h,p,d,v,m,g,y,w,S;e.each(k(),function(e,r){r.datamin=t,r.datamax=n,r.used=!1});for(i=0;i<u.length;++i)l=u[i],l.datapoints={points:[]},E(b.processRawData,[l,l.data,l.datapoints]);for(i=0;i<u.length;++i){l=u[i],w=l.data,S=l.datapoints.format;if(!S){S=[],S.push({x:!0,number:!0,required:!0}),S.push({y:!0,number:!0,required:!0});if(l.bars.show||l.lines.show&&l.lines.fill){var T=!!(l.bars.show&&l.bars.zero||l.lines.show&&l.lines.zero);S.push({y:!0,number:!0,required:!1,defaultValue:0,autoscale:T}),l.bars.horizontal&&(delete S[S.length-1].y,S[S.length-1].x=!0)}l.datapoints.format=S}if(l.datapoints.pointsize!=null)continue;l.datapoints.pointsize=S.length,h=l.datapoints.pointsize,c=l.datapoints.points;var N=l.lines.show&&l.lines.steps;l.xaxis.used=l.yaxis.used=!0;for(s=o=0;s<w.length;++s,o+=h){y=w[s];var C=y==null;if(!C)for(a=0;a<h;++a)m=y[a],g=S[a],g&&(g.number&&m!=null&&(m=+m,isNaN(m)?m=null:m==Infinity?m=r:m==-Infinity&&(m=-r)),m==null&&(g.required&&(C=!0),g.defaultValue!=null&&(m=g.defaultValue))),c[o+a]=m;if(C)for(a=0;a<h;++a)m=c[o+a],m!=null&&(g=S[a],g.autoscale&&(g.x&&x(l.xaxis,m,m),g.y&&x(l.yaxis,m,m))),c[o+a]=null;else if(N&&o>0&&c[o-h]!=null&&c[o-h]!=c[o]&&c[o-h+1]!=c[o+1]){for(a=0;a<h;++a)c[o+h+a]=c[o+a];c[o+1]=c[o-h+1],o+=h}}}for(i=0;i<u.length;++i)l=u[i],E(b.processDatapoints,[l,l.datapoints]);for(i=0;i<u.length;++i){l=u[i],c=l.datapoints.points,h=l.datapoints.pointsize,S=l.datapoints.format;var L=t,A=t,O=n,M=n;for(s=0;s<c.length;s+=h){if(c[s]==null)continue;for(a=0;a<h;++a){m=c[s+a],g=S[a];if(!g||g.autoscale===!1||m==r||m==-r)continue;g.x&&(m<L&&(L=m),m>O&&(O=m)),g.y&&(m<A&&(A=m),m>M&&(M=m))}}if(l.bars.show){var _;switch(l.bars.align){case"left":_=0;break;case"right":_=-l.bars.barWidth;break;case"center":_=-l.bars.barWidth/2;break;default:throw new Error("Invalid bar alignment: "+l.bars.align)}l.bars.horizontal?(A+=_,M+=_+l.bars.barWidth):(L+=_,O+=_+l.bars.barWidth)}x(l.xaxis,L,O),x(l.yaxis,A,M)}e.each(k(),function(e,r){r.datamin==t&&(r.datamin=null),r.datamax==n&&(r.datamax=null)})}function D(){t.css("padding",0).children(":not(.flot-base,.flot-overlay)").remove(),t.css("position")=="static"&&t.css("position","relative"),f=new n("flot-base",t),l=new n("flot-overlay",t),h=f.context,p=l.context,c=e(l.element).unbind();var r=t.data("plot");r&&(r.shutdown(),l.clear()),t.data("plot",w)}function P(){a.grid.hoverable&&(c.mousemove(at),c.bind("mouseleave",ft)),a.grid.clickable&&c.click(lt),E(b.bindEvents,[c])}function H(){ot&&clearTimeout(ot),c.unbind("mousemove",at),c.unbind("mouseleave",ft),c.unbind("click",lt),E(b.shutdown,[c])}function B(e){function t(e){return e}var n,r,i=e.options.transform||t,s=e.options.inverseTransform;e.direction=="x"?(n=e.scale=g/Math.abs(i(e.max)-i(e.min)),r=Math.min(i(e.max),i(e.min))):(n=e.scale=y/Math.abs(i(e.max)-i(e.min)),n=-n,r=Math.max(i(e.max),i(e.min))),i==t?e.p2c=function(e){return(e-r)*n}:e.p2c=function(e){return(i(e)-r)*n},s?e.c2p=function(e){return s(r+e/n)}:e.c2p=function(e){return r+e/n}}function j(e){var t=e.options,n=e.ticks||[],r=t.labelWidth||0,i=t.labelHeight||0,s=r||e.direction=="x"?Math.floor(f.width/(n.length||1)):null;legacyStyles=e.direction+"Axis "+e.direction+e.n+"Axis",layer="flot-"+e.direction+"-axis flot-"+e.direction+e.n+"-axis "+legacyStyles,font=t.font||"flot-tick-label tickLabel";for(var o=0;o<n.length;++o){var u=n[o];if(!u.label)continue;var a=f.getTextInfo(layer,u.label,font,null,s);r=Math.max(r,a.width),i=Math.max(i,a.height)}e.labelWidth=t.labelWidth||r,e.labelHeight=t.labelHeight||i}function F(t){var n=t.labelWidth,r=t.labelHeight,i=t.options.position,s=t.options.tickLength,o=a.grid.axisMargin,u=a.grid.labelMargin,l=t.direction=="x"?d:v,c,h,p=e.grep(l,function(e){return e&&e.options.position==i&&e.reserveSpace});e.inArray(t,p)==p.length-1&&(o=0);if(s==null){var g=e.grep(l,function(e){return e&&e.reserveSpace});h=e.inArray(t,g)==0,h?s="full":s=5}isNaN(+s)||(u+=+s),t.direction=="x"?(r+=u,i=="bottom"?(m.bottom+=r+o,t.box={top:f.height-m.bottom,height:r}):(t.box={top:m.top+o,height:r},m.top+=r+o)):(n+=u,i=="left"?(t.box={left:m.left+o,width:n},m.left+=n+o):(m.right+=n+o,t.box={left:f.width-m.right,width:n})),t.position=i,t.tickLength=s,t.box.padding=u,t.innermost=h}function I(e){e.direction=="x"?(e.box.left=m.left-e.labelWidth/2,e.box.width=f.width-m.left-m.right+e.labelWidth):(e.box.top=m.top-e.labelHeight/2,e.box.height=f.height-m.bottom-m.top+e.labelHeight)}function q(){var t=a.grid.minBorderMargin,n={x:0,y:0},r,i;if(t==null){t=0;for(r=0;r<u.length;++r)t=Math.max(t,2*(u[r].points.radius+u[r].points.lineWidth/2))}n.x=n.y=Math.ceil(t),e.each(k(),function(e,t){var r=t.direction;t.reserveSpace&&(n[r]=Math.ceil(Math.max(n[r],(r=="x"?t.labelWidth:t.labelHeight)/2)))}),m.left=Math.max(n.x,m.left),m.right=Math.max(n.x,m.right),m.top=Math.max(n.y,m.top),m.bottom=Math.max(n.y,m.bottom)}function R(){var t,n=k(),r=a.grid.show;for(var i in m){var s=a.grid.margin||0;m[i]=typeof s=="number"?s:s[i]||0}E(b.processOffset,[m]);for(var i in m)typeof a.grid.borderWidth=="object"?m[i]+=r?a.grid.borderWidth[i]:0:m[i]+=r?a.grid.borderWidth:0;e.each(n,function(e,t){t.show=t.options.show,t.show==null&&(t.show=t.used),t.reserveSpace=t.show||t.options.reserveSpace,U(t)});if(r){var o=e.grep(n,function(e){return e.reserveSpace});e.each(o,function(e,t){z(t),W(t),X(t,t.ticks),j(t)});for(t=o.length-1;t>=0;--t)F(o[t]);q(),e.each(o,function(e,t){I(t)})}g=f.width-m.left-m.right,y=f.height-m.bottom-m.top,e.each(n,function(e,t){B(t)}),r&&G(),it()}function U(e){var t=e.options,n=+(t.min!=null?t.min:e.datamin),r=+(t.max!=null?t.max:e.datamax),i=r-n;if(i==0){var s=r==0?1:.01;t.min==null&&(n-=s);if(t.max==null||t.min!=null)r+=s}else{var o=t.autoscaleMargin;o!=null&&(t.min==null&&(n-=i*o,n<0&&e.datamin!=null&&e.datamin>=0&&(n=0)),t.max==null&&(r+=i*o,r>0&&e.datamax!=null&&e.datamax<=0&&(r=0)))}e.min=n,e.max=r}function z(t){var n=t.options,r;typeof n.ticks=="number"&&n.ticks>0?r=n.ticks:r=.3*Math.sqrt(t.direction=="x"?f.width:f.height);var s=(t.max-t.min)/r,o=-Math.floor(Math.log(s)/Math.LN10),u=n.tickDecimals;u!=null&&o>u&&(o=u);var a=Math.pow(10,-o),l=s/a,c;l<1.5?c=1:l<3?(c=2,l>2.25&&(u==null||o+1<=u)&&(c=2.5,++o)):l<7.5?c=5:c=10,c*=a,n.minTickSize!=null&&c<n.minTickSize&&(c=n.minTickSize),t.delta=s,t.tickDecimals=Math.max(0,u!=null?u:o),t.tickSize=n.tickSize||c;if(n.mode=="time"&&!t.tickGenerator)throw new Error("Time mode requires the flot.time plugin.");t.tickGenerator||(t.tickGenerator=function(e){var t=[],n=i(e.min,e.tickSize),r=0,s=Number.NaN,o;do o=s,s=n+r*e.tickSize,t.push(s),++r;while(s<e.max&&s!=o);return t},t.tickFormatter=function(e,t){var n=t.tickDecimals?Math.pow(10,t.tickDecimals):1,r=""+Math.round(e*n)/n;if(t.tickDecimals!=null){var i=r.indexOf("."),s=i==-1?0:r.length-i-1;if(s<t.tickDecimals)return(s?r:r+".")+(""+n).substr(1,t.tickDecimals-s)}return r}),e.isFunction(n.tickFormatter)&&(t.tickFormatter=function(e,t){return""+n.tickFormatter(e,t)});if(n.alignTicksWithAxis!=null){var h=(t.direction=="x"?d:v)[n.alignTicksWithAxis-1];if(h&&h.used&&h!=t){var p=t.tickGenerator(t);p.length>0&&(n.min==null&&(t.min=Math.min(t.min,p[0])),n.max==null&&p.length>1&&(t.max=Math.max(t.max,p[p.length-1]))),t.tickGenerator=function(e){var t=[],n,r;for(r=0;r<h.ticks.length;++r)n=(h.ticks[r].v-h.min)/(h.max-h.min),n=e.min+n*(e.max-e.min),t.push(n);return t};if(!t.mode&&n.tickDecimals==null){var m=Math.max(0,-Math.floor(Math.log(t.delta)/Math.LN10)+1),g=t.tickGenerator(t);g.length>1&&/\..*0$/.test((g[1]-g[0]).toFixed(m))||(t.tickDecimals=m)}}}}function W(t){var n=t.options.ticks,r=[];n==null||typeof n=="number"&&n>0?r=t.tickGenerator(t):n&&(e.isFunction(n)?r=n(t):r=n);var i,s;t.ticks=[];for(i=0;i<r.length;++i){var o=null,u=r[i];typeof u=="object"?(s=+u[0],u.length>1&&(o=u[1])):s=+u,o==null&&(o=t.tickFormatter(s,t)),isNaN(s)||t.ticks.push({v:s,label:o})}}function X(e,t){e.options.autoscaleMargin&&t.length>0&&(e.options.min==null&&(e.min=Math.min(e.min,t[0].v)),e.options.max==null&&t.length>1&&(e.max=Math.max(e.max,t[t.length-1].v)))}function V(){f.clear(),E(b.drawBackground,[h]);var e=a.grid;e.show&&e.backgroundColor&&K(),e.show&&!e.aboveData&&Q();for(var t=0;t<u.length;++t)E(b.drawSeries,[h,u[t]]),Y(u[t]);E(b.draw,[h]),e.show&&e.aboveData&&Q(),f.render(),ht()}function J(e,t){var n,r,i,s,o=k();for(var u=0;u<o.length;++u){n=o[u];if(n.direction==t){s=t+n.n+"axis",!e[s]&&n.n==1&&(s=t+"axis");if(e[s]){r=e[s].from,i=e[s].to;break}}}e[s]||(n=t=="x"?d[0]:v[0],r=e[t+"1"],i=e[t+"2"]);if(r!=null&&i!=null&&r>i){var a=r;r=i,i=a}return{from:r,to:i,axis:n}}function K(){h.save(),h.translate(m.left,m.top),h.fillStyle=bt(a.grid.backgroundColor,y,0,"rgba(255, 255, 255, 0)"),h.fillRect(0,0,g,y),h.restore()}function Q(){var t,n,r,i;h.save(),h.translate(m.left,m.top);var s=a.grid.markings;if(s){e.isFunction(s)&&(n=w.getAxes(),n.xmin=n.xaxis.min,n.xmax=n.xaxis.max,n.ymin=n.yaxis.min,n.ymax=n.yaxis.max,s=s(n));for(t=0;t<s.length;++t){var o=s[t],u=J(o,"x"),f=J(o,"y");u.from==null&&(u.from=u.axis.min),u.to==null&&(u.to=u.axis.max),f.from==null&&(f.from=f.axis.min),f.to==null&&(f.to=f.axis.max);if(u.to<u.axis.min||u.from>u.axis.max||f.to<f.axis.min||f.from>f.axis.max)continue;u.from=Math.max(u.from,u.axis.min),u.to=Math.min(u.to,u.axis.max),f.from=Math.max(f.from,f.axis.min),f.to=Math.min(f.to,f.axis.max);if(u.from==u.to&&f.from==f.to)continue;u.from=u.axis.p2c(u.from),u.to=u.axis.p2c(u.to),f.from=f.axis.p2c(f.from),f.to=f.axis.p2c(f.to),u.from==u.to||f.from==f.to?(h.beginPath(),h.strokeStyle=o.color||a.grid.markingsColor,h.lineWidth=o.lineWidth||a.grid.markingsLineWidth,h.moveTo(u.from,f.from),h.lineTo(u.to,f.to),h.stroke()):(h.fillStyle=o.color||a.grid.markingsColor,h.fillRect(u.from,f.to,u.to-u.from,f.from-f.to))}}n=k(),r=a.grid.borderWidth;for(var l=0;l<n.length;++l){var c=n[l],p=c.box,d=c.tickLength,v,b,E,S;if(!c.show||c.ticks.length==0)continue;h.lineWidth=1,c.direction=="x"?(v=0,d=="full"?b=c.position=="top"?0:y:b=p.top-m.top+(c.position=="top"?p.height:0)):(b=0,d=="full"?v=c.position=="left"?0:g:v=p.left-m.left+(c.position=="left"?p.width:0)),c.innermost||(h.strokeStyle=c.options.color,h.beginPath(),E=S=0,c.direction=="x"?E=g+1:S=y+1,h.lineWidth==1&&(c.direction=="x"?b=Math.floor(b)+.5:v=Math.floor(v)+.5),h.moveTo(v,b),h.lineTo(v+E,b+S),h.stroke()),h.strokeStyle=c.options.tickColor,h.beginPath();for(t=0;t<c.ticks.length;++t){var x=c.ticks[t].v;E=S=0;if(isNaN(x)||x<c.min||x>c.max||d=="full"&&(typeof r=="object"&&r[c.position]>0||r>0)&&(x==c.min||x==c.max))continue;c.direction=="x"?(v=c.p2c(x),S=d=="full"?-y:d,c.position=="top"&&(S=-S)):(b=c.p2c(x),E=d=="full"?-g:d,c.position=="left"&&(E=-E)),h.lineWidth==1&&(c.direction=="x"?v=Math.floor(v)+.5:b=Math.floor(b)+.5),h.moveTo(v,b),h.lineTo(v+E,b+S)}h.stroke()}r&&(i=a.grid.borderColor,typeof r=="object"||typeof i=="object"?(typeof r!="object"&&(r={top:r,right:r,bottom:r,left:r}),typeof i!="object"&&(i={top:i,right:i,bottom:i,left:i}),r.top>0&&(h.strokeStyle=i.top,h.lineWidth=r.top,h.beginPath(),h.moveTo(0-r.left,0-r.top/2),h.lineTo(g,0-r.top/2),h.stroke()),r.right>0&&(h.strokeStyle=i.right,h.lineWidth=r.right,h.beginPath(),h.moveTo(g+r.right/2,0-r.top),h.lineTo(g+r.right/2,y),h.stroke()),r.bottom>0&&(h.strokeStyle=i.bottom,h.lineWidth=r.bottom,h.beginPath(),h.moveTo(g+r.right,y+r.bottom/2),h.lineTo(0,y+r.bottom/2),h.stroke()),r.left>0&&(h.strokeStyle=i.left,h.lineWidth=r.left,h.beginPath(),h.moveTo(0-r.left/2,y+r.bottom),h.lineTo(0-r.left/2,0),h.stroke())):(h.lineWidth=r,h.strokeStyle=a.grid.borderColor,h.strokeRect(-r/2,-r/2,g+r,y+r))),h.restore()}function G(){e.each(k(),function(e,t){if(!t.show||t.ticks.length==0)return;var n=t.box,r=t.direction+"Axis "+t.direction+t.n+"Axis",i="flot-"+t.direction+"-axis flot-"+t.direction+t.n+"-axis "+r,s=t.options.font||"flot-tick-label tickLabel",o,u,a,l,c;f.removeText(i);for(var h=0;h<t.ticks.length;++h){o=t.ticks[h];if(!o.label||o.v<t.min||o.v>t.max)continue;t.direction=="x"?(l="center",u=m.left+t.p2c(o.v),t.position=="bottom"?a=n.top+n.padding:(a=n.top+n.height-n.padding,c="bottom")):(c="middle",a=m.top+t.p2c(o.v),t.position=="left"?(u=n.left+n.width-n.padding,l="right"):u=n.left+n.padding),f.addText(i,u,a,o.label,s,null,null,l,c)}})}function Y(e){e.lines.show&&Z(e),e.bars.show&&nt(e),e.points.show&&et(e)}function Z(e){function t(e,t,n,r,i){var s=e.points,o=e.pointsize,u=null,a=null;h.beginPath();for(var f=o;f<s.length;f+=o){var l=s[f-o],c=s[f-o+1],p=s[f],d=s[f+1];if(l==null||p==null)continue;if(c<=d&&c<i.min){if(d<i.min)continue;l=(i.min-c)/(d-c)*(p-l)+l,c=i.min}else if(d<=c&&d<i.min){if(c<i.min)continue;p=(i.min-c)/(d-c)*(p-l)+l,d=i.min}if(c>=d&&c>i.max){if(d>i.max)continue;l=(i.max-c)/(d-c)*(p-l)+l,c=i.max}else if(d>=c&&d>i.max){if(c>i.max)continue;p=(i.max-c)/(d-c)*(p-l)+l,d=i.max}if(l<=p&&l<r.min){if(p<r.min)continue;c=(r.min-l)/(p-l)*(d-c)+c,l=r.min}else if(p<=l&&p<r.min){if(l<r.min)continue;d=(r.min-l)/(p-l)*(d-c)+c,p=r.min}if(l>=p&&l>r.max){if(p>r.max)continue;c=(r.max-l)/(p-l)*(d-c)+c,l=r.max}else if(p>=l&&p>r.max){if(l>r.max)continue;d=(r.max-l)/(p-l)*(d-c)+c,p=r.max}(l!=u||c!=a)&&h.moveTo(r.p2c(l)+t,i.p2c(c)+n),u=p,a=d,h.lineTo(r.p2c(p)+t,i.p2c(d)+n)}h.stroke()}function n(e,t,n){var r=e.points,i=e.pointsize,s=Math.min(Math.max(0,n.min),n.max),o=0,u,a=!1,f=1,l=0,c=0;for(;;){if(i>0&&o>r.length+i)break;o+=i;var p=r[o-i],d=r[o-i+f],v=r[o],m=r[o+f];if(a){if(i>0&&p!=null&&v==null){c=o,i=-i,f=2;continue}if(i<0&&o==l+i){h.fill(),a=!1,i=-i,f=1,o=l=c+i;continue}}if(p==null||v==null)continue;if(p<=v&&p<t.min){if(v<t.min)continue;d=(t.min-p)/(v-p)*(m-d)+d,p=t.min}else if(v<=p&&v<t.min){if(p<t.min)continue;m=(t.min-p)/(v-p)*(m-d)+d,v=t.min}if(p>=v&&p>t.max){if(v>t.max)continue;d=(t.max-p)/(v-p)*(m-d)+d,p=t.max}else if(v>=p&&v>t.max){if(p>t.max)continue;m=(t.max-p)/(v-p)*(m-d)+d,v=t.max}a||(h.beginPath(),h.moveTo(t.p2c(p),n.p2c(s)),a=!0);if(d>=n.max&&m>=n.max){h.lineTo(t.p2c(p),n.p2c(n.max)),h.lineTo(t.p2c(v),n.p2c(n.max));continue}if(d<=n.min&&m<=n.min){h.lineTo(t.p2c(p),n.p2c(n.min)),h.lineTo(t.p2c(v),n.p2c(n.min));continue}var g=p,y=v;d<=m&&d<n.min&&m>=n.min?(p=(n.min-d)/(m-d)*(v-p)+p,d=n.min):m<=d&&m<n.min&&d>=n.min&&(v=(n.min-d)/(m-d)*(v-p)+p,m=n.min),d>=m&&d>n.max&&m<=n.max?(p=(n.max-d)/(m-d)*(v-p)+p,d=n.max):m>=d&&m>n.max&&d<=n.max&&(v=(n.max-d)/(m-d)*(v-p)+p,m=n.max),p!=g&&h.lineTo(t.p2c(g),n.p2c(d)),h.lineTo(t.p2c(p),n.p2c(d)),h.lineTo(t.p2c(v),n.p2c(m)),v!=y&&(h.lineTo(t.p2c(v),n.p2c(m)),h.lineTo(t.p2c(y),n.p2c(m)))}}h.save(),h.translate(m.left,m.top),h.lineJoin="round";var r=e.lines.lineWidth,i=e.shadowSize;if(r>0&&i>0){h.lineWidth=i,h.strokeStyle="rgba(0,0,0,0.1)";var s=Math.PI/18;t(e.datapoints,Math.sin(s)*(r/2+i/2),Math.cos(s)*(r/2+i/2),e.xaxis,e.yaxis),h.lineWidth=i/2,t(e.datapoints,Math.sin(s)*(r/2+i/4),Math.cos(s)*(r/2+i/4),e.xaxis,e.yaxis)}h.lineWidth=r,h.strokeStyle=e.color;var o=rt(e.lines,e.color,0,y);o&&(h.fillStyle=o,n(e.datapoints,e.xaxis,e.yaxis)),r>0&&t(e.datapoints,0,0,e.xaxis,e.yaxis),h.restore()}function et(e){function t(e,t,n,r,i,s,o,u){var a=e.points,f=e.pointsize;for(var l=0;l<a.length;l+=f){var c=a[l],p=a[l+1];if(c==null||c<s.min||c>s.max||p<o.min||p>o.max)continue;h.beginPath(),c=s.p2c(c),p=o.p2c(p)+r,u=="circle"?h.arc(c,p,t,0,i?Math.PI:Math.PI*2,!1):u(h,c,p,t,i),h.closePath(),n&&(h.fillStyle=n,h.fill()),h.stroke()}}h.save(),h.translate(m.left,m.top);var n=e.points.lineWidth,r=e.shadowSize,i=e.points.radius,s=e.points.symbol;n==0&&(n=1e-4);if(n>0&&r>0){var o=r/2;h.lineWidth=o,h.strokeStyle="rgba(0,0,0,0.1)",t(e.datapoints,i,null,o+o/2,!0,e.xaxis,e.yaxis,s),h.strokeStyle="rgba(0,0,0,0.2)",t(e.datapoints,i,null,o/2,!0,e.xaxis,e.yaxis,s)}h.lineWidth=n,h.strokeStyle=e.color,t(e.datapoints,i,rt(e.points,e.color),0,!1,e.xaxis,e.yaxis,s),h.restore()}function tt(e,t,n,r,i,s,o,u,a,f,l,c){var h,p,d,v,m,g,y,b,w;l?(b=g=y=!0,m=!1,h=n,p=e,v=t+r,d=t+i,p<h&&(w=p,p=h,h=w,m=!0,g=!1)):(m=g=y=!0,b=!1,h=e+r,p=e+i,d=n,v=t,v<d&&(w=v,v=d,d=w,b=!0,y=!1));if(p<u.min||h>u.max||v<a.min||d>a.max)return;h<u.min&&(h=u.min,m=!1),p>u.max&&(p=u.max,g=!1),d<a.min&&(d=a.min,b=!1),v>a.max&&(v=a.max,y=!1),h=u.p2c(h),d=a.p2c(d),p=u.p2c(p),v=a.p2c(v),o&&(f.beginPath(),f.moveTo(h,d),f.lineTo(h,v),f.lineTo(p,v),f.lineTo(p,d),f.fillStyle=o(d,v),f.fill()),c>0&&(m||g||y||b)&&(f.beginPath(),f.moveTo(h,d+s),m?f.lineTo(h,v+s):f.moveTo(h,v+s),y?f.lineTo(p,v+s):f.moveTo(p,v+s),g?f.lineTo(p,d+s):f.moveTo(p,d+s),b?f.lineTo(h,d+s):f.moveTo(h,d+s),f.stroke())}function nt(e){function t(t,n,r,i,s,o,u){var a=t.points,f=t.pointsize;for(var l=0;l<a.length;l+=f){if(a[l]==null)continue;tt(a[l],a[l+1],a[l+2],n,r,i,s,o,u,h,e.bars.horizontal,e.bars.lineWidth)}}h.save(),h.translate(m.left,m.top),h.lineWidth=e.bars.lineWidth,h.strokeStyle=e.color;var n;switch(e.bars.align){case"left":n=0;break;case"right":n=-e.bars.barWidth;break;case"center":n=-e.bars.barWidth/2;break;default:throw new Error("Invalid bar alignment: "+e.bars.align)}var r=e.bars.fill?function(t,n){return rt(e.bars,e.color,t,n)}:null;t(e.datapoints,n,n+e.bars.barWidth,0,r,e.xaxis,e.yaxis),h.restore()}function rt(t,n,r,i){var s=t.fill;if(!s)return null;if(t.fillColor)return bt(t.fillColor,r,i,n);var o=e.color.parse(n);return o.a=typeof s=="number"?s:.4,o.normalize(),o.toString()}function it(){t.find(".legend").remove();if(!a.legend.show)return;var n=[],r=[],i=!1,s=a.legend.labelFormatter,o,f;for(var l=0;l<u.length;++l)o=u[l],o.label&&(f=s?s(o.label,o):o.label,f&&r.push({label:f,color:o.color}));if(a.legend.sorted)if(e.isFunction(a.legend.sorted))r.sort(a.legend.sorted);else if(a.legend.sorted=="reverse")r.reverse();else{var c=a.legend.sorted!="descending";r.sort(function(e,t){return e.label==t.label?0:e.label<t.label!=c?1:-1})}for(var l=0;l<r.length;++l){var h=r[l];l%a.legend.noColumns==0&&(i&&n.push("</tr>"),n.push("<tr>"),i=!0),n.push('<td class="legendColorBox"><div style="border:1px solid '+a.legend.labelBoxBorderColor+';padding:1px"><div style="width:4px;height:0;border:5px solid '+h.color+';overflow:hidden"></div></div></td>'+'<td class="legendLabel">'+h.label+"</td>")}i&&n.push("</tr>");if(n.length==0)return;var p='<table style="font-size:smaller;color:'+a.grid.color+'">'+n.join("")+"</table>";if(a.legend.container!=null)e(a.legend.container).html(p);else{var d="",v=a.legend.position,g=a.legend.margin;g[0]==null&&(g=[g,g]),v.charAt(0)=="n"?d+="top:"+(g[1]+m.top)+"px;":v.charAt(0)=="s"&&(d+="bottom:"+(g[1]+m.bottom)+"px;"),v.charAt(1)=="e"?d+="right:"+(g[0]+m.right)+"px;":v.charAt(1)=="w"&&(d+="left:"+(g[0]+m.left)+"px;");var y=e('<div class="legend">'+p.replace('style="','style="position:absolute;'+d+";")+"</div>").appendTo(t);if(a.legend.backgroundOpacity!=0){var b=a.legend.backgroundColor;b==null&&(b=a.grid.backgroundColor,b&&typeof b=="string"?b=e.color.parse(b):b=e.color.extract(y,"background-color"),b.a=1,b=b.toString());var w=y.children();e('<div style="position:absolute;width:'+w.width()+"px;height:"+w.height()+"px;"+d+"background-color:"+b+';"> </div>').prependTo(y).css("opacity",a.legend.backgroundOpacity)}}}function ut(e,t,n){var r=a.grid.mouseActiveRadius,i=r*r+1,s=null,o=!1,f,l,c;for(f=u.length-1;f>=0;--f){if(!n(u[f]))continue;var h=u[f],p=h.xaxis,d=h.yaxis,v=h.datapoints.points,m=p.c2p(e),g=d.c2p(t),y=r/p.scale,b=r/d.scale;c=h.datapoints.pointsize,p.options.inverseTransform&&(y=Number.MAX_VALUE),d.options.inverseTransform&&(b=Number.MAX_VALUE);if(h.lines.show||h.points.show)for(l=0;l<v.length;l+=c){var w=v[l],E=v[l+1];if(w==null)continue;if(w-m>y||w-m<-y||E-g>b||E-g<-b)continue;var S=Math.abs(p.p2c(w)-e),x=Math.abs(d.p2c(E)-t),T=S*S+x*x;T<i&&(i=T,s=[f,l/c])}if(h.bars.show&&!s){var N=h.bars.align=="left"?0:-h.bars.barWidth/2,C=N+h.bars.barWidth;for(l=0;l<v.length;l+=c){var w=v[l],E=v[l+1],k=v[l+2];if(w==null)continue;if(u[f].bars.horizontal?m<=Math.max(k,w)&&m>=Math.min(k,w)&&g>=E+N&&g<=E+C:m>=w+N&&m<=w+C&&g>=Math.min(k,E)&&g<=Math.max(k,E))s=[f,l/c]}}}return s?(f=s[0],l=s[1],c=u[f].datapoints.pointsize,{datapoint:u[f].datapoints.points.slice(l*c,(l+1)*c),dataIndex:l,series:u[f],seriesIndex:f}):null}function at(e){a.grid.hoverable&&ct("plothover",e,function(e){return e["hoverable"]!=0})}function ft(e){a.grid.hoverable&&ct("plothover",e,function(e){return!1})}function lt(e){ct("plotclick",e,function(e){return e["clickable"]!=0})}function ct(e,n,r){var i=c.offset(),s=n.pageX-i.left-m.left,o=n.pageY-i.top-m.top,u=L({left:s,top:o});u.pageX=n.pageX,u.pageY=n.pageY;var f=ut(s,o,r);f&&(f.pageX=parseInt(f.series.xaxis.p2c(f.datapoint[0])+i.left+m.left,10),f.pageY=parseInt(f.series.yaxis.p2c(f.datapoint[1])+i.top+m.top,10));if(a.grid.autoHighlight){for(var l=0;l<st.length;++l){var h=st[l];h.auto==e&&(!f||h.series!=f.series||h.point[0]!=f.datapoint[0]||h.point[1]!=f.datapoint[1])&&vt(h.series,h.point)}f&&dt(f.series,f.datapoint,e)}t.trigger(e,[u,f])}function ht(){var e=a.interaction.redrawOverlayInterval;if(e==-1){pt();return}ot||(ot=setTimeout(pt,e))}function pt(){ot=null,p.save(),l.clear(),p.translate(m.left,m.top);var e,t;for(e=0;e<st.length;++e)t=st[e],t.series.bars.show?yt(t.series,t.point):gt(t.series,t.point);p.restore(),E(b.drawOverlay,[p])}function dt(e,t,n){typeof e=="number"&&(e=u[e]);if(typeof t=="number"){var r=e.datapoints.pointsize;t=e.datapoints.points.slice(r*t,r*(t+1))}var i=mt(e,t);i==-1?(st.push({series:e,point:t,auto:n}),ht()):n||(st[i].auto=!1)}function vt(e,t){if(e==null&&t==null){st=[],ht();return}typeof e=="number"&&(e=u[e]);if(typeof t=="number"){var n=e.datapoints.pointsize;t=e.datapoints.points.slice(n*t,n*(t+1))}var r=mt(e,t);r!=-1&&(st.splice(r,1),ht())}function mt(e,t){for(var n=0;n<st.length;++n){var r=st[n];if(r.series==e&&r.point[0]==t[0]&&r.point[1]==t[1])return n}return-1}function gt(t,n){var r=n[0],i=n[1],s=t.xaxis,o=t.yaxis,u=typeof t.highlightColor=="string"?t.highlightColor:e.color.parse(t.color).scale("a",.5).toString();if(r<s.min||r>s.max||i<o.min||i>o.max)return;var a=t.points.radius+t.points.lineWidth/2;p.lineWidth=a,p.strokeStyle=u;var f=1.5*a;r=s.p2c(r),i=o.p2c(i),p.beginPath(),t.points.symbol=="circle"?p.arc(r,i,f,0,2*Math.PI,!1):t.points.symbol(p,r,i,f,!1),p.closePath(),p.stroke()}function yt(t,n){var r=typeof t.highlightColor=="string"?t.highlightColor:e.color.parse(t.color).scale("a",.5).toString(),i=r,s=t.bars.align=="left"?0:-t.bars.barWidth/2;p.lineWidth=t.bars.lineWidth,p.strokeStyle=r,tt(n[0],n[1],n[2]||0,s,s+t.bars.barWidth,0,function(){return i},t.xaxis,t.yaxis,p,t.bars.horizontal,t.bars.lineWidth)}function bt(t,n,r,i){if(typeof t=="string")return t;var s=h.createLinearGradient(0,r,0,n);for(var o=0,u=t.colors.length;o<u;++o){var a=t.colors[o];if(typeof a!="string"){var f=e.color.parse(i);a.brightness!=null&&(f=f.scale("rgb",a.brightness)),a.opacity!=null&&(f.a*=a.opacity),a=f.toString()}s.addColorStop(o/(u-1),a)}return s}var u=[],a={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:!0,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:.85,sorted:null},xaxis:{show:null,position:"bottom",mode:null,font:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null},yaxis:{autoscaleMargin:.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:!1,radius:3,lineWidth:2,fill:!0,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:!1,fillColor:null,steps:!1},bars:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,align:"left",horizontal:!1,zero:!0},shadowSize:3,highlightColor:null},grid:{show:!0,aboveData:!1,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,margin:0,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:!1,hoverable:!1,autoHighlight:!0,mouseActiveRadius:10},interaction:{redrawOverlayInterval:1e3/60},hooks:{}},f=null,l=null,c=null,h=null,p=null,d=[],v=[],m={left:0,right:0,top:0,bottom
+:0},g=0,y=0,b={processOptions:[],processRawData:[],processDatapoints:[],processOffset:[],drawBackground:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},w=this;w.setData=T,w.setupGrid=R,w.draw=V,w.getPlaceholder=function(){return t},w.getCanvas=function(){return f.element},w.getPlotOffset=function(){return m},w.width=function(){return g},w.height=function(){return y},w.offset=function(){var e=c.offset();return e.left+=m.left,e.top+=m.top,e},w.getData=function(){return u},w.getAxes=function(){var t={},n;return e.each(d.concat(v),function(e,n){n&&(t[n.direction+(n.n!=1?n.n:"")+"axis"]=n)}),t},w.getXAxes=function(){return d},w.getYAxes=function(){return v},w.c2p=L,w.p2c=A,w.getOptions=function(){return a},w.highlight=dt,w.unhighlight=vt,w.triggerRedrawOverlay=ht,w.pointOffset=function(e){return{left:parseInt(d[C(e,"x")-1].p2c(+e.x)+m.left,10),top:parseInt(v[C(e,"y")-1].p2c(+e.y)+m.top,10)}},w.shutdown=H,w.resize=function(){var e=t.width(),n=t.height();f.resize(e,n),l.resize(e,n)},w.hooks=b,S(w),x(s),D(),T(r),R(),V(),P();var st=[],ot=null}function i(e,t){return t*Math.floor(e/t)}var t=Object.prototype.hasOwnProperty;n.prototype.resize=function(e,t){if(e<=0||t<=0)throw new Error("Invalid dimensions for plot, width = "+e+", height = "+t);var n=this.element,r=this.context,i=this.pixelRatio;this.width!=e&&(n.width=e*i,n.style.width=e+"px",this.width=e),this.height!=t&&(n.height=t*i,n.style.height=t+"px",this.height=t),r.restore(),r.save(),r.scale(i,i)},n.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)},n.prototype.render=function(){var e=this._textCache;for(var n in e)if(t.call(e,n)){var r=this.getTextLayer(n),i=e[n];r.hide();for(var s in i)if(t.call(i,s)){var o=i[s];for(var u in o)if(t.call(o,u)){var a=o[u].positions;for(var f=0,l;l=a[f];f++)l.active?l.rendered||(r.append(l.element),l.rendered=!0):(a.splice(f--,1),l.rendered&&l.element.detach());a.length==0&&delete o[u]}}r.show()}},n.prototype.getTextLayer=function(t){var n=this.text[t];return n==null&&(this.textContainer==null&&(this.textContainer=e("<div class='flot-text'></div>").css({position:"absolute",top:0,left:0,bottom:0,right:0,"font-size":"smaller",color:"#545454"}).insertAfter(this.element)),n=this.text[t]=e("<div></div>").addClass(t).css({position:"absolute",top:0,left:0,bottom:0,right:0}).appendTo(this.textContainer)),n},n.prototype.getTextInfo=function(t,n,r,i,s){var o,u,a,f;n=""+n,typeof r=="object"?o=r.style+" "+r.variant+" "+r.weight+" "+r.size+"px/"+r.lineHeight+"px "+r.family:o=r,u=this._textCache[t],u==null&&(u=this._textCache[t]={}),a=u[o],a==null&&(a=u[o]={}),f=a[n];if(f==null){var l=e("<div></div>").html(n).css({position:"absolute","max-width":s,top:-9999}).appendTo(this.getTextLayer(t));typeof r=="object"?l.css({font:o,color:r.color}):typeof r=="string"&&l.addClass(r),f=a[n]={width:l.outerWidth(!0),height:l.outerHeight(!0),element:l,positions:[]},l.detach()}return f},n.prototype.addText=function(e,t,n,r,i,s,o,u,a){var f=this.getTextInfo(e,r,i,s,o),l=f.positions;u=="center"?t-=f.width/2:u=="right"&&(t-=f.width),a=="middle"?n-=f.height/2:a=="bottom"&&(n-=f.height);for(var c=0,h;h=l[c];c++)if(h.x==t&&h.y==n){h.active=!0;return}h={active:!0,rendered:!1,element:l.length?f.element.clone():f.element,x:t,y:n},l.push(h),h.element.css({top:Math.round(n),left:Math.round(t),"text-align":u})},n.prototype.removeText=function(e,n,r,i,s,o){if(i==null){var u=this._textCache[e];if(u!=null)for(var a in u)if(t.call(u,a)){var f=u[a];for(var l in f)if(t.call(f,l)){var c=f[l].positions;for(var h=0,p;p=c[h];h++)p.active=!1}}}else{var c=this.getTextInfo(e,i,s,o).positions;for(var h=0,p;p=c[h];h++)p.x==n&&p.y==r&&(p.active=!1)}},e.plot=function(t,n,i){var s=new r(e(t),n,i,e.plot.plugins);return s},e.plot.version="0.8.1",e.plot.plugins=[],e.fn.plot=function(t,n){return this.each(function(){e.plot(this,t,n)})}}(jQuery); \ No newline at end of file
diff --git a/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.time.js b/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.time.js
new file mode 100644
index 0000000000..7ec12753c1
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.time.js
@@ -0,0 +1,431 @@
+/* Pretty handling of time axes.
+
+Copyright (c) 2007-2013 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Set axis.mode to "time" to enable. See the section "Time series data" in
+API.txt for details.
+
+*/
+
+(function($) {
+
+ var options = {
+ xaxis: {
+ timezone: null, // "browser" for local to the client or timezone for timezone-js
+ timeformat: null, // format string to use
+ twelveHourClock: false, // 12 or 24 time in time mode
+ monthNames: null // list of names of months
+ }
+ };
+
+ // round to nearby lower multiple of base
+
+ function floorInBase(n, base) {
+ return base * Math.floor(n / base);
+ }
+
+ // Returns a string with the date d formatted according to fmt.
+ // A subset of the Open Group's strftime format is supported.
+
+ function formatDate(d, fmt, monthNames, dayNames) {
+
+ if (typeof d.strftime == "function") {
+ return d.strftime(fmt);
+ }
+
+ var leftPad = function(n, pad) {
+ n = "" + n;
+ pad = "" + (pad == null ? "0" : pad);
+ return n.length == 1 ? pad + n : n;
+ };
+
+ var r = [];
+ var escape = false;
+ var hours = d.getHours();
+ var isAM = hours < 12;
+
+ if (monthNames == null) {
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+ }
+
+ if (dayNames == null) {
+ dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ }
+
+ var hours12;
+
+ if (hours > 12) {
+ hours12 = hours - 12;
+ } else if (hours == 0) {
+ hours12 = 12;
+ } else {
+ hours12 = hours;
+ }
+
+ for (var i = 0; i < fmt.length; ++i) {
+
+ var c = fmt.charAt(i);
+
+ if (escape) {
+ switch (c) {
+ case 'a': c = "" + dayNames[d.getDay()]; break;
+ case 'b': c = "" + monthNames[d.getMonth()]; break;
+ case 'd': c = leftPad(d.getDate()); break;
+ case 'e': c = leftPad(d.getDate(), " "); break;
+ case 'h': // For back-compat with 0.7; remove in 1.0
+ case 'H': c = leftPad(hours); break;
+ case 'I': c = leftPad(hours12); break;
+ case 'l': c = leftPad(hours12, " "); break;
+ case 'm': c = leftPad(d.getMonth() + 1); break;
+ case 'M': c = leftPad(d.getMinutes()); break;
+ // quarters not in Open Group's strftime specification
+ case 'q':
+ c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
+ case 'S': c = leftPad(d.getSeconds()); break;
+ case 'y': c = leftPad(d.getFullYear() % 100); break;
+ case 'Y': c = "" + d.getFullYear(); break;
+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
+ case 'w': c = "" + d.getDay(); break;
+ }
+ r.push(c);
+ escape = false;
+ } else {
+ if (c == "%") {
+ escape = true;
+ } else {
+ r.push(c);
+ }
+ }
+ }
+
+ return r.join("");
+ }
+
+ // To have a consistent view of time-based data independent of which time
+ // zone the client happens to be in we need a date-like object independent
+ // of time zones. This is done through a wrapper that only calls the UTC
+ // versions of the accessor methods.
+
+ function makeUtcWrapper(d) {
+
+ function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
+ sourceObj[sourceMethod] = function() {
+ return targetObj[targetMethod].apply(targetObj, arguments);
+ };
+ };
+
+ var utc = {
+ date: d
+ };
+
+ // support strftime, if found
+
+ if (d.strftime != undefined) {
+ addProxyMethod(utc, "strftime", d, "strftime");
+ }
+
+ addProxyMethod(utc, "getTime", d, "getTime");
+ addProxyMethod(utc, "setTime", d, "setTime");
+
+ var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
+
+ for (var p = 0; p < props.length; p++) {
+ addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
+ addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
+ }
+
+ return utc;
+ };
+
+ // select time zone strategy. This returns a date-like object tied to the
+ // desired timezone
+
+ function dateGenerator(ts, opts) {
+ if (opts.timezone == "browser") {
+ return new Date(ts);
+ } else if (!opts.timezone || opts.timezone == "utc") {
+ return makeUtcWrapper(new Date(ts));
+ } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
+ var d = new timezoneJS.Date();
+ // timezone-js is fickle, so be sure to set the time zone before
+ // setting the time.
+ d.setTimezone(opts.timezone);
+ d.setTime(ts);
+ return d;
+ } else {
+ return makeUtcWrapper(new Date(ts));
+ }
+ }
+
+ // map of app. size of time units in milliseconds
+
+ var timeUnitSize = {
+ "second": 1000,
+ "minute": 60 * 1000,
+ "hour": 60 * 60 * 1000,
+ "day": 24 * 60 * 60 * 1000,
+ "month": 30 * 24 * 60 * 60 * 1000,
+ "quarter": 3 * 30 * 24 * 60 * 60 * 1000,
+ "year": 365.2425 * 24 * 60 * 60 * 1000
+ };
+
+ // the allowed tick sizes, after 1 year we use
+ // an integer algorithm
+
+ var baseSpec = [
+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
+ [30, "second"],
+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
+ [30, "minute"],
+ [1, "hour"], [2, "hour"], [4, "hour"],
+ [8, "hour"], [12, "hour"],
+ [1, "day"], [2, "day"], [3, "day"],
+ [0.25, "month"], [0.5, "month"], [1, "month"],
+ [2, "month"]
+ ];
+
+ // we don't know which variant(s) we'll need yet, but generating both is
+ // cheap
+
+ var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
+ [1, "year"]]);
+ var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
+ [1, "year"]]);
+
+ function init(plot) {
+ plot.hooks.processOptions.push(function (plot, options) {
+ $.each(plot.getAxes(), function(axisName, axis) {
+
+ var opts = axis.options;
+
+ if (opts.mode == "time") {
+ axis.tickGenerator = function(axis) {
+
+ var ticks = [];
+ var d = dateGenerator(axis.min, opts);
+ var minSize = 0;
+
+ // make quarter use a possibility if quarters are
+ // mentioned in either of these options
+
+ var spec = (opts.tickSize && opts.tickSize[1] ===
+ "quarter") ||
+ (opts.minTickSize && opts.minTickSize[1] ===
+ "quarter") ? specQuarters : specMonths;
+
+ if (opts.minTickSize != null) {
+ if (typeof opts.tickSize == "number") {
+ minSize = opts.tickSize;
+ } else {
+ minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
+ }
+ }
+
+ for (var i = 0; i < spec.length - 1; ++i) {
+ if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
+ break;
+ }
+ }
+
+ var size = spec[i][0];
+ var unit = spec[i][1];
+
+ // special-case the possibility of several years
+
+ if (unit == "year") {
+
+ // if given a minTickSize in years, just use it,
+ // ensuring that it's an integer
+
+ if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
+ size = Math.floor(opts.minTickSize[0]);
+ } else {
+
+ var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
+ var norm = (axis.delta / timeUnitSize.year) / magn;
+
+ if (norm < 1.5) {
+ size = 1;
+ } else if (norm < 3) {
+ size = 2;
+ } else if (norm < 7.5) {
+ size = 5;
+ } else {
+ size = 10;
+ }
+
+ size *= magn;
+ }
+
+ // minimum size for years is 1
+
+ if (size < 1) {
+ size = 1;
+ }
+ }
+
+ axis.tickSize = opts.tickSize || [size, unit];
+ var tickSize = axis.tickSize[0];
+ unit = axis.tickSize[1];
+
+ var step = tickSize * timeUnitSize[unit];
+
+ if (unit == "second") {
+ d.setSeconds(floorInBase(d.getSeconds(), tickSize));
+ } else if (unit == "minute") {
+ d.setMinutes(floorInBase(d.getMinutes(), tickSize));
+ } else if (unit == "hour") {
+ d.setHours(floorInBase(d.getHours(), tickSize));
+ } else if (unit == "month") {
+ d.setMonth(floorInBase(d.getMonth(), tickSize));
+ } else if (unit == "quarter") {
+ d.setMonth(3 * floorInBase(d.getMonth() / 3,
+ tickSize));
+ } else if (unit == "year") {
+ d.setFullYear(floorInBase(d.getFullYear(), tickSize));
+ }
+
+ // reset smaller components
+
+ d.setMilliseconds(0);
+
+ if (step >= timeUnitSize.minute) {
+ d.setSeconds(0);
+ }
+ if (step >= timeUnitSize.hour) {
+ d.setMinutes(0);
+ }
+ if (step >= timeUnitSize.day) {
+ d.setHours(0);
+ }
+ if (step >= timeUnitSize.day * 4) {
+ d.setDate(1);
+ }
+ if (step >= timeUnitSize.month * 2) {
+ d.setMonth(floorInBase(d.getMonth(), 3));
+ }
+ if (step >= timeUnitSize.quarter * 2) {
+ d.setMonth(floorInBase(d.getMonth(), 6));
+ }
+ if (step >= timeUnitSize.year) {
+ d.setMonth(0);
+ }
+
+ var carry = 0;
+ var v = Number.NaN;
+ var prev;
+
+ do {
+
+ prev = v;
+ v = d.getTime();
+ ticks.push(v);
+
+ if (unit == "month" || unit == "quarter") {
+ if (tickSize < 1) {
+
+ // a bit complicated - we'll divide the
+ // month/quarter up but we need to take
+ // care of fractions so we don't end up in
+ // the middle of a day
+
+ d.setDate(1);
+ var start = d.getTime();
+ d.setMonth(d.getMonth() +
+ (unit == "quarter" ? 3 : 1));
+ var end = d.getTime();
+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
+ carry = d.getHours();
+ d.setHours(0);
+ } else {
+ d.setMonth(d.getMonth() +
+ tickSize * (unit == "quarter" ? 3 : 1));
+ }
+ } else if (unit == "year") {
+ d.setFullYear(d.getFullYear() + tickSize);
+ } else {
+ d.setTime(v + step);
+ }
+ } while (v < axis.max && v != prev);
+
+ return ticks;
+ };
+
+ axis.tickFormatter = function (v, axis) {
+
+ var d = dateGenerator(v, axis.options);
+
+ // first check global format
+
+ if (opts.timeformat != null) {
+ return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
+ }
+
+ // possibly use quarters if quarters are mentioned in
+ // any of these places
+
+ var useQuarters = (axis.options.tickSize &&
+ axis.options.tickSize[1] == "quarter") ||
+ (axis.options.minTickSize &&
+ axis.options.minTickSize[1] == "quarter");
+
+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
+ var span = axis.max - axis.min;
+ var suffix = (opts.twelveHourClock) ? " %p" : "";
+ var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
+ var fmt;
+
+ if (t < timeUnitSize.minute) {
+ fmt = hourCode + ":%M:%S" + suffix;
+ } else if (t < timeUnitSize.day) {
+ if (span < 2 * timeUnitSize.day) {
+ fmt = hourCode + ":%M" + suffix;
+ } else {
+ fmt = "%b %d " + hourCode + ":%M" + suffix;
+ }
+ } else if (t < timeUnitSize.month) {
+ fmt = "%b %d";
+ } else if ((useQuarters && t < timeUnitSize.quarter) ||
+ (!useQuarters && t < timeUnitSize.year)) {
+ if (span < timeUnitSize.year) {
+ fmt = "%b";
+ } else {
+ fmt = "%b %Y";
+ }
+ } else if (useQuarters && t < timeUnitSize.year) {
+ if (span < timeUnitSize.year) {
+ fmt = "Q%q";
+ } else {
+ fmt = "Q%q %Y";
+ }
+ } else {
+ fmt = "%Y";
+ }
+
+ var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
+
+ return rt;
+ };
+ }
+ });
+ });
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'time',
+ version: '1.0'
+ });
+
+ // Time-axis support used to be in Flot core, which exposed the
+ // formatDate function on the plot object. Various plugins depend
+ // on the function, so we need to re-expose it here.
+
+ $.plot.formatDate = formatDate;
+
+})(jQuery);
diff --git a/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.time.min.js b/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.time.min.js
new file mode 100644
index 0000000000..21d8477238
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/jquery.flot-0.8.1.time.min.js
@@ -0,0 +1,9 @@
+/* Pretty handling of time axes.
+
+Copyright (c) 2007-2013 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Set axis.mode to "time" to enable. See the section "Time series data" in
+API.txt for details.
+
+*/(function(e){function n(e,t){return t*Math.floor(e/t)}function r(e,t,n,r){if(typeof e.strftime=="function")return e.strftime(t);var i=function(e,t){return e=""+e,t=""+(t==null?"0":t),e.length==1?t+e:e},s=[],o=!1,u=e.getHours(),a=u<12;n==null&&(n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]),r==null&&(r=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]);var f;u>12?f=u-12:u==0?f=12:f=u;for(var l=0;l<t.length;++l){var c=t.charAt(l);if(o){switch(c){case"a":c=""+r[e.getDay()];break;case"b":c=""+n[e.getMonth()];break;case"d":c=i(e.getDate());break;case"e":c=i(e.getDate()," ");break;case"h":case"H":c=i(u);break;case"I":c=i(f);break;case"l":c=i(f," ");break;case"m":c=i(e.getMonth()+1);break;case"M":c=i(e.getMinutes());break;case"q":c=""+(Math.floor(e.getMonth()/3)+1);break;case"S":c=i(e.getSeconds());break;case"y":c=i(e.getFullYear()%100);break;case"Y":c=""+e.getFullYear();break;case"p":c=a?"am":"pm";break;case"P":c=a?"AM":"PM";break;case"w":c=""+e.getDay()}s.push(c),o=!1}else c=="%"?o=!0:s.push(c)}return s.join("")}function i(e){function t(e,t,n,r){e[t]=function(){return n[r].apply(n,arguments)}}var n={date:e};e.strftime!=undefined&&t(n,"strftime",e,"strftime"),t(n,"getTime",e,"getTime"),t(n,"setTime",e,"setTime");var r=["Date","Day","FullYear","Hours","Milliseconds","Minutes","Month","Seconds"];for(var i=0;i<r.length;i++)t(n,"get"+r[i],e,"getUTC"+r[i]),t(n,"set"+r[i],e,"setUTC"+r[i]);return n}function s(e,t){if(t.timezone=="browser")return new Date(e);if(!t.timezone||t.timezone=="utc")return i(new Date(e));if(typeof timezoneJS!="undefined"&&typeof timezoneJS.Date!="undefined"){var n=new timezoneJS.Date;return n.setTimezone(t.timezone),n.setTime(e),n}return i(new Date(e))}function l(t){t.hooks.processOptions.push(function(t,i){e.each(t.getAxes(),function(e,t){var i=t.options;i.mode=="time"&&(t.tickGenerator=function(e){var t=[],r=s(e.min,i),u=0,l=i.tickSize&&i.tickSize[1]==="quarter"||i.minTickSize&&i.minTickSize[1]==="quarter"?f:a;i.minTickSize!=null&&(typeof i.tickSize=="number"?u=i.tickSize:u=i.minTickSize[0]*o[i.minTickSize[1]]);for(var c=0;c<l.length-1;++c)if(e.delta<(l[c][0]*o[l[c][1]]+l[c+1][0]*o[l[c+1][1]])/2&&l[c][0]*o[l[c][1]]>=u)break;var h=l[c][0],p=l[c][1];if(p=="year"){if(i.minTickSize!=null&&i.minTickSize[1]=="year")h=Math.floor(i.minTickSize[0]);else{var d=Math.pow(10,Math.floor(Math.log(e.delta/o.year)/Math.LN10)),v=e.delta/o.year/d;v<1.5?h=1:v<3?h=2:v<7.5?h=5:h=10,h*=d}h<1&&(h=1)}e.tickSize=i.tickSize||[h,p];var m=e.tickSize[0];p=e.tickSize[1];var g=m*o[p];p=="second"?r.setSeconds(n(r.getSeconds(),m)):p=="minute"?r.setMinutes(n(r.getMinutes(),m)):p=="hour"?r.setHours(n(r.getHours(),m)):p=="month"?r.setMonth(n(r.getMonth(),m)):p=="quarter"?r.setMonth(3*n(r.getMonth()/3,m)):p=="year"&&r.setFullYear(n(r.getFullYear(),m)),r.setMilliseconds(0),g>=o.minute&&r.setSeconds(0),g>=o.hour&&r.setMinutes(0),g>=o.day&&r.setHours(0),g>=o.day*4&&r.setDate(1),g>=o.month*2&&r.setMonth(n(r.getMonth(),3)),g>=o.quarter*2&&r.setMonth(n(r.getMonth(),6)),g>=o.year&&r.setMonth(0);var y=0,b=Number.NaN,w;do{w=b,b=r.getTime(),t.push(b);if(p=="month"||p=="quarter")if(m<1){r.setDate(1);var E=r.getTime();r.setMonth(r.getMonth()+(p=="quarter"?3:1));var S=r.getTime();r.setTime(b+y*o.hour+(S-E)*m),y=r.getHours(),r.setHours(0)}else r.setMonth(r.getMonth()+m*(p=="quarter"?3:1));else p=="year"?r.setFullYear(r.getFullYear()+m):r.setTime(b+g)}while(b<e.max&&b!=w);return t},t.tickFormatter=function(e,t){var n=s(e,t.options);if(i.timeformat!=null)return r(n,i.timeformat,i.monthNames,i.dayNames);var u=t.options.tickSize&&t.options.tickSize[1]=="quarter"||t.options.minTickSize&&t.options.minTickSize[1]=="quarter",a=t.tickSize[0]*o[t.tickSize[1]],f=t.max-t.min,l=i.twelveHourClock?" %p":"",c=i.twelveHourClock?"%I":"%H",h;a<o.minute?h=c+":%M:%S"+l:a<o.day?f<2*o.day?h=c+":%M"+l:h="%b %d "+c+":%M"+l:a<o.month?h="%b %d":u&&a<o.quarter||!u&&a<o.year?f<o.year?h="%b":h="%b %Y":u&&a<o.year?f<o.year?h="Q%q":h="Q%q %Y":h="%Y";var p=r(n,h,i.monthNames,i.dayNames);return p})})})}var t={xaxis:{timezone:null,timeformat:null,twelveHourClock:!1,monthNames:null}},o={second:1e3,minute:6e4,hour:36e5,day:864e5,month:2592e6,quarter:7776e6,year:525949.2*60*1e3},u=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"]],a=u.concat([[3,"month"],[6,"month"],[1,"year"]]),f=u.concat([[1,"quarter"],[2,"quarter"],[1,"year"]]);e.plot.plugins.push({init:l,options:t,name:"time",version:"1.0"}),e.plot.formatDate=r})(jQuery); \ No newline at end of file
diff --git a/deps/rabbitmq_management/priv/www/js/json2-2016.10.28.js b/deps/rabbitmq_management/priv/www/js/json2-2016.10.28.js
new file mode 100644
index 0000000000..c69dd69ff1
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/json2-2016.10.28.js
@@ -0,0 +1,506 @@
+// json2.js
+// 2016-10-28
+// Public Domain.
+// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+// See https://www.JSON.org/js.html
+// This code should be minified before deployment.
+// See https://javascript.crockford.com/jsmin.html
+
+// USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+// NOT CONTROL.
+
+// This file creates a global JSON object containing two methods: stringify
+// and parse. This file provides the ES5 JSON capability to ES3 systems.
+// If a project might run on IE8 or earlier, then this file should be included.
+// This file does nothing on ES5 systems.
+
+// JSON.stringify(value, replacer, space)
+// value any JavaScript value, usually an object or array.
+// replacer an optional parameter that determines how object
+// values are stringified for objects. It can be a
+// function or an array of strings.
+// space an optional parameter that specifies the indentation
+// of nested structures. If it is omitted, the text will
+// be packed without extra whitespace. If it is a number,
+// it will specify the number of spaces to indent at each
+// level. If it is a string (such as "\t" or "&nbsp;"),
+// it contains the characters used to indent at each level.
+// This method produces a JSON text from a JavaScript value.
+// When an object value is found, if the object contains a toJSON
+// method, its toJSON method will be called and the result will be
+// stringified. A toJSON method does not serialize: it returns the
+// value represented by the name/value pair that should be serialized,
+// or undefined if nothing should be serialized. The toJSON method
+// will be passed the key associated with the value, and this will be
+// bound to the value.
+
+// For example, this would serialize Dates as ISO strings.
+
+// Date.prototype.toJSON = function (key) {
+// function f(n) {
+// // Format integers to have at least two digits.
+// return (n < 10)
+// ? "0" + n
+// : n;
+// }
+// return this.getUTCFullYear() + "-" +
+// f(this.getUTCMonth() + 1) + "-" +
+// f(this.getUTCDate()) + "T" +
+// f(this.getUTCHours()) + ":" +
+// f(this.getUTCMinutes()) + ":" +
+// f(this.getUTCSeconds()) + "Z";
+// };
+
+// You can provide an optional replacer method. It will be passed the
+// key and value of each member, with this bound to the containing
+// object. The value that is returned from your method will be
+// serialized. If your method returns undefined, then the member will
+// be excluded from the serialization.
+
+// If the replacer parameter is an array of strings, then it will be
+// used to select the members to be serialized. It filters the results
+// such that only members with keys listed in the replacer array are
+// stringified.
+
+// Values that do not have JSON representations, such as undefined or
+// functions, will not be serialized. Such values in objects will be
+// dropped; in arrays they will be replaced with null. You can use
+// a replacer function to replace those with JSON values.
+
+// JSON.stringify(undefined) returns undefined.
+
+// The optional space parameter produces a stringification of the
+// value that is filled with line breaks and indentation to make it
+// easier to read.
+
+// If the space parameter is a non-empty string, then that string will
+// be used for indentation. If the space parameter is a number, then
+// the indentation will be that many spaces.
+
+// Example:
+
+// text = JSON.stringify(["e", {pluribus: "unum"}]);
+// // text is '["e",{"pluribus":"unum"}]'
+
+// text = JSON.stringify(["e", {pluribus: "unum"}], null, "\t");
+// // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+// text = JSON.stringify([new Date()], function (key, value) {
+// return this[key] instanceof Date
+// ? "Date(" + this[key] + ")"
+// : value;
+// });
+// // text is '["Date(---current time---)"]'
+
+// JSON.parse(text, reviver)
+// This method parses a JSON text to produce an object or array.
+// It can throw a SyntaxError exception.
+
+// The optional reviver parameter is a function that can filter and
+// transform the results. It receives each of the keys and values,
+// and its return value is used instead of the original value.
+// If it returns what it received, then the structure is not modified.
+// If it returns undefined then the member is deleted.
+
+// Example:
+
+// // Parse the text. Values that look like ISO date strings will
+// // be converted to Date objects.
+
+// myData = JSON.parse(text, function (key, value) {
+// var a;
+// if (typeof value === "string") {
+// a =
+// /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+// if (a) {
+// return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+// +a[5], +a[6]));
+// }
+// }
+// return value;
+// });
+
+// myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+// var d;
+// if (typeof value === "string" &&
+// value.slice(0, 5) === "Date(" &&
+// value.slice(-1) === ")") {
+// d = new Date(value.slice(5, -1));
+// if (d) {
+// return d;
+// }
+// }
+// return value;
+// });
+
+// This is a reference implementation. You are free to copy, modify, or
+// redistribute.
+
+/*jslint
+ eval, for, this
+*/
+
+/*property
+ JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (typeof JSON !== "object") {
+ JSON = {};
+}
+
+(function () {
+ "use strict";
+
+ var rx_one = /^[\],:{}\s]*$/;
+ var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
+ var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
+ var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
+ var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+ var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10
+ ? "0" + n
+ : n;
+ }
+
+ function this_value() {
+ return this.valueOf();
+ }
+
+ if (typeof Date.prototype.toJSON !== "function") {
+
+ Date.prototype.toJSON = function () {
+
+ return isFinite(this.valueOf())
+ ? this.getUTCFullYear() + "-" +
+ f(this.getUTCMonth() + 1) + "-" +
+ f(this.getUTCDate()) + "T" +
+ f(this.getUTCHours()) + ":" +
+ f(this.getUTCMinutes()) + ":" +
+ f(this.getUTCSeconds()) + "Z"
+ : null;
+ };
+
+ Boolean.prototype.toJSON = this_value;
+ Number.prototype.toJSON = this_value;
+ String.prototype.toJSON = this_value;
+ }
+
+ var gap;
+ var indent;
+ var meta;
+ var rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ rx_escapable.lastIndex = 0;
+ return rx_escapable.test(string)
+ ? "\"" + string.replace(rx_escapable, function (a) {
+ var c = meta[a];
+ return typeof c === "string"
+ ? c
+ : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + "\""
+ : "\"" + string + "\"";
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i; // The loop counter.
+ var k; // The member key.
+ var v; // The member value.
+ var length;
+ var mind = gap;
+ var partial;
+ var value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === "object" &&
+ typeof value.toJSON === "function") {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === "function") {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case "string":
+ return quote(value);
+
+ case "number":
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value)
+ ? String(value)
+ : "null";
+
+ case "boolean":
+ case "null":
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce "null". The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is "object", we might be dealing with an object or an array or
+// null.
+
+ case "object":
+
+// Due to a specification blunder in ECMAScript, typeof null is "object",
+// so watch out for that case.
+
+ if (!value) {
+ return "null";
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === "[object Array]") {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || "null";
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0
+ ? "[]"
+ : gap
+ ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]"
+ : "[" + partial.join(",") + "]";
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === "object") {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ if (typeof rep[i] === "string") {
+ k = rep[i];
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (
+ gap
+ ? ": "
+ : ":"
+ ) + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (
+ gap
+ ? ": "
+ : ":"
+ ) + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0
+ ? "{}"
+ : gap
+ ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}"
+ : "{" + partial.join(",") + "}";
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== "function") {
+ meta = { // table of character substitutions
+ "\b": "\\b",
+ "\t": "\\t",
+ "\n": "\\n",
+ "\f": "\\f",
+ "\r": "\\r",
+ "\"": "\\\"",
+ "\\": "\\\\"
+ };
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = "";
+ indent = "";
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === "number") {
+ for (i = 0; i < space; i += 1) {
+ indent += " ";
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === "string") {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== "function" &&
+ (typeof replacer !== "object" ||
+ typeof replacer.length !== "number")) {
+ throw new Error("JSON.stringify");
+ }
+
+// Make a fake root object containing our value under the key of "".
+// Return the result of stringifying the value.
+
+ return str("", {"": value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== "function") {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k;
+ var v;
+ var value = holder[key];
+ if (value && typeof value === "object") {
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ rx_dangerous.lastIndex = 0;
+ if (rx_dangerous.test(text)) {
+ text = text.replace(rx_dangerous, function (a) {
+ return "\\u" +
+ ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with "()" and "new"
+// because they can cause invocation, and "=" because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with "@" (a non-JSON character). Second, we
+// replace all simple value tokens with "]" characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or "]" or
+// "," or ":" or "{" or "}". If that is so, then the text is safe for eval.
+
+ if (
+ rx_one.test(
+ text
+ .replace(rx_two, "@")
+ .replace(rx_three, "]")
+ .replace(rx_four, "")
+ )
+ ) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The "{" operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval("(" + text + ")");
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return (typeof reviver === "function")
+ ? walk({"": j}, "")
+ : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError("JSON.parse");
+ };
+ }
+}());
diff --git a/deps/rabbitmq_management/priv/www/js/main.js b/deps/rabbitmq_management/priv/www/js/main.js
new file mode 100644
index 0000000000..46da9698d9
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/main.js
@@ -0,0 +1,1717 @@
+$(document).ready(function() {
+ if (enable_uaa) {
+ get(uaa_location + "/info", "application/json", function(req) {
+ if (req.status !== 200) {
+ replace_content('outer', format('login_uaa', {}));
+ replace_content('login-status', '<p class="warning">' + uaa_location + " does not appear to be a running UAA instance or may not have a trusted SSL certificate" + '</p> <button id="loginWindow" onclick="uaa_login_window()">Single Sign On</button>');
+ } else {
+ replace_content('outer', format('login_uaa', {}));
+ }
+ });
+ } else {
+ replace_content('outer', format('login', {}));
+ start_app_login();
+ }
+});
+
+function dispatcher_add(fun) {
+ dispatcher_modules.push(fun);
+ if (dispatcher_modules.length == extension_count) {
+ start_app();
+ }
+}
+
+function dispatcher() {
+ for (var i in dispatcher_modules) {
+ dispatcher_modules[i](this);
+ }
+}
+
+function set_auth_pref(userinfo) {
+ // clear a local storage value used by earlier versions
+ clear_local_pref('auth');
+
+ var b64 = b64_encode_utf8(userinfo);
+ var date = new Date();
+ var login_session_timeout = get_login_session_timeout();
+
+ if (login_session_timeout) {
+ date.setMinutes(date.getMinutes() + login_session_timeout);
+ } else {
+ // 8 hours from now
+ date.setHours(date.getHours() + 8);
+ }
+ store_cookie_value_with_expiration('auth', encodeURIComponent(b64), date);
+}
+
+function getParameterByName(name) {
+ var match = RegExp('[#&]' + name + '=([^&]*)').exec(window.location.hash);
+ return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
+}
+
+function getAccessToken() {
+ return getParameterByName('access_token');
+}
+
+function start_app_login() {
+ app = new Sammy.Application(function () {
+ this.get('#/', function() {});
+ this.put('#/login', function() {
+ username = this.params['username'];
+ password = this.params['password'];
+ set_auth_pref(username + ':' + password);
+ check_login();
+ });
+ });
+ if (enable_uaa) {
+ var token = getAccessToken();
+ if (token != null) {
+ set_auth_pref(uaa_client_id + ':' + token);
+ store_pref('uaa_token', token);
+ check_login();
+ } else if(has_auth_cookie_value()) {
+ check_login();
+ };
+ } else {
+ app.run();
+ if (get_cookie_value('auth') != null) {
+ check_login();
+ }
+ }
+}
+
+
+function uaa_logout_window() {
+ uaa_invalid = true;
+ uaa_login_window();
+}
+
+function uaa_login_window() {
+ var redirect;
+ if (window.location.hash != "") {
+ redirect = window.location.href.split(window.location.hash)[0];
+ } else {
+ redirect = window.location.href
+ };
+ var loginRedirectUrl;
+ if (uaa_invalid) {
+ loginRedirectUrl = Singular.properties.uaaLocation + '/logout.do?client_id=' + Singular.properties.clientId + '&redirect=' + redirect;
+ } else {
+ loginRedirectUrl = Singular.properties.uaaLocation + '/oauth/authorize?response_type=token&client_id=' + Singular.properties.clientId + '&redirect_uri=' + redirect;
+ };
+ window.open(loginRedirectUrl, "LOGIN_WINDOW");
+}
+
+function check_login() {
+ user = JSON.parse(sync_get('/whoami'));
+ if (user == false) {
+ // clear a local storage value used by earlier versions
+ clear_pref('auth');
+ clear_pref('uaa_token');
+ clear_cookie_value('auth');
+ if (enable_uaa) {
+ uaa_invalid = true;
+ replace_content('login-status', '<button id="loginWindow" onclick="uaa_login_window()">Log out</button>');
+ } else {
+ replace_content('login-status', '<p>Login failed</p>');
+ }
+ }
+ else {
+ hide_popup_warn();
+ replace_content('outer', format('layout', {}));
+ var user_login_session_timeout = parseInt(user.login_session_timeout);
+ // Update auth login_session_timeout if changed
+ if (has_auth_cookie_value() && !isNaN(user_login_session_timeout) &&
+ user_login_session_timeout !== get_login_session_timeout()) {
+
+ update_login_session_timeout(user_login_session_timeout);
+ }
+ setup_global_vars();
+ setup_constant_events();
+ update_vhosts();
+ update_interval();
+ setup_extensions();
+ }
+}
+
+function get_login_session_timeout() {
+ parseInt(get_cookie_value('login_session_timeout'));
+}
+
+function update_login_session_timeout(login_session_timeout) {
+ var auth_info = get_cookie_value('auth');
+ var date = new Date();
+ // `login_session_timeout` minutes from now
+ date.setMinutes(date.getMinutes() + login_session_timeout);
+ store_cookie_value('login_session_timeout', login_session_timeout);
+ store_cookie_value_with_expiration('auth', auth_info, date);
+}
+
+function start_app() {
+ if (app !== undefined) {
+ app.unload();
+ }
+ // Oh boy. Sammy uses various different methods to determine if
+ // the URL hash has changed. Unsurprisingly this is a native event
+ // in modern browsers, and falls back to an icky polling function
+ // in MSIE. But it looks like there's a bug. The polling function
+ // should get installed when the app is started. But it's guarded
+ // behind if (Sammy.HashLocationProxy._interval != null). And of
+ // course that's not specific to the application; it's pretty
+ // global. So we need to manually clear that in order for links to
+ // work in MSIE.
+ // Filed as https://github.com/quirkey/sammy/issues/171
+ //
+ // Note for when we upgrade: HashLocationProxy has become
+ // DefaultLocationProxy in later versions, but otherwise the issue
+ // remains.
+
+ // updated to the version 0.7.6 this _interval = null is fixed
+ // just leave the history here.
+ //Sammy.HashLocationProxy._interval = null;
+
+ app = new Sammy.Application(dispatcher);
+ app.run();
+
+ var url = this.location.toString();
+ var hash = this.location.hash;
+ var pathname = this.location.pathname;
+ if (url.indexOf('#') == -1) {
+ this.location = url + '#/';
+ } else if (hash.indexOf('#token_type') != - 1 && pathname == '/') {
+ // This is equivalent to previous `if` clause when uaa authorisation is used.
+ // Tokens are passed in the url hash, so the url always contains a #.
+ // We need to check the current path is `/` and token is present,
+ // so we can redirect to `/#/`
+ this.location = url.replace(/#token_type.+/gi, "#/");
+ }
+}
+
+function setup_constant_events() {
+ $('#update-every').on('change', function() {
+ var interval = $(this).val();
+ store_pref('interval', interval);
+ if (interval == '')
+ interval = null;
+ else
+ interval = parseInt(interval);
+ set_timer_interval(interval);
+ });
+ $('#show-vhost').on('change', function() {
+ current_vhost = $(this).val();
+ store_pref('vhost', current_vhost);
+ update();
+ });
+ if (!vhosts_interesting) {
+ $('#vhost-form').hide();
+ }
+}
+
+function update_vhosts() {
+ var vhosts = JSON.parse(sync_get('/vhosts'));
+ vhosts_interesting = vhosts.length > 1;
+ if (vhosts_interesting)
+ $('#vhost-form').show();
+ else
+ $('#vhost-form').hide();
+ var select = $('#show-vhost').get(0);
+ select.options.length = vhosts.length + 1;
+ var index = 0;
+ for (var i = 0; i < vhosts.length; i++) {
+ var vhost = vhosts[i].name;
+ select.options[i + 1] = new Option(vhost, vhost);
+ if (vhost == current_vhost) index = i + 1;
+ }
+ select.selectedIndex = index;
+ current_vhost = select.options[index].value;
+ store_pref('vhost', current_vhost);
+}
+
+function setup_extensions() {
+ var extensions = JSON.parse(sync_get('/extensions'));
+ extension_count = 0;
+ for (var i in extensions) {
+ var extension = extensions[i];
+ if ($.isPlainObject(extension) && extension.hasOwnProperty("javascript")) {
+ dynamic_load(extension.javascript);
+ extension_count++;
+ }
+ }
+}
+
+function dynamic_load(filename) {
+ var element = document.createElement('script');
+ element.setAttribute('type', 'text/javascript');
+ element.setAttribute('src', 'js/' + filename);
+ document.getElementsByTagName("head")[0].appendChild(element);
+}
+
+function update_interval() {
+ var intervalStr = get_pref('interval');
+ var interval;
+
+ if (intervalStr == null) interval = 5000;
+ else if (intervalStr == '') interval = null;
+ else interval = parseInt(intervalStr);
+
+ if (isNaN(interval)) interval = null; // Prevent DoS if cookie malformed
+
+ set_timer_interval(interval);
+
+ var select = $('#update-every').get(0);
+ var opts = select.options;
+ for (var i = 0; i < opts.length; i++) {
+ if (opts[i].value == intervalStr) {
+ select.selectedIndex = i;
+ break;
+ }
+ }
+}
+
+function go_to(url) {
+ this.location = url;
+}
+
+function set_timer_interval(interval) {
+ timer_interval = interval;
+ reset_timer();
+}
+
+function reset_timer() {
+ clearInterval(timer);
+ if (timer_interval != null) {
+ timer = setInterval(partial_update, timer_interval);
+ }
+}
+
+function update_manual(div, query) {
+ var path;
+ var template;
+ if (query == 'memory' || query == 'binary') {
+ path = current_reqs['node']['path'] + '?' + query + '=true';
+ template = query;
+ }
+
+ var data = JSON.parse(sync_get(path));
+
+ replace_content(div, format(template, data));
+ postprocess_partial();
+}
+
+function render(reqs, template, highlight) {
+ var old_template = current_template;
+ current_template = template;
+ current_reqs = reqs;
+ for (var i in outstanding_reqs) {
+ outstanding_reqs[i].abort();
+ }
+ outstanding_reqs = [];
+ current_highlight = highlight;
+ if (old_template !== current_template) {
+ window.scrollTo(0, 0);
+ }
+ update();
+}
+
+function update() {
+ replace_content('debug', '');
+ clearInterval(timer);
+ with_update(function(html) {
+ update_navigation();
+ replace_content('main', html);
+ postprocess();
+ postprocess_partial();
+ render_charts();
+ maybe_scroll();
+ reset_timer();
+ });
+}
+
+function partial_update() {
+ if (!$(".pagination_class").is(":focus")) {
+ if ($('.updatable').length > 0) {
+ if (update_counter >= 200) {
+ update_counter = 0;
+ full_refresh();
+ return;
+ }
+ with_update(function(html) {
+ update_counter++;
+ replace_content('scratch', html);
+ var befores = $('#main .updatable');
+ var afters = $('#scratch .updatable');
+ if (befores.length != afters.length) {
+ console.log("before/after mismatch! Doing a full reload...");
+ full_refresh();
+ }
+ for (var i = 0; i < befores.length; i++) {
+ $(befores[i]).empty().append($(afters[i]).contents());
+ }
+ replace_content('scratch', '');
+ postprocess_partial();
+ render_charts();
+ });
+ }
+ }
+}
+
+function update_navigation() {
+ var l1 = '';
+ var l2 = '';
+ var descend = null;
+
+ for (var k in NAVIGATION) {
+ var val = NAVIGATION[k];
+ var path = val;
+ while (!leaf(path)) {
+ path = first_showable_child(path);
+ }
+ var selected = false;
+ if (contains_current_highlight(val)) {
+ selected = true;
+ if (!leaf(val)) {
+ descend = nav(val);
+ }
+ }
+ if (show(path)) {
+ l1 += '<li><a href="' + nav(path) + '"' +
+ (selected ? ' class="selected"' : '') + '>' + k + '</a></li>';
+ }
+ }
+
+ if (descend) {
+ l2 = obj_to_ul(descend);
+ $('#main').addClass('with-rhs');
+ }
+ else {
+ $('#main').removeClass('with-rhs');
+ }
+
+ replace_content('tabs', l1);
+ replace_content('rhs', l2);
+}
+
+function nav(pair) {
+ return pair[0];
+}
+
+function show(pair) {
+ return jQuery.inArray(pair[1], user_tags) != -1;
+}
+
+function leaf(pair) {
+ return typeof(nav(pair)) == 'string';
+}
+
+function first_showable_child(pair) {
+ var items = pair[0];
+ var ks = keys(items);
+ for (var i = 0; i < ks.length; i++) {
+ var child = items[ks[i]];
+ if (show(child)) return child;
+ }
+ return items[ks[0]]; // We'll end up not showing it anyway
+}
+
+function contains_current_highlight(val) {
+ if (leaf(val)) {
+ return current_highlight == nav(val);
+ }
+ else {
+ var b = false;
+ for (var k in val) {
+ b |= contains_current_highlight(val[k]);
+ }
+ return b;
+ }
+}
+
+function obj_to_ul(val) {
+ var res = '<ul>';
+ for (var k in val) {
+ var obj = val[k];
+ if (show(obj)) {
+ res += '<li>';
+ if (leaf(obj)) {
+ res += '<a href="' + nav(obj) + '"' +
+ (current_highlight == nav(obj) ? ' class="selected"' : '') +
+ '>' + k + '</a>';
+ }
+ else {
+ res += obj_to_ul(nav(obj));
+ }
+ res += '</li>';
+ }
+ }
+ return res + '</ul>';
+}
+
+function full_refresh() {
+ store_pref('position', x_position() + ',' + y_position());
+ location.reload();
+}
+
+function maybe_scroll() {
+ var pos = get_pref('position');
+ if (pos) {
+ clear_pref('position');
+ var xy = pos.split(",");
+ window.scrollTo(parseInt(xy[0]), parseInt(xy[1]));
+ }
+}
+
+function x_position() {
+ return window.pageXOffset ?
+ window.pageXOffset :
+ document.documentElement.scrollLeft ?
+ document.documentElement.scrollLeft :
+ document.body.scrollLeft;
+}
+
+function y_position() {
+ return window.pageYOffset ?
+ window.pageYOffset :
+ document.documentElement.scrollTop ?
+ document.documentElement.scrollTop :
+ document.body.scrollTop;
+}
+
+function with_update(fun) {
+ if(outstanding_reqs.length > 0){
+ return false;
+ }
+ with_reqs(apply_state(current_reqs), [], function(json) {
+ var html = format(current_template, json);
+ fun(html);
+ update_status('ok');
+ });
+ return true;
+}
+
+function apply_state(reqs) {
+ var reqs2 = {};
+ for (k in reqs) {
+ var req = reqs[k];
+ var options = {};
+ if (typeof(req) == "object") {
+ options = req.options;
+ req = req.path;
+ }
+ var req2;
+ if (options['vhost'] != undefined && current_vhost != '') {
+ var indexPage = req.indexOf("?page=");
+ if (indexPage >- 1) {
+ pageUrl = req.substr(indexPage);
+ req2 = req.substr(0,indexPage) + '/' + esc(current_vhost) + pageUrl;
+ } else
+
+ req2 = req + '/' + esc(current_vhost);
+ }
+ else {
+ req2 = req;
+ }
+ var qs = [];
+ if (options['sort'] != undefined && current_sort != null) {
+ qs.push('sort=' + current_sort);
+ qs.push('sort_reverse=' + current_sort_reverse);
+ }
+ if (options['ranges'] != undefined) {
+ for (i in options['ranges']) {
+ var type = options['ranges'][i];
+ var range = get_pref('chart-range').split('|');
+ var prefix;
+ if (type.substring(0, 8) == 'lengths-') {
+ prefix = 'lengths';
+ }
+ else if (type.substring(0, 10) == 'msg-rates-') {
+ prefix = 'msg_rates';
+ }
+ else if (type.substring(0, 11) == 'data-rates-') {
+ prefix = 'data_rates';
+ }
+ else if (type == 'node-stats') {
+ prefix = 'node_stats';
+ }
+ qs.push(prefix + '_age=' + parseInt(range[0]));
+ qs.push(prefix + '_incr=' + parseInt(range[1]));
+ }
+ }
+ /* Unknown options are used as query parameters as is. */
+ Object.keys(options).forEach(function (key) {
+ /* Skip known keys we already handled and undefined parameters. */
+ if (key == 'vhost' || key == 'sort' || key == 'ranges')
+ return;
+ if (!key || options[key] == undefined)
+ return;
+
+ qs.push(esc(key) + '=' + esc(options[key]));
+ });
+ qs = qs.join('&');
+ if (qs != '')
+ if (req2.indexOf("?page=") >- 1)
+ qs = '&' + qs;
+ else
+ qs = '?' + qs;
+
+ reqs2[k] = req2 + qs;
+ }
+ return reqs2;
+}
+
+function show_popup(type, text, _mode) {
+ var cssClass = '.form-popup-' + type;
+ function hide() {
+ $(cssClass).fadeOut(100, function() {
+ $(this).remove();
+ });
+ }
+ hide();
+ $('#outer').after(format('popup', {'type': type, 'text': text}));
+ $(cssClass).fadeIn(100);
+ $(cssClass + ' span').on('click', function () {
+ $('.popup-owner').removeClass('popup-owner');
+ hide();
+ });
+}
+
+function hide_popup_warn() {
+ var cssClass = '.form-popup-warn';
+ $('.popup-owner').removeClass('popup-owner');
+ $(cssClass).fadeOut(100, function() {
+ $(this).remove();
+ });
+}
+
+function submit_import(form) {
+ if (form.file.value) {
+ var confirm_upload = confirm('Are you sure you want to import a definitions file? Some entities (vhosts, users, queues, etc) may be overwritten!');
+ if (confirm_upload === true) {
+ var file = form.file.files[0]; // FUTURE: limit upload file size (?)
+ var vhost_upload = $("select[name='vhost-upload'] option:selected");
+ var vhost_selected = vhost_upload.index() > 0;
+
+ var vhost_name = null;
+ if (vhost_selected) {
+ vhost_name = vhost_upload.val();
+ }
+
+ var vhost_part = '';
+ if (vhost_name) {
+ vhost_part = '/' + esc(vhost_name);
+ }
+
+ if (enable_uaa) {
+ var form_action = "/definitions" + vhost_part + '?token=' + get_pref('uaa_token');
+ } else {
+ var form_action = "/definitions" + vhost_part + '?auth=' + get_cookie_value('auth');
+ };
+ var fd = new FormData();
+ fd.append('file', file);
+ with_req('POST', form_action, fd, function(resp) {
+ show_popup('info', 'Your definitions were imported successfully.');
+ });
+ }
+ }
+ return false;
+};
+
+function postprocess() {
+ $('form.confirm-queue').on('submit', function() {
+ return confirm("Are you sure? The queue is going to be deleted. " +
+ "Messages cannot be recovered after deletion.");
+ });
+
+ $('form.confirm-purge-queue').on('submit', function() {
+ return confirm("Are you sure? Messages cannot be recovered after purging.");
+ });
+
+ $('form.confirm').on('submit', function() {
+ return confirm("Are you sure? This object cannot be recovered " +
+ "after deletion.");
+ });
+
+ $('label').map(function() {
+ if ($(this).attr('for') == '') {
+ var id = 'auto-label-' + Math.floor(Math.random()*1000000000);
+ var input = $(this).parents('tr').first().find('input, select');
+ if (input.attr('id') == '') {
+ $(this).attr('for', id);
+ input.attr('id', id);
+ }
+ }
+ });
+
+ $('#download-definitions').on('click', function() {
+ var idx = $("select[name='vhost-download'] option:selected").index();
+ var vhost = ((idx <=0 ) ? "" : "/" + esc($("select[name='vhost-download'] option:selected").val()));
+ if (enable_uaa) {
+ var path = 'api/definitions' + vhost + '?download=' +
+ esc($('#download-filename').val()) +
+ '&token=' + get_pref('uaa_token');
+ } else {
+ var path = 'api/definitions' + vhost + '?download=' +
+ esc($('#download-filename').val()) +
+ '&auth=' + get_cookie_value('auth');
+ };
+ window.location = path;
+ setTimeout('app.run()');
+ return false;
+ });
+
+ $('.update-manual').on('click', function() {
+ update_manual($(this).attr('for'), $(this).attr('query'));
+ });
+
+ $(document).on('keyup', '.multifield input', function() {
+ update_multifields();
+ });
+
+ $(document).on('change', '.multifield select', function() {
+ update_multifields();
+ });
+
+ $('.controls-appearance').on('change', function() {
+ var params = $(this).get(0).options;
+ var selected = $(this).val();
+
+ for (i = 0; i < params.length; i++) {
+ var param = params[i].value;
+ if (param == selected) {
+ $('#' + param + '-div').slideDown(100);
+ } else {
+ $('#' + param + '-div').slideUp(100);
+ }
+ }
+ });
+
+ $(document).on('click', '.help', function() {
+ show_popup('help', HELP[$(this).attr('id')]);
+ });
+
+ $(document).on('click', '.popup-options-link', function() {
+ $('.popup-owner').removeClass('popup-owner');
+ $(this).addClass('popup-owner');
+ var template = $(this).attr('type') + '-options';
+ show_popup('options', format(template, {span: $(this)}), 'fade');
+ });
+
+ $(document).on('click', '.rate-visibility-option', function() {
+ var k = $(this).attr('data-pref');
+ var show = get_pref(k) !== 'true';
+ store_pref(k, '' + show);
+ partial_update();
+ });
+
+ $(document).on('focus', 'input, select', function() {
+ update_counter = 0; // If there's interaction, reset the counter.
+ });
+
+ $('.tag-link').on('click', function() {
+ $('#tags').val($(this).attr('tag'));
+ });
+
+ $('.argument-link').on('click', function() {
+ var field = $(this).attr('field');
+ var row = $('#' + field).find('.mf').last();
+ var key = row.find('input').first();
+ var value = row.find('input').last();
+ var type = row.find('select').last();
+ key.val($(this).attr('key'));
+ value.val($(this).attr('value'));
+ type.val($(this).attr('type'));
+ update_multifields();
+ });
+
+ $(document).on('click', 'form.auto-submit select, form.auto-submit input', function(){
+ $(this).parents('form').submit();
+ });
+
+ $('#filter').on('keyup', debounce(update_filter, 500));
+
+ $('#filter-regex-mode').on('change', update_filter_regex_mode);
+
+ $('#truncate').on('keyup', debounce(update_truncate, 500));
+
+ if (! user_administrator) {
+ $('.administrator-only').remove();
+ }
+
+ update_multifields();
+}
+
+function url_pagination_template(template, defaultPage, defaultPageSize){
+ var page_number_request = fmt_page_number_request(template, defaultPage);
+ var page_size = fmt_page_size_request(template, defaultPageSize);
+ var name_request = fmt_filter_name_request(template, "");
+ var use_regex = fmt_regex_request(template, "") == "checked";
+ if (use_regex) {
+ name_request = esc(name_request);
+ }
+ return '/' + template +
+ '?page=' + page_number_request +
+ '&page_size=' + page_size +
+ '&name=' + name_request +
+ '&use_regex=' + use_regex;
+}
+
+function stored_page_info(template, page_start){
+ var pageSize = fmt_strip_tags($('#' + template+'-pagesize').val());
+ var filterName = fmt_strip_tags($('#' + template+'-name').val());
+
+ store_pref(template + '_current_page_number', page_start);
+ if (filterName != null && filterName != undefined) {
+ store_pref(template + '_current_filter_name', filterName);
+ }
+ var regex_on = $("#" + template + "-filter-regex-mode").is(':checked');
+
+ if (regex_on != null && regex_on != undefined) {
+ store_pref(template + '_current_regex', regex_on ? "checked" : " " );
+ }
+
+ if (pageSize != null && pageSize != undefined) {
+ store_pref(template + '_current_page_size', pageSize);
+ }
+}
+
+function update_pages(template, page_start){
+ stored_page_info(template, page_start);
+ switch (template) {
+ case 'queues' : renderQueues(); break;
+ case 'exchanges' : renderExchanges(); break;
+ case 'connections' : renderConnections(); break;
+ case 'channels' : renderChannels(); break;
+ }
+}
+
+function renderQueues() {
+ ensure_queues_chart_range();
+ render({'queues': {
+ path: url_pagination_template('queues', 1, 100),
+ options: {
+ sort: true,
+ vhost: true,
+ pagination: true
+ }
+ }, 'vhosts': '/vhosts'}, 'queues', '#/queues');
+}
+
+function renderExchanges() {
+ render({'exchanges': {path: url_pagination_template('exchanges', 1, 100),
+ options: {sort:true, vhost:true, pagination:true}},
+ 'vhosts': '/vhosts'}, 'exchanges', '#/exchanges');
+}
+
+function renderConnections() {
+ render({'connections': {path: url_pagination_template('connections', 1, 100),
+ options: {sort:true}}},
+ 'connections', '#/connections');
+}
+
+function renderChannels() {
+ render({'channels': {path: url_pagination_template('channels', 1, 100),
+ options: {sort:true}}},
+ 'channels', '#/channels');
+}
+
+function update_pages_from_ui(sender) {
+ var val = $(sender).val();
+ var raw = !!$(sender).attr('data-page-start') ? $(sender).attr('data-page-start') : val;
+ var s = fmt_escape_html(fmt_strip_tags(raw));
+ update_pages(current_template, s);
+}
+
+function postprocess_partial() {
+ $('.pagination_class_input').on('keypress', function(e) {
+ if (e.keyCode == 13) {
+ update_pages_from_ui(this);
+ }
+ });
+
+ $('.pagination_class_checkbox').on('click', function(e) {
+ update_pages_from_ui(this);
+ });
+
+ $('.pagination_class_select').on('change', function(e) {
+ update_pages_from_ui(this);
+ });
+
+ setup_visibility();
+
+ $('#main').off('click', 'div.section h2, div.section-hidden h2');
+ $('#main').on('click', 'div.section h2, div.section-hidden h2', function() {
+ toggle_visibility($(this));
+ });
+
+ $('.sort').on('click', function() {
+ var sort = $(this).attr('sort');
+ if (current_sort == sort) {
+ current_sort_reverse = ! current_sort_reverse;
+ }
+ else {
+ current_sort = sort;
+ current_sort_reverse = false;
+ }
+ update();
+ });
+
+ // TODO remove this hack when we get rid of "updatable"
+ if ($('#filter-warning-show').length > 0) {
+ $('#filter-truncate').addClass('filter-warning');
+ }
+ else {
+ $('#filter-truncate').removeClass('filter-warning');
+ }
+}
+
+function update_multifields() {
+ $('div.multifield').each(function(index) {
+ update_multifield($(this), true);
+ });
+}
+
+function update_multifield(multifield, dict) {
+ var largest_id = 0;
+ var empty_found = false;
+ var name = multifield.attr('id');
+ var type_inputs = $('#' + name + ' *[name$="_mftype"]');
+ type_inputs.each(function(index) {
+ var re = new RegExp(name + '_([0-9]*)_mftype');
+ var match = $(this).attr('name').match(re);
+ if (!match) return;
+ var id = parseInt(match[1]);
+ largest_id = Math.max(id, largest_id);
+ var prefix = name + '_' + id;
+ var type = $(this).val();
+ var input = $('#' + prefix + '_mfvalue');
+ if (type == 'list') {
+ if (input.length == 1) {
+ input.replaceWith('<div class="multifield-sub" id="' + prefix +
+ '"></div>');
+ }
+ update_multifield($('#' + prefix), false);
+ }
+ else {
+ if (input.length == 1) {
+ var key = dict ? $('#' + prefix + '_mfkey').val() : '';
+ var value = input.val();
+ if (key == '' && value == '') {
+ if (index == type_inputs.length - 1) {
+ empty_found = true;
+ }
+ else {
+ $(this).parents('.mf').first().remove();
+ }
+ }
+ }
+ else {
+ $('#' + prefix).replaceWith(multifield_input(prefix, 'value',
+ 'text'));
+ }
+ }
+ });
+ if (!empty_found) {
+ var prefix = name + '_' + (largest_id + 1);
+ var t = multifield.hasClass('string-only') ? 'hidden' : 'select';
+ var val_type = multifield_input(prefix, 'value', 'text') + ' ' +
+ multifield_input(prefix, 'type', t);
+
+ if (dict) {
+ multifield.append('<table class="mf"><tr><td>' +
+ multifield_input(prefix, 'key', 'text') +
+ '</td><td class="equals"> = </td><td>' +
+ val_type + '</td></tr></table>');
+ }
+ else {
+ multifield.append('<div class="mf">' + val_type + '</div>');
+ }
+ }
+}
+
+function multifield_input(prefix, suffix, type) {
+ if (type == 'hidden' ) {
+ return '<input type="hidden" id="' + prefix + '_mf' + suffix +
+ '" name="' + prefix + '_mf' + suffix + '" value="string"/>';
+ }
+ else if (type == 'text' ) {
+ return '<input type="text" id="' + prefix + '_mf' + suffix +
+ '" name="' + prefix + '_mf' + suffix + '" value=""/>';
+ }
+ else if (type == 'select' ) {
+ return '<select id="' + prefix + '_mf' + suffix + '" name="' + prefix +
+ '_mf' + suffix + '">' +
+ '<option value="string">String</option>' +
+ '<option value="number">Number</option>' +
+ '<option value="boolean">Boolean</option>' +
+ '<option value="list">List</option>' +
+ '</select>';
+ }
+}
+
+function update_filter_regex(jElem) {
+ current_filter_regex = null;
+ jElem.parents('.filter').children('.status-error').remove();
+ if (current_filter_regex_on && $.trim(current_filter).length > 0) {
+ try {
+ current_filter_regex = new RegExp(current_filter,'i');
+ } catch (e) {
+ jElem.parents('.filter').append('<p class="status-error">' +
+ fmt_escape_html(e.message) + '</p>');
+ }
+ }
+}
+
+function update_filter_regex_mode() {
+ current_filter_regex_on = $(this).is(':checked');
+ update_filter_regex($(this));
+ partial_update();
+}
+
+function update_filter() {
+ current_filter = $(this).val();
+ var table = $(this).parents('table').first();
+ table.removeClass('filter-active');
+ if ($(this).val() != '') {
+ table.addClass('filter-active');
+ }
+ update_filter_regex($(this));
+ partial_update();
+}
+
+function update_truncate() {
+ var current_truncate_str =
+ $(this).val().replace(new RegExp('\\D', 'g'), '');
+ if (current_truncate_str == '') {
+ current_truncate_str = '0';
+ }
+ if ($(this).val() != current_truncate_str) {
+ $(this).val(current_truncate_str);
+ }
+ var current_truncate = parseInt(current_truncate_str, 10);
+ store_pref('truncate', current_truncate);
+ partial_update();
+}
+
+function setup_visibility() {
+ $('div.section,div.section-hidden').each(function(_index) {
+ var pref = section_pref(current_template,
+ $(this).children('h2').text());
+ var show = get_pref(pref);
+ if (show == null) {
+ show = $(this).hasClass('section');
+ }
+ else {
+ show = show == 't';
+ }
+ if (show) {
+ $(this).addClass('section-visible');
+ // Workaround for... something. Although div.hider is
+ // display:block anyway, not explicitly setting this
+ // prevents the first slideToggle() from animating
+ // successfully; instead the element just vanishes.
+ $(this).find('.hider').attr('style', 'display:block;');
+ }
+ else {
+ $(this).addClass('section-invisible');
+ }
+ });
+}
+
+function toggle_visibility(item) {
+ var hider = item.next();
+ var all = item.parent();
+ var pref = section_pref(current_template, item.text());
+ item.next().slideToggle(100);
+ if (all.hasClass('section-visible')) {
+ if (all.hasClass('section'))
+ store_pref(pref, 'f');
+ else
+ clear_pref(pref);
+ all.removeClass('section-visible');
+ all.addClass('section-invisible');
+ }
+ else {
+ if (all.hasClass('section-hidden')) {
+ store_pref(pref, 't');
+ } else {
+ clear_pref(pref);
+ }
+ all.removeClass('section-invisible');
+ all.addClass('section-visible');
+ }
+}
+
+function publish_msg(params0) {
+ try {
+ var params = params_magic(params0);
+ publish_msg0(params);
+ } catch (e) {
+ show_popup('warn', fmt_escape_html(e));
+ return false;
+ }
+}
+
+function publish_msg0(params) {
+ var path = fill_path_template('/exchanges/:vhost/:name/publish', params);
+ params['payload_encoding'] = 'string';
+ params['properties'] = {};
+ params['properties']['delivery_mode'] = parseInt(params['delivery_mode']);
+ if (params['headers'] != '')
+ params['properties']['headers'] = params['headers'];
+ var props = [['content_type', 'str'],
+ ['content_encoding', 'str'],
+ ['correlation_id', 'str'],
+ ['reply_to', 'str'],
+ ['expiration', 'str'],
+ ['message_id', 'str'],
+ ['type', 'str'],
+ ['user_id', 'str'],
+ ['app_id', 'str'],
+ ['cluster_id', 'str'],
+ ['priority', 'int'],
+ ['timestamp', 'int']];
+ for (var i in props) {
+ var name = props[i][0];
+ var type = props[i][1];
+ if (params['props'][name] != undefined && params['props'][name] != '') {
+ var value = params['props'][name];
+ if (type == 'int') value = parseInt(value);
+ params['properties'][name] = value;
+ }
+ }
+ with_req('POST', path, JSON.stringify(params), function(resp) {
+ var result = JSON.parse(resp.responseText);
+ if (result.routed) {
+ show_popup('info', 'Message published.');
+ } else {
+ show_popup('warn', 'Message published, but not routed.');
+ }
+ });
+}
+
+function get_msgs(params) {
+ var path = fill_path_template('/queues/:vhost/:name/get', params);
+ with_req('POST', path, JSON.stringify(params), function(resp) {
+ var msgs = JSON.parse(resp.responseText);
+ if (msgs.length == 0) {
+ show_popup('info', 'Queue is empty');
+ } else {
+ $('#msg-wrapper').slideUp(200);
+ replace_content('msg-wrapper', format('messages', {'msgs': msgs}));
+ $('#msg-wrapper').slideDown(200);
+ }
+ });
+}
+
+function with_reqs(reqs, acc, fun) {
+ if (keys(reqs).length > 0) {
+ var key = keys(reqs)[0];
+ with_req('GET', reqs[key], null, function(resp) {
+ acc[key] = JSON.parse(resp.responseText);
+ var remainder = {};
+ for (var k in reqs) {
+ if (k != key) remainder[k] = reqs[k];
+ }
+ with_reqs(remainder, acc, fun);
+ });
+ }
+ else {
+ fun(acc);
+ }
+}
+
+function replace_content(id, html) {
+ $("#" + id).html(html);
+}
+
+var ejs_cached = {};
+
+function format(template, json) {
+ try {
+ var cache = true;
+ if (!(template in ejs_cached)) {
+ ejs_cached[template] = true;
+ cache = false;
+ }
+ var tmpl = new EJS({url: 'js/tmpl/' + template + '.ejs', cache: cache});
+ return tmpl.render(json);
+ } catch (err) {
+ clearInterval(timer);
+ console.log("Uncaught error: " + err);
+ console.log("Stack: " + err['stack']);
+ debug(err['name'] + ": " + err['message'] + "\n" + err['stack'] + "\n");
+ }
+}
+
+function update_status(status) {
+ var text;
+ if (status == 'ok')
+ text = "Refreshed " + fmt_date(new Date());
+ else if (status == 'error') {
+ var next_try = new Date(new Date().getTime() + timer_interval);
+ text = "Error: could not connect to server since " +
+ fmt_date(last_successful_connect) + ". Will retry at " +
+ fmt_date(next_try) + ".";
+ }
+ else
+ throw("Unknown status " + status);
+
+ var html = format('status', {status: status, text: text});
+ replace_content('status', html);
+}
+
+function has_auth_cookie_value() {
+ return get_cookie_value('auth') != null;
+}
+
+function auth_header() {
+ if(has_auth_cookie_value() && enable_uaa) {
+ return "Bearer " + decodeURIComponent(get_pref('uaa_token'));
+ } else {
+ if(has_auth_cookie_value()) {
+ return "Basic " + decodeURIComponent(get_cookie_value('auth'));
+ } else {
+ return null;
+ }
+ }
+}
+
+function with_req(method, path, body, fun) {
+ if(!has_auth_cookie_value()) {
+ // navigate to the login form
+ location.reload();
+ return;
+ }
+
+ var json;
+ var req = xmlHttpRequest();
+ req.open(method, 'api' + path, true );
+ var header = auth_header();
+ if (header !== null) {
+ req.setRequestHeader('authorization', header);
+ }
+ req.setRequestHeader('x-vhost', current_vhost);
+ req.onreadystatechange = function () {
+ if (req.readyState == 4) {
+ var ix = jQuery.inArray(req, outstanding_reqs);
+ if (ix != -1) {
+ outstanding_reqs.splice(ix, 1);
+ }
+ if (check_bad_response(req, true)) {
+ last_successful_connect = new Date();
+ fun(req);
+ }
+ }
+ };
+ outstanding_reqs.push(req);
+ req.send(body);
+}
+
+function get(url, accept, callback) {
+ var req = new XMLHttpRequest();
+ req.open("GET", url);
+ req.setRequestHeader("Accept", accept);
+ req.send();
+
+ req.onreadystatechange = function() {
+ if (req.readyState == XMLHttpRequest.DONE) {
+ callback(req);
+ }
+ };
+}
+
+function sync_get(path) {
+ return sync_req('GET', [], path);
+}
+
+function sync_put(sammy, path_template) {
+ return sync_req('PUT', sammy.params, path_template);
+}
+
+function sync_delete(sammy, path_template, options) {
+ return sync_req('DELETE', sammy.params, path_template, options);
+}
+
+function sync_post(sammy, path_template) {
+ return sync_req('POST', sammy.params, path_template);
+}
+
+function sync_req(type, params0, path_template, options) {
+ var params;
+ var path;
+ try {
+ params = params_magic(params0);
+ path = fill_path_template(path_template, params);
+ } catch (e) {
+ show_popup('warn', fmt_escape_html(e));
+ return false;
+ }
+ var req = xmlHttpRequest();
+ req.open(type, 'api' + path, false);
+ req.setRequestHeader('content-type', 'application/json');
+ req.setRequestHeader('authorization', auth_header());
+
+ if (options != undefined || options != null) {
+ if (options.headers != undefined || options.headers != null) {
+ jQuery.each(options.headers, function (k, v) {
+ req.setRequestHeader(k, v);
+ });
+ }
+ }
+
+ try {
+ if (type == 'GET')
+ req.send(null);
+ else
+ req.send(JSON.stringify(params));
+ }
+ catch (e) {
+ if (e.number == 0x80004004) {
+ // 0x80004004 means "Operation aborted."
+ // https://support.microsoft.com/kb/186063
+ // MSIE6 appears to do this in response to HTTP 204.
+ }
+ }
+
+ if (check_bad_response(req, false)) {
+ if (type == 'GET')
+ return req.responseText;
+ else
+ // rabbitmq/rabbitmq-management#732
+ // https://developer.mozilla.org/en-US/docs/Glossary/Truthy
+ return {result: true, http_status: req.status, req_params: params};
+ }
+ else {
+ return false;
+ }
+}
+
+function check_bad_response(req, full_page_404) {
+ // 1223 == 204 - see https://www.enhanceie.com/ie/bugs.asp
+ // MSIE7 and 8 appear to do this in response to HTTP 204.
+ if ((req.status >= 200 && req.status < 300) || req.status == 1223) {
+ return true;
+ }
+ else if (req.status == 404 && full_page_404) {
+ var html = format('404', {});
+ replace_content('main', html);
+ }
+ else if (req.status >= 400 && req.status <= 404) {
+ var reason = JSON.parse(req.responseText).reason;
+ if (typeof(reason) != 'string') reason = JSON.stringify(reason);
+
+ var error = JSON.parse(req.responseText).error;
+ if (typeof(error) != 'string') error = JSON.stringify(error);
+
+ if (error == 'bad_request' || error == 'not_found' || error == 'not_authorised') {
+ show_popup('warn', fmt_escape_html(reason));
+ } else if (error == 'page_out_of_range') {
+ var seconds = 60;
+ if (last_page_out_of_range_error > 0)
+ seconds = (new Date().getTime() - last_page_out_of_range_error.getTime())/1000;
+ if (seconds > 3) {
+ Sammy.log('server reports page is out of range, redirecting to page 1');
+ var contexts = ["queues", "exchanges", "connections", "channels"];
+ var matches = /api\/(.*)\?/.exec(req.responseURL);
+ if (matches != null && matches.length > 1) {
+ contexts.forEach(function(item) {
+ if (matches[1].indexOf(item) == 0) {update_pages(item, 1)};
+ });
+ } else update_pages(current_template, 1);
+
+ last_page_out_of_range_error = new Date();
+ }
+ }
+ }
+ else if (req.status == 408) {
+ update_status('timeout');
+ }
+ else if (req.status == 0) { // Non-MSIE: could not connect
+ update_status('error');
+ }
+ else if (req.status > 12000) { // MSIE: could not connect
+ update_status('error');
+ }
+ else if (req.status == 503) { // Proxy: could not connect
+ update_status('error');
+ }
+ else {
+ debug("Management API returned status code " + req.status + " - <strong>" + fmt_escape_html_one_line(req.responseText) + "</strong>");
+ clearInterval(timer);
+ }
+
+ return false;
+}
+
+function fill_path_template(template, params) {
+ var re = /:[a-zA-Z_]*/g;
+ return template.replace(re, function(m) {
+ var str = esc(params[m.substring(1)]);
+ if (str == '') {
+ throw(m.substring(1) + " is required");
+ }
+ return str;
+ });
+}
+
+function params_magic(params) {
+ return check_password(maybe_remove_fields(collapse_multifields(params)));
+}
+
+function collapse_multifields(params0) {
+ function set(x) { return x != '' && x != undefined }
+
+ var params = {};
+ var ks = keys(params0);
+ var ids = [];
+ for (i in ks) {
+ var key = ks[i];
+ var match = key.match(/([a-z]*)_([0-9_]*)_mftype/);
+ var match2 = key.match(/[a-z]*_[0-9_]*_mfkey/);
+ var match3 = key.match(/[a-z]*_[0-9_]*_mfvalue/);
+ if (match == null && match2 == null && match3 == null) {
+ params[key] = params0[key];
+ }
+ else if (match == null) {
+ // Do nothing, value is handled below
+ }
+ else {
+ var name = match[1];
+ var id = match[2];
+ ids.push([name, id]);
+ }
+ }
+ ids.sort();
+ var id_map = {};
+ for (i in ids) {
+ var name = ids[i][0];
+ var id = ids[i][1];
+ if (params[name] == undefined) {
+ params[name] = {};
+ id_map[name] = {};
+ }
+ var id_parts = id.split('_');
+ var k = params0[name + '_' + id_parts[0] + '_mfkey'];
+ var v = params0[name + '_' + id + '_mfvalue'];
+ var t = params0[name + '_' + id + '_mftype'];
+ var val = null;
+ var top_level = id_parts.length == 1;
+ if (t == 'list') {
+ val = [];
+ id_map[name][id] = val;
+ }
+ else if ((set(k) && top_level) || set(v)) {
+ if (t == 'boolean') {
+ if (v != 'true' && v != 'false')
+ throw(k + ' must be "true" or "false"; got ' + v);
+ val = (v == 'true');
+ }
+ else if (t == 'number') {
+ var n = parseFloat(v);
+ if (isNaN(n))
+ throw(k + ' must be a number; got ' + v);
+ val = n;
+ }
+ else {
+ val = v;
+ }
+ }
+ if (val != null) {
+ if (top_level) {
+ params[name][k] = val;
+ }
+ else {
+ var prefix = id_parts.slice(0, id_parts.length - 1).join('_');
+ id_map[name][prefix].push(val);
+ }
+ }
+ }
+ if (params.hasOwnProperty('queuetype')) {
+ delete params['queuetype'];
+ params['arguments']['x-queue-type'] = queue_type;
+ if (queue_type == 'quorum' ||
+ queue_type == 'stream') {
+ params['durable'] = true;
+ params['auto_delete'] = false;
+ }
+ }
+ return params;
+}
+
+function check_password(params) {
+ if (params['password'] != undefined) {
+ if (params['password'] == '') {
+ throw("Please specify a password.");
+ }
+ if (params['password'] != params['password_confirm']) {
+ throw("Passwords do not match.");
+ }
+ delete params['password_confirm'];
+ }
+
+ return params;
+}
+
+function maybe_remove_fields(params) {
+ $('.controls-appearance').each(function(index) {
+ var options = $(this).get(0).options;
+ var selected = $(this).val();
+
+ for (i = 0; i < options.length; i++) {
+ var option = options[i].value;
+ if (option != selected) {
+ delete params[option];
+ }
+ }
+ delete params[$(this).attr('name')];
+ });
+ return params;
+}
+
+function put_parameter(sammy, mandatory_keys, num_keys, bool_keys,
+ arrayable_keys) {
+ for (var i in sammy.params) {
+ if (i === 'length' || !sammy.params.hasOwnProperty(i)) continue;
+ if (sammy.params[i] == '' && jQuery.inArray(i, mandatory_keys) == -1) {
+ delete sammy.params[i];
+ }
+ else if (jQuery.inArray(i, num_keys) != -1) {
+ sammy.params[i] = parseInt(sammy.params[i]);
+ }
+ else if (jQuery.inArray(i, bool_keys) != -1) {
+ sammy.params[i] = sammy.params[i] == 'true';
+ }
+ else if (jQuery.inArray(i, arrayable_keys) != -1) {
+ sammy.params[i] = sammy.params[i].split(' ');
+ if (sammy.params[i].length == 1) {
+ sammy.params[i] = sammy.params[i][0];
+ }
+ }
+ }
+ var params = {"component": sammy.params.component,
+ "vhost": sammy.params.vhost,
+ "name": sammy.params.name,
+ "value": params_magic(sammy.params)};
+ delete params.value.vhost;
+ delete params.value.component;
+ delete params.value.name;
+ sammy.params = params;
+ if (sync_put(sammy, '/parameters/:component/:vhost/:name')) update();
+}
+
+function put_cast_params(sammy, path, mandatory_keys, num_keys, bool_keys) {
+ for (var i in sammy.params) {
+ if (i === 'length' || !sammy.params.hasOwnProperty(i)) continue;
+ if (sammy.params[i] == '' && jQuery.inArray(i, mandatory_keys) == -1) {
+ delete sammy.params[i];
+ }
+ else if (jQuery.inArray(i, num_keys) != -1) {
+ sammy.params[i] = parseInt(sammy.params[i]);
+ }
+ else if (jQuery.inArray(i, bool_keys) != -1) {
+ sammy.params[i] = sammy.params[i] == 'true';
+ }
+ }
+ if (sync_put(sammy, path)) update();
+}
+
+function update_column_options(sammy) {
+ var mode = sammy.params['mode'];
+ for (var group in COLUMNS[mode]) {
+ var options = COLUMNS[mode][group];
+ for (var i = 0; i < options.length; i++) {
+ var key = options[i][0];
+ var value = sammy.params[mode + '-' + key] != undefined;
+ store_pref('column-' + mode + '-' + key, value);
+ }
+ }
+
+ partial_update();
+}
+
+function debug(str) {
+ $('<p>' + str + '</p>').appendTo('#debug');
+}
+
+function keys(obj) {
+ var ks = [];
+ for (var k in obj) {
+ ks.push(k);
+ }
+ return ks;
+}
+
+// Don't use the jQuery AJAX support, it seems to have trouble reporting
+// server-down type errors.
+function xmlHttpRequest() {
+ var res;
+ try {
+ res = new XMLHttpRequest();
+ }
+ catch(e) {
+ res = new ActiveXObject("Microsoft.XMLHttp");
+ }
+ return res;
+}
+
+// Our base64 library takes a string that is really a byte sequence,
+// and will throw if given a string with chars > 255 (and hence not
+// DTRT for chars > 127). So encode a unicode string as a UTF-8
+// sequence of "bytes".
+function b64_encode_utf8(str) {
+ return base64.encode(encode_utf8(str));
+}
+
+// encodeURIComponent handles utf-8, unescape does not. Neat!
+function encode_utf8(str) {
+ return unescape(encodeURIComponent(str));
+}
+
+(function($){
+ $.fn.extend({
+ center: function () {
+ return this.each(function() {
+ var top = ($(window).height() - $(this).outerHeight()) / 2;
+ var left = ($(window).width() - $(this).outerWidth()) / 2;
+ $(this).css({margin:0, top: (top > 0 ? top : 0)+'px', left: (left > 0 ? left : 0)+'px'});
+ });
+ }
+ });
+})(jQuery);
+
+function debounce(f, delay) {
+ var timeout = null;
+
+ return function() {
+ var obj = this;
+ var args = arguments;
+
+ function delayed () {
+ f.apply(obj, args);
+ timeout = null;
+ }
+ if (timeout) clearTimeout(timeout);
+ timeout = setTimeout(delayed, delay);
+ }
+}
+
+function rename_multifield(params, from, to) {
+ var new_params = {};
+ for(var key in params){
+ var match = key.match("^" + from + "_[0-9_]*_mftype$");
+ var match2 = key.match("^" + from + "_[0-9_]*_mfkey$");
+ var match3 = key.match("^" + from + "_[0-9_]*_mfvalue$");
+ if (match != null) {
+ new_params[match[0].replace(from, to)] = params[match];
+ }
+ else if (match2 != null) {
+ new_params[match2[0].replace(from, to)] = params[match2];
+ }
+ else if (match3 != null) {
+ new_params[match3[0].replace(from, to)] = params[match3];
+ }
+ else {
+ new_params[key] = params[key]
+ }
+ }
+ return new_params;
+}
+
+function select_queue_type(queuetype) {
+ queue_type = queuetype.value;
+ update();
+}
+
+function is_quorum(queue) {
+ if (queue["arguments"]) {
+ if (queue["arguments"]["x-queue-type"]) {
+ return queue["arguments"]["x-queue-type"] === "quorum";
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+function is_stream(queue) {
+ if (queue["arguments"]) {
+ if (queue["arguments"]["x-queue-type"]) {
+ return queue["arguments"]["x-queue-type"] === "stream";
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+function is_classic(queue) {
+ if (queue["arguments"]) {
+ if (queue["arguments"]["x-queue-type"]) {
+ return queue["arguments"]["x-queue-type"] === "classic";
+ } else {
+ return true;
+ }
+ } else {
+ return true;
+ }
+}
+
+function ensure_queues_chart_range() {
+ var range = get_pref('chart-range');
+ // Note: the queues page uses the 'basic' range type
+ var fixup_range;
+ var valid_range = false;
+ var range_type = get_chart_range_type('queues');
+ var chart_periods = CHART_RANGES[range_type];
+ for (var i = 0; i < chart_periods.length; ++i) {
+ var data = chart_periods[i];
+ var val = data[0];
+ if (range === val) {
+ valid_range = true;
+ break;
+ }
+ // If the range needs to be adjusted, use the last
+ // valid one
+ fixup_range = val;
+ }
+ if (!valid_range) {
+ store_pref('chart-range', fixup_range);
+ }
+}
+
+function get_chart_range_type(arg) {
+ /*
+ * 'arg' can be:
+ * lengths-over for the Overview page
+ * lengths-q for the per-queue page
+ * queues for setting up the queues range
+ */
+ if (arg === 'lengths-over') {
+ return 'global';
+ }
+ if (arg === 'msg-rates-over') {
+ return 'global';
+ }
+ if (arg === 'lengths-q') {
+ return 'basic';
+ }
+ if (arg === 'msg-rates-q') {
+ return 'basic';
+ }
+ if (arg === 'queues') {
+ return 'basic';
+ }
+ if (arg === 'queue-churn') {
+ return 'basic';
+ }
+ if (arg === 'channel-churn') {
+ return 'basic';
+ }
+ if (arg === 'connection-churn') {
+ return 'basic';
+ }
+
+ console.log('[WARNING]: range type not found for arg: ' + arg);
+ return 'basic';
+}
diff --git a/deps/rabbitmq_management/priv/www/js/prefs.js b/deps/rabbitmq_management/priv/www/js/prefs.js
new file mode 100644
index 0000000000..b2ad9fba46
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/prefs.js
@@ -0,0 +1,164 @@
+function local_storage_available() {
+ try {
+ return 'localStorage' in window && window['localStorage'] !== null;
+ } catch (e) {
+ return false;
+ }
+}
+
+function store_cookie_value(k, v) {
+ var d = parse_cookie();
+ d[short_key(k)] = v;
+ store_cookie(d);
+}
+
+function store_cookie_value_with_expiration(k, v, expiration_date) {
+ var d = parse_cookie();
+ d[short_key(k)] = v;
+ store_cookie_with_expiration(d, expiration_date);
+}
+
+function clear_cookie_value(k) {
+ var d = parse_cookie();
+ delete d[short_key(k)];
+ store_cookie(d);
+}
+
+function get_cookie_value(k) {
+ var r;
+ r = parse_cookie()[short_key(k)];
+ return r == undefined ? default_pref(k) : r;
+}
+
+function store_pref(k, v) {
+ if (local_storage_available()) {
+ window.localStorage['rabbitmq.' + k] = v;
+ }
+ else {
+ var d = parse_cookie();
+ d[short_key(k)] = v;
+ store_cookie(d);
+ }
+}
+
+function clear_pref(k) {
+ if (local_storage_available()) {
+ window.localStorage.removeItem('rabbitmq.' + k);
+ }
+ else {
+ var d = parse_cookie();
+ delete d[short_key(k)];
+ store_cookie(d);
+ }
+}
+
+function clear_local_pref(k) {
+ if (local_storage_available()) {
+ window.localStorage.removeItem('rabbitmq.' + k);
+ }
+}
+
+function get_pref(k) {
+ var val;
+ if (local_storage_available()) {
+ val = window.localStorage['rabbitmq.' + k];
+ }
+ else {
+ val = parse_cookie()[short_key(k)];
+
+ }
+ var res = (val == undefined) ? default_pref(k) : val;
+ return res;
+}
+
+function section_pref(template, name) {
+ return 'visible|' + template + '|' + name;
+}
+
+function show_column(mode, column) {
+ return get_pref('column-' + mode + '-' + column) == 'true';
+}
+
+// ---------------------------------------------------------------------------
+
+function default_pref(k) {
+ if (k.substring(0, 11) == 'chart-size-') return 'small';
+ if (k.substring(0, 10) == 'rate-mode-') return 'chart';
+ if (k.substring(0, 11) == 'chart-line-') return 'true';
+ if (k == 'truncate') return '100';
+ if (k == 'chart-range') return '60|5';
+ if (k.substring(0, 7) == 'column-')
+ return default_column_pref(k.substring(7));
+ return null;
+}
+
+function default_column_pref(key0) {
+ var ix = key0.indexOf('-');
+ var mode = key0.substring(0, ix);
+ var key = key0.substring(ix + 1);
+ for (var group in COLUMNS[mode]) {
+ var options = COLUMNS[mode][group];
+ for (var i = 0; i < options.length; i++) {
+ if (options[i][0] == key) {
+ return '' + options[i][2];
+ }
+ }
+ }
+ return 'false';
+}
+
+// ---------------------------------------------------------------------------
+
+function parse_cookie() {
+ var c = get_cookie('m');
+ var items = c.length == 0 ? [] : c.split('|');
+
+ var start = 0;
+ var dict = {};
+ for (var i in items) {
+ var kv = items[i].split(':');
+ dict[kv[0]] = unescape(kv[1]);
+ }
+ return dict;
+}
+
+function store_cookie(dict) {
+ var date = new Date();
+ date.setFullYear(date.getFullYear() + 1);
+ store_cookie_with_expiration(dict, date);
+}
+
+function store_cookie_with_expiration(dict, expiration_date) {
+ var enc = [];
+ for (var k in dict) {
+ enc.push(k + ':' + escape(dict[k]));
+ }
+ document.cookie = 'm=' + enc.join('|') + '; expires=' + expiration_date.toUTCString();
+}
+
+function get_cookie(key) {
+ var cookies = document.cookie.split(';');
+ for (var i in cookies) {
+ var kv = jQuery.trim(cookies[i]).split('=');
+ if (kv[0] == key) return kv[1];
+ }
+ return '';
+}
+
+// Try to economise on space since cookies have limited length.
+function short_key(k) {
+ var res = Math.abs(k.hashCode() << 16 >> 16);
+ res = res.toString(16);
+ return res;
+}
+
+String.prototype.hashCode = function() {
+ var hash = 0;
+ if (this.length == 0) return code;
+ for (i = 0; i < this.length; i++) {
+ char = this.charCodeAt(i);
+ hash = 31*hash+char;
+ hash = hash & hash; // Convert to 32bit integer
+ }
+ return hash;
+}
diff --git a/deps/rabbitmq_management/priv/www/js/sammy-0.7.6.js b/deps/rabbitmq_management/priv/www/js/sammy-0.7.6.js
new file mode 100644
index 0000000000..b0ff981c06
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/sammy-0.7.6.js
@@ -0,0 +1,2156 @@
+// name: sammy
+// version: 0.7.6
+
+// Sammy.js / http://sammyjs.org
+
+(function(factory){
+ // Support module loading scenarios
+ if (typeof define === 'function' && define.amd){
+ // AMD Anonymous Module
+ define(['jquery'], factory);
+ } else {
+ // No module loader (plain <script> tag) - put directly in global namespace
+ jQuery.sammy = window.Sammy = factory(jQuery);
+ }
+})(function($){
+
+ var Sammy,
+ PATH_REPLACER = "([^\/]+)",
+ PATH_NAME_MATCHER = /:([\w\d]+)/g,
+ QUERY_STRING_MATCHER = /\?([^#]*)?$/,
+ // mainly for making `arguments` an Array
+ _makeArray = function(nonarray) { return Array.prototype.slice.call(nonarray); },
+ // borrowed from jQuery
+ _isFunction = function( obj ) { return Object.prototype.toString.call(obj) === "[object Function]"; },
+ _isArray = function( obj ) { return Object.prototype.toString.call(obj) === "[object Array]"; },
+ _isRegExp = function( obj ) { return Object.prototype.toString.call(obj) === "[object RegExp]"; },
+ _decode = function( str ) { return decodeURIComponent((str || '').replace(/\+/g, ' ')); },
+ _encode = encodeURIComponent,
+ _escapeHTML = function(s) {
+ return String(s).replace(/&(?!\w+;)/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
+ },
+ _routeWrapper = function(verb) {
+ return function() {
+ return this.route.apply(this, [verb].concat(Array.prototype.slice.call(arguments)));
+ };
+ },
+ _template_cache = {},
+ _has_history = !!(window.history && history.pushState),
+ loggers = [];
+
+
+ // `Sammy` (also aliased as $.sammy) is not only the namespace for a
+ // number of prototypes, its also a top level method that allows for easy
+ // creation/management of `Sammy.Application` instances. There are a
+ // number of different forms for `Sammy()` but each returns an instance
+ // of `Sammy.Application`. When a new instance is created using
+ // `Sammy` it is added to an Object called `Sammy.apps`. This
+ // provides for an easy way to get at existing Sammy applications. Only one
+ // instance is allowed per `element_selector` so when calling
+ // `Sammy('selector')` multiple times, the first time will create
+ // the application and the following times will extend the application
+ // already added to that selector.
+ //
+ // ### Example
+ //
+ // // returns the app at #main or a new app
+ // Sammy('#main')
+ //
+ // // equivalent to "new Sammy.Application", except appends to apps
+ // Sammy();
+ // Sammy(function() { ... });
+ //
+ // // extends the app at '#main' with function.
+ // Sammy('#main', function() { ... });
+ //
+ Sammy = function() {
+ var args = _makeArray(arguments),
+ app, selector;
+ Sammy.apps = Sammy.apps || {};
+ if (args.length === 0 || args[0] && _isFunction(args[0])) { // Sammy()
+ return Sammy.apply(Sammy, ['body'].concat(args));
+ } else if (typeof (selector = args.shift()) == 'string') { // Sammy('#main')
+ app = Sammy.apps[selector] || new Sammy.Application();
+ app.element_selector = selector;
+ if (args.length > 0) {
+ $.each(args, function(i, plugin) {
+ app.use(plugin);
+ });
+ }
+ // if the selector changes make sure the reference in Sammy.apps changes
+ if (app.element_selector != selector) {
+ delete Sammy.apps[selector];
+ }
+ Sammy.apps[app.element_selector] = app;
+ return app;
+ }
+ };
+
+ Sammy.VERSION = '0.7.6';
+
+ // Add to the global logger pool. Takes a function that accepts an
+ // unknown number of arguments and should print them or send them somewhere
+ // The first argument is always a timestamp.
+ Sammy.addLogger = function(logger) {
+ loggers.push(logger);
+ };
+
+ // Sends a log message to each logger listed in the global
+ // loggers pool. Can take any number of arguments.
+ // Also prefixes the arguments with a timestamp.
+ Sammy.log = function() {
+ var args = _makeArray(arguments);
+ args.unshift("[" + Date() + "]");
+ $.each(loggers, function(i, logger) {
+ logger.apply(Sammy, args);
+ });
+ };
+
+ if (typeof window.console != 'undefined') {
+ if (typeof window.console.log === 'function' && _isFunction(window.console.log.apply)) {
+ Sammy.addLogger(function() {
+ window.console.log.apply(window.console, arguments);
+ });
+ } else {
+ Sammy.addLogger(function() {
+ window.console.log(arguments);
+ });
+ }
+ } else if (typeof console != 'undefined') {
+ Sammy.addLogger(function() {
+ console.log.apply(console, arguments);
+ });
+ }
+
+ $.extend(Sammy, {
+ makeArray: _makeArray,
+ isFunction: _isFunction,
+ isArray: _isArray
+ });
+
+ // Sammy.Object is the base for all other Sammy classes. It provides some useful
+ // functionality, including cloning, iterating, etc.
+ Sammy.Object = function(obj) { // constructor
+ return $.extend(this, obj || {});
+ };
+
+ $.extend(Sammy.Object.prototype, {
+
+ // Escape HTML in string, use in templates to prevent script injection.
+ // Also aliased as `h()`
+ escapeHTML: _escapeHTML,
+ h: _escapeHTML,
+
+ // Returns a copy of the object with Functions removed.
+ toHash: function() {
+ var json = {};
+ $.each(this, function(k,v) {
+ if (!_isFunction(v)) {
+ json[k] = v;
+ }
+ });
+ return json;
+ },
+
+ // Renders a simple HTML version of this Objects attributes.
+ // Does not render functions.
+ // For example. Given this Sammy.Object:
+ //
+ // var s = new Sammy.Object({first_name: 'Sammy', last_name: 'Davis Jr.'});
+ // s.toHTML()
+ // //=> '<strong>first_name</strong> Sammy<br /><strong>last_name</strong> Davis Jr.<br />'
+ //
+ toHTML: function() {
+ var display = "";
+ $.each(this, function(k, v) {
+ if (!_isFunction(v)) {
+ display += "<strong>" + k + "</strong> " + v + "<br />";
+ }
+ });
+ return display;
+ },
+
+ // Returns an array of keys for this object. If `attributes_only`
+ // is true will not return keys that map to a `function()`
+ keys: function(attributes_only) {
+ var keys = [];
+ for (var property in this) {
+ if (!_isFunction(this[property]) || !attributes_only) {
+ keys.push(property);
+ }
+ }
+ return keys;
+ },
+
+ // Checks if the object has a value at `key` and that the value is not empty
+ has: function(key) {
+ return this[key] && $.trim(this[key].toString()) !== '';
+ },
+
+ // convenience method to join as many arguments as you want
+ // by the first argument - useful for making paths
+ join: function() {
+ var args = _makeArray(arguments);
+ var delimiter = args.shift();
+ return args.join(delimiter);
+ },
+
+ // Shortcut to Sammy.log
+ log: function() {
+ Sammy.log.apply(Sammy, arguments);
+ },
+
+ // Returns a string representation of this object.
+ // if `include_functions` is true, it will also toString() the
+ // methods of this object. By default only prints the attributes.
+ toString: function(include_functions) {
+ var s = [];
+ $.each(this, function(k, v) {
+ if (!_isFunction(v) || include_functions) {
+ s.push('"' + k + '": ' + v.toString());
+ }
+ });
+ return "Sammy.Object: {" + s.join(',') + "}";
+ }
+ });
+
+
+ // Return whether the event targets this window.
+ Sammy.targetIsThisWindow = function targetIsThisWindow(event, tagName) {
+ var targetElement = $(event.target).closest(tagName);
+ if (targetElement.length === 0) { return true; }
+
+ var targetWindow = targetElement.attr('target');
+ if (!targetWindow || targetWindow === window.name || targetWindow === '_self') { return true; }
+ if (targetWindow === '_blank') { return false; }
+ if (targetWindow === 'top' && window === window.top) { return true; }
+ return false;
+ };
+
+
+ // The DefaultLocationProxy is the default location proxy for all Sammy applications.
+ // A location proxy is a prototype that conforms to a simple interface. The purpose
+ // of a location proxy is to notify the Sammy.Application its bound to when the location
+ // or 'external state' changes.
+ //
+ // The `DefaultLocationProxy` watches for changes to the path of the current window and
+ // is also able to set the path based on changes in the application. It does this by
+ // using different methods depending on what is available in the current browser. In
+ // the latest and greatest browsers it used the HTML5 History API and the `pushState`
+ // `popState` events/methods. This allows you to use Sammy to serve a site behind normal
+ // URI paths as opposed to the older default of hash (#) based routing. Because the server
+ // can interpret the changed path on a refresh or re-entry, though, it requires additional
+ // support on the server side. If you'd like to force disable HTML5 history support, please
+ // use the `disable_push_state` setting on `Sammy.Application`. If pushState support
+ // is enabled, `DefaultLocationProxy` also binds to all links on the page. If a link is clicked
+ // that matches the current set of routes, the URL is changed using pushState instead of
+ // fully setting the location and the app is notified of the change.
+ //
+ // If the browser does not have support for HTML5 History, `DefaultLocationProxy` automatically
+ // falls back to the older hash based routing. The newest browsers (IE, Safari > 4, FF >= 3.6)
+ // support a 'onhashchange' DOM event, thats fired whenever the location.hash changes.
+ // In this situation the DefaultLocationProxy just binds to this event and delegates it to
+ // the application. In the case of older browsers a poller is set up to track changes to the
+ // hash.
+ Sammy.DefaultLocationProxy = function(app, run_interval_every) {
+ this.app = app;
+ // set is native to false and start the poller immediately
+ this.is_native = false;
+ this.has_history = _has_history;
+ this._startPolling(run_interval_every);
+ };
+
+ Sammy.DefaultLocationProxy.fullPath = function(location_obj) {
+ // Bypass the `window.location.hash` attribute. If a question mark
+ // appears in the hash IE6 will strip it and all of the following
+ // characters from `window.location.hash`.
+ var matches = location_obj.toString().match(/^[^#]*(#.+)$/);
+ var hash = matches ? matches[1] : '';
+ return [location_obj.pathname, location_obj.search, hash].join('');
+ };
+$.extend(Sammy.DefaultLocationProxy.prototype , {
+ // bind the proxy events to the current app.
+ bind: function() {
+ var proxy = this, app = this.app, lp = Sammy.DefaultLocationProxy;
+ $(window).bind('hashchange.' + this.app.eventNamespace(), function(e, non_native) {
+ // if we receive a native hash change event, set the proxy accordingly
+ // and stop polling
+ if (proxy.is_native === false && !non_native) {
+ proxy.is_native = true;
+ window.clearInterval(lp._interval);
+ lp._interval = null;
+ }
+ app.trigger('location-changed');
+ });
+ if (_has_history && !app.disable_push_state) {
+ // bind to popstate
+ $(window).bind('popstate.' + this.app.eventNamespace(), function(e) {
+ app.trigger('location-changed');
+ });
+ // bind to link clicks that have routes
+ $(document).delegate('a', 'click.history-' + this.app.eventNamespace(), function (e) {
+ if (e.isDefaultPrevented() || e.metaKey || e.ctrlKey) {
+ return;
+ }
+ var full_path = lp.fullPath(this),
+ // Get anchor's host name in a cross browser compatible way.
+ // IE looses hostname property when setting href in JS
+ // with a relative URL, e.g. a.setAttribute('href',"/whatever").
+ // Circumvent this problem by creating a new link with given URL and
+ // querying that for a hostname.
+ hostname = this.hostname ? this.hostname : function (a) {
+ var l = document.createElement("a");
+ l.href = a.href;
+ return l.hostname;
+ }(this);
+
+ if (hostname == window.location.hostname &&
+ app.lookupRoute('get', full_path) &&
+ Sammy.targetIsThisWindow(e, 'a')) {
+ e.preventDefault();
+ proxy.setLocation(full_path);
+ return false;
+ }
+ });
+ }
+ if (!lp._bindings) {
+ lp._bindings = 0;
+ }
+ lp._bindings++;
+ },
+
+ // unbind the proxy events from the current app
+ unbind: function() {
+ $(window).unbind('hashchange.' + this.app.eventNamespace());
+ $(window).unbind('popstate.' + this.app.eventNamespace());
+ $(document).undelegate('a', 'click.history-' + this.app.eventNamespace());
+ Sammy.DefaultLocationProxy._bindings--;
+ if (Sammy.DefaultLocationProxy._bindings <= 0) {
+ window.clearInterval(Sammy.DefaultLocationProxy._interval);
+ Sammy.DefaultLocationProxy._interval = null;
+ }
+ },
+
+ // get the current location from the hash.
+ getLocation: function() {
+ return Sammy.DefaultLocationProxy.fullPath(window.location);
+ },
+
+ // set the current location to `new_location`
+ setLocation: function(new_location) {
+ if (/^([^#\/]|$)/.test(new_location)) { // non-prefixed url
+ if (_has_history && !this.app.disable_push_state) {
+ new_location = '/' + new_location;
+ } else {
+ new_location = '#!/' + new_location;
+ }
+ }
+ if (new_location != this.getLocation()) {
+ // HTML5 History exists and new_location is a full path
+ if (_has_history && !this.app.disable_push_state && /^\//.test(new_location)) {
+ history.pushState({ path: new_location }, window.title, new_location);
+ this.app.trigger('location-changed');
+ } else {
+ return (window.location = new_location);
+ }
+ }
+ },
+
+ _startPolling: function(every) {
+ // set up interval
+ var proxy = this;
+ if (!Sammy.DefaultLocationProxy._interval) {
+ if (!every) { every = 10; }
+ var hashCheck = function() {
+ var current_location = proxy.getLocation();
+ if (typeof Sammy.DefaultLocationProxy._last_location == 'undefined' ||
+ current_location != Sammy.DefaultLocationProxy._last_location) {
+ window.setTimeout(function() {
+ $(window).trigger('hashchange', [true]);
+ }, 0);
+ }
+ Sammy.DefaultLocationProxy._last_location = current_location;
+ };
+ hashCheck();
+ Sammy.DefaultLocationProxy._interval = window.setInterval(hashCheck, every);
+ }
+ }
+ });
+
+
+ // Sammy.Application is the Base prototype for defining 'applications'.
+ // An 'application' is a collection of 'routes' and bound events that is
+ // attached to an element when `run()` is called.
+ // The only argument an 'app_function' is evaluated within the context of the application.
+ Sammy.Application = function(app_function) {
+ var app = this;
+ this.routes = {};
+ this.listeners = new Sammy.Object({});
+ this.arounds = [];
+ this.befores = [];
+ // generate a unique namespace
+ this.namespace = (new Date()).getTime() + '-' + parseInt(Math.random() * 1000, 10);
+ this.context_prototype = function() { Sammy.EventContext.apply(this, arguments); };
+ this.context_prototype.prototype = new Sammy.EventContext();
+
+ if (_isFunction(app_function)) {
+ app_function.apply(this, [this]);
+ }
+ // set the location proxy if not defined to the default (DefaultLocationProxy)
+ if (!this._location_proxy) {
+ this.setLocationProxy(new Sammy.DefaultLocationProxy(this, this.run_interval_every));
+ }
+ if (this.debug) {
+ this.bindToAllEvents(function(e, data) {
+ app.log(app.toString(), e.cleaned_type, data || {});
+ });
+ }
+ };
+
+ Sammy.Application.prototype = $.extend({}, Sammy.Object.prototype, {
+
+ // the four route verbs
+ ROUTE_VERBS: ['get','post','put','delete'],
+
+ // An array of the default events triggered by the
+ // application during its lifecycle
+ APP_EVENTS: ['run', 'unload', 'lookup-route', 'run-route', 'route-found', 'event-context-before', 'event-context-after', 'changed', 'error', 'check-form-submission', 'redirect', 'location-changed'],
+
+ _last_route: null,
+ _location_proxy: null,
+ _running: false,
+
+ // Defines what element the application is bound to. Provide a selector
+ // (parseable by `jQuery()`) and this will be used by `$element()`
+ element_selector: 'body',
+
+ // When set to true, logs all of the default events using `log()`
+ debug: false,
+
+ // When set to true, and the error() handler is not overridden, will actually
+ // raise JS errors in routes (500) and when routes can't be found (404)
+ raise_errors: false,
+
+ // The time in milliseconds that the URL is queried for changes
+ run_interval_every: 50,
+
+ // if using the `DefaultLocationProxy` setting this to true will force the app to use
+ // traditional hash based routing as opposed to the new HTML5 PushState support
+ disable_push_state: false,
+
+ // The default template engine to use when using `partial()` in an
+ // `EventContext`. `template_engine` can either be a string that
+ // corresponds to the name of a method/helper on EventContext or it can be a function
+ // that takes two arguments, the content of the unrendered partial and an optional
+ // JS object that contains interpolation data. Template engine is only called/referred
+ // to if the extension of the partial is null or unknown. See `partial()`
+ // for more information
+ template_engine: null,
+
+ // //=> Sammy.Application: body
+ toString: function() {
+ return 'Sammy.Application:' + this.element_selector;
+ },
+
+ // returns a jQuery object of the Applications bound element.
+ $element: function(selector) {
+ return selector ? $(this.element_selector).find(selector) : $(this.element_selector);
+ },
+
+ // `use()` is the entry point for including Sammy plugins.
+ // The first argument to use should be a function() that is evaluated
+ // in the context of the current application, just like the `app_function`
+ // argument to the `Sammy.Application` constructor.
+ //
+ // Any additional arguments are passed to the app function sequentially.
+ //
+ // For much more detail about plugins, check out:
+ // [http://sammyjs.org/docs/plugins](http://sammyjs.org/docs/plugins)
+ //
+ // ### Example
+ //
+ // var MyPlugin = function(app, prepend) {
+ //
+ // this.helpers({
+ // myhelper: function(text) {
+ // alert(prepend + " " + text);
+ // }
+ // });
+ //
+ // };
+ //
+ // var app = $.sammy(function() {
+ //
+ // this.use(MyPlugin, 'This is my plugin');
+ //
+ // this.get('#/', function() {
+ // this.myhelper('and dont you forget it!');
+ // //=> Alerts: This is my plugin and dont you forget it!
+ // });
+ //
+ // });
+ //
+ // If plugin is passed as a string it assumes your are trying to load
+ // Sammy."Plugin". This is the preferred way of loading core Sammy plugins
+ // as it allows for better error-messaging.
+ //
+ // ### Example
+ //
+ // $.sammy(function() {
+ // this.use('Mustache'); //=> Sammy.Mustache
+ // this.use('Storage'); //=> Sammy.Storage
+ // });
+ //
+ use: function() {
+ // flatten the arguments
+ var args = _makeArray(arguments),
+ plugin = args.shift(),
+ plugin_name = plugin || '';
+ try {
+ args.unshift(this);
+ if (typeof plugin == 'string') {
+ plugin_name = 'Sammy.' + plugin;
+ plugin = Sammy[plugin];
+ }
+ plugin.apply(this, args);
+ } catch(e) {
+ if (typeof plugin === 'undefined') {
+ this.error("Plugin Error: called use() but plugin (" + plugin_name.toString() + ") is not defined", e);
+ } else if (!_isFunction(plugin)) {
+ this.error("Plugin Error: called use() but '" + plugin_name.toString() + "' is not a function", e);
+ } else {
+ this.error("Plugin Error", e);
+ }
+ }
+ return this;
+ },
+
+ // Sets the location proxy for the current app. By default this is set to
+ // a new `Sammy.DefaultLocationProxy` on initialization. However, you can set
+ // the location_proxy inside you're app function to give your app a custom
+ // location mechanism. See `Sammy.DefaultLocationProxy` and `Sammy.DataLocationProxy`
+ // for examples.
+ //
+ // `setLocationProxy()` takes an initialized location proxy.
+ //
+ // ### Example
+ //
+ // // to bind to data instead of the default hash;
+ // var app = $.sammy(function() {
+ // this.setLocationProxy(new Sammy.DataLocationProxy(this));
+ // });
+ //
+ setLocationProxy: function(new_proxy) {
+ var original_proxy = this._location_proxy;
+ this._location_proxy = new_proxy;
+ if (this.isRunning()) {
+ if (original_proxy) {
+ // if there is already a location proxy, unbind it.
+ original_proxy.unbind();
+ }
+ this._location_proxy.bind();
+ }
+ },
+
+ // provide log() override for inside an app that includes the relevant application element_selector
+ log: function() {
+ Sammy.log.apply(Sammy, Array.prototype.concat.apply([this.element_selector],arguments));
+ },
+
+
+ // `route()` is the main method for defining routes within an application.
+ // For great detail on routes, check out:
+ // [http://sammyjs.org/docs/routes](http://sammyjs.org/docs/routes)
+ //
+ // This method also has aliases for each of the different verbs (eg. `get()`, `post()`, etc.)
+ //
+ // ### Arguments
+ //
+ // * `verb` A String in the set of ROUTE_VERBS or 'any'. 'any' will add routes for each
+ // of the ROUTE_VERBS. If only two arguments are passed,
+ // the first argument is the path, the second is the callback and the verb
+ // is assumed to be 'any'.
+ // * `path` A Regexp or a String representing the path to match to invoke this verb.
+ // * `callback` A Function that is called/evaluated when the route is run see: `runRoute()`.
+ // It is also possible to pass a string as the callback, which is looked up as the name
+ // of a method on the application.
+ //
+ route: function(verb, path) {
+ var app = this, param_names = [], add_route, path_match, callback = Array.prototype.slice.call(arguments,2);
+
+ // if the method signature is just (path, callback)
+ // assume the verb is 'any'
+ if (callback.length === 0 && _isFunction(path)) {
+ callback = [path];
+ path = verb;
+ verb = 'any';
+ }
+
+ verb = verb.toLowerCase(); // ensure verb is lower case
+
+ // if path is a string turn it into a regex
+ if (path.constructor == String) {
+
+ // Needs to be explicitly set because IE will maintain the index unless NULL is returned,
+ // which means that with two consecutive routes that contain params, the second set of params will not be found and end up in splat instead of params
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/RegExp/lastIndex
+ PATH_NAME_MATCHER.lastIndex = 0;
+
+ // find the names
+ while ((path_match = PATH_NAME_MATCHER.exec(path)) !== null) {
+ param_names.push(path_match[1]);
+ }
+ // replace with the path replacement
+ path = new RegExp(path.replace(PATH_NAME_MATCHER, PATH_REPLACER) + "$");
+ }
+ // lookup callbacks
+ $.each(callback,function(i,cb){
+ if (typeof(cb) === 'string') {
+ callback[i] = app[cb];
+ }
+ });
+
+ add_route = function(with_verb) {
+ var r = {verb: with_verb, path: path, callback: callback, param_names: param_names};
+ // add route to routes array
+ app.routes[with_verb] = app.routes[with_verb] || [];
+ // place routes in order of definition
+ app.routes[with_verb].push(r);
+ };
+
+ if (verb === 'any') {
+ $.each(this.ROUTE_VERBS, function(i, v) { add_route(v); });
+ } else {
+ add_route(verb);
+ }
+
+ // return the app
+ return this;
+ },
+
+ // Alias for route('get', ...)
+ get: _routeWrapper('get'),
+
+ // Alias for route('post', ...)
+ post: _routeWrapper('post'),
+
+ // Alias for route('put', ...)
+ put: _routeWrapper('put'),
+
+ // Alias for route('delete', ...)
+ del: _routeWrapper('delete'),
+
+ // Alias for route('any', ...)
+ any: _routeWrapper('any'),
+
+ // `mapRoutes` takes an array of arrays, each array being passed to route()
+ // as arguments, this allows for mass definition of routes. Another benefit is
+ // this makes it possible/easier to load routes via remote JSON.
+ //
+ // ### Example
+ //
+ // var app = $.sammy(function() {
+ //
+ // this.mapRoutes([
+ // ['get', '#/', function() { this.log('index'); }],
+ // // strings in callbacks are looked up as methods on the app
+ // ['post', '#/create', 'addUser'],
+ // // No verb assumes 'any' as the verb
+ // [/dowhatever/, function() { this.log(this.verb, this.path)}];
+ // ]);
+ // });
+ //
+ mapRoutes: function(route_array) {
+ var app = this;
+ $.each(route_array, function(i, route_args) {
+ app.route.apply(app, route_args);
+ });
+ return this;
+ },
+
+ // A unique event namespace defined per application.
+ // All events bound with `bind()` are automatically bound within this space.
+ eventNamespace: function() {
+ return ['sammy-app', this.namespace].join('-');
+ },
+
+ // Works just like `jQuery.fn.bind()` with a couple notable differences.
+ //
+ // * It binds all events to the application element
+ // * All events are bound within the `eventNamespace()`
+ // * Events are not actually bound until the application is started with `run()`
+ // * callbacks are evaluated within the context of a Sammy.EventContext
+ //
+ bind: function(name, data, callback) {
+ var app = this;
+ // build the callback
+ // if the arity is 2, callback is the second argument
+ if (typeof callback == 'undefined') { callback = data; }
+ var listener_callback = function() {
+ // pull off the context from the arguments to the callback
+ var e, context, data;
+ e = arguments[0];
+ data = arguments[1];
+ if (data && data.context) {
+ context = data.context;
+ delete data.context;
+ } else {
+ context = new app.context_prototype(app, 'bind', e.type, data, e.target);
+ }
+ e.cleaned_type = e.type.replace(app.eventNamespace(), '');
+ callback.apply(context, [e, data]);
+ };
+
+ // it could be that the app element doesnt exist yet
+ // so attach to the listeners array and then run()
+ // will actually bind the event.
+ if (!this.listeners[name]) { this.listeners[name] = []; }
+ this.listeners[name].push(listener_callback);
+ if (this.isRunning()) {
+ // if the app is running
+ // *actually* bind the event to the app element
+ this._listen(name, listener_callback);
+ }
+ return this;
+ },
+
+ // Triggers custom events defined with `bind()`
+ //
+ // ### Arguments
+ //
+ // * `name` The name of the event. Automatically prefixed with the `eventNamespace()`
+ // * `data` An optional Object that can be passed to the bound callback.
+ // * `context` An optional context/Object in which to execute the bound callback.
+ // If no context is supplied a the context is a new `Sammy.EventContext`
+ //
+ trigger: function(name, data) {
+ this.$element().trigger([name, this.eventNamespace()].join('.'), [data]);
+ return this;
+ },
+
+ // Reruns the current route
+ refresh: function() {
+ this.last_location = null;
+ this.trigger('location-changed');
+ return this;
+ },
+
+ // Takes a single callback that is pushed on to a stack.
+ // Before any route is run, the callbacks are evaluated in order within
+ // the current `Sammy.EventContext`
+ //
+ // If any of the callbacks explicitly return false, execution of any
+ // further callbacks and the route itself is halted.
+ //
+ // You can also provide a set of options that will define when to run this
+ // before based on the route it proceeds.
+ //
+ // ### Example
+ //
+ // var app = $.sammy(function() {
+ //
+ // // will run at #/route but not at #/
+ // this.before('#/route', function() {
+ // //...
+ // });
+ //
+ // // will run at #/ but not at #/route
+ // this.before({except: {path: '#/route'}}, function() {
+ // this.log('not before #/route');
+ // });
+ //
+ // this.get('#/', function() {});
+ //
+ // this.get('#/route', function() {});
+ //
+ // });
+ //
+ // See `contextMatchesOptions()` for a full list of supported options
+ //
+ before: function(options, callback) {
+ if (_isFunction(options)) {
+ callback = options;
+ options = {};
+ }
+ this.befores.push([options, callback]);
+ return this;
+ },
+
+ // A shortcut for binding a callback to be run after a route is executed.
+ // After callbacks have no guarunteed order.
+ after: function(callback) {
+ return this.bind('event-context-after', callback);
+ },
+
+
+ // Adds an around filter to the application. around filters are functions
+ // that take a single argument `callback` which is the entire route
+ // execution path wrapped up in a closure. This means you can decide whether
+ // or not to proceed with execution by not invoking `callback` or,
+ // more usefully wrapping callback inside the result of an asynchronous execution.
+ //
+ // ### Example
+ //
+ // The most common use case for around() is calling a _possibly_ async function
+ // and executing the route within the functions callback:
+ //
+ // var app = $.sammy(function() {
+ //
+ // var current_user = false;
+ //
+ // function checkLoggedIn(callback) {
+ // // /session returns a JSON representation of the logged in user
+ // // or an empty object
+ // if (!current_user) {
+ // $.getJSON('/session', function(json) {
+ // if (json.login) {
+ // // show the user as logged in
+ // current_user = json;
+ // // execute the route path
+ // callback();
+ // } else {
+ // // show the user as not logged in
+ // current_user = false;
+ // // the context of aroundFilters is an EventContext
+ // this.redirect('#/login');
+ // }
+ // });
+ // } else {
+ // // execute the route path
+ // callback();
+ // }
+ // };
+ //
+ // this.around(checkLoggedIn);
+ //
+ // });
+ //
+ around: function(callback) {
+ this.arounds.push(callback);
+ return this;
+ },
+
+ // Adds a onComplete function to the application. onComplete functions are executed
+ // at the end of a chain of route callbacks, if they call next(). Unlike after,
+ // which is called as soon as the route is complete, onComplete is like a final next()
+ // for all routes, and is thus run asynchronously
+ //
+ // ### Example
+ //
+ // app.get('/chain',function(context,next) {
+ // console.log('chain1');
+ // next();
+ // },function(context,next) {
+ // console.log('chain2');
+ // next();
+ // });
+ //
+ // app.get('/link',function(context,next) {
+ // console.log('link1');
+ // next();
+ // },function(context,next) {
+ // console.log('link2');
+ // next();
+ // });
+ //
+ // app.onComplete(function() {
+ // console.log("Running finally");
+ // });
+ //
+ // If you go to '/chain', you will get the following messages:
+ //
+ // chain1
+ // chain2
+ // Running onComplete
+ //
+ //
+ // If you go to /link, you will get the following messages:
+ //
+ // link1
+ // link2
+ // Running onComplete
+ //
+ //
+ // It really comes to play when doing asynchronous:
+ //
+ // app.get('/chain',function(context,next) {
+ // $.get('/my/url',function() {
+ // console.log('chain1');
+ // next();
+ // });
+ // },function(context,next) {
+ // console.log('chain2');
+ // next();
+ // });
+ //
+ onComplete: function(callback) {
+ this._onComplete = callback;
+ return this;
+ },
+
+ // Returns `true` if the current application is running.
+ isRunning: function() {
+ return this._running;
+ },
+
+ // Helpers extends the EventContext prototype specific to this app.
+ // This allows you to define app specific helper functions that can be used
+ // whenever you're inside of an event context (templates, routes, bind).
+ //
+ // ### Example
+ //
+ // var app = $.sammy(function() {
+ //
+ // helpers({
+ // upcase: function(text) {
+ // return text.toString().toUpperCase();
+ // }
+ // });
+ //
+ // get('#/', function() { with(this) {
+ // // inside of this context I can use the helpers
+ // $('#main').html(upcase($('#main').text());
+ // }});
+ //
+ // });
+ //
+ //
+ // ### Arguments
+ //
+ // * `extensions` An object collection of functions to extend the context.
+ //
+ helpers: function(extensions) {
+ $.extend(this.context_prototype.prototype, extensions);
+ return this;
+ },
+
+ // Helper extends the event context just like `helpers()` but does it
+ // a single method at a time. This is especially useful for dynamically named
+ // helpers
+ //
+ // ### Example
+ //
+ // // Trivial example that adds 3 helper methods to the context dynamically
+ // var app = $.sammy(function(app) {
+ //
+ // $.each([1,2,3], function(i, num) {
+ // app.helper('helper' + num, function() {
+ // this.log("I'm helper number " + num);
+ // });
+ // });
+ //
+ // this.get('#/', function() {
+ // this.helper2(); //=> I'm helper number 2
+ // });
+ // });
+ //
+ // ### Arguments
+ //
+ // * `name` The name of the method
+ // * `method` The function to be added to the prototype at `name`
+ //
+ helper: function(name, method) {
+ this.context_prototype.prototype[name] = method;
+ return this;
+ },
+
+ // Actually starts the application's lifecycle. `run()` should be invoked
+ // within a document.ready block to ensure the DOM exists before binding events, etc.
+ //
+ // ### Example
+ //
+ // var app = $.sammy(function() { ... }); // your application
+ // $(function() { // document.ready
+ // app.run();
+ // });
+ //
+ // ### Arguments
+ //
+ // * `start_url` Optionally, a String can be passed which the App will redirect to
+ // after the events/routes have been bound.
+ run: function(start_url) {
+ if (this.isRunning()) { return false; }
+ var app = this;
+
+ // actually bind all the listeners
+ $.each(this.listeners.toHash(), function(name, callbacks) {
+ $.each(callbacks, function(i, listener_callback) {
+ app._listen(name, listener_callback);
+ });
+ });
+
+ this.trigger('run', {start_url: start_url});
+ this._running = true;
+ // set last location
+ this.last_location = null;
+ if (!(/\#(.+)/.test(this.getLocation())) && typeof start_url != 'undefined') {
+ this.setLocation(start_url);
+ }
+ // check url
+ this._checkLocation();
+ this._location_proxy.bind();
+ this.bind('location-changed', function() {
+ app._checkLocation();
+ });
+
+ // bind to submit to capture post/put/delete routes
+ this.bind('submit', function(e) {
+ if ( !Sammy.targetIsThisWindow(e, 'form') ) { return true; }
+ var returned = app._checkFormSubmission($(e.target).closest('form'));
+ return (returned === false) ? e.preventDefault() : false;
+ });
+
+ // bind unload to body unload
+ $(window).bind('unload', function() {
+ app.unload();
+ });
+
+ // trigger html changed
+ return this.trigger('changed');
+ },
+
+ // The opposite of `run()`, un-binds all event listeners and intervals
+ // `run()` Automatically binds a `onunload` event to run this when
+ // the document is closed.
+ unload: function() {
+ if (!this.isRunning()) { return false; }
+ var app = this;
+ this.trigger('unload');
+ // clear interval
+ this._location_proxy.unbind();
+ // unbind form submits
+ this.$element().unbind('submit').removeClass(app.eventNamespace());
+ // unbind all events
+ $.each(this.listeners.toHash() , function(name, listeners) {
+ $.each(listeners, function(i, listener_callback) {
+ app._unlisten(name, listener_callback);
+ });
+ });
+ this._running = false;
+ return this;
+ },
+
+ // Not only runs `unbind` but also destroys the app reference.
+ destroy: function() {
+ this.unload();
+ delete Sammy.apps[this.element_selector];
+ return this;
+ },
+
+ // Will bind a single callback function to every event that is already
+ // being listened to in the app. This includes all the `APP_EVENTS`
+ // as well as any custom events defined with `bind()`.
+ //
+ // Used internally for debug logging.
+ bindToAllEvents: function(callback) {
+ var app = this;
+ // bind to the APP_EVENTS first
+ $.each(this.APP_EVENTS, function(i, e) {
+ app.bind(e, callback);
+ });
+ // next, bind to listener names (only if they dont exist in APP_EVENTS)
+ $.each(this.listeners.keys(true), function(i, name) {
+ if ($.inArray(name, app.APP_EVENTS) == -1) {
+ app.bind(name, callback);
+ }
+ });
+ return this;
+ },
+
+ // Returns a copy of the given path with any query string after the hash
+ // removed.
+ routablePath: function(path) {
+ return path.replace(QUERY_STRING_MATCHER, '');
+ },
+
+ // Given a verb and a String path, will return either a route object or false
+ // if a matching route can be found within the current defined set.
+ lookupRoute: function(verb, path) {
+ var app = this, routed = false, i = 0, l, route;
+ if (typeof this.routes[verb] != 'undefined') {
+ l = this.routes[verb].length;
+ for (; i < l; i++) {
+ route = this.routes[verb][i];
+ if (app.routablePath(path).match(route.path)) {
+ routed = route;
+ break;
+ }
+ }
+ }
+ return routed;
+ },
+
+ // First, invokes `lookupRoute()` and if a route is found, parses the
+ // possible URL params and then invokes the route's callback within a new
+ // `Sammy.EventContext`. If the route can not be found, it calls
+ // `notFound()`. If `raise_errors` is set to `true` and
+ // the `error()` has not been overridden, it will throw an actual JS
+ // error.
+ //
+ // You probably will never have to call this directly.
+ //
+ // ### Arguments
+ //
+ // * `verb` A String for the verb.
+ // * `path` A String path to lookup.
+ // * `params` An Object of Params pulled from the URI or passed directly.
+ //
+ // ### Returns
+ //
+ // Either returns the value returned by the route callback or raises a 404 Not Found error.
+ //
+ runRoute: function(verb, path, params, target) {
+ var app = this,
+ route = this.lookupRoute(verb, path),
+ context,
+ wrapped_route,
+ arounds,
+ around,
+ befores,
+ before,
+ callback_args,
+ path_params,
+ final_returned;
+
+ if (this.debug) {
+ this.log('runRoute', [verb, path].join(' '));
+ }
+
+ this.trigger('run-route', {verb: verb, path: path, params: params});
+ if (typeof params == 'undefined') { params = {}; }
+
+ $.extend(params, this._parseQueryString(path));
+
+ if (route) {
+ this.trigger('route-found', {route: route});
+ // pull out the params from the path
+ if ((path_params = route.path.exec(this.routablePath(path))) !== null) {
+ // first match is the full path
+ path_params.shift();
+ // for each of the matches
+ $.each(path_params, function(i, param) {
+ // if theres a matching param name
+ if (route.param_names[i]) {
+ // set the name to the match
+ params[route.param_names[i]] = _decode(param);
+ } else {
+ // initialize 'splat'
+ if (!params.splat) { params.splat = []; }
+ params.splat.push(_decode(param));
+ }
+ });
+ }
+
+ // set event context
+ context = new this.context_prototype(this, verb, path, params, target);
+ // ensure arrays
+ arounds = this.arounds.slice(0);
+ befores = this.befores.slice(0);
+ // set the callback args to the context + contents of the splat
+ callback_args = [context];
+ if (params.splat) {
+ callback_args = callback_args.concat(params.splat);
+ }
+ // wrap the route up with the before filters
+ wrapped_route = function() {
+ var returned, i, nextRoute;
+ while (befores.length > 0) {
+ before = befores.shift();
+ // check the options
+ if (app.contextMatchesOptions(context, before[0])) {
+ returned = before[1].apply(context, [context]);
+ if (returned === false) { return false; }
+ }
+ }
+ app.last_route = route;
+ context.trigger('event-context-before', {context: context});
+ // run multiple callbacks
+ if (typeof(route.callback) === "function") {
+ route.callback = [route.callback];
+ }
+ if (route.callback && route.callback.length) {
+ i = -1;
+ nextRoute = function() {
+ i++;
+ if (route.callback[i]) {
+ returned = route.callback[i].apply(context,callback_args);
+ } else if (app._onComplete && typeof(app._onComplete === "function")) {
+ app._onComplete(context);
+ }
+ };
+ callback_args.push(nextRoute);
+ nextRoute();
+ }
+ context.trigger('event-context-after', {context: context});
+ return returned;
+ };
+ $.each(arounds.reverse(), function(i, around) {
+ var last_wrapped_route = wrapped_route;
+ wrapped_route = function() { return around.apply(context, [last_wrapped_route]); };
+ });
+ try {
+ final_returned = wrapped_route();
+ } catch(e) {
+ this.error(['500 Error', verb, path].join(' '), e);
+ }
+ return final_returned;
+ } else {
+ return this.notFound(verb, path);
+ }
+ },
+
+ // Matches an object of options against an `EventContext` like object that
+ // contains `path` and `verb` attributes. Internally Sammy uses this
+ // for matching `before()` filters against specific options. You can set the
+ // object to _only_ match certain paths or verbs, or match all paths or verbs _except_
+ // those that match the options.
+ //
+ // ### Example
+ //
+ // var app = $.sammy(),
+ // context = {verb: 'get', path: '#/mypath'};
+ //
+ // // match against a path string
+ // app.contextMatchesOptions(context, '#/mypath'); //=> true
+ // app.contextMatchesOptions(context, '#/otherpath'); //=> false
+ // // equivalent to
+ // app.contextMatchesOptions(context, {only: {path:'#/mypath'}}); //=> true
+ // app.contextMatchesOptions(context, {only: {path:'#/otherpath'}}); //=> false
+ // // match against a path regexp
+ // app.contextMatchesOptions(context, /path/); //=> true
+ // app.contextMatchesOptions(context, /^path/); //=> false
+ // // match only a verb
+ // app.contextMatchesOptions(context, {only: {verb:'get'}}); //=> true
+ // app.contextMatchesOptions(context, {only: {verb:'post'}}); //=> false
+ // // match all except a verb
+ // app.contextMatchesOptions(context, {except: {verb:'post'}}); //=> true
+ // app.contextMatchesOptions(context, {except: {verb:'get'}}); //=> false
+ // // match all except a path
+ // app.contextMatchesOptions(context, {except: {path:'#/otherpath'}}); //=> true
+ // app.contextMatchesOptions(context, {except: {path:'#/mypath'}}); //=> false
+ // // match all except a verb and a path
+ // app.contextMatchesOptions(context, {except: {path:'#/otherpath', verb:'post'}}); //=> true
+ // app.contextMatchesOptions(context, {except: {path:'#/mypath', verb:'post'}}); //=> true
+ // app.contextMatchesOptions(context, {except: {path:'#/mypath', verb:'get'}}); //=> false
+ // // match multiple paths
+ // app.contextMatchesOptions(context, {path: ['#/mypath', '#/otherpath']}); //=> true
+ // app.contextMatchesOptions(context, {path: ['#/otherpath', '#/thirdpath']}); //=> false
+ // // equivalent to
+ // app.contextMatchesOptions(context, {only: {path: ['#/mypath', '#/otherpath']}}); //=> true
+ // app.contextMatchesOptions(context, {only: {path: ['#/otherpath', '#/thirdpath']}}); //=> false
+ // // match all except multiple paths
+ // app.contextMatchesOptions(context, {except: {path: ['#/mypath', '#/otherpath']}}); //=> false
+ // app.contextMatchesOptions(context, {except: {path: ['#/otherpath', '#/thirdpath']}}); //=> true
+ // // match all except multiple paths and verbs
+ // app.contextMatchesOptions(context, {except: {path: ['#/mypath', '#/otherpath'], verb: ['get', 'post']}}); //=> false
+ // app.contextMatchesOptions(context, {except: {path: ['#/otherpath', '#/thirdpath'], verb: ['get', 'post']}}); //=> true
+ //
+ contextMatchesOptions: function(context, match_options, positive) {
+ var options = match_options;
+ // normalize options
+ if (typeof options === 'string' || _isRegExp(options)) {
+ options = {path: options};
+ }
+ if (typeof positive === 'undefined') {
+ positive = true;
+ }
+ // empty options always match
+ if ($.isEmptyObject(options)) {
+ return true;
+ }
+ // Do we have to match against multiple paths?
+ if (_isArray(options.path)){
+ var results, numopt, opts, len;
+ results = [];
+ for (numopt = 0, len = options.path.length; numopt < len; numopt += 1) {
+ opts = $.extend({}, options, {path: options.path[numopt]});
+ results.push(this.contextMatchesOptions(context, opts));
+ }
+ var matched = $.inArray(true, results) > -1 ? true : false;
+ return positive ? matched : !matched;
+ }
+ if (options.only) {
+ return this.contextMatchesOptions(context, options.only, true);
+ } else if (options.except) {
+ return this.contextMatchesOptions(context, options.except, false);
+ }
+ var path_matched = true, verb_matched = true;
+ if (options.path) {
+ if (!_isRegExp(options.path)) {
+ options.path = new RegExp(options.path.toString() + '$');
+ }
+ path_matched = options.path.test(context.path);
+ }
+ if (options.verb) {
+ if(typeof options.verb === 'string') {
+ verb_matched = options.verb === context.verb;
+ } else {
+ verb_matched = options.verb.indexOf(context.verb) > -1;
+ }
+ }
+ return positive ? (verb_matched && path_matched) : !(verb_matched && path_matched);
+ },
+
+
+ // Delegates to the `location_proxy` to get the current location.
+ // See `Sammy.DefaultLocationProxy` for more info on location proxies.
+ getLocation: function() {
+ return this._location_proxy.getLocation();
+ },
+
+ // Delegates to the `location_proxy` to set the current location.
+ // See `Sammy.DefaultLocationProxy` for more info on location proxies.
+ //
+ // ### Arguments
+ //
+ // * `new_location` A new location string (e.g. '#/')
+ //
+ setLocation: function(new_location) {
+ return this._location_proxy.setLocation(new_location);
+ },
+
+ // Swaps the content of `$element()` with `content`
+ // You can override this method to provide an alternate swap behavior
+ // for `EventContext.partial()`.
+ //
+ // ### Example
+ //
+ // var app = $.sammy(function() {
+ //
+ // // implements a 'fade out'/'fade in'
+ // this.swap = function(content, callback) {
+ // var context = this;
+ // context.$element().fadeOut('slow', function() {
+ // context.$element().html(content);
+ // context.$element().fadeIn('slow', function() {
+ // if (callback) {
+ // callback.apply();
+ // }
+ // });
+ // });
+ // };
+ //
+ // });
+ //
+ swap: function(content, callback) {
+ var $el = this.$element().html(content);
+ if (_isFunction(callback)) { callback(content); }
+ return $el;
+ },
+
+ // a simple global cache for templates. Uses the same semantics as
+ // `Sammy.Cache` and `Sammy.Storage` so can easily be replaced with
+ // a persistent storage that lasts beyond the current request.
+ templateCache: function(key, value) {
+ if (typeof value != 'undefined') {
+ return _template_cache[key] = value;
+ } else {
+ return _template_cache[key];
+ }
+ },
+
+ // clear the templateCache
+ clearTemplateCache: function() {
+ return (_template_cache = {});
+ },
+
+ // This throws a '404 Not Found' error by invoking `error()`.
+ // Override this method or `error()` to provide custom
+ // 404 behavior (i.e redirecting to / or showing a warning)
+ notFound: function(verb, path) {
+ var ret = this.error(['404 Not Found', verb, path].join(' '));
+ return (verb === 'get') ? ret : true;
+ },
+
+ // The base error handler takes a string `message` and an `Error`
+ // object. If `raise_errors` is set to `true` on the app level,
+ // this will re-throw the error to the browser. Otherwise it will send the error
+ // to `log()`. Override this method to provide custom error handling
+ // e.g logging to a server side component or displaying some feedback to the
+ // user.
+ error: function(message, original_error) {
+ if (!original_error) { original_error = new Error(); }
+ original_error.message = [message, original_error.message].join(' ');
+ this.trigger('error', {message: original_error.message, error: original_error});
+ if (this.raise_errors) {
+ throw(original_error);
+ } else {
+ this.log(original_error.message, original_error);
+ }
+ },
+
+ _checkLocation: function() {
+ var location, returned;
+ // get current location
+ location = this.getLocation();
+ // compare to see if hash has changed
+ if (!this.last_location || this.last_location[0] != 'get' || this.last_location[1] != location) {
+ // reset last location
+ this.last_location = ['get', location];
+ // lookup route for current hash
+ returned = this.runRoute('get', location);
+ }
+ return returned;
+ },
+
+ _getFormVerb: function(form) {
+ var $form = $(form), verb, $_method;
+ $_method = $form.find('input[name="_method"]');
+ if ($_method.length > 0) { verb = $_method.val(); }
+ if (!verb) { verb = $form[0].getAttribute('method'); }
+ if (!verb || verb === '') { verb = 'get'; }
+ return $.trim(verb.toString().toLowerCase());
+ },
+
+ _checkFormSubmission: function(form) {
+ var $form, path, verb, params, returned;
+ this.trigger('check-form-submission', {form: form});
+ $form = $(form);
+ path = $form.attr('action') || '';
+ verb = this._getFormVerb($form);
+
+ if (this.debug) {
+ this.log('_checkFormSubmission', $form, path, verb);
+ }
+
+ if (verb === 'get') {
+ params = this._serializeFormParams($form);
+ if (params !== '') { path += '?' + params; }
+ this.setLocation(path);
+ returned = false;
+ } else {
+ params = $.extend({}, this._parseFormParams($form));
+ returned = this.runRoute(verb, path, params, form.get(0));
+ }
+ return (typeof returned == 'undefined') ? false : returned;
+ },
+
+ _serializeFormParams: function($form) {
+ var queryString = "",
+ fields = $form.serializeArray(),
+ i;
+ if (fields.length > 0) {
+ queryString = this._encodeFormPair(fields[0].name, fields[0].value);
+ for (i = 1; i < fields.length; i++) {
+ queryString = queryString + "&" + this._encodeFormPair(fields[i].name, fields[i].value);
+ }
+ }
+ return queryString;
+ },
+
+ _encodeFormPair: function(name, value){
+ return _encode(name) + "=" + _encode(value);
+ },
+
+ _parseFormParams: function($form) {
+ var params = {},
+ form_fields = $form.serializeArray(),
+ i;
+ for (i = 0; i < form_fields.length; i++) {
+ params = this._parseParamPair(params, form_fields[i].name, form_fields[i].value);
+ }
+ return params;
+ },
+
+ _parseQueryString: function(path) {
+ var params = {}, parts, pairs, pair, i;
+
+ parts = path.match(QUERY_STRING_MATCHER);
+ if (parts && parts[1]) {
+ pairs = parts[1].split('&');
+ for (i = 0; i < pairs.length; i++) {
+ pair = pairs[i].split('=');
+ params = this._parseParamPair(params, _decode(pair[0]), _decode(pair[1] || ""));
+ }
+ }
+ return params;
+ },
+
+ _parseParamPair: function(params, key, value) {
+ if (typeof params[key] !== 'undefined') {
+ if (_isArray(params[key])) {
+ params[key].push(value);
+ } else {
+ params[key] = [params[key], value];
+ }
+ } else {
+ params[key] = value;
+ }
+ return params;
+ },
+
+ _listen: function(name, callback) {
+ return this.$element().bind([name, this.eventNamespace()].join('.'), callback);
+ },
+
+ _unlisten: function(name, callback) {
+ return this.$element().unbind([name, this.eventNamespace()].join('.'), callback);
+ }
+
+ });
+
+ // `Sammy.RenderContext` is an object that makes sequential template loading,
+ // rendering and interpolation seamless even when dealing with asynchronous
+ // operations.
+ //
+ // `RenderContext` objects are not usually created directly, rather they are
+ // instantiated from an `Sammy.EventContext` by using `render()`, `load()` or
+ // `partial()` which all return `RenderContext` objects.
+ //
+ // `RenderContext` methods always returns a modified `RenderContext`
+ // for chaining (like jQuery itself).
+ //
+ // The core magic is in the `then()` method which puts the callback passed as
+ // an argument into a queue to be executed once the previous callback is complete.
+ // All the methods of `RenderContext` are wrapped in `then()` which allows you
+ // to queue up methods by chaining, but maintaining a guaranteed execution order
+ // even with remote calls to fetch templates.
+ //
+ Sammy.RenderContext = function(event_context) {
+ this.event_context = event_context;
+ this.callbacks = [];
+ this.previous_content = null;
+ this.content = null;
+ this.next_engine = false;
+ this.waiting = false;
+ };
+
+ Sammy.RenderContext.prototype = $.extend({}, Sammy.Object.prototype, {
+
+ // The "core" of the `RenderContext` object, adds the `callback` to the
+ // queue. If the context is `waiting` (meaning an async operation is happening)
+ // then the callback will be executed in order, once the other operations are
+ // complete. If there is no currently executing operation, the `callback`
+ // is executed immediately.
+ //
+ // The value returned from the callback is stored in `content` for the
+ // subsequent operation. If you return `false`, the queue will pause, and
+ // the next callback in the queue will not be executed until `next()` is
+ // called. This allows for the guaranteed order of execution while working
+ // with async operations.
+ //
+ // If then() is passed a string instead of a function, the string is looked
+ // up as a helper method on the event context.
+ //
+ // ### Example
+ //
+ // this.get('#/', function() {
+ // // initialize the RenderContext
+ // // Even though `load()` executes async, the next `then()`
+ // // wont execute until the load finishes
+ // this.load('myfile.txt')
+ // .then(function(content) {
+ // // the first argument to then is the content of the
+ // // prev operation
+ // $('#main').html(content);
+ // });
+ // });
+ //
+ then: function(callback) {
+ if (!_isFunction(callback)) {
+ // if a string is passed to then, assume we want to call
+ // a helper on the event context in its context
+ if (typeof callback === 'string' && callback in this.event_context) {
+ var helper = this.event_context[callback];
+ callback = function(content) {
+ return helper.apply(this.event_context, [content]);
+ };
+ } else {
+ return this;
+ }
+ }
+ var context = this;
+ if (this.waiting) {
+ this.callbacks.push(callback);
+ } else {
+ this.wait();
+ window.setTimeout(function() {
+ var returned = callback.apply(context, [context.content, context.previous_content]);
+ if (returned !== false) {
+ context.next(returned);
+ }
+ }, 0);
+ }
+ return this;
+ },
+
+ // Pause the `RenderContext` queue. Combined with `next()` allows for async
+ // operations.
+ //
+ // ### Example
+ //
+ // this.get('#/', function() {
+ // this.load('mytext.json')
+ // .then(function(content) {
+ // var context = this,
+ // data = JSON.parse(content);
+ // // pause execution
+ // context.wait();
+ // // post to a url
+ // $.post(data.url, {}, function(response) {
+ // context.next(JSON.parse(response));
+ // });
+ // })
+ // .then(function(data) {
+ // // data is json from the previous post
+ // $('#message').text(data.status);
+ // });
+ // });
+ wait: function() {
+ this.waiting = true;
+ },
+
+ // Resume the queue, setting `content` to be used in the next operation.
+ // See `wait()` for an example.
+ next: function(content) {
+ this.waiting = false;
+ if (typeof content !== 'undefined') {
+ this.previous_content = this.content;
+ this.content = content;
+ }
+ if (this.callbacks.length > 0) {
+ this.then(this.callbacks.shift());
+ }
+ },
+
+ // Load a template into the context.
+ // The `location` can either be a string specifying the remote path to the
+ // file, a jQuery object, or a DOM element.
+ //
+ // No interpolation happens by default, the content is stored in
+ // `content`.
+ //
+ // In the case of a path, unless the option `{cache: false}` is passed the
+ // data is stored in the app's `templateCache()`.
+ //
+ // If a jQuery or DOM object is passed the `innerHTML` of the node is pulled in.
+ // This is useful for nesting templates as part of the initial page load wrapped
+ // in invisible elements or `<script>` tags. With template paths, the template
+ // engine is looked up by the extension. For DOM/jQuery embedded templates,
+ // this isnt possible, so there are a couple of options:
+ //
+ // * pass an `{engine:}` option.
+ // * define the engine in the `data-engine` attribute of the passed node.
+ // * just store the raw template data and use `interpolate()` manually
+ //
+ // If a `callback` is passed it is executed after the template load.
+ load: function(location, options, callback) {
+ var context = this;
+ return this.then(function() {
+ var should_cache, cached, is_json, location_array;
+ if (_isFunction(options)) {
+ callback = options;
+ options = {};
+ } else {
+ options = $.extend({}, options);
+ }
+ if (callback) { this.then(callback); }
+ if (typeof location === 'string') {
+ // it's a path
+ is_json = (location.match(/\.json(\?|$)/) || options.json);
+ should_cache = is_json ? options.cache === true : options.cache !== false;
+ context.next_engine = context.event_context.engineFor(location);
+ delete options.cache;
+ delete options.json;
+ if (options.engine) {
+ context.next_engine = options.engine;
+ delete options.engine;
+ }
+ if (should_cache && (cached = this.event_context.app.templateCache(location))) {
+ return cached;
+ }
+ this.wait();
+ $.ajax($.extend({
+ url: location,
+ data: {},
+ dataType: is_json ? 'json' : 'text',
+ type: 'get',
+ success: function(data) {
+ if (should_cache) {
+ context.event_context.app.templateCache(location, data);
+ }
+ context.next(data);
+ }
+ }, options));
+ return false;
+ } else {
+ // it's a dom/jQuery
+ if (location.nodeType) {
+ return location.innerHTML;
+ }
+ if (location.selector) {
+ // it's a jQuery
+ context.next_engine = location.attr('data-engine');
+ if (options.clone === false) {
+ return location.remove()[0].innerHTML.toString();
+ } else {
+ return location[0].innerHTML.toString();
+ }
+ }
+ }
+ });
+ },
+
+ // Load partials
+ //
+ // ### Example
+ //
+ // this.loadPartials({mypartial: '/path/to/partial'});
+ //
+ loadPartials: function(partials) {
+ var name;
+ if(partials) {
+ this.partials = this.partials || {};
+ for(name in partials) {
+ (function(context, name) {
+ context.load(partials[name])
+ .then(function(template) {
+ this.partials[name] = template;
+ });
+ })(this, name);
+ }
+ }
+ return this;
+ },
+
+ // `load()` a template and then `interpolate()` it with data.
+ //
+ // can be called with multiple different signatures:
+ //
+ // this.render(callback);
+ // this.render('/location');
+ // this.render('/location', {some: data});
+ // this.render('/location', callback);
+ // this.render('/location', {some: data}, callback);
+ // this.render('/location', {some: data}, {my: partials});
+ // this.render('/location', callback, {my: partials});
+ // this.render('/location', {some: data}, callback, {my: partials});
+ //
+ // ### Example
+ //
+ // this.get('#/', function() {
+ // this.render('mytemplate.template', {name: 'test'});
+ // });
+ //
+ render: function(location, data, callback, partials) {
+ if (_isFunction(location) && !data) {
+ // invoked as render(callback)
+ return this.then(location);
+ } else {
+ if(_isFunction(data)) {
+ // invoked as render(location, callback, [partials])
+ partials = callback;
+ callback = data;
+ data = null;
+ } else if(callback && !_isFunction(callback)) {
+ // invoked as render(location, data, partials)
+ partials = callback;
+ callback = null;
+ }
+
+ return this.loadPartials(partials)
+ .load(location)
+ .interpolate(data, location)
+ .then(callback);
+ }
+ },
+
+ // `render()` the `location` with `data` and then `swap()` the
+ // app's `$element` with the rendered content.
+ partial: function(location, data, callback, partials) {
+ if (_isFunction(callback)) {
+ // invoked as partial(location, data, callback, [partials])
+ return this.render(location, data, partials).swap(callback);
+ } else if (_isFunction(data)) {
+ // invoked as partial(location, callback, [partials])
+ return this.render(location, {}, callback).swap(data);
+ } else {
+ // invoked as partial(location, data, [partials])
+ return this.render(location, data, callback).swap();
+ }
+ },
+
+ // defers the call of function to occur in order of the render queue.
+ // The function can accept any number of arguments as long as the last
+ // argument is a callback function. This is useful for putting arbitrary
+ // asynchronous functions into the queue. The content passed to the
+ // callback is passed as `content` to the next item in the queue.
+ //
+ // ### Example
+ //
+ // this.send($.getJSON, '/app.json')
+ // .then(function(json) {
+ // $('#message).text(json['message']);
+ // });
+ //
+ //
+ send: function() {
+ var context = this,
+ args = _makeArray(arguments),
+ fun = args.shift();
+
+ if (_isArray(args[0])) { args = args[0]; }
+
+ return this.then(function(content) {
+ args.push(function(response) { context.next(response); });
+ context.wait();
+ fun.apply(fun, args);
+ return false;
+ });
+ },
+
+ // iterates over an array, applying the callback for each item item. the
+ // callback takes the same style of arguments as `jQuery.each()` (index, item).
+ // The return value of each callback is collected as a single string and stored
+ // as `content` to be used in the next iteration of the `RenderContext`.
+ collect: function(array, callback, now) {
+ var context = this;
+ var coll = function() {
+ if (_isFunction(array)) {
+ callback = array;
+ array = this.content;
+ }
+ var contents = [], doms = false;
+ $.each(array, function(i, item) {
+ var returned = callback.apply(context, [i, item]);
+ if (returned.jquery && returned.length == 1) {
+ returned = returned[0];
+ doms = true;
+ }
+ contents.push(returned);
+ return returned;
+ });
+ return doms ? contents : contents.join('');
+ };
+ return now ? coll() : this.then(coll);
+ },
+
+ // loads a template, and then interpolates it for each item in the `data`
+ // array. If a callback is passed, it will call the callback with each
+ // item in the array _after_ interpolation
+ renderEach: function(location, name, data, callback) {
+ if (_isArray(name)) {
+ callback = data;
+ data = name;
+ name = null;
+ }
+ return this.load(location).then(function(content) {
+ var rctx = this;
+ if (!data) {
+ data = _isArray(this.previous_content) ? this.previous_content : [];
+ }
+ if (callback) {
+ $.each(data, function(i, value) {
+ var idata = {}, engine = this.next_engine || location;
+ if (name) {
+ idata[name] = value;
+ } else {
+ idata = value;
+ }
+ callback(value, rctx.event_context.interpolate(content, idata, engine));
+ });
+ } else {
+ return this.collect(data, function(i, value) {
+ var idata = {}, engine = this.next_engine || location;
+ if (name) {
+ idata[name] = value;
+ } else {
+ idata = value;
+ }
+ return this.event_context.interpolate(content, idata, engine);
+ }, true);
+ }
+ });
+ },
+
+ // uses the previous loaded `content` and the `data` object to interpolate
+ // a template. `engine` defines the templating/interpolation method/engine
+ // that should be used. If `engine` is not passed, the `next_engine` is
+ // used. If `retain` is `true`, the final interpolated data is appended to
+ // the `previous_content` instead of just replacing it.
+ interpolate: function(data, engine, retain) {
+ var context = this;
+ return this.then(function(content, prev) {
+ if (!data && prev) { data = prev; }
+ if (this.next_engine) {
+ engine = this.next_engine;
+ this.next_engine = false;
+ }
+ var rendered = context.event_context.interpolate(content, data, engine, this.partials);
+ return retain ? prev + rendered : rendered;
+ });
+ },
+
+ // Swap the return contents ensuring order. See `Application#swap`
+ swap: function(callback) {
+ return this.then(function(content) {
+ this.event_context.swap(content, callback);
+ return content;
+ }).trigger('changed', {});
+ },
+
+ // Same usage as `jQuery.fn.appendTo()` but uses `then()` to ensure order
+ appendTo: function(selector) {
+ return this.then(function(content) {
+ $(selector).append(content);
+ }).trigger('changed', {});
+ },
+
+ // Same usage as `jQuery.fn.prependTo()` but uses `then()` to ensure order
+ prependTo: function(selector) {
+ return this.then(function(content) {
+ $(selector).prepend(content);
+ }).trigger('changed', {});
+ },
+
+ // Replaces the `$(selector)` using `html()` with the previously loaded
+ // `content`
+ replace: function(selector) {
+ return this.then(function(content) {
+ $(selector).html(content);
+ }).trigger('changed', {});
+ },
+
+ // trigger the event in the order of the event context. Same semantics
+ // as `Sammy.EventContext#trigger()`. If data is omitted, `content`
+ // is sent as `{content: content}`
+ trigger: function(name, data) {
+ return this.then(function(content) {
+ if (typeof data == 'undefined') { data = {content: content}; }
+ this.event_context.trigger(name, data);
+ return content;
+ });
+ }
+
+ });
+
+ // `Sammy.EventContext` objects are created every time a route is run or a
+ // bound event is triggered. The callbacks for these events are evaluated within a `Sammy.EventContext`
+ // This within these callbacks the special methods of `EventContext` are available.
+ //
+ // ### Example
+ //
+ // $.sammy(function() {
+ // // The context here is this Sammy.Application
+ // this.get('#/:name', function() {
+ // // The context here is a new Sammy.EventContext
+ // if (this.params['name'] == 'sammy') {
+ // this.partial('name.html.erb', {name: 'Sammy'});
+ // } else {
+ // this.redirect('#/somewhere-else')
+ // }
+ // });
+ // });
+ //
+ // Initialize a new EventContext
+ //
+ // ### Arguments
+ //
+ // * `app` The `Sammy.Application` this event is called within.
+ // * `verb` The verb invoked to run this context/route.
+ // * `path` The string path invoked to run this context/route.
+ // * `params` An Object of optional params to pass to the context. Is converted
+ // to a `Sammy.Object`.
+ // * `target` a DOM element that the event that holds this context originates
+ // from. For post, put and del routes, this is the form element that triggered
+ // the route.
+ //
+ Sammy.EventContext = function(app, verb, path, params, target) {
+ this.app = app;
+ this.verb = verb;
+ this.path = path;
+ this.params = new Sammy.Object(params);
+ this.target = target;
+ };
+
+ Sammy.EventContext.prototype = $.extend({}, Sammy.Object.prototype, {
+
+ // A shortcut to the app's `$element()`
+ $element: function() {
+ return this.app.$element(_makeArray(arguments).shift());
+ },
+
+ // Look up a templating engine within the current app and context.
+ // `engine` can be one of the following:
+ //
+ // * a function: should conform to `function(content, data) { return interpolated; }`
+ // * a template path: 'template.ejs', looks up the extension to match to
+ // the `ejs()` helper
+ // * a string referring to the helper: "mustache" => `mustache()`
+ //
+ // If no engine is found, use the app's default `template_engine`
+ //
+ engineFor: function(engine) {
+ var context = this, engine_match;
+ // if path is actually an engine function just return it
+ if (_isFunction(engine)) { return engine; }
+ // lookup engine name by path extension
+ engine = (engine || context.app.template_engine).toString();
+ if ((engine_match = engine.match(/\.([^\.\?\#]+)(\?|$)/))) {
+ engine = engine_match[1];
+ }
+ // set the engine to the default template engine if no match is found
+ if (engine && _isFunction(context[engine])) {
+ return context[engine];
+ }
+
+ if (context.app.template_engine) {
+ return this.engineFor(context.app.template_engine);
+ }
+ return function(content, data) { return content; };
+ },
+
+ // using the template `engine` found with `engineFor()`, interpolate the
+ // `data` into `content`
+ interpolate: function(content, data, engine, partials) {
+ return this.engineFor(engine).apply(this, [content, data, partials]);
+ },
+
+ // Create and return a `Sammy.RenderContext` calling `render()` on it.
+ // Loads the template and interpolate the data, however does not actual
+ // place it in the DOM.
+ //
+ // ### Example
+ //
+ // // mytemplate.mustache <div class="name">{{name}}</div>
+ // render('mytemplate.mustache', {name: 'quirkey'});
+ // // sets the `content` to <div class="name">quirkey</div>
+ // render('mytemplate.mustache', {name: 'quirkey'})
+ // .appendTo('ul');
+ // // appends the rendered content to $('ul')
+ //
+ render: function(location, data, callback, partials) {
+ return new Sammy.RenderContext(this).render(location, data, callback, partials);
+ },
+
+ // Create and return a `Sammy.RenderContext` calling `renderEach()` on it.
+ // Loads the template and interpolates the data for each item,
+ // however does not actually place it in the DOM.
+ //
+ // `name` is an optional parameter (if it is an array, it is used as `data`,
+ // and the third parameter used as `callback`, if set).
+ //
+ // If `data` is not provided, content from the previous step in the chain
+ // (if it is an array) is used, and `name` is used as the key for each
+ // element of the array (useful for referencing in template).
+ //
+ // ### Example
+ //
+ // // mytemplate.mustache <div class="name">{{name}}</div>
+ // renderEach('mytemplate.mustache', [{name: 'quirkey'}, {name: 'endor'}])
+ // // sets the `content` to <div class="name">quirkey</div><div class="name">endor</div>
+ // renderEach('mytemplate.mustache', [{name: 'quirkey'}, {name: 'endor'}]).appendTo('ul');
+ // // appends the rendered content to $('ul')
+ //
+ // // names.json: ["quirkey", "endor"]
+ // this.load('names.json').renderEach('mytemplate.mustache', 'name').appendTo('ul');
+ // // uses the template to render each item in the JSON array
+ //
+ renderEach: function(location, name, data, callback) {
+ return new Sammy.RenderContext(this).renderEach(location, name, data, callback);
+ },
+
+ // create a new `Sammy.RenderContext` calling `load()` with `location` and
+ // `options`. Called without interpolation or placement, this allows for
+ // preloading/caching the templates.
+ load: function(location, options, callback) {
+ return new Sammy.RenderContext(this).load(location, options, callback);
+ },
+
+ // create a new `Sammy.RenderContext` calling `loadPartials()` with `partials`.
+ loadPartials: function(partials) {
+ return new Sammy.RenderContext(this).loadPartials(partials);
+ },
+
+ // `render()` the `location` with `data` and then `swap()` the
+ // app's `$element` with the rendered content.
+ partial: function(location, data, callback, partials) {
+ return new Sammy.RenderContext(this).partial(location, data, callback, partials);
+ },
+
+ // create a new `Sammy.RenderContext` calling `send()` with an arbitrary
+ // function
+ send: function() {
+ var rctx = new Sammy.RenderContext(this);
+ return rctx.send.apply(rctx, arguments);
+ },
+
+ // Changes the location of the current window. If `to` begins with
+ // '#' it only changes the document's hash. If passed more than 1 argument
+ // redirect will join them together with forward slashes.
+ //
+ // ### Example
+ //
+ // redirect('#/other/route');
+ // // equivalent to
+ // redirect('#', 'other', 'route');
+ //
+ redirect: function() {
+ var to, args = _makeArray(arguments),
+ current_location = this.app.getLocation(),
+ l = args.length;
+ if (l > 1) {
+ var i = 0, paths = [], pairs = [], params = {}, has_params = false;
+ for (; i < l; i++) {
+ if (typeof args[i] == 'string') {
+ paths.push(args[i]);
+ } else {
+ $.extend(params, args[i]);
+ has_params = true;
+ }
+ }
+ to = paths.join('/');
+ if (has_params) {
+ for (var k in params) {
+ pairs.push(this.app._encodeFormPair(k, params[k]));
+ }
+ to += '?' + pairs.join('&');
+ }
+ } else {
+ to = args[0];
+ }
+ this.trigger('redirect', {to: to});
+ this.app.last_location = [this.verb, this.path];
+ this.app.setLocation(to);
+ if (new RegExp(to).test(current_location)) {
+ this.app.trigger('location-changed');
+ }
+ },
+
+ // Triggers events on `app` within the current context.
+ trigger: function(name, data) {
+ if (typeof data == 'undefined') { data = {}; }
+ if (!data.context) { data.context = this; }
+ return this.app.trigger(name, data);
+ },
+
+ // A shortcut to app's `eventNamespace()`
+ eventNamespace: function() {
+ return this.app.eventNamespace();
+ },
+
+ // A shortcut to app's `swap()`
+ swap: function(contents, callback) {
+ return this.app.swap(contents, callback);
+ },
+
+ // Raises a possible `notFound()` error for the current path.
+ notFound: function() {
+ return this.app.notFound(this.verb, this.path);
+ },
+
+ // Default JSON parsing uses jQuery's `parseJSON()`. Include `Sammy.JSON`
+ // plugin for the more conformant "crockford special".
+ json: function(string) {
+ return $.parseJSON(string);
+ },
+
+ // //=> Sammy.EventContext: get #/ {}
+ toString: function() {
+ return "Sammy.EventContext: " + [this.verb, this.path, this.params].join(' ');
+ }
+
+ });
+
+ return Sammy;
+});
diff --git a/deps/rabbitmq_management/priv/www/js/sammy-0.7.6.min.js b/deps/rabbitmq_management/priv/www/js/sammy-0.7.6.min.js
new file mode 100644
index 0000000000..a08d509b59
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/sammy-0.7.6.min.js
@@ -0,0 +1,5 @@
+// -- Sammy.js -- /sammy.js
+// http://sammyjs.org
+// Version: 0.7.6
+// Built: 2014-08-26 10:45:34 +0300
+(function(factory){if(typeof define==="function"&&define.amd){define(["jquery"],factory)}else{jQuery.sammy=window.Sammy=factory(jQuery)}})(function($){var Sammy,PATH_REPLACER="([^/]+)",PATH_NAME_MATCHER=/:([\w\d]+)/g,QUERY_STRING_MATCHER=/\?([^#]*)?$/,_makeArray=function(nonarray){return Array.prototype.slice.call(nonarray)},_isFunction=function(obj){return Object.prototype.toString.call(obj)==="[object Function]"},_isArray=function(obj){return Object.prototype.toString.call(obj)==="[object Array]"},_isRegExp=function(obj){return Object.prototype.toString.call(obj)==="[object RegExp]"},_decode=function(str){return decodeURIComponent((str||"").replace(/\+/g," "))},_encode=encodeURIComponent,_escapeHTML=function(s){return String(s).replace(/&(?!\w+;)/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},_routeWrapper=function(verb){return function(){return this.route.apply(this,[verb].concat(Array.prototype.slice.call(arguments)))}},_template_cache={},_has_history=!!(window.history&&history.pushState),loggers=[];Sammy=function(){var args=_makeArray(arguments),app,selector;Sammy.apps=Sammy.apps||{};if(args.length===0||args[0]&&_isFunction(args[0])){return Sammy.apply(Sammy,["body"].concat(args))}else if(typeof(selector=args.shift())=="string"){app=Sammy.apps[selector]||new Sammy.Application;app.element_selector=selector;if(args.length>0){$.each(args,function(i,plugin){app.use(plugin)})}if(app.element_selector!=selector){delete Sammy.apps[selector]}Sammy.apps[app.element_selector]=app;return app}};Sammy.VERSION="0.7.6";Sammy.addLogger=function(logger){loggers.push(logger)};Sammy.log=function(){var args=_makeArray(arguments);args.unshift("["+Date()+"]");$.each(loggers,function(i,logger){logger.apply(Sammy,args)})};if(typeof window.console!="undefined"){if(typeof window.console.log==="function"&&_isFunction(window.console.log.apply)){Sammy.addLogger(function(){window.console.log.apply(window.console,arguments)})}else{Sammy.addLogger(function(){window.console.log(arguments)})}}else if(typeof console!="undefined"){Sammy.addLogger(function(){console.log.apply(console,arguments)})}$.extend(Sammy,{makeArray:_makeArray,isFunction:_isFunction,isArray:_isArray});Sammy.Object=function(obj){return $.extend(this,obj||{})};$.extend(Sammy.Object.prototype,{escapeHTML:_escapeHTML,h:_escapeHTML,toHash:function(){var json={};$.each(this,function(k,v){if(!_isFunction(v)){json[k]=v}});return json},toHTML:function(){var display="";$.each(this,function(k,v){if(!_isFunction(v)){display+="<strong>"+k+"</strong> "+v+"<br />"}});return display},keys:function(attributes_only){var keys=[];for(var property in this){if(!_isFunction(this[property])||!attributes_only){keys.push(property)}}return keys},has:function(key){return this[key]&&$.trim(this[key].toString())!==""},join:function(){var args=_makeArray(arguments);var delimiter=args.shift();return args.join(delimiter)},log:function(){Sammy.log.apply(Sammy,arguments)},toString:function(include_functions){var s=[];$.each(this,function(k,v){if(!_isFunction(v)||include_functions){s.push('"'+k+'": '+v.toString())}});return"Sammy.Object: {"+s.join(",")+"}"}});Sammy.targetIsThisWindow=function targetIsThisWindow(event,tagName){var targetElement=$(event.target).closest(tagName);if(targetElement.length===0){return true}var targetWindow=targetElement.attr("target");if(!targetWindow||targetWindow===window.name||targetWindow==="_self"){return true}if(targetWindow==="_blank"){return false}if(targetWindow==="top"&&window===window.top){return true}return false};Sammy.DefaultLocationProxy=function(app,run_interval_every){this.app=app;this.is_native=false;this.has_history=_has_history;this._startPolling(run_interval_every)};Sammy.DefaultLocationProxy.fullPath=function(location_obj){var matches=location_obj.toString().match(/^[^#]*(#.+)$/);var hash=matches?matches[1]:"";return[location_obj.pathname,location_obj.search,hash].join("")};$.extend(Sammy.DefaultLocationProxy.prototype,{bind:function(){var proxy=this,app=this.app,lp=Sammy.DefaultLocationProxy;$(window).bind("hashchange."+this.app.eventNamespace(),function(e,non_native){if(proxy.is_native===false&&!non_native){proxy.is_native=true;window.clearInterval(lp._interval);lp._interval=null}app.trigger("location-changed")});if(_has_history&&!app.disable_push_state){$(window).bind("popstate."+this.app.eventNamespace(),function(e){app.trigger("location-changed")});$(document).delegate("a","click.history-"+this.app.eventNamespace(),function(e){if(e.isDefaultPrevented()||e.metaKey||e.ctrlKey){return}var full_path=lp.fullPath(this),hostname=this.hostname?this.hostname:function(a){var l=document.createElement("a");l.href=a.href;return l.hostname}(this);if(hostname==window.location.hostname&&app.lookupRoute("get",full_path)&&Sammy.targetIsThisWindow(e,"a")){e.preventDefault();proxy.setLocation(full_path);return false}})}if(!lp._bindings){lp._bindings=0}lp._bindings++},unbind:function(){$(window).unbind("hashchange."+this.app.eventNamespace());$(window).unbind("popstate."+this.app.eventNamespace());$(document).undelegate("a","click.history-"+this.app.eventNamespace());Sammy.DefaultLocationProxy._bindings--;if(Sammy.DefaultLocationProxy._bindings<=0){window.clearInterval(Sammy.DefaultLocationProxy._interval);Sammy.DefaultLocationProxy._interval=null}},getLocation:function(){return Sammy.DefaultLocationProxy.fullPath(window.location)},setLocation:function(new_location){if(/^([^#\/]|$)/.test(new_location)){if(_has_history&&!this.app.disable_push_state){new_location="/"+new_location}else{new_location="#!/"+new_location}}if(new_location!=this.getLocation()){if(_has_history&&!this.app.disable_push_state&&/^\//.test(new_location)){history.pushState({path:new_location},window.title,new_location);this.app.trigger("location-changed")}else{return window.location=new_location}}},_startPolling:function(every){var proxy=this;if(!Sammy.DefaultLocationProxy._interval){if(!every){every=10}var hashCheck=function(){var current_location=proxy.getLocation();if(typeof Sammy.DefaultLocationProxy._last_location=="undefined"||current_location!=Sammy.DefaultLocationProxy._last_location){window.setTimeout(function(){$(window).trigger("hashchange",[true])},0)}Sammy.DefaultLocationProxy._last_location=current_location};hashCheck();Sammy.DefaultLocationProxy._interval=window.setInterval(hashCheck,every)}}});Sammy.Application=function(app_function){var app=this;this.routes={};this.listeners=new Sammy.Object({});this.arounds=[];this.befores=[];this.namespace=(new Date).getTime()+"-"+parseInt(Math.random()*1e3,10);this.context_prototype=function(){Sammy.EventContext.apply(this,arguments)};this.context_prototype.prototype=new Sammy.EventContext;if(_isFunction(app_function)){app_function.apply(this,[this])}if(!this._location_proxy){this.setLocationProxy(new Sammy.DefaultLocationProxy(this,this.run_interval_every))}if(this.debug){this.bindToAllEvents(function(e,data){app.log(app.toString(),e.cleaned_type,data||{})})}};Sammy.Application.prototype=$.extend({},Sammy.Object.prototype,{ROUTE_VERBS:["get","post","put","delete"],APP_EVENTS:["run","unload","lookup-route","run-route","route-found","event-context-before","event-context-after","changed","error","check-form-submission","redirect","location-changed"],_last_route:null,_location_proxy:null,_running:false,element_selector:"body",debug:false,raise_errors:false,run_interval_every:50,disable_push_state:false,template_engine:null,toString:function(){return"Sammy.Application:"+this.element_selector},$element:function(selector){return selector?$(this.element_selector).find(selector):$(this.element_selector)},use:function(){var args=_makeArray(arguments),plugin=args.shift(),plugin_name=plugin||"";try{args.unshift(this);if(typeof plugin=="string"){plugin_name="Sammy."+plugin;plugin=Sammy[plugin]}plugin.apply(this,args)}catch(e){if(typeof plugin==="undefined"){this.error("Plugin Error: called use() but plugin ("+plugin_name.toString()+") is not defined",e)}else if(!_isFunction(plugin)){this.error("Plugin Error: called use() but '"+plugin_name.toString()+"' is not a function",e)}else{this.error("Plugin Error",e)}}return this},setLocationProxy:function(new_proxy){var original_proxy=this._location_proxy;this._location_proxy=new_proxy;if(this.isRunning()){if(original_proxy){original_proxy.unbind()}this._location_proxy.bind()}},log:function(){Sammy.log.apply(Sammy,Array.prototype.concat.apply([this.element_selector],arguments))},route:function(verb,path){var app=this,param_names=[],add_route,path_match,callback=Array.prototype.slice.call(arguments,2);if(callback.length===0&&_isFunction(path)){callback=[path];path=verb;verb="any"}verb=verb.toLowerCase();if(path.constructor==String){PATH_NAME_MATCHER.lastIndex=0;while((path_match=PATH_NAME_MATCHER.exec(path))!==null){param_names.push(path_match[1])}path=new RegExp(path.replace(PATH_NAME_MATCHER,PATH_REPLACER)+"$")}$.each(callback,function(i,cb){if(typeof cb==="string"){callback[i]=app[cb]}});add_route=function(with_verb){var r={verb:with_verb,path:path,callback:callback,param_names:param_names};app.routes[with_verb]=app.routes[with_verb]||[];app.routes[with_verb].push(r)};if(verb==="any"){$.each(this.ROUTE_VERBS,function(i,v){add_route(v)})}else{add_route(verb)}return this},get:_routeWrapper("get"),post:_routeWrapper("post"),put:_routeWrapper("put"),del:_routeWrapper("delete"),any:_routeWrapper("any"),mapRoutes:function(route_array){var app=this;$.each(route_array,function(i,route_args){app.route.apply(app,route_args)});return this},eventNamespace:function(){return["sammy-app",this.namespace].join("-")},bind:function(name,data,callback){var app=this;if(typeof callback=="undefined"){callback=data}var listener_callback=function(){var e,context,data;e=arguments[0];data=arguments[1];if(data&&data.context){context=data.context;delete data.context}else{context=new app.context_prototype(app,"bind",e.type,data,e.target)}e.cleaned_type=e.type.replace(app.eventNamespace(),"");callback.apply(context,[e,data])};if(!this.listeners[name]){this.listeners[name]=[]}this.listeners[name].push(listener_callback);if(this.isRunning()){this._listen(name,listener_callback)}return this},trigger:function(name,data){this.$element().trigger([name,this.eventNamespace()].join("."),[data]);return this},refresh:function(){this.last_location=null;this.trigger("location-changed");return this},before:function(options,callback){if(_isFunction(options)){callback=options;options={}}this.befores.push([options,callback]);return this},after:function(callback){return this.bind("event-context-after",callback)},around:function(callback){this.arounds.push(callback);return this},onComplete:function(callback){this._onComplete=callback;return this},isRunning:function(){return this._running},helpers:function(extensions){$.extend(this.context_prototype.prototype,extensions);return this},helper:function(name,method){this.context_prototype.prototype[name]=method;return this},run:function(start_url){if(this.isRunning()){return false}var app=this;$.each(this.listeners.toHash(),function(name,callbacks){$.each(callbacks,function(i,listener_callback){app._listen(name,listener_callback)})});this.trigger("run",{start_url:start_url});this._running=true;this.last_location=null;if(!/\#(.+)/.test(this.getLocation())&&typeof start_url!="undefined"){this.setLocation(start_url)}this._checkLocation();this._location_proxy.bind();this.bind("location-changed",function(){app._checkLocation()});this.bind("submit",function(e){if(!Sammy.targetIsThisWindow(e,"form")){return true}var returned=app._checkFormSubmission($(e.target).closest("form"));return returned===false?e.preventDefault():false});$(window).bind("unload",function(){app.unload()});return this.trigger("changed")},unload:function(){if(!this.isRunning()){return false}var app=this;this.trigger("unload");this._location_proxy.unbind();this.$element().unbind("submit").removeClass(app.eventNamespace());$.each(this.listeners.toHash(),function(name,listeners){$.each(listeners,function(i,listener_callback){app._unlisten(name,listener_callback)})});this._running=false;return this},destroy:function(){this.unload();delete Sammy.apps[this.element_selector];return this},bindToAllEvents:function(callback){var app=this;$.each(this.APP_EVENTS,function(i,e){app.bind(e,callback)});$.each(this.listeners.keys(true),function(i,name){if($.inArray(name,app.APP_EVENTS)==-1){app.bind(name,callback)}});return this},routablePath:function(path){return path.replace(QUERY_STRING_MATCHER,"")},lookupRoute:function(verb,path){var app=this,routed=false,i=0,l,route;if(typeof this.routes[verb]!="undefined"){l=this.routes[verb].length;for(;i<l;i++){route=this.routes[verb][i];if(app.routablePath(path).match(route.path)){routed=route;break}}}return routed},runRoute:function(verb,path,params,target){var app=this,route=this.lookupRoute(verb,path),context,wrapped_route,arounds,around,befores,before,callback_args,path_params,final_returned;if(this.debug){this.log("runRoute",[verb,path].join(" "))}this.trigger("run-route",{verb:verb,path:path,params:params});if(typeof params=="undefined"){params={}}$.extend(params,this._parseQueryString(path));if(route){this.trigger("route-found",{route:route});if((path_params=route.path.exec(this.routablePath(path)))!==null){path_params.shift();$.each(path_params,function(i,param){if(route.param_names[i]){params[route.param_names[i]]=_decode(param)}else{if(!params.splat){params.splat=[]}params.splat.push(_decode(param))}})}context=new this.context_prototype(this,verb,path,params,target);arounds=this.arounds.slice(0);befores=this.befores.slice(0);callback_args=[context];if(params.splat){callback_args=callback_args.concat(params.splat)}wrapped_route=function(){var returned,i,nextRoute;while(befores.length>0){before=befores.shift();if(app.contextMatchesOptions(context,before[0])){returned=before[1].apply(context,[context]);if(returned===false){return false}}}app.last_route=route;context.trigger("event-context-before",{context:context});if(typeof route.callback==="function"){route.callback=[route.callback]}if(route.callback&&route.callback.length){i=-1;nextRoute=function(){i++;if(route.callback[i]){returned=route.callback[i].apply(context,callback_args)}else if(app._onComplete&&typeof(app._onComplete==="function")){app._onComplete(context)}};callback_args.push(nextRoute);nextRoute()}context.trigger("event-context-after",{context:context});return returned};$.each(arounds.reverse(),function(i,around){var last_wrapped_route=wrapped_route;wrapped_route=function(){return around.apply(context,[last_wrapped_route])}});try{final_returned=wrapped_route()}catch(e){this.error(["500 Error",verb,path].join(" "),e)}return final_returned}else{return this.notFound(verb,path)}},contextMatchesOptions:function(context,match_options,positive){var options=match_options;if(typeof options==="string"||_isRegExp(options)){options={path:options}}if(typeof positive==="undefined"){positive=true}if($.isEmptyObject(options)){return true}if(_isArray(options.path)){var results,numopt,opts,len;results=[];for(numopt=0,len=options.path.length;numopt<len;numopt+=1){opts=$.extend({},options,{path:options.path[numopt]});results.push(this.contextMatchesOptions(context,opts))}var matched=$.inArray(true,results)>-1?true:false;return positive?matched:!matched}if(options.only){return this.contextMatchesOptions(context,options.only,true)}else if(options.except){return this.contextMatchesOptions(context,options.except,false)}var path_matched=true,verb_matched=true;if(options.path){if(!_isRegExp(options.path)){options.path=new RegExp(options.path.toString()+"$")}path_matched=options.path.test(context.path)}if(options.verb){if(typeof options.verb==="string"){verb_matched=options.verb===context.verb}else{verb_matched=options.verb.indexOf(context.verb)>-1}}return positive?verb_matched&&path_matched:!(verb_matched&&path_matched)},getLocation:function(){return this._location_proxy.getLocation()},setLocation:function(new_location){return this._location_proxy.setLocation(new_location)},swap:function(content,callback){var $el=this.$element().html(content);if(_isFunction(callback)){callback(content)}return $el},templateCache:function(key,value){if(typeof value!="undefined"){return _template_cache[key]=value}else{return _template_cache[key]}},clearTemplateCache:function(){return _template_cache={}},notFound:function(verb,path){var ret=this.error(["404 Not Found",verb,path].join(" "));return verb==="get"?ret:true},error:function(message,original_error){if(!original_error){original_error=new Error}original_error.message=[message,original_error.message].join(" ");this.trigger("error",{message:original_error.message,error:original_error});if(this.raise_errors){throw original_error}else{this.log(original_error.message,original_error)}},_checkLocation:function(){var location,returned;location=this.getLocation();if(!this.last_location||this.last_location[0]!="get"||this.last_location[1]!=location){this.last_location=["get",location];returned=this.runRoute("get",location)}return returned},_getFormVerb:function(form){var $form=$(form),verb,$_method;$_method=$form.find('input[name="_method"]');if($_method.length>0){verb=$_method.val()}if(!verb){verb=$form[0].getAttribute("method")}if(!verb||verb===""){verb="get"}return $.trim(verb.toString().toLowerCase())},_checkFormSubmission:function(form){var $form,path,verb,params,returned;this.trigger("check-form-submission",{form:form});$form=$(form);path=$form.attr("action")||"";verb=this._getFormVerb($form);if(this.debug){this.log("_checkFormSubmission",$form,path,verb)}if(verb==="get"){params=this._serializeFormParams($form);if(params!==""){path+="?"+params}this.setLocation(path);returned=false}else{params=$.extend({},this._parseFormParams($form));returned=this.runRoute(verb,path,params,form.get(0))}return typeof returned=="undefined"?false:returned},_serializeFormParams:function($form){var queryString="",fields=$form.serializeArray(),i;if(fields.length>0){queryString=this._encodeFormPair(fields[0].name,fields[0].value);for(i=1;i<fields.length;i++){queryString=queryString+"&"+this._encodeFormPair(fields[i].name,fields[i].value)}}return queryString},_encodeFormPair:function(name,value){return _encode(name)+"="+_encode(value)},_parseFormParams:function($form){var params={},form_fields=$form.serializeArray(),i;for(i=0;i<form_fields.length;i++){params=this._parseParamPair(params,form_fields[i].name,form_fields[i].value)}return params},_parseQueryString:function(path){var params={},parts,pairs,pair,i;parts=path.match(QUERY_STRING_MATCHER);if(parts&&parts[1]){pairs=parts[1].split("&");for(i=0;i<pairs.length;i++){pair=pairs[i].split("=");params=this._parseParamPair(params,_decode(pair[0]),_decode(pair[1]||""))}}return params},_parseParamPair:function(params,key,value){if(typeof params[key]!=="undefined"){if(_isArray(params[key])){params[key].push(value)}else{params[key]=[params[key],value]}}else{params[key]=value}return params},_listen:function(name,callback){return this.$element().bind([name,this.eventNamespace()].join("."),callback)},_unlisten:function(name,callback){return this.$element().unbind([name,this.eventNamespace()].join("."),callback)}});Sammy.RenderContext=function(event_context){this.event_context=event_context;this.callbacks=[];this.previous_content=null;this.content=null;this.next_engine=false;this.waiting=false};Sammy.RenderContext.prototype=$.extend({},Sammy.Object.prototype,{then:function(callback){if(!_isFunction(callback)){if(typeof callback==="string"&&callback in this.event_context){var helper=this.event_context[callback];callback=function(content){return helper.apply(this.event_context,[content])}}else{return this}}var context=this;if(this.waiting){this.callbacks.push(callback)}else{this.wait();window.setTimeout(function(){var returned=callback.apply(context,[context.content,context.previous_content]);if(returned!==false){context.next(returned)}},0)}return this},wait:function(){this.waiting=true},next:function(content){this.waiting=false;if(typeof content!=="undefined"){this.previous_content=this.content;this.content=content}if(this.callbacks.length>0){this.then(this.callbacks.shift())}},load:function(location,options,callback){var context=this;return this.then(function(){var should_cache,cached,is_json,location_array;if(_isFunction(options)){callback=options;options={}}else{options=$.extend({},options)}if(callback){this.then(callback)}if(typeof location==="string"){is_json=location.match(/\.json(\?|$)/)||options.json;should_cache=is_json?options.cache===true:options.cache!==false;context.next_engine=context.event_context.engineFor(location);delete options.cache;delete options.json;if(options.engine){context.next_engine=options.engine;delete options.engine}if(should_cache&&(cached=this.event_context.app.templateCache(location))){return cached}this.wait();$.ajax($.extend({url:location,data:{},dataType:is_json?"json":"text",type:"get",success:function(data){if(should_cache){context.event_context.app.templateCache(location,data)}context.next(data)}},options));return false}else{if(location.nodeType){return location.innerHTML}if(location.selector){context.next_engine=location.attr("data-engine");if(options.clone===false){return location.remove()[0].innerHTML.toString()}else{return location[0].innerHTML.toString()}}}})},loadPartials:function(partials){var name;if(partials){this.partials=this.partials||{};for(name in partials){(function(context,name){context.load(partials[name]).then(function(template){this.partials[name]=template})})(this,name)}}return this},render:function(location,data,callback,partials){if(_isFunction(location)&&!data){return this.then(location)}else{if(_isFunction(data)){partials=callback;callback=data;data=null}else if(callback&&!_isFunction(callback)){partials=callback;callback=null}return this.loadPartials(partials).load(location).interpolate(data,location).then(callback)}},partial:function(location,data,callback,partials){if(_isFunction(callback)){return this.render(location,data,partials).swap(callback)}else if(_isFunction(data)){return this.render(location,{},callback).swap(data)}else{return this.render(location,data,callback).swap()}},send:function(){var context=this,args=_makeArray(arguments),fun=args.shift();if(_isArray(args[0])){args=args[0]}return this.then(function(content){args.push(function(response){context.next(response)});context.wait();fun.apply(fun,args);return false})},collect:function(array,callback,now){var context=this;var coll=function(){if(_isFunction(array)){callback=array;array=this.content}var contents=[],doms=false;$.each(array,function(i,item){var returned=callback.apply(context,[i,item]);if(returned.jquery&&returned.length==1){returned=returned[0];doms=true}contents.push(returned);return returned});return doms?contents:contents.join("")};return now?coll():this.then(coll)},renderEach:function(location,name,data,callback){if(_isArray(name)){callback=data;data=name;name=null}return this.load(location).then(function(content){var rctx=this;if(!data){data=_isArray(this.previous_content)?this.previous_content:[]}if(callback){$.each(data,function(i,value){var idata={},engine=this.next_engine||location;if(name){idata[name]=value}else{idata=value}callback(value,rctx.event_context.interpolate(content,idata,engine))})}else{return this.collect(data,function(i,value){var idata={},engine=this.next_engine||location;if(name){idata[name]=value}else{idata=value}return this.event_context.interpolate(content,idata,engine)},true)}})},interpolate:function(data,engine,retain){var context=this;return this.then(function(content,prev){if(!data&&prev){data=prev}if(this.next_engine){engine=this.next_engine;this.next_engine=false}var rendered=context.event_context.interpolate(content,data,engine,this.partials);return retain?prev+rendered:rendered})},swap:function(callback){return this.then(function(content){this.event_context.swap(content,callback);return content}).trigger("changed",{})},appendTo:function(selector){return this.then(function(content){$(selector).append(content)}).trigger("changed",{})},prependTo:function(selector){return this.then(function(content){$(selector).prepend(content)}).trigger("changed",{})},replace:function(selector){return this.then(function(content){$(selector).html(content)}).trigger("changed",{})},trigger:function(name,data){return this.then(function(content){if(typeof data=="undefined"){data={content:content}}this.event_context.trigger(name,data);return content})}});Sammy.EventContext=function(app,verb,path,params,target){this.app=app;this.verb=verb;this.path=path;this.params=new Sammy.Object(params);this.target=target};Sammy.EventContext.prototype=$.extend({},Sammy.Object.prototype,{$element:function(){return this.app.$element(_makeArray(arguments).shift())},engineFor:function(engine){var context=this,engine_match;if(_isFunction(engine)){return engine}engine=(engine||context.app.template_engine).toString();if(engine_match=engine.match(/\.([^\.\?\#]+)(\?|$)/)){engine=engine_match[1]}if(engine&&_isFunction(context[engine])){return context[engine]}if(context.app.template_engine){return this.engineFor(context.app.template_engine)}return function(content,data){return content}},interpolate:function(content,data,engine,partials){return this.engineFor(engine).apply(this,[content,data,partials])},render:function(location,data,callback,partials){return new Sammy.RenderContext(this).render(location,data,callback,partials)},renderEach:function(location,name,data,callback){return new Sammy.RenderContext(this).renderEach(location,name,data,callback)},load:function(location,options,callback){return new Sammy.RenderContext(this).load(location,options,callback)},loadPartials:function(partials){return new Sammy.RenderContext(this).loadPartials(partials)},partial:function(location,data,callback,partials){return new Sammy.RenderContext(this).partial(location,data,callback,partials)},send:function(){var rctx=new Sammy.RenderContext(this);return rctx.send.apply(rctx,arguments)},redirect:function(){var to,args=_makeArray(arguments),current_location=this.app.getLocation(),l=args.length;if(l>1){var i=0,paths=[],pairs=[],params={},has_params=false;for(;i<l;i++){if(typeof args[i]=="string"){paths.push(args[i])}else{$.extend(params,args[i]);has_params=true}}to=paths.join("/");if(has_params){for(var k in params){pairs.push(this.app._encodeFormPair(k,params[k]))}to+="?"+pairs.join("&")}}else{to=args[0]}this.trigger("redirect",{to:to});this.app.last_location=[this.verb,this.path];this.app.setLocation(to);if(new RegExp(to).test(current_location)){this.app.trigger("location-changed")}},trigger:function(name,data){if(typeof data=="undefined"){data={}}if(!data.context){data.context=this}return this.app.trigger(name,data)},eventNamespace:function(){return this.app.eventNamespace()},swap:function(contents,callback){return this.app.swap(contents,callback)},notFound:function(){return this.app.notFound(this.verb,this.path)},json:function(string){return $.parseJSON(string)},toString:function(){return"Sammy.EventContext: "+[this.verb,this.path,this.params].join(" ")}});return Sammy}); \ No newline at end of file
diff --git a/deps/rabbitmq_management/priv/www/js/singular/client_frame.html b/deps/rabbitmq_management/priv/www/js/singular/client_frame.html
new file mode 100644
index 0000000000..7c0f98a2e2
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/singular/client_frame.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<iframe style="display: none;" id="authorizeFrame" src="about:blank"></iframe>
+</body>
+<script type="application/javascript" src="./rpFrame.js"></script>
+<script type="application/javascript">
+ var authorizeWindow = document.getElementById('authorizeFrame').contentWindow;
+ var rpFrame = RPFrame(window.parent.Singular, authorizeWindow, window);
+ window.addEventListener('message', rpFrame.receiveMessage, false);
+
+ rpFrame.checkClientConfig();
+ rpFrame.startCheckingSession();
+
+ window.fetchAccessToken = rpFrame.fetchAccessToken;
+ window.afterAccess = rpFrame.afterAccess;
+ window.afterAuthorize = rpFrame.afterAuthorize;
+</script>
+</html>
diff --git a/deps/rabbitmq_management/priv/www/js/singular/postaccess.html b/deps/rabbitmq_management/priv/www/js/singular/postaccess.html
new file mode 100644
index 0000000000..7e6799550e
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/singular/postaccess.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript">
+ window.parent.afterAccess(parseInt(window.location.search.substring(1)));
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/deps/rabbitmq_management/priv/www/js/singular/postauth.html b/deps/rabbitmq_management/priv/www/js/singular/postauth.html
new file mode 100644
index 0000000000..9c6dd1c021
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/singular/postauth.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript">
+ window.parent.afterAuthorize();
+ </script>
+</head>
+<body>
+</body>
+</html> \ No newline at end of file
diff --git a/deps/rabbitmq_management/priv/www/js/singular/rpFrame.js b/deps/rabbitmq_management/priv/www/js/singular/rpFrame.js
new file mode 100644
index 0000000000..ba5279b36d
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/singular/rpFrame.js
@@ -0,0 +1 @@
+var RPFrame=function(t){var n={};function r(e){if(n[e])return n[e].exports;var i=n[e]={i:e,l:!1,exports:{}};return t[e].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=n,r.d=function(t,n,e){r.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:e})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,n){if(1&n&&(t=r(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var e=Object.create(null);if(r.r(e),Object.defineProperty(e,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var i in t)r.d(e,i,function(n){return t[n]}.bind(null,i));return e},r.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(n,"a",n),n},r.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},r.p="",r(r.s=127)}([function(t,n,r){var e=r(2),i=r(18),o=r(11),u=r(12),c=r(19),a=function(t,n,r){var f,s,l,h,v=t&a.F,p=t&a.G,g=t&a.S,d=t&a.P,y=t&a.B,m=p?e:g?e[n]||(e[n]={}):(e[n]||{}).prototype,b=p?i:i[n]||(i[n]={}),S=b.prototype||(b.prototype={});for(f in p&&(r=n),r)l=((s=!v&&m&&void 0!==m[f])?m:r)[f],h=y&&s?c(l,e):d&&"function"==typeof l?c(Function.call,l):l,m&&u(m,f,l,t&a.U),b[f]!=l&&o(b,f,h),d&&S[f]!=l&&(S[f]=l)};e.core=i,a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},function(t,n,r){var e=r(4);t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n,r){var e=r(49)("wks"),i=r(33),o=r(2).Symbol,u="function"==typeof o;(t.exports=function(t){return e[t]||(e[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=e},function(t,n,r){t.exports=!r(3)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(1),i=r(91),o=r(22),u=Object.defineProperty;n.f=r(6)?Object.defineProperty:function(t,n,r){if(e(t),n=o(n,!0),e(r),i)try{return u(t,n,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},function(t,n,r){var e=r(24),i=Math.min;t.exports=function(t){return t>0?i(e(t),9007199254740991):0}},function(t,n,r){var e=r(23);t.exports=function(t){return Object(e(t))}},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n,r){var e=r(7),i=r(32);t.exports=r(6)?function(t,n,r){return e.f(t,n,i(1,r))}:function(t,n,r){return t[n]=r,t}},function(t,n,r){var e=r(2),i=r(11),o=r(14),u=r(33)("src"),c=Function.toString,a=(""+c).split("toString");r(18).inspectSource=function(t){return c.call(t)},(t.exports=function(t,n,r,c){var f="function"==typeof r;f&&(o(r,"name")||i(r,"name",n)),t[n]!==r&&(f&&(o(r,u)||i(r,u,t[n]?""+t[n]:a.join(String(n)))),t===e?t[n]=r:c?t[n]?t[n]=r:i(t,n,r):(delete t[n],i(t,n,r)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[u]||c.call(this)})},function(t,n,r){var e=r(0),i=r(3),o=r(23),u=/"/g,c=function(t,n,r,e){var i=String(o(t)),c="<"+n;return""!==r&&(c+=" "+r+'="'+String(e).replace(u,"&quot;")+'"'),c+">"+i+"</"+n+">"};t.exports=function(t,n){var r={};r[t]=n(c),e(e.P+e.F*i(function(){var n=""[t]('"');return n!==n.toLowerCase()||n.split('"').length>3}),"String",r)}},function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},function(t,n,r){var e=r(46),i=r(23);t.exports=function(t){return e(i(t))}},function(t,n,r){var e=r(47),i=r(32),o=r(15),u=r(22),c=r(14),a=r(91),f=Object.getOwnPropertyDescriptor;n.f=r(6)?f:function(t,n){if(t=o(t),n=u(n,!0),a)try{return f(t,n)}catch(t){}if(c(t,n))return i(!e.f.call(t,n),t[n])}},function(t,n,r){var e=r(14),i=r(9),o=r(67)("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),e(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},function(t,n){var r=t.exports={version:"2.5.6"};"number"==typeof __e&&(__e=r)},function(t,n,r){var e=r(10);t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,i){return t.call(n,r,e,i)}}return function(){return t.apply(n,arguments)}}},function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},function(t,n,r){"use strict";var e=r(3);t.exports=function(t,n){return!!t&&e(function(){n?t.call(null,function(){},1):t.call(null)})}},function(t,n,r){var e=r(4);t.exports=function(t,n){if(!e(t))return t;var r,i;if(n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;if("function"==typeof(r=t.valueOf)&&!e(i=r.call(t)))return i;if(!n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},function(t,n,r){var e=r(0),i=r(18),o=r(3);t.exports=function(t,n){var r=(i.Object||{})[t]||Object[t],u={};u[t]=n(r),e(e.S+e.F*o(function(){r(1)}),"Object",u)}},function(t,n,r){var e=r(19),i=r(46),o=r(9),u=r(8),c=r(84);t.exports=function(t,n){var r=1==t,a=2==t,f=3==t,s=4==t,l=6==t,h=5==t||l,v=n||c;return function(n,c,p){for(var g,d,y=o(n),m=i(y),b=e(c,p,3),S=u(m.length),w=0,x=r?v(n,S):a?v(n,0):void 0;S>w;w++)if((h||w in m)&&(d=b(g=m[w],w,y),t))if(r)x[w]=d;else if(d)switch(t){case 3:return!0;case 5:return g;case 6:return w;case 2:x.push(g)}else if(s)return!1;return l?-1:f||s?s:x}}},function(t,n,r){"use strict";if(r(6)){var e=r(30),i=r(2),o=r(3),u=r(0),c=r(60),a=r(90),f=r(19),s=r(39),l=r(32),h=r(11),v=r(41),p=r(24),g=r(8),d=r(117),y=r(35),m=r(22),b=r(14),S=r(48),w=r(4),x=r(9),_=r(81),E=r(36),O=r(17),M=r(37).f,P=r(83),F=r(33),A=r(5),I=r(26),j=r(50),N=r(57),k=r(86),T=r(44),L=r(54),R=r(38),C=r(85),W=r(107),D=r(7),U=r(16),G=D.f,V=U.f,B=i.RangeError,z=i.TypeError,J=i.Uint8Array,q=Array.prototype,Y=a.ArrayBuffer,K=a.DataView,H=I(0),X=I(2),$=I(3),Z=I(4),Q=I(5),tt=I(6),nt=j(!0),rt=j(!1),et=k.values,it=k.keys,ot=k.entries,ut=q.lastIndexOf,ct=q.reduce,at=q.reduceRight,ft=q.join,st=q.sort,lt=q.slice,ht=q.toString,vt=q.toLocaleString,pt=A("iterator"),gt=A("toStringTag"),dt=F("typed_constructor"),yt=F("def_constructor"),mt=c.CONSTR,bt=c.TYPED,St=c.VIEW,wt=I(1,function(t,n){return Mt(N(t,t[yt]),n)}),xt=o(function(){return 1===new J(new Uint16Array([1]).buffer)[0]}),_t=!!J&&!!J.prototype.set&&o(function(){new J(1).set({})}),Et=function(t,n){var r=p(t);if(r<0||r%n)throw B("Wrong offset!");return r},Ot=function(t){if(w(t)&&bt in t)return t;throw z(t+" is not a typed array!")},Mt=function(t,n){if(!(w(t)&&dt in t))throw z("It is not a typed array constructor!");return new t(n)},Pt=function(t,n){return Ft(N(t,t[yt]),n)},Ft=function(t,n){for(var r=0,e=n.length,i=Mt(t,e);e>r;)i[r]=n[r++];return i},At=function(t,n,r){G(t,n,{get:function(){return this._d[r]}})},It=function(t){var n,r,e,i,o,u,c=x(t),a=arguments.length,s=a>1?arguments[1]:void 0,l=void 0!==s,h=P(c);if(null!=h&&!_(h)){for(u=h.call(c),e=[],n=0;!(o=u.next()).done;n++)e.push(o.value);c=e}for(l&&a>2&&(s=f(s,arguments[2],2)),n=0,r=g(c.length),i=Mt(this,r);r>n;n++)i[n]=l?s(c[n],n):c[n];return i},jt=function(){for(var t=0,n=arguments.length,r=Mt(this,n);n>t;)r[t]=arguments[t++];return r},Nt=!!J&&o(function(){vt.call(new J(1))}),kt=function(){return vt.apply(Nt?lt.call(Ot(this)):Ot(this),arguments)},Tt={copyWithin:function(t,n){return W.call(Ot(this),t,n,arguments.length>2?arguments[2]:void 0)},every:function(t){return Z(Ot(this),t,arguments.length>1?arguments[1]:void 0)},fill:function(t){return C.apply(Ot(this),arguments)},filter:function(t){return Pt(this,X(Ot(this),t,arguments.length>1?arguments[1]:void 0))},find:function(t){return Q(Ot(this),t,arguments.length>1?arguments[1]:void 0)},findIndex:function(t){return tt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},forEach:function(t){H(Ot(this),t,arguments.length>1?arguments[1]:void 0)},indexOf:function(t){return rt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},includes:function(t){return nt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},join:function(t){return ft.apply(Ot(this),arguments)},lastIndexOf:function(t){return ut.apply(Ot(this),arguments)},map:function(t){return wt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},reduce:function(t){return ct.apply(Ot(this),arguments)},reduceRight:function(t){return at.apply(Ot(this),arguments)},reverse:function(){for(var t,n=Ot(this).length,r=Math.floor(n/2),e=0;e<r;)t=this[e],this[e++]=this[--n],this[n]=t;return this},some:function(t){return $(Ot(this),t,arguments.length>1?arguments[1]:void 0)},sort:function(t){return st.call(Ot(this),t)},subarray:function(t,n){var r=Ot(this),e=r.length,i=y(t,e);return new(N(r,r[yt]))(r.buffer,r.byteOffset+i*r.BYTES_PER_ELEMENT,g((void 0===n?e:y(n,e))-i))}},Lt=function(t,n){return Pt(this,lt.call(Ot(this),t,n))},Rt=function(t){Ot(this);var n=Et(arguments[1],1),r=this.length,e=x(t),i=g(e.length),o=0;if(i+n>r)throw B("Wrong length!");for(;o<i;)this[n+o]=e[o++]},Ct={entries:function(){return ot.call(Ot(this))},keys:function(){return it.call(Ot(this))},values:function(){return et.call(Ot(this))}},Wt=function(t,n){return w(t)&&t[bt]&&"symbol"!=typeof n&&n in t&&String(+n)==String(n)},Dt=function(t,n){return Wt(t,n=m(n,!0))?l(2,t[n]):V(t,n)},Ut=function(t,n,r){return!(Wt(t,n=m(n,!0))&&w(r)&&b(r,"value"))||b(r,"get")||b(r,"set")||r.configurable||b(r,"writable")&&!r.writable||b(r,"enumerable")&&!r.enumerable?G(t,n,r):(t[n]=r.value,t)};mt||(U.f=Dt,D.f=Ut),u(u.S+u.F*!mt,"Object",{getOwnPropertyDescriptor:Dt,defineProperty:Ut}),o(function(){ht.call({})})&&(ht=vt=function(){return ft.call(this)});var Gt=v({},Tt);v(Gt,Ct),h(Gt,pt,Ct.values),v(Gt,{slice:Lt,set:Rt,constructor:function(){},toString:ht,toLocaleString:kt}),At(Gt,"buffer","b"),At(Gt,"byteOffset","o"),At(Gt,"byteLength","l"),At(Gt,"length","e"),G(Gt,gt,{get:function(){return this[bt]}}),t.exports=function(t,n,r,a){var f=t+((a=!!a)?"Clamped":"")+"Array",l="get"+t,v="set"+t,p=i[f],y=p||{},m=p&&O(p),b=!p||!c.ABV,x={},_=p&&p.prototype,P=function(t,r){G(t,r,{get:function(){return function(t,r){var e=t._d;return e.v[l](r*n+e.o,xt)}(this,r)},set:function(t){return function(t,r,e){var i=t._d;a&&(e=(e=Math.round(e))<0?0:e>255?255:255&e),i.v[v](r*n+i.o,e,xt)}(this,r,t)},enumerable:!0})};b?(p=r(function(t,r,e,i){s(t,p,f,"_d");var o,u,c,a,l=0,v=0;if(w(r)){if(!(r instanceof Y||"ArrayBuffer"==(a=S(r))||"SharedArrayBuffer"==a))return bt in r?Ft(p,r):It.call(p,r);o=r,v=Et(e,n);var y=r.byteLength;if(void 0===i){if(y%n)throw B("Wrong length!");if((u=y-v)<0)throw B("Wrong length!")}else if((u=g(i)*n)+v>y)throw B("Wrong length!");c=u/n}else c=d(r),o=new Y(u=c*n);for(h(t,"_d",{b:o,o:v,l:u,e:c,v:new K(o)});l<c;)P(t,l++)}),_=p.prototype=E(Gt),h(_,"constructor",p)):o(function(){p(1)})&&o(function(){new p(-1)})&&L(function(t){new p,new p(null),new p(1.5),new p(t)},!0)||(p=r(function(t,r,e,i){var o;return s(t,p,f),w(r)?r instanceof Y||"ArrayBuffer"==(o=S(r))||"SharedArrayBuffer"==o?void 0!==i?new y(r,Et(e,n),i):void 0!==e?new y(r,Et(e,n)):new y(r):bt in r?Ft(p,r):It.call(p,r):new y(d(r))}),H(m!==Function.prototype?M(y).concat(M(m)):M(y),function(t){t in p||h(p,t,y[t])}),p.prototype=_,e||(_.constructor=p));var F=_[pt],A=!!F&&("values"==F.name||null==F.name),I=Ct.values;h(p,dt,!0),h(_,bt,f),h(_,St,!0),h(_,yt,p),(a?new p(1)[gt]==f:gt in _)||G(_,gt,{get:function(){return f}}),x[f]=p,u(u.G+u.W+u.F*(p!=y),x),u(u.S,f,{BYTES_PER_ELEMENT:n}),u(u.S+u.F*o(function(){y.of.call(p,1)}),f,{from:It,of:jt}),"BYTES_PER_ELEMENT"in _||h(_,"BYTES_PER_ELEMENT",n),u(u.P,f,Tt),R(f),u(u.P+u.F*_t,f,{set:Rt}),u(u.P+u.F*!A,f,Ct),e||_.toString==ht||(_.toString=ht),u(u.P+u.F*o(function(){new p(1).slice()}),f,{slice:Lt}),u(u.P+u.F*(o(function(){return[1,2].toLocaleString()!=new p([1,2]).toLocaleString()})||!o(function(){_.toLocaleString.call([1,2])})),f,{toLocaleString:kt}),T[f]=A?F:I,e||A||h(_,pt,I)}}else t.exports=function(){}},function(t,n,r){var e=r(112),i=r(0),o=r(49)("metadata"),u=o.store||(o.store=new(r(115))),c=function(t,n,r){var i=u.get(t);if(!i){if(!r)return;u.set(t,i=new e)}var o=i.get(n);if(!o){if(!r)return;i.set(n,o=new e)}return o};t.exports={store:u,map:c,has:function(t,n,r){var e=c(n,r,!1);return void 0!==e&&e.has(t)},get:function(t,n,r){var e=c(n,r,!1);return void 0===e?void 0:e.get(t)},set:function(t,n,r,e){c(r,e,!0).set(t,n)},keys:function(t,n){var r=c(t,n,!1),e=[];return r&&r.forEach(function(t,n){e.push(n)}),e},key:function(t){return void 0===t||"symbol"==typeof t?t:String(t)},exp:function(t){i(i.S,"Reflect",t)}}},function(t,n,r){var e=r(33)("meta"),i=r(4),o=r(14),u=r(7).f,c=0,a=Object.isExtensible||function(){return!0},f=!r(3)(function(){return a(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=t.exports={KEY:e,NEED:!1,fastKey:function(t,n){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,e)){if(!a(t))return"F";if(!n)return"E";s(t)}return t[e].i},getWeak:function(t,n){if(!o(t,e)){if(!a(t))return!0;if(!n)return!1;s(t)}return t[e].w},onFreeze:function(t){return f&&l.NEED&&a(t)&&!o(t,e)&&s(t),t}}},function(t,n){t.exports=!1},function(t,n,r){var e=r(5)("unscopables"),i=Array.prototype;null==i[e]&&r(11)(i,e,{}),t.exports=function(t){i[e][t]=!0}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},function(t,n,r){var e=r(93),i=r(68);t.exports=Object.keys||function(t){return e(t,i)}},function(t,n,r){var e=r(24),i=Math.max,o=Math.min;t.exports=function(t,n){return(t=e(t))<0?i(t+n,0):o(t,n)}},function(t,n,r){var e=r(1),i=r(94),o=r(68),u=r(67)("IE_PROTO"),c=function(){},a=function(){var t,n=r(65)("iframe"),e=o.length;for(n.style.display="none",r(69).appendChild(n),n.src="javascript:",(t=n.contentWindow.document).open(),t.write("<script>document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a.prototype[o[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c.prototype=e(t),r=new c,c.prototype=null,r[u]=t):r=a(),void 0===n?r:i(r,n)}},function(t,n,r){var e=r(93),i=r(68).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,i)}},function(t,n,r){"use strict";var e=r(2),i=r(7),o=r(6),u=r(5)("species");t.exports=function(t){var n=e[t];o&&n&&!n[u]&&i.f(n,u,{configurable:!0,get:function(){return this}})}},function(t,n){t.exports=function(t,n,r,e){if(!(t instanceof n)||void 0!==e&&e in t)throw TypeError(r+": incorrect invocation!");return t}},function(t,n,r){var e=r(19),i=r(105),o=r(81),u=r(1),c=r(8),a=r(83),f={},s={};(n=t.exports=function(t,n,r,l,h){var v,p,g,d,y=h?function(){return t}:a(t),m=e(r,l,n?2:1),b=0;if("function"!=typeof y)throw TypeError(t+" is not iterable!");if(o(y)){for(v=c(t.length);v>b;b++)if((d=n?m(u(p=t[b])[0],p[1]):m(t[b]))===f||d===s)return d}else for(g=y.call(t);!(p=g.next()).done;)if((d=i(g,m,p.value,n))===f||d===s)return d}).BREAK=f,n.RETURN=s},function(t,n,r){var e=r(12);t.exports=function(t,n,r){for(var i in n)e(t,i,n[i],r);return t}},function(t,n,r){var e=r(7).f,i=r(14),o=r(5)("toStringTag");t.exports=function(t,n,r){t&&!i(t=r?t:t.prototype,o)&&e(t,o,{configurable:!0,value:n})}},function(t,n,r){var e=r(0),i=r(23),o=r(3),u=r(71),c="["+u+"]",a=RegExp("^"+c+c+"*"),f=RegExp(c+c+"*$"),s=function(t,n,r){var i={},c=o(function(){return!!u[t]()||"​…"!="​…"[t]()}),a=i[t]=c?n(l):u[t];r&&(i[r]=a),e(e.P+e.F*c,"String",i)},l=s.trim=function(t,n){return t=String(i(t)),1&n&&(t=t.replace(a,"")),2&n&&(t=t.replace(f,"")),t};t.exports=s},function(t,n){t.exports={}},function(t,n,r){var e=r(4);t.exports=function(t,n){if(!e(t)||t._t!==n)throw TypeError("Incompatible receiver, "+n+" required!");return t}},function(t,n,r){var e=r(20);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,r){var e=r(20),i=r(5)("toStringTag"),o="Arguments"==e(function(){return arguments}());t.exports=function(t){var n,r,u;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=function(t,n){try{return t[n]}catch(t){}}(n=Object(t),i))?r:o?e(n):"Object"==(u=e(n))&&"function"==typeof n.callee?"Arguments":u}},function(t,n,r){var e=r(18),i=r(2),o=i["__core-js_shared__"]||(i["__core-js_shared__"]={});(t.exports=function(t,n){return o[t]||(o[t]=void 0!==n?n:{})})("versions",[]).push({version:e.version,mode:r(30)?"pure":"global",copyright:"© 2018 Denis Pushkarev (zloirock.ru)"})},function(t,n,r){var e=r(15),i=r(8),o=r(35);t.exports=function(t){return function(n,r,u){var c,a=e(n),f=i(a.length),s=o(u,f);if(t&&r!=r){for(;f>s;)if((c=a[s++])!=c)return!0}else for(;f>s;s++)if((t||s in a)&&a[s]===r)return t||s||0;return!t&&-1}}},function(t,n){n.f=Object.getOwnPropertySymbols},function(t,n,r){var e=r(20);t.exports=Array.isArray||function(t){return"Array"==e(t)}},function(t,n,r){var e=r(4),i=r(20),o=r(5)("match");t.exports=function(t){var n;return e(t)&&(void 0!==(n=t[o])?!!n:"RegExp"==i(t))}},function(t,n,r){var e=r(5)("iterator"),i=!1;try{var o=[7][e]();o.return=function(){i=!0},Array.from(o,function(){throw 2})}catch(t){}t.exports=function(t,n){if(!n&&!i)return!1;var r=!1;try{var o=[7],u=o[e]();u.next=function(){return{done:r=!0}},o[e]=function(){return u},t(o)}catch(t){}return r}},function(t,n,r){"use strict";var e=r(1);t.exports=function(){var t=e(this),n="";return t.global&&(n+="g"),t.ignoreCase&&(n+="i"),t.multiline&&(n+="m"),t.unicode&&(n+="u"),t.sticky&&(n+="y"),n}},function(t,n,r){"use strict";var e=r(11),i=r(12),o=r(3),u=r(23),c=r(5);t.exports=function(t,n,r){var a=c(t),f=r(u,a,""[t]),s=f[0],l=f[1];o(function(){var n={};return n[a]=function(){return 7},7!=""[t](n)})&&(i(String.prototype,t,s),e(RegExp.prototype,a,2==n?function(t,n){return l.call(t,this,n)}:function(t){return l.call(t,this)}))}},function(t,n,r){var e=r(1),i=r(10),o=r(5)("species");t.exports=function(t,n){var r,u=e(t).constructor;return void 0===u||null==(r=e(u)[o])?n:i(r)}},function(t,n,r){var e=r(2).navigator;t.exports=e&&e.userAgent||""},function(t,n,r){"use strict";var e=r(2),i=r(0),o=r(12),u=r(41),c=r(29),a=r(40),f=r(39),s=r(4),l=r(3),h=r(54),v=r(42),p=r(72);t.exports=function(t,n,r,g,d,y){var m=e[t],b=m,S=d?"set":"add",w=b&&b.prototype,x={},_=function(t){var n=w[t];o(w,t,"delete"==t?function(t){return!(y&&!s(t))&&n.call(this,0===t?0:t)}:"has"==t?function(t){return!(y&&!s(t))&&n.call(this,0===t?0:t)}:"get"==t?function(t){return y&&!s(t)?void 0:n.call(this,0===t?0:t)}:"add"==t?function(t){return n.call(this,0===t?0:t),this}:function(t,r){return n.call(this,0===t?0:t,r),this})};if("function"==typeof b&&(y||w.forEach&&!l(function(){(new b).entries().next()}))){var E=new b,O=E[S](y?{}:-0,1)!=E,M=l(function(){E.has(1)}),P=h(function(t){new b(t)}),F=!y&&l(function(){for(var t=new b,n=5;n--;)t[S](n,n);return!t.has(-0)});P||((b=n(function(n,r){f(n,b,t);var e=p(new m,n,b);return null!=r&&a(r,d,e[S],e),e})).prototype=w,w.constructor=b),(M||F)&&(_("delete"),_("has"),d&&_("get")),(F||O)&&_(S),y&&w.clear&&delete w.clear}else b=g.getConstructor(n,t,d,S),u(b.prototype,r),c.NEED=!0;return v(b,t),x[t]=b,i(i.G+i.W+i.F*(b!=m),x),y||g.setStrong(b,t,d),b}},function(t,n,r){for(var e,i=r(2),o=r(11),u=r(33),c=u("typed_array"),a=u("view"),f=!(!i.ArrayBuffer||!i.DataView),s=f,l=0,h="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");l<9;)(e=i[h[l++]])?(o(e.prototype,c,!0),o(e.prototype,a,!0)):s=!1;t.exports={ABV:f,CONSTR:s,TYPED:c,VIEW:a}},function(t,n,r){"use strict";t.exports=r(30)||!r(3)(function(){var t=Math.random();__defineSetter__.call(null,t,function(){}),delete r(2)[t]})},function(t,n,r){"use strict";var e=r(0);t.exports=function(t){e(e.S,t,{of:function(){for(var t=arguments.length,n=new Array(t);t--;)n[t]=arguments[t];return new this(n)}})}},function(t,n,r){"use strict";var e=r(0),i=r(10),o=r(19),u=r(40);t.exports=function(t){e(e.S,t,{from:function(t){var n,r,e,c,a=arguments[1];return i(this),(n=void 0!==a)&&i(a),null==t?new this:(r=[],n?(e=0,c=o(a,arguments[2],2),u(t,!1,function(t){r.push(c(t,e++))})):u(t,!1,r.push,r),new this(r))}})}},function(t,n){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(t){"object"==typeof window&&(r=window)}t.exports=r},function(t,n,r){var e=r(4),i=r(2).document,o=e(i)&&e(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n,r){var e=r(2),i=r(18),o=r(30),u=r(92),c=r(7).f;t.exports=function(t){var n=i.Symbol||(i.Symbol=o?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},function(t,n,r){var e=r(49)("keys"),i=r(33);t.exports=function(t){return e[t]||(e[t]=i(t))}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n,r){var e=r(2).document;t.exports=e&&e.documentElement},function(t,n,r){var e=r(4),i=r(1),o=function(t,n){if(i(t),!e(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,n,e){try{(e=r(19)(Function.call,r(16).f(Object.prototype,"__proto__").set,2))(t,[]),n=!(t instanceof Array)}catch(t){n=!0}return function(t,r){return o(t,r),n?t.__proto__=r:e(t,r),t}}({},!1):void 0),check:o}},function(t,n){t.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},function(t,n,r){var e=r(4),i=r(70).set;t.exports=function(t,n,r){var o,u=n.constructor;return u!==r&&"function"==typeof u&&(o=u.prototype)!==r.prototype&&e(o)&&i&&i(t,o),t}},function(t,n,r){"use strict";var e=r(24),i=r(23);t.exports=function(t){var n=String(i(this)),r="",o=e(t);if(o<0||o==1/0)throw RangeError("Count can't be negative");for(;o>0;(o>>>=1)&&(n+=n))1&o&&(r+=n);return r}},function(t,n){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},function(t,n){var r=Math.expm1;t.exports=!r||r(10)>22025.465794806718||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(t){return 0==(t=+t)?t:t>-1e-6&&t<1e-6?t+t*t/2:Math.exp(t)-1}:r},function(t,n,r){var e=r(24),i=r(23);t.exports=function(t){return function(n,r){var o,u,c=String(i(n)),a=e(r),f=c.length;return a<0||a>=f?t?"":void 0:(o=c.charCodeAt(a))<55296||o>56319||a+1===f||(u=c.charCodeAt(a+1))<56320||u>57343?t?c.charAt(a):o:t?c.slice(a,a+2):u-56320+(o-55296<<10)+65536}}},function(t,n,r){"use strict";var e=r(30),i=r(0),o=r(12),u=r(11),c=r(44),a=r(78),f=r(42),s=r(17),l=r(5)("iterator"),h=!([].keys&&"next"in[].keys()),v=function(){return this};t.exports=function(t,n,r,p,g,d,y){a(r,n,p);var m,b,S,w=function(t){if(!h&&t in O)return O[t];switch(t){case"keys":case"values":return function(){return new r(this,t)}}return function(){return new r(this,t)}},x=n+" Iterator",_="values"==g,E=!1,O=t.prototype,M=O[l]||O["@@iterator"]||g&&O[g],P=M||w(g),F=g?_?w("entries"):P:void 0,A="Array"==n&&O.entries||M;if(A&&(S=s(A.call(new t)))!==Object.prototype&&S.next&&(f(S,x,!0),e||"function"==typeof S[l]||u(S,l,v)),_&&M&&"values"!==M.name&&(E=!0,P=function(){return M.call(this)}),e&&!y||!h&&!E&&O[l]||u(O,l,P),c[n]=P,c[x]=v,g)if(m={values:_?P:w("values"),keys:d?P:w("keys"),entries:F},y)for(b in m)b in O||o(O,b,m[b]);else i(i.P+i.F*(h||E),n,m);return m}},function(t,n,r){"use strict";var e=r(36),i=r(32),o=r(42),u={};r(11)(u,r(5)("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:i(1,r)}),o(t,n+" Iterator")}},function(t,n,r){var e=r(53),i=r(23);t.exports=function(t,n,r){if(e(n))throw TypeError("String#"+r+" doesn't accept regex!");return String(i(t))}},function(t,n,r){var e=r(5)("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(r){try{return n[e]=!1,!"/./"[t](n)}catch(t){}}return!0}},function(t,n,r){var e=r(44),i=r(5)("iterator"),o=Array.prototype;t.exports=function(t){return void 0!==t&&(e.Array===t||o[i]===t)}},function(t,n,r){"use strict";var e=r(7),i=r(32);t.exports=function(t,n,r){n in t?e.f(t,n,i(0,r)):t[n]=r}},function(t,n,r){var e=r(48),i=r(5)("iterator"),o=r(44);t.exports=r(18).getIteratorMethod=function(t){if(null!=t)return t[i]||t["@@iterator"]||o[e(t)]}},function(t,n,r){var e=r(221);t.exports=function(t,n){return new(e(t))(n)}},function(t,n,r){"use strict";var e=r(9),i=r(35),o=r(8);t.exports=function(t){for(var n=e(this),r=o(n.length),u=arguments.length,c=i(u>1?arguments[1]:void 0,r),a=u>2?arguments[2]:void 0,f=void 0===a?r:i(a,r);f>c;)n[c++]=t;return n}},function(t,n,r){"use strict";var e=r(31),i=r(108),o=r(44),u=r(15);t.exports=r(77)(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,i(1)):i(0,"keys"==n?r:"values"==n?t[r]:[r,t[r]])},"values"),o.Arguments=o.Array,e("keys"),e("values"),e("entries")},function(t,n,r){var e,i,o,u=r(19),c=r(98),a=r(69),f=r(65),s=r(2),l=s.process,h=s.setImmediate,v=s.clearImmediate,p=s.MessageChannel,g=s.Dispatch,d=0,y={},m=function(){var t=+this;if(y.hasOwnProperty(t)){var n=y[t];delete y[t],n()}},b=function(t){m.call(t.data)};h&&v||(h=function(t){for(var n=[],r=1;arguments.length>r;)n.push(arguments[r++]);return y[++d]=function(){c("function"==typeof t?t:Function(t),n)},e(d),d},v=function(t){delete y[t]},"process"==r(20)(l)?e=function(t){l.nextTick(u(m,t,1))}:g&&g.now?e=function(t){g.now(u(m,t,1))}:p?(o=(i=new p).port2,i.port1.onmessage=b,e=u(o.postMessage,o,1)):s.addEventListener&&"function"==typeof postMessage&&!s.importScripts?(e=function(t){s.postMessage(t+"","*")},s.addEventListener("message",b,!1)):e="onreadystatechange"in f("script")?function(t){a.appendChild(f("script")).onreadystatechange=function(){a.removeChild(this),m.call(t)}}:function(t){setTimeout(u(m,t,1),0)}),t.exports={set:h,clear:v}},function(t,n,r){var e=r(2),i=r(87).set,o=e.MutationObserver||e.WebKitMutationObserver,u=e.process,c=e.Promise,a="process"==r(20)(u);t.exports=function(){var t,n,r,f=function(){var e,i;for(a&&(e=u.domain)&&e.exit();t;){i=t.fn,t=t.next;try{i()}catch(e){throw t?r():n=void 0,e}}n=void 0,e&&e.enter()};if(a)r=function(){u.nextTick(f)};else if(!o||e.navigator&&e.navigator.standalone)if(c&&c.resolve){var s=c.resolve(void 0);r=function(){s.then(f)}}else r=function(){i.call(e,f)};else{var l=!0,h=document.createTextNode("");new o(f).observe(h,{characterData:!0}),r=function(){h.data=l=!l}}return function(e){var i={fn:e,next:void 0};n&&(n.next=i),t||(t=i,r()),n=i}}},function(t,n,r){"use strict";var e=r(10);function i(t){var n,r;this.promise=new t(function(t,e){if(void 0!==n||void 0!==r)throw TypeError("Bad Promise constructor");n=t,r=e}),this.resolve=e(n),this.reject=e(r)}t.exports.f=function(t){return new i(t)}},function(t,n,r){"use strict";var e=r(2),i=r(6),o=r(30),u=r(60),c=r(11),a=r(41),f=r(3),s=r(39),l=r(24),h=r(8),v=r(117),p=r(37).f,g=r(7).f,d=r(85),y=r(42),m="prototype",b="Wrong index!",S=e.ArrayBuffer,w=e.DataView,x=e.Math,_=e.RangeError,E=e.Infinity,O=S,M=x.abs,P=x.pow,F=x.floor,A=x.log,I=x.LN2,j=i?"_b":"buffer",N=i?"_l":"byteLength",k=i?"_o":"byteOffset";function T(t,n,r){var e,i,o,u=new Array(r),c=8*r-n-1,a=(1<<c)-1,f=a>>1,s=23===n?P(2,-24)-P(2,-77):0,l=0,h=t<0||0===t&&1/t<0?1:0;for((t=M(t))!=t||t===E?(i=t!=t?1:0,e=a):(e=F(A(t)/I),t*(o=P(2,-e))<1&&(e--,o*=2),(t+=e+f>=1?s/o:s*P(2,1-f))*o>=2&&(e++,o/=2),e+f>=a?(i=0,e=a):e+f>=1?(i=(t*o-1)*P(2,n),e+=f):(i=t*P(2,f-1)*P(2,n),e=0));n>=8;u[l++]=255&i,i/=256,n-=8);for(e=e<<n|i,c+=n;c>0;u[l++]=255&e,e/=256,c-=8);return u[--l]|=128*h,u}function L(t,n,r){var e,i=8*r-n-1,o=(1<<i)-1,u=o>>1,c=i-7,a=r-1,f=t[a--],s=127&f;for(f>>=7;c>0;s=256*s+t[a],a--,c-=8);for(e=s&(1<<-c)-1,s>>=-c,c+=n;c>0;e=256*e+t[a],a--,c-=8);if(0===s)s=1-u;else{if(s===o)return e?NaN:f?-E:E;e+=P(2,n),s-=u}return(f?-1:1)*e*P(2,s-n)}function R(t){return t[3]<<24|t[2]<<16|t[1]<<8|t[0]}function C(t){return[255&t]}function W(t){return[255&t,t>>8&255]}function D(t){return[255&t,t>>8&255,t>>16&255,t>>24&255]}function U(t){return T(t,52,8)}function G(t){return T(t,23,4)}function V(t,n,r){g(t[m],n,{get:function(){return this[r]}})}function B(t,n,r,e){var i=v(+r);if(i+n>t[N])throw _(b);var o=t[j]._b,u=i+t[k],c=o.slice(u,u+n);return e?c:c.reverse()}function z(t,n,r,e,i,o){var u=v(+r);if(u+n>t[N])throw _(b);for(var c=t[j]._b,a=u+t[k],f=e(+i),s=0;s<n;s++)c[a+s]=f[o?s:n-s-1]}if(u.ABV){if(!f(function(){S(1)})||!f(function(){new S(-1)})||f(function(){return new S,new S(1.5),new S(NaN),"ArrayBuffer"!=S.name})){for(var J,q=(S=function(t){return s(this,S),new O(v(t))})[m]=O[m],Y=p(O),K=0;Y.length>K;)(J=Y[K++])in S||c(S,J,O[J]);o||(q.constructor=S)}var H=new w(new S(2)),X=w[m].setInt8;H.setInt8(0,2147483648),H.setInt8(1,2147483649),!H.getInt8(0)&&H.getInt8(1)||a(w[m],{setInt8:function(t,n){X.call(this,t,n<<24>>24)},setUint8:function(t,n){X.call(this,t,n<<24>>24)}},!0)}else S=function(t){s(this,S,"ArrayBuffer");var n=v(t);this._b=d.call(new Array(n),0),this[N]=n},w=function(t,n,r){s(this,w,"DataView"),s(t,S,"DataView");var e=t[N],i=l(n);if(i<0||i>e)throw _("Wrong offset!");if(i+(r=void 0===r?e-i:h(r))>e)throw _("Wrong length!");this[j]=t,this[k]=i,this[N]=r},i&&(V(S,"byteLength","_l"),V(w,"buffer","_b"),V(w,"byteLength","_l"),V(w,"byteOffset","_o")),a(w[m],{getInt8:function(t){return B(this,1,t)[0]<<24>>24},getUint8:function(t){return B(this,1,t)[0]},getInt16:function(t){var n=B(this,2,t,arguments[1]);return(n[1]<<8|n[0])<<16>>16},getUint16:function(t){var n=B(this,2,t,arguments[1]);return n[1]<<8|n[0]},getInt32:function(t){return R(B(this,4,t,arguments[1]))},getUint32:function(t){return R(B(this,4,t,arguments[1]))>>>0},getFloat32:function(t){return L(B(this,4,t,arguments[1]),23,4)},getFloat64:function(t){return L(B(this,8,t,arguments[1]),52,8)},setInt8:function(t,n){z(this,1,t,C,n)},setUint8:function(t,n){z(this,1,t,C,n)},setInt16:function(t,n){z(this,2,t,W,n,arguments[2])},setUint16:function(t,n){z(this,2,t,W,n,arguments[2])},setInt32:function(t,n){z(this,4,t,D,n,arguments[2])},setUint32:function(t,n){z(this,4,t,D,n,arguments[2])},setFloat32:function(t,n){z(this,4,t,G,n,arguments[2])},setFloat64:function(t,n){z(this,8,t,U,n,arguments[2])}});y(S,"ArrayBuffer"),y(w,"DataView"),c(w[m],u.VIEW,!0),n.ArrayBuffer=S,n.DataView=w},function(t,n,r){t.exports=!r(6)&&!r(3)(function(){return 7!=Object.defineProperty(r(65)("div"),"a",{get:function(){return 7}}).a})},function(t,n,r){n.f=r(5)},function(t,n,r){var e=r(14),i=r(15),o=r(50)(!1),u=r(67)("IE_PROTO");t.exports=function(t,n){var r,c=i(t),a=0,f=[];for(r in c)r!=u&&e(c,r)&&f.push(r);for(;n.length>a;)e(c,r=n[a++])&&(~o(f,r)||f.push(r));return f}},function(t,n,r){var e=r(7),i=r(1),o=r(34);t.exports=r(6)?Object.defineProperties:function(t,n){i(t);for(var r,u=o(n),c=u.length,a=0;c>a;)e.f(t,r=u[a++],n[r]);return t}},function(t,n,r){var e=r(15),i=r(37).f,o={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];t.exports.f=function(t){return u&&"[object Window]"==o.call(t)?function(t){try{return i(t)}catch(t){return u.slice()}}(t):i(e(t))}},function(t,n,r){"use strict";var e=r(34),i=r(51),o=r(47),u=r(9),c=r(46),a=Object.assign;t.exports=!a||r(3)(function(){var t={},n={},r=Symbol(),e="abcdefghijklmnopqrst";return t[r]=7,e.split("").forEach(function(t){n[t]=t}),7!=a({},t)[r]||Object.keys(a({},n)).join("")!=e})?function(t,n){for(var r=u(t),a=arguments.length,f=1,s=i.f,l=o.f;a>f;)for(var h,v=c(arguments[f++]),p=s?e(v).concat(s(v)):e(v),g=p.length,d=0;g>d;)l.call(v,h=p[d++])&&(r[h]=v[h]);return r}:a},function(t,n,r){"use strict";var e=r(10),i=r(4),o=r(98),u=[].slice,c={};t.exports=Function.bind||function(t){var n=e(this),r=u.call(arguments,1),a=function(){var e=r.concat(u.call(arguments));return this instanceof a?function(t,n,r){if(!(n in c)){for(var e=[],i=0;i<n;i++)e[i]="a["+i+"]";c[n]=Function("F,a","return new F("+e.join(",")+")")}return c[n](t,r)}(n,e.length,e):o(n,e,t)};return i(n.prototype)&&(a.prototype=n.prototype),a}},function(t,n){t.exports=function(t,n,r){var e=void 0===r;switch(n.length){case 0:return e?t():t.call(r);case 1:return e?t(n[0]):t.call(r,n[0]);case 2:return e?t(n[0],n[1]):t.call(r,n[0],n[1]);case 3:return e?t(n[0],n[1],n[2]):t.call(r,n[0],n[1],n[2]);case 4:return e?t(n[0],n[1],n[2],n[3]):t.call(r,n[0],n[1],n[2],n[3])}return t.apply(r,n)}},function(t,n,r){var e=r(2).parseInt,i=r(43).trim,o=r(71),u=/^[-+]?0[xX]/;t.exports=8!==e(o+"08")||22!==e(o+"0x16")?function(t,n){var r=i(String(t),3);return e(r,n>>>0||(u.test(r)?16:10))}:e},function(t,n,r){var e=r(2).parseFloat,i=r(43).trim;t.exports=1/e(r(71)+"-0")!=-1/0?function(t){var n=i(String(t),3),r=e(n);return 0===r&&"-"==n.charAt(0)?-0:r}:e},function(t,n,r){var e=r(20);t.exports=function(t,n){if("number"!=typeof t&&"Number"!=e(t))throw TypeError(n);return+t}},function(t,n,r){var e=r(4),i=Math.floor;t.exports=function(t){return!e(t)&&isFinite(t)&&i(t)===t}},function(t,n){t.exports=Math.log1p||function(t){return(t=+t)>-1e-8&&t<1e-8?t-t*t/2:Math.log(1+t)}},function(t,n,r){var e=r(74),i=Math.pow,o=i(2,-52),u=i(2,-23),c=i(2,127)*(2-u),a=i(2,-126);t.exports=Math.fround||function(t){var n,r,i=Math.abs(t),f=e(t);return i<a?f*(i/a/u+1/o-1/o)*a*u:(r=(n=(1+u/o)*i)-(n-i))>c||r!=r?f*(1/0):f*r}},function(t,n,r){var e=r(1);t.exports=function(t,n,r,i){try{return i?n(e(r)[0],r[1]):n(r)}catch(n){var o=t.return;throw void 0!==o&&e(o.call(t)),n}}},function(t,n,r){var e=r(10),i=r(9),o=r(46),u=r(8);t.exports=function(t,n,r,c,a){e(n);var f=i(t),s=o(f),l=u(f.length),h=a?l-1:0,v=a?-1:1;if(r<2)for(;;){if(h in s){c=s[h],h+=v;break}if(h+=v,a?h<0:l<=h)throw TypeError("Reduce of empty array with no initial value")}for(;a?h>=0:l>h;h+=v)h in s&&(c=n(c,s[h],h,f));return c}},function(t,n,r){"use strict";var e=r(9),i=r(35),o=r(8);t.exports=[].copyWithin||function(t,n){var r=e(this),u=o(r.length),c=i(t,u),a=i(n,u),f=arguments.length>2?arguments[2]:void 0,s=Math.min((void 0===f?u:i(f,u))-a,u-c),l=1;for(a<c&&c<a+s&&(l=-1,a+=s-1,c+=s-1);s-- >0;)a in r?r[c]=r[a]:delete r[c],c+=l,a+=l;return r}},function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},function(t,n,r){r(6)&&"g"!=/./g.flags&&r(7).f(RegExp.prototype,"flags",{configurable:!0,get:r(55)})},function(t,n){t.exports=function(t){try{return{e:!1,v:t()}}catch(t){return{e:!0,v:t}}}},function(t,n,r){var e=r(1),i=r(4),o=r(89);t.exports=function(t,n){if(e(t),i(n)&&n.constructor===t)return n;var r=o.f(t);return(0,r.resolve)(n),r.promise}},function(t,n,r){"use strict";var e=r(113),i=r(45);t.exports=r(59)("Map",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{get:function(t){var n=e.getEntry(i(this,"Map"),t);return n&&n.v},set:function(t,n){return e.def(i(this,"Map"),0===t?0:t,n)}},e,!0)},function(t,n,r){"use strict";var e=r(7).f,i=r(36),o=r(41),u=r(19),c=r(39),a=r(40),f=r(77),s=r(108),l=r(38),h=r(6),v=r(29).fastKey,p=r(45),g=h?"_s":"size",d=function(t,n){var r,e=v(n);if("F"!==e)return t._i[e];for(r=t._f;r;r=r.n)if(r.k==n)return r};t.exports={getConstructor:function(t,n,r,f){var s=t(function(t,e){c(t,s,n,"_i"),t._t=n,t._i=i(null),t._f=void 0,t._l=void 0,t[g]=0,null!=e&&a(e,r,t[f],t)});return o(s.prototype,{clear:function(){for(var t=p(this,n),r=t._i,e=t._f;e;e=e.n)e.r=!0,e.p&&(e.p=e.p.n=void 0),delete r[e.i];t._f=t._l=void 0,t[g]=0},delete:function(t){var r=p(this,n),e=d(r,t);if(e){var i=e.n,o=e.p;delete r._i[e.i],e.r=!0,o&&(o.n=i),i&&(i.p=o),r._f==e&&(r._f=i),r._l==e&&(r._l=o),r[g]--}return!!e},forEach:function(t){p(this,n);for(var r,e=u(t,arguments.length>1?arguments[1]:void 0,3);r=r?r.n:this._f;)for(e(r.v,r.k,this);r&&r.r;)r=r.p},has:function(t){return!!d(p(this,n),t)}}),h&&e(s.prototype,"size",{get:function(){return p(this,n)[g]}}),s},def:function(t,n,r){var e,i,o=d(t,n);return o?o.v=r:(t._l=o={i:i=v(n,!0),k:n,v:r,p:e=t._l,n:void 0,r:!1},t._f||(t._f=o),e&&(e.n=o),t[g]++,"F"!==i&&(t._i[i]=o)),t},getEntry:d,setStrong:function(t,n,r){f(t,n,function(t,r){this._t=p(t,n),this._k=r,this._l=void 0},function(){for(var t=this._k,n=this._l;n&&n.r;)n=n.p;return this._t&&(this._l=n=n?n.n:this._t._f)?s(0,"keys"==t?n.k:"values"==t?n.v:[n.k,n.v]):(this._t=void 0,s(1))},r?"entries":"values",!r,!0),l(n)}}},function(t,n,r){"use strict";var e=r(113),i=r(45);t.exports=r(59)("Set",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(i(this,"Set"),t=0===t?0:t,t)}},e)},function(t,n,r){"use strict";var e,i=r(26)(0),o=r(12),u=r(29),c=r(96),a=r(116),f=r(4),s=r(3),l=r(45),h=u.getWeak,v=Object.isExtensible,p=a.ufstore,g={},d=function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},y={get:function(t){if(f(t)){var n=h(t);return!0===n?p(l(this,"WeakMap")).get(t):n?n[this._i]:void 0}},set:function(t,n){return a.def(l(this,"WeakMap"),t,n)}},m=t.exports=r(59)("WeakMap",d,y,a,!0,!0);s(function(){return 7!=(new m).set((Object.freeze||Object)(g),7).get(g)})&&(c((e=a.getConstructor(d,"WeakMap")).prototype,y),u.NEED=!0,i(["delete","has","get","set"],function(t){var n=m.prototype,r=n[t];o(n,t,function(n,i){if(f(n)&&!v(n)){this._f||(this._f=new e);var o=this._f[t](n,i);return"set"==t?this:o}return r.call(this,n,i)})}))},function(t,n,r){"use strict";var e=r(41),i=r(29).getWeak,o=r(1),u=r(4),c=r(39),a=r(40),f=r(26),s=r(14),l=r(45),h=f(5),v=f(6),p=0,g=function(t){return t._l||(t._l=new d)},d=function(){this.a=[]},y=function(t,n){return h(t.a,function(t){return t[0]===n})};d.prototype={get:function(t){var n=y(this,t);if(n)return n[1]},has:function(t){return!!y(this,t)},set:function(t,n){var r=y(this,t);r?r[1]=n:this.a.push([t,n])},delete:function(t){var n=v(this.a,function(n){return n[0]===t});return~n&&this.a.splice(n,1),!!~n}},t.exports={getConstructor:function(t,n,r,o){var f=t(function(t,e){c(t,f,n,"_i"),t._t=n,t._i=p++,t._l=void 0,null!=e&&a(e,r,t[o],t)});return e(f.prototype,{delete:function(t){if(!u(t))return!1;var r=i(t);return!0===r?g(l(this,n)).delete(t):r&&s(r,this._i)&&delete r[this._i]},has:function(t){if(!u(t))return!1;var r=i(t);return!0===r?g(l(this,n)).has(t):r&&s(r,this._i)}}),f},def:function(t,n,r){var e=i(o(n),!0);return!0===e?g(t).set(n,r):e[t._i]=r,t},ufstore:g}},function(t,n,r){var e=r(24),i=r(8);t.exports=function(t){if(void 0===t)return 0;var n=e(t),r=i(n);if(n!==r)throw RangeError("Wrong length!");return r}},function(t,n,r){var e=r(37),i=r(51),o=r(1),u=r(2).Reflect;t.exports=u&&u.ownKeys||function(t){var n=e.f(o(t)),r=i.f;return r?n.concat(r(t)):n}},function(t,n,r){"use strict";var e=r(52),i=r(4),o=r(8),u=r(19),c=r(5)("isConcatSpreadable");t.exports=function t(n,r,a,f,s,l,h,v){for(var p,g,d=s,y=0,m=!!h&&u(h,v,3);y<f;){if(y in a){if(p=m?m(a[y],y,r):a[y],g=!1,i(p)&&(g=void 0!==(g=p[c])?!!g:e(p)),g&&l>0)d=t(n,r,p,o(p.length),d,l-1)-1;else{if(d>=9007199254740991)throw TypeError();n[d]=p}d++}y++}return d}},function(t,n,r){var e=r(8),i=r(73),o=r(23);t.exports=function(t,n,r,u){var c=String(o(t)),a=c.length,f=void 0===r?" ":String(r),s=e(n);if(s<=a||""==f)return c;var l=s-a,h=i.call(f,Math.ceil(l/f.length));return h.length>l&&(h=h.slice(0,l)),u?h+c:c+h}},function(t,n,r){var e=r(34),i=r(15),o=r(47).f;t.exports=function(t){return function(n){for(var r,u=i(n),c=e(u),a=c.length,f=0,s=[];a>f;)o.call(u,r=c[f++])&&s.push(t?[r,u[r]]:u[r]);return s}}},function(t,n,r){var e=r(48),i=r(123);t.exports=function(t){return function(){if(e(this)!=t)throw TypeError(t+"#toJSON isn't generic");return i(this)}}},function(t,n,r){var e=r(40);t.exports=function(t,n){var r=[];return e(t,!1,r.push,r,n),r}},function(t,n){t.exports=Math.scale||function(t,n,r,e,i){return 0===arguments.length||t!=t||n!=n||r!=r||e!=e||i!=i?NaN:t===1/0||t===-1/0?t:(t-n)*(i-e)/(r-n)+e}},function(t,n,r){t.exports=r.p+"postaccess.html"},function(t,n,r){t.exports=r.p+"postauth.html"},function(t,n,r){r(128),t.exports=r(331)},function(t,n,r){"use strict";(function(t){function e(){return t._babelPolyfill||"undefined"!=typeof window&&window._babelPolyfill?null:r(129)}Object.defineProperty(n,"__esModule",{value:!0}),n.idempotentBabelPolyfill=e,n.default=e()}).call(this,r(64))},function(t,n,r){"use strict";(function(t){if(r(130),r(327),r(328),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed");t._babelPolyfill=!0;var n="defineProperty";function e(t,r,e){t[r]||Object[n](t,r,{writable:!0,configurable:!0,value:e})}e(String.prototype,"padLeft","".padStart),e(String.prototype,"padRight","".padEnd),"pop,reverse,shift,keys,values,entries,indexOf,every,some,forEach,map,filter,find,findIndex,includes,join,slice,concat,push,splice,unshift,sort,lastIndexOf,reduce,reduceRight,copyWithin,fill".split(",").forEach(function(t){[][t]&&e(Array,t,Function.call.bind([][t]))})}).call(this,r(64))},function(t,n,r){r(131),r(133),r(134),r(135),r(136),r(137),r(138),r(139),r(140),r(141),r(142),r(143),r(144),r(145),r(146),r(147),r(149),r(150),r(151),r(152),r(153),r(154),r(155),r(156),r(157),r(158),r(159),r(160),r(161),r(162),r(163),r(164),r(165),r(166),r(167),r(168),r(169),r(170),r(171),r(172),r(173),r(174),r(175),r(176),r(177),r(178),r(179),r(180),r(181),r(182),r(183),r(184),r(185),r(186),r(187),r(188),r(189),r(190),r(191),r(192),r(193),r(194),r(195),r(196),r(197),r(198),r(199),r(200),r(201),r(202),r(203),r(204),r(205),r(206),r(207),r(208),r(209),r(211),r(212),r(214),r(215),r(216),r(217),r(218),r(219),r(220),r(222),r(223),r(224),r(225),r(226),r(227),r(228),r(229),r(230),r(231),r(232),r(233),r(234),r(86),r(235),r(236),r(109),r(237),r(238),r(239),r(240),r(241),r(112),r(114),r(115),r(242),r(243),r(244),r(245),r(246),r(247),r(248),r(249),r(250),r(251),r(252),r(253),r(254),r(255),r(256),r(257),r(258),r(259),r(260),r(261),r(262),r(263),r(264),r(265),r(266),r(267),r(268),r(269),r(270),r(271),r(272),r(273),r(274),r(275),r(276),r(277),r(278),r(279),r(280),r(281),r(282),r(283),r(284),r(285),r(286),r(287),r(288),r(289),r(290),r(291),r(292),r(293),r(294),r(295),r(296),r(297),r(298),r(299),r(300),r(301),r(302),r(303),r(304),r(305),r(306),r(307),r(308),r(309),r(310),r(311),r(312),r(313),r(314),r(315),r(316),r(317),r(318),r(319),r(320),r(321),r(322),r(323),r(324),r(325),r(326),t.exports=r(18)},function(t,n,r){"use strict";var e=r(2),i=r(14),o=r(6),u=r(0),c=r(12),a=r(29).KEY,f=r(3),s=r(49),l=r(42),h=r(33),v=r(5),p=r(92),g=r(66),d=r(132),y=r(52),m=r(1),b=r(4),S=r(15),w=r(22),x=r(32),_=r(36),E=r(95),O=r(16),M=r(7),P=r(34),F=O.f,A=M.f,I=E.f,j=e.Symbol,N=e.JSON,k=N&&N.stringify,T=v("_hidden"),L=v("toPrimitive"),R={}.propertyIsEnumerable,C=s("symbol-registry"),W=s("symbols"),D=s("op-symbols"),U=Object.prototype,G="function"==typeof j,V=e.QObject,B=!V||!V.prototype||!V.prototype.findChild,z=o&&f(function(){return 7!=_(A({},"a",{get:function(){return A(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=F(U,n);e&&delete U[n],A(t,n,r),e&&t!==U&&A(U,n,e)}:A,J=function(t){var n=W[t]=_(j.prototype);return n._k=t,n},q=G&&"symbol"==typeof j.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof j},Y=function(t,n,r){return t===U&&Y(D,n,r),m(t),n=w(n,!0),m(r),i(W,n)?(r.enumerable?(i(t,T)&&t[T][n]&&(t[T][n]=!1),r=_(r,{enumerable:x(0,!1)})):(i(t,T)||A(t,T,x(1,{})),t[T][n]=!0),z(t,n,r)):A(t,n,r)},K=function(t,n){m(t);for(var r,e=d(n=S(n)),i=0,o=e.length;o>i;)Y(t,r=e[i++],n[r]);return t},H=function(t){var n=R.call(this,t=w(t,!0));return!(this===U&&i(W,t)&&!i(D,t))&&(!(n||!i(this,t)||!i(W,t)||i(this,T)&&this[T][t])||n)},X=function(t,n){if(t=S(t),n=w(n,!0),t!==U||!i(W,n)||i(D,n)){var r=F(t,n);return!r||!i(W,n)||i(t,T)&&t[T][n]||(r.enumerable=!0),r}},$=function(t){for(var n,r=I(S(t)),e=[],o=0;r.length>o;)i(W,n=r[o++])||n==T||n==a||e.push(n);return e},Z=function(t){for(var n,r=t===U,e=I(r?D:S(t)),o=[],u=0;e.length>u;)!i(W,n=e[u++])||r&&!i(U,n)||o.push(W[n]);return o};G||(c((j=function(){if(this instanceof j)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===U&&n.call(D,r),i(this,T)&&i(this[T],t)&&(this[T][t]=!1),z(this,t,x(1,r))};return o&&B&&z(U,t,{configurable:!0,set:n}),J(t)}).prototype,"toString",function(){return this._k}),O.f=X,M.f=Y,r(37).f=E.f=$,r(47).f=H,r(51).f=Z,o&&!r(30)&&c(U,"propertyIsEnumerable",H,!0),p.f=function(t){return J(v(t))}),u(u.G+u.W+u.F*!G,{Symbol:j});for(var Q="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),tt=0;Q.length>tt;)v(Q[tt++]);for(var nt=P(v.store),rt=0;nt.length>rt;)g(nt[rt++]);u(u.S+u.F*!G,"Symbol",{for:function(t){return i(C,t+="")?C[t]:C[t]=j(t)},keyFor:function(t){if(!q(t))throw TypeError(t+" is not a symbol!");for(var n in C)if(C[n]===t)return n},useSetter:function(){B=!0},useSimple:function(){B=!1}}),u(u.S+u.F*!G,"Object",{create:function(t,n){return void 0===n?_(t):K(_(t),n)},defineProperty:Y,defineProperties:K,getOwnPropertyDescriptor:X,getOwnPropertyNames:$,getOwnPropertySymbols:Z}),N&&u(u.S+u.F*(!G||f(function(){var t=j();return"[null]"!=k([t])||"{}"!=k({a:t})||"{}"!=k(Object(t))})),"JSON",{stringify:function(t){for(var n,r,e=[t],i=1;arguments.length>i;)e.push(arguments[i++]);if(r=n=e[1],(b(n)||void 0!==t)&&!q(t))return y(n)||(n=function(t,n){if("function"==typeof r&&(n=r.call(this,t,n)),!q(n))return n}),e[1]=n,k.apply(N,e)}}),j.prototype[L]||r(11)(j.prototype,L,j.prototype.valueOf),l(j,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},function(t,n,r){var e=r(34),i=r(51),o=r(47);t.exports=function(t){var n=e(t),r=i.f;if(r)for(var u,c=r(t),a=o.f,f=0;c.length>f;)a.call(t,u=c[f++])&&n.push(u);return n}},function(t,n,r){var e=r(0);e(e.S,"Object",{create:r(36)})},function(t,n,r){var e=r(0);e(e.S+e.F*!r(6),"Object",{defineProperty:r(7).f})},function(t,n,r){var e=r(0);e(e.S+e.F*!r(6),"Object",{defineProperties:r(94)})},function(t,n,r){var e=r(15),i=r(16).f;r(25)("getOwnPropertyDescriptor",function(){return function(t,n){return i(e(t),n)}})},function(t,n,r){var e=r(9),i=r(17);r(25)("getPrototypeOf",function(){return function(t){return i(e(t))}})},function(t,n,r){var e=r(9),i=r(34);r(25)("keys",function(){return function(t){return i(e(t))}})},function(t,n,r){r(25)("getOwnPropertyNames",function(){return r(95).f})},function(t,n,r){var e=r(4),i=r(29).onFreeze;r(25)("freeze",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(4),i=r(29).onFreeze;r(25)("seal",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(4),i=r(29).onFreeze;r(25)("preventExtensions",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(4);r(25)("isFrozen",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(4);r(25)("isSealed",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(4);r(25)("isExtensible",function(t){return function(n){return!!e(n)&&(!t||t(n))}})},function(t,n,r){var e=r(0);e(e.S+e.F,"Object",{assign:r(96)})},function(t,n,r){var e=r(0);e(e.S,"Object",{is:r(148)})},function(t,n){t.exports=Object.is||function(t,n){return t===n?0!==t||1/t==1/n:t!=t&&n!=n}},function(t,n,r){var e=r(0);e(e.S,"Object",{setPrototypeOf:r(70).set})},function(t,n,r){"use strict";var e=r(48),i={};i[r(5)("toStringTag")]="z",i+""!="[object z]"&&r(12)(Object.prototype,"toString",function(){return"[object "+e(this)+"]"},!0)},function(t,n,r){var e=r(0);e(e.P,"Function",{bind:r(97)})},function(t,n,r){var e=r(7).f,i=Function.prototype,o=/^\s*function ([^ (]*)/;"name"in i||r(6)&&e(i,"name",{configurable:!0,get:function(){try{return(""+this).match(o)[1]}catch(t){return""}}})},function(t,n,r){"use strict";var e=r(4),i=r(17),o=r(5)("hasInstance"),u=Function.prototype;o in u||r(7).f(u,o,{value:function(t){if("function"!=typeof this||!e(t))return!1;if(!e(this.prototype))return t instanceof this;for(;t=i(t);)if(this.prototype===t)return!0;return!1}})},function(t,n,r){var e=r(0),i=r(99);e(e.G+e.F*(parseInt!=i),{parseInt:i})},function(t,n,r){var e=r(0),i=r(100);e(e.G+e.F*(parseFloat!=i),{parseFloat:i})},function(t,n,r){"use strict";var e=r(2),i=r(14),o=r(20),u=r(72),c=r(22),a=r(3),f=r(37).f,s=r(16).f,l=r(7).f,h=r(43).trim,v=e.Number,p=v,g=v.prototype,d="Number"==o(r(36)(g)),y="trim"in String.prototype,m=function(t){var n=c(t,!1);if("string"==typeof n&&n.length>2){var r,e,i,o=(n=y?n.trim():h(n,3)).charCodeAt(0);if(43===o||45===o){if(88===(r=n.charCodeAt(2))||120===r)return NaN}else if(48===o){switch(n.charCodeAt(1)){case 66:case 98:e=2,i=49;break;case 79:case 111:e=8,i=55;break;default:return+n}for(var u,a=n.slice(2),f=0,s=a.length;f<s;f++)if((u=a.charCodeAt(f))<48||u>i)return NaN;return parseInt(a,e)}}return+n};if(!v(" 0o1")||!v("0b1")||v("+0x1")){v=function(t){var n=arguments.length<1?0:t,r=this;return r instanceof v&&(d?a(function(){g.valueOf.call(r)}):"Number"!=o(r))?u(new p(m(n)),r,v):m(n)};for(var b,S=r(6)?f(p):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),w=0;S.length>w;w++)i(p,b=S[w])&&!i(v,b)&&l(v,b,s(p,b));v.prototype=g,g.constructor=v,r(12)(e,"Number",v)}},function(t,n,r){"use strict";var e=r(0),i=r(24),o=r(101),u=r(73),c=1..toFixed,a=Math.floor,f=[0,0,0,0,0,0],s="Number.toFixed: incorrect invocation!",l=function(t,n){for(var r=-1,e=n;++r<6;)e+=t*f[r],f[r]=e%1e7,e=a(e/1e7)},h=function(t){for(var n=6,r=0;--n>=0;)r+=f[n],f[n]=a(r/t),r=r%t*1e7},v=function(){for(var t=6,n="";--t>=0;)if(""!==n||0===t||0!==f[t]){var r=String(f[t]);n=""===n?r:n+u.call("0",7-r.length)+r}return n},p=function(t,n,r){return 0===n?r:n%2==1?p(t,n-1,r*t):p(t*t,n/2,r)};e(e.P+e.F*(!!c&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!r(3)(function(){c.call({})})),"Number",{toFixed:function(t){var n,r,e,c,a=o(this,s),f=i(t),g="",d="0";if(f<0||f>20)throw RangeError(s);if(a!=a)return"NaN";if(a<=-1e21||a>=1e21)return String(a);if(a<0&&(g="-",a=-a),a>1e-21)if(r=(n=function(t){for(var n=0,r=t;r>=4096;)n+=12,r/=4096;for(;r>=2;)n+=1,r/=2;return n}(a*p(2,69,1))-69)<0?a*p(2,-n,1):a/p(2,n,1),r*=4503599627370496,(n=52-n)>0){for(l(0,r),e=f;e>=7;)l(1e7,0),e-=7;for(l(p(10,e,1),0),e=n-1;e>=23;)h(1<<23),e-=23;h(1<<e),l(1,1),h(2),d=v()}else l(0,r),l(1<<-n,0),d=v()+u.call("0",f);return d=f>0?g+((c=d.length)<=f?"0."+u.call("0",f-c)+d:d.slice(0,c-f)+"."+d.slice(c-f)):g+d}})},function(t,n,r){"use strict";var e=r(0),i=r(3),o=r(101),u=1..toPrecision;e(e.P+e.F*(i(function(){return"1"!==u.call(1,void 0)})||!i(function(){u.call({})})),"Number",{toPrecision:function(t){var n=o(this,"Number#toPrecision: incorrect invocation!");return void 0===t?u.call(n):u.call(n,t)}})},function(t,n,r){var e=r(0);e(e.S,"Number",{EPSILON:Math.pow(2,-52)})},function(t,n,r){var e=r(0),i=r(2).isFinite;e(e.S,"Number",{isFinite:function(t){return"number"==typeof t&&i(t)}})},function(t,n,r){var e=r(0);e(e.S,"Number",{isInteger:r(102)})},function(t,n,r){var e=r(0);e(e.S,"Number",{isNaN:function(t){return t!=t}})},function(t,n,r){var e=r(0),i=r(102),o=Math.abs;e(e.S,"Number",{isSafeInteger:function(t){return i(t)&&o(t)<=9007199254740991}})},function(t,n,r){var e=r(0);e(e.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},function(t,n,r){var e=r(0);e(e.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},function(t,n,r){var e=r(0),i=r(100);e(e.S+e.F*(Number.parseFloat!=i),"Number",{parseFloat:i})},function(t,n,r){var e=r(0),i=r(99);e(e.S+e.F*(Number.parseInt!=i),"Number",{parseInt:i})},function(t,n,r){var e=r(0),i=r(103),o=Math.sqrt,u=Math.acosh;e(e.S+e.F*!(u&&710==Math.floor(u(Number.MAX_VALUE))&&u(1/0)==1/0),"Math",{acosh:function(t){return(t=+t)<1?NaN:t>94906265.62425156?Math.log(t)+Math.LN2:i(t-1+o(t-1)*o(t+1))}})},function(t,n,r){var e=r(0),i=Math.asinh;e(e.S+e.F*!(i&&1/i(0)>0),"Math",{asinh:function t(n){return isFinite(n=+n)&&0!=n?n<0?-t(-n):Math.log(n+Math.sqrt(n*n+1)):n}})},function(t,n,r){var e=r(0),i=Math.atanh;e(e.S+e.F*!(i&&1/i(-0)<0),"Math",{atanh:function(t){return 0==(t=+t)?t:Math.log((1+t)/(1-t))/2}})},function(t,n,r){var e=r(0),i=r(74);e(e.S,"Math",{cbrt:function(t){return i(t=+t)*Math.pow(Math.abs(t),1/3)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{clz32:function(t){return(t>>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},function(t,n,r){var e=r(0),i=Math.exp;e(e.S,"Math",{cosh:function(t){return(i(t=+t)+i(-t))/2}})},function(t,n,r){var e=r(0),i=r(75);e(e.S+e.F*(i!=Math.expm1),"Math",{expm1:i})},function(t,n,r){var e=r(0);e(e.S,"Math",{fround:r(104)})},function(t,n,r){var e=r(0),i=Math.abs;e(e.S,"Math",{hypot:function(t,n){for(var r,e,o=0,u=0,c=arguments.length,a=0;u<c;)a<(r=i(arguments[u++]))?(o=o*(e=a/r)*e+1,a=r):o+=r>0?(e=r/a)*e:r;return a===1/0?1/0:a*Math.sqrt(o)}})},function(t,n,r){var e=r(0),i=Math.imul;e(e.S+e.F*r(3)(function(){return-5!=i(4294967295,5)||2!=i.length}),"Math",{imul:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e;return 0|i*o+((65535&r>>>16)*o+i*(65535&e>>>16)<<16>>>0)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{log10:function(t){return Math.log(t)*Math.LOG10E}})},function(t,n,r){var e=r(0);e(e.S,"Math",{log1p:r(103)})},function(t,n,r){var e=r(0);e(e.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},function(t,n,r){var e=r(0);e(e.S,"Math",{sign:r(74)})},function(t,n,r){var e=r(0),i=r(75),o=Math.exp;e(e.S+e.F*r(3)(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(i(t)-i(-t))/2:(o(t-1)-o(-t-1))*(Math.E/2)}})},function(t,n,r){var e=r(0),i=r(75),o=Math.exp;e(e.S,"Math",{tanh:function(t){var n=i(t=+t),r=i(-t);return n==1/0?1:r==1/0?-1:(n-r)/(o(t)+o(-t))}})},function(t,n,r){var e=r(0);e(e.S,"Math",{trunc:function(t){return(t>0?Math.floor:Math.ceil)(t)}})},function(t,n,r){var e=r(0),i=r(35),o=String.fromCharCode,u=String.fromCodePoint;e(e.S+e.F*(!!u&&1!=u.length),"String",{fromCodePoint:function(t){for(var n,r=[],e=arguments.length,u=0;e>u;){if(n=+arguments[u++],i(n,1114111)!==n)throw RangeError(n+" is not a valid code point");r.push(n<65536?o(n):o(55296+((n-=65536)>>10),n%1024+56320))}return r.join("")}})},function(t,n,r){var e=r(0),i=r(15),o=r(8);e(e.S,"String",{raw:function(t){for(var n=i(t.raw),r=o(n.length),e=arguments.length,u=[],c=0;r>c;)u.push(String(n[c++])),c<e&&u.push(String(arguments[c]));return u.join("")}})},function(t,n,r){"use strict";r(43)("trim",function(t){return function(){return t(this,3)}})},function(t,n,r){"use strict";var e=r(76)(!0);r(77)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},function(t,n,r){"use strict";var e=r(0),i=r(76)(!1);e(e.P,"String",{codePointAt:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(0),i=r(8),o=r(79),u="".endsWith;e(e.P+e.F*r(80)("endsWith"),"String",{endsWith:function(t){var n=o(this,t,"endsWith"),r=arguments.length>1?arguments[1]:void 0,e=i(n.length),c=void 0===r?e:Math.min(i(r),e),a=String(t);return u?u.call(n,a,c):n.slice(c-a.length,c)===a}})},function(t,n,r){"use strict";var e=r(0),i=r(79);e(e.P+e.F*r(80)("includes"),"String",{includes:function(t){return!!~i(this,t,"includes").indexOf(t,arguments.length>1?arguments[1]:void 0)}})},function(t,n,r){var e=r(0);e(e.P,"String",{repeat:r(73)})},function(t,n,r){"use strict";var e=r(0),i=r(8),o=r(79),u="".startsWith;e(e.P+e.F*r(80)("startsWith"),"String",{startsWith:function(t){var n=o(this,t,"startsWith"),r=i(Math.min(arguments.length>1?arguments[1]:void 0,n.length)),e=String(t);return u?u.call(n,e,r):n.slice(r,r+e.length)===e}})},function(t,n,r){"use strict";r(13)("anchor",function(t){return function(n){return t(this,"a","name",n)}})},function(t,n,r){"use strict";r(13)("big",function(t){return function(){return t(this,"big","","")}})},function(t,n,r){"use strict";r(13)("blink",function(t){return function(){return t(this,"blink","","")}})},function(t,n,r){"use strict";r(13)("bold",function(t){return function(){return t(this,"b","","")}})},function(t,n,r){"use strict";r(13)("fixed",function(t){return function(){return t(this,"tt","","")}})},function(t,n,r){"use strict";r(13)("fontcolor",function(t){return function(n){return t(this,"font","color",n)}})},function(t,n,r){"use strict";r(13)("fontsize",function(t){return function(n){return t(this,"font","size",n)}})},function(t,n,r){"use strict";r(13)("italics",function(t){return function(){return t(this,"i","","")}})},function(t,n,r){"use strict";r(13)("link",function(t){return function(n){return t(this,"a","href",n)}})},function(t,n,r){"use strict";r(13)("small",function(t){return function(){return t(this,"small","","")}})},function(t,n,r){"use strict";r(13)("strike",function(t){return function(){return t(this,"strike","","")}})},function(t,n,r){"use strict";r(13)("sub",function(t){return function(){return t(this,"sub","","")}})},function(t,n,r){"use strict";r(13)("sup",function(t){return function(){return t(this,"sup","","")}})},function(t,n,r){var e=r(0);e(e.S,"Date",{now:function(){return(new Date).getTime()}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(22);e(e.P+e.F*r(3)(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(t){var n=i(this),r=o(n);return"number"!=typeof r||isFinite(r)?n.toISOString():null}})},function(t,n,r){var e=r(0),i=r(210);e(e.P+e.F*(Date.prototype.toISOString!==i),"Date",{toISOString:i})},function(t,n,r){"use strict";var e=r(3),i=Date.prototype.getTime,o=Date.prototype.toISOString,u=function(t){return t>9?t:"0"+t};t.exports=e(function(){return"0385-07-25T07:06:39.999Z"!=o.call(new Date(-5e13-1))})||!e(function(){o.call(new Date(NaN))})?function(){if(!isFinite(i.call(this)))throw RangeError("Invalid time value");var t=this,n=t.getUTCFullYear(),r=t.getUTCMilliseconds(),e=n<0?"-":n>9999?"+":"";return e+("00000"+Math.abs(n)).slice(e?-6:-4)+"-"+u(t.getUTCMonth()+1)+"-"+u(t.getUTCDate())+"T"+u(t.getUTCHours())+":"+u(t.getUTCMinutes())+":"+u(t.getUTCSeconds())+"."+(r>99?r:"0"+u(r))+"Z"}:o},function(t,n,r){var e=Date.prototype,i=e.toString,o=e.getTime;new Date(NaN)+""!="Invalid Date"&&r(12)(e,"toString",function(){var t=o.call(this);return t==t?i.call(this):"Invalid Date"})},function(t,n,r){var e=r(5)("toPrimitive"),i=Date.prototype;e in i||r(11)(i,e,r(213))},function(t,n,r){"use strict";var e=r(1),i=r(22);t.exports=function(t){if("string"!==t&&"number"!==t&&"default"!==t)throw TypeError("Incorrect hint");return i(e(this),"number"!=t)}},function(t,n,r){var e=r(0);e(e.S,"Array",{isArray:r(52)})},function(t,n,r){"use strict";var e=r(19),i=r(0),o=r(9),u=r(105),c=r(81),a=r(8),f=r(82),s=r(83);i(i.S+i.F*!r(54)(function(t){Array.from(t)}),"Array",{from:function(t){var n,r,i,l,h=o(t),v="function"==typeof this?this:Array,p=arguments.length,g=p>1?arguments[1]:void 0,d=void 0!==g,y=0,m=s(h);if(d&&(g=e(g,p>2?arguments[2]:void 0,2)),null==m||v==Array&&c(m))for(r=new v(n=a(h.length));n>y;y++)f(r,y,d?g(h[y],y):h[y]);else for(l=m.call(h),r=new v;!(i=l.next()).done;y++)f(r,y,d?u(l,g,[i.value,y],!0):i.value);return r.length=y,r}})},function(t,n,r){"use strict";var e=r(0),i=r(82);e(e.S+e.F*r(3)(function(){function t(){}return!(Array.of.call(t)instanceof t)}),"Array",{of:function(){for(var t=0,n=arguments.length,r=new("function"==typeof this?this:Array)(n);n>t;)i(r,t,arguments[t++]);return r.length=n,r}})},function(t,n,r){"use strict";var e=r(0),i=r(15),o=[].join;e(e.P+e.F*(r(46)!=Object||!r(21)(o)),"Array",{join:function(t){return o.call(i(this),void 0===t?",":t)}})},function(t,n,r){"use strict";var e=r(0),i=r(69),o=r(20),u=r(35),c=r(8),a=[].slice;e(e.P+e.F*r(3)(function(){i&&a.call(i)}),"Array",{slice:function(t,n){var r=c(this.length),e=o(this);if(n=void 0===n?r:n,"Array"==e)return a.call(this,t,n);for(var i=u(t,r),f=u(n,r),s=c(f-i),l=new Array(s),h=0;h<s;h++)l[h]="String"==e?this.charAt(i+h):this[i+h];return l}})},function(t,n,r){"use strict";var e=r(0),i=r(10),o=r(9),u=r(3),c=[].sort,a=[1,2,3];e(e.P+e.F*(u(function(){a.sort(void 0)})||!u(function(){a.sort(null)})||!r(21)(c)),"Array",{sort:function(t){return void 0===t?c.call(o(this)):c.call(o(this),i(t))}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(0),o=r(21)([].forEach,!0);e(e.P+e.F*!o,"Array",{forEach:function(t){return i(this,t,arguments[1])}})},function(t,n,r){var e=r(4),i=r(52),o=r(5)("species");t.exports=function(t){var n;return i(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!i(n.prototype)||(n=void 0),e(n)&&null===(n=n[o])&&(n=void 0)),void 0===n?Array:n}},function(t,n,r){"use strict";var e=r(0),i=r(26)(1);e(e.P+e.F*!r(21)([].map,!0),"Array",{map:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(2);e(e.P+e.F*!r(21)([].filter,!0),"Array",{filter:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(3);e(e.P+e.F*!r(21)([].some,!0),"Array",{some:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(4);e(e.P+e.F*!r(21)([].every,!0),"Array",{every:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(106);e(e.P+e.F*!r(21)([].reduce,!0),"Array",{reduce:function(t){return i(this,t,arguments.length,arguments[1],!1)}})},function(t,n,r){"use strict";var e=r(0),i=r(106);e(e.P+e.F*!r(21)([].reduceRight,!0),"Array",{reduceRight:function(t){return i(this,t,arguments.length,arguments[1],!0)}})},function(t,n,r){"use strict";var e=r(0),i=r(50)(!1),o=[].indexOf,u=!!o&&1/[1].indexOf(1,-0)<0;e(e.P+e.F*(u||!r(21)(o)),"Array",{indexOf:function(t){return u?o.apply(this,arguments)||0:i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(15),o=r(24),u=r(8),c=[].lastIndexOf,a=!!c&&1/[1].lastIndexOf(1,-0)<0;e(e.P+e.F*(a||!r(21)(c)),"Array",{lastIndexOf:function(t){if(a)return c.apply(this,arguments)||0;var n=i(this),r=u(n.length),e=r-1;for(arguments.length>1&&(e=Math.min(e,o(arguments[1]))),e<0&&(e=r+e);e>=0;e--)if(e in n&&n[e]===t)return e||0;return-1}})},function(t,n,r){var e=r(0);e(e.P,"Array",{copyWithin:r(107)}),r(31)("copyWithin")},function(t,n,r){var e=r(0);e(e.P,"Array",{fill:r(85)}),r(31)("fill")},function(t,n,r){"use strict";var e=r(0),i=r(26)(5),o=!0;"find"in[]&&Array(1).find(function(){o=!1}),e(e.P+e.F*o,"Array",{find:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(31)("find")},function(t,n,r){"use strict";var e=r(0),i=r(26)(6),o="findIndex",u=!0;o in[]&&Array(1)[o](function(){u=!1}),e(e.P+e.F*u,"Array",{findIndex:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(31)(o)},function(t,n,r){r(38)("Array")},function(t,n,r){var e=r(2),i=r(72),o=r(7).f,u=r(37).f,c=r(53),a=r(55),f=e.RegExp,s=f,l=f.prototype,h=/a/g,v=/a/g,p=new f(h)!==h;if(r(6)&&(!p||r(3)(function(){return v[r(5)("match")]=!1,f(h)!=h||f(v)==v||"/a/i"!=f(h,"i")}))){f=function(t,n){var r=this instanceof f,e=c(t),o=void 0===n;return!r&&e&&t.constructor===f&&o?t:i(p?new s(e&&!o?t.source:t,n):s((e=t instanceof f)?t.source:t,e&&o?a.call(t):n),r?this:l,f)};for(var g=function(t){t in f||o(f,t,{configurable:!0,get:function(){return s[t]},set:function(n){s[t]=n}})},d=u(s),y=0;d.length>y;)g(d[y++]);l.constructor=f,f.prototype=l,r(12)(e,"RegExp",f)}r(38)("RegExp")},function(t,n,r){"use strict";r(109);var e=r(1),i=r(55),o=r(6),u=/./.toString,c=function(t){r(12)(RegExp.prototype,"toString",t,!0)};r(3)(function(){return"/a/b"!=u.call({source:"a",flags:"b"})})?c(function(){var t=e(this);return"/".concat(t.source,"/","flags"in t?t.flags:!o&&t instanceof RegExp?i.call(t):void 0)}):"toString"!=u.name&&c(function(){return u.call(this)})},function(t,n,r){r(56)("match",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=null==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(56)("replace",2,function(t,n,r){return[function(e,i){"use strict";var o=t(this),u=null==e?void 0:e[n];return void 0!==u?u.call(e,o,i):r.call(String(o),e,i)},r]})},function(t,n,r){r(56)("search",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=null==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(56)("split",2,function(t,n,e){"use strict";var i=r(53),o=e,u=[].push;if("c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length){var c=void 0===/()??/.exec("")[1];e=function(t,n){var r=String(this);if(void 0===t&&0===n)return[];if(!i(t))return o.call(r,t,n);var e,a,f,s,l,h=[],v=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),p=0,g=void 0===n?4294967295:n>>>0,d=new RegExp(t.source,v+"g");for(c||(e=new RegExp("^"+d.source+"$(?!\\s)",v));(a=d.exec(r))&&!((f=a.index+a[0].length)>p&&(h.push(r.slice(p,a.index)),!c&&a.length>1&&a[0].replace(e,function(){for(l=1;l<arguments.length-2;l++)void 0===arguments[l]&&(a[l]=void 0)}),a.length>1&&a.index<r.length&&u.apply(h,a.slice(1)),s=a[0].length,p=f,h.length>=g));)d.lastIndex===a.index&&d.lastIndex++;return p===r.length?!s&&d.test("")||h.push(""):h.push(r.slice(p)),h.length>g?h.slice(0,g):h}}else"0".split(void 0,0).length&&(e=function(t,n){return void 0===t&&0===n?[]:o.call(this,t,n)});return[function(r,i){var o=t(this),u=null==r?void 0:r[n];return void 0!==u?u.call(r,o,i):e.call(String(o),r,i)},e]})},function(t,n,r){"use strict";var e,i,o,u,c=r(30),a=r(2),f=r(19),s=r(48),l=r(0),h=r(4),v=r(10),p=r(39),g=r(40),d=r(57),y=r(87).set,m=r(88)(),b=r(89),S=r(110),w=r(58),x=r(111),_=a.TypeError,E=a.process,O=E&&E.versions,M=O&&O.v8||"",P=a.Promise,F="process"==s(E),A=function(){},I=i=b.f,j=!!function(){try{var t=P.resolve(1),n=(t.constructor={})[r(5)("species")]=function(t){t(A,A)};return(F||"function"==typeof PromiseRejectionEvent)&&t.then(A)instanceof n&&0!==M.indexOf("6.6")&&-1===w.indexOf("Chrome/66")}catch(t){}}(),N=function(t){var n;return!(!h(t)||"function"!=typeof(n=t.then))&&n},k=function(t,n){if(!t._n){t._n=!0;var r=t._c;m(function(){for(var e=t._v,i=1==t._s,o=0,u=function(n){var r,o,u,c=i?n.ok:n.fail,a=n.resolve,f=n.reject,s=n.domain;try{c?(i||(2==t._h&&R(t),t._h=1),!0===c?r=e:(s&&s.enter(),r=c(e),s&&(s.exit(),u=!0)),r===n.promise?f(_("Promise-chain cycle")):(o=N(r))?o.call(r,a,f):a(r)):f(e)}catch(t){s&&!u&&s.exit(),f(t)}};r.length>o;)u(r[o++]);t._c=[],t._n=!1,n&&!t._h&&T(t)})}},T=function(t){y.call(a,function(){var n,r,e,i=t._v,o=L(t);if(o&&(n=S(function(){F?E.emit("unhandledRejection",i,t):(r=a.onunhandledrejection)?r({promise:t,reason:i}):(e=a.console)&&e.error&&e.error("Unhandled promise rejection",i)}),t._h=F||L(t)?2:1),t._a=void 0,o&&n.e)throw n.v})},L=function(t){return 1!==t._h&&0===(t._a||t._c).length},R=function(t){y.call(a,function(){var n;F?E.emit("rejectionHandled",t):(n=a.onrejectionhandled)&&n({promise:t,reason:t._v})})},C=function(t){var n=this;n._d||(n._d=!0,(n=n._w||n)._v=t,n._s=2,n._a||(n._a=n._c.slice()),k(n,!0))},W=function(t){var n,r=this;if(!r._d){r._d=!0,r=r._w||r;try{if(r===t)throw _("Promise can't be resolved itself");(n=N(t))?m(function(){var e={_w:r,_d:!1};try{n.call(t,f(W,e,1),f(C,e,1))}catch(t){C.call(e,t)}}):(r._v=t,r._s=1,k(r,!1))}catch(t){C.call({_w:r,_d:!1},t)}}};j||(P=function(t){p(this,P,"Promise","_h"),v(t),e.call(this);try{t(f(W,this,1),f(C,this,1))}catch(t){C.call(this,t)}},(e=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1}).prototype=r(41)(P.prototype,{then:function(t,n){var r=I(d(this,P));return r.ok="function"!=typeof t||t,r.fail="function"==typeof n&&n,r.domain=F?E.domain:void 0,this._c.push(r),this._a&&this._a.push(r),this._s&&k(this,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),o=function(){var t=new e;this.promise=t,this.resolve=f(W,t,1),this.reject=f(C,t,1)},b.f=I=function(t){return t===P||t===u?new o(t):i(t)}),l(l.G+l.W+l.F*!j,{Promise:P}),r(42)(P,"Promise"),r(38)("Promise"),u=r(18).Promise,l(l.S+l.F*!j,"Promise",{reject:function(t){var n=I(this);return(0,n.reject)(t),n.promise}}),l(l.S+l.F*(c||!j),"Promise",{resolve:function(t){return x(c&&this===u?P:this,t)}}),l(l.S+l.F*!(j&&r(54)(function(t){P.all(t).catch(A)})),"Promise",{all:function(t){var n=this,r=I(n),e=r.resolve,i=r.reject,o=S(function(){var r=[],o=0,u=1;g(t,!1,function(t){var c=o++,a=!1;r.push(void 0),u++,n.resolve(t).then(function(t){a||(a=!0,r[c]=t,--u||e(r))},i)}),--u||e(r)});return o.e&&i(o.v),r.promise},race:function(t){var n=this,r=I(n),e=r.reject,i=S(function(){g(t,!1,function(t){n.resolve(t).then(r.resolve,e)})});return i.e&&e(i.v),r.promise}})},function(t,n,r){"use strict";var e=r(116),i=r(45);r(59)("WeakSet",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(i(this,"WeakSet"),t,!0)}},e,!1,!0)},function(t,n,r){"use strict";var e=r(0),i=r(60),o=r(90),u=r(1),c=r(35),a=r(8),f=r(4),s=r(2).ArrayBuffer,l=r(57),h=o.ArrayBuffer,v=o.DataView,p=i.ABV&&s.isView,g=h.prototype.slice,d=i.VIEW;e(e.G+e.W+e.F*(s!==h),{ArrayBuffer:h}),e(e.S+e.F*!i.CONSTR,"ArrayBuffer",{isView:function(t){return p&&p(t)||f(t)&&d in t}}),e(e.P+e.U+e.F*r(3)(function(){return!new h(2).slice(1,void 0).byteLength}),"ArrayBuffer",{slice:function(t,n){if(void 0!==g&&void 0===n)return g.call(u(this),t);for(var r=u(this).byteLength,e=c(t,r),i=c(void 0===n?r:n,r),o=new(l(this,h))(a(i-e)),f=new v(this),s=new v(o),p=0;e<i;)s.setUint8(p++,f.getUint8(e++));return o}}),r(38)("ArrayBuffer")},function(t,n,r){var e=r(0);e(e.G+e.W+e.F*!r(60).ABV,{DataView:r(90).DataView})},function(t,n,r){r(27)("Int8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}},!0)},function(t,n,r){r(27)("Int16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Int32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Float32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Float64",8,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){var e=r(0),i=r(10),o=r(1),u=(r(2).Reflect||{}).apply,c=Function.apply;e(e.S+e.F*!r(3)(function(){u(function(){})}),"Reflect",{apply:function(t,n,r){var e=i(t),a=o(r);return u?u(e,n,a):c.call(e,n,a)}})},function(t,n,r){var e=r(0),i=r(36),o=r(10),u=r(1),c=r(4),a=r(3),f=r(97),s=(r(2).Reflect||{}).construct,l=a(function(){function t(){}return!(s(function(){},[],t)instanceof t)}),h=!a(function(){s(function(){})});e(e.S+e.F*(l||h),"Reflect",{construct:function(t,n){o(t),u(n);var r=arguments.length<3?t:o(arguments[2]);if(h&&!l)return s(t,n,r);if(t==r){switch(n.length){case 0:return new t;case 1:return new t(n[0]);case 2:return new t(n[0],n[1]);case 3:return new t(n[0],n[1],n[2]);case 4:return new t(n[0],n[1],n[2],n[3])}var e=[null];return e.push.apply(e,n),new(f.apply(t,e))}var a=r.prototype,v=i(c(a)?a:Object.prototype),p=Function.apply.call(t,v,n);return c(p)?p:v}})},function(t,n,r){var e=r(7),i=r(0),o=r(1),u=r(22);i(i.S+i.F*r(3)(function(){Reflect.defineProperty(e.f({},1,{value:1}),1,{value:2})}),"Reflect",{defineProperty:function(t,n,r){o(t),n=u(n,!0),o(r);try{return e.f(t,n,r),!0}catch(t){return!1}}})},function(t,n,r){var e=r(0),i=r(16).f,o=r(1);e(e.S,"Reflect",{deleteProperty:function(t,n){var r=i(o(t),n);return!(r&&!r.configurable)&&delete t[n]}})},function(t,n,r){"use strict";var e=r(0),i=r(1),o=function(t){this._t=i(t),this._i=0;var n,r=this._k=[];for(n in t)r.push(n)};r(78)(o,"Object",function(){var t,n=this._k;do{if(this._i>=n.length)return{value:void 0,done:!0}}while(!((t=n[this._i++])in this._t));return{value:t,done:!1}}),e(e.S,"Reflect",{enumerate:function(t){return new o(t)}})},function(t,n,r){var e=r(16),i=r(17),o=r(14),u=r(0),c=r(4),a=r(1);u(u.S,"Reflect",{get:function t(n,r){var u,f,s=arguments.length<3?n:arguments[2];return a(n)===s?n[r]:(u=e.f(n,r))?o(u,"value")?u.value:void 0!==u.get?u.get.call(s):void 0:c(f=i(n))?t(f,r,s):void 0}})},function(t,n,r){var e=r(16),i=r(0),o=r(1);i(i.S,"Reflect",{getOwnPropertyDescriptor:function(t,n){return e.f(o(t),n)}})},function(t,n,r){var e=r(0),i=r(17),o=r(1);e(e.S,"Reflect",{getPrototypeOf:function(t){return i(o(t))}})},function(t,n,r){var e=r(0);e(e.S,"Reflect",{has:function(t,n){return n in t}})},function(t,n,r){var e=r(0),i=r(1),o=Object.isExtensible;e(e.S,"Reflect",{isExtensible:function(t){return i(t),!o||o(t)}})},function(t,n,r){var e=r(0);e(e.S,"Reflect",{ownKeys:r(118)})},function(t,n,r){var e=r(0),i=r(1),o=Object.preventExtensions;e(e.S,"Reflect",{preventExtensions:function(t){i(t);try{return o&&o(t),!0}catch(t){return!1}}})},function(t,n,r){var e=r(7),i=r(16),o=r(17),u=r(14),c=r(0),a=r(32),f=r(1),s=r(4);c(c.S,"Reflect",{set:function t(n,r,c){var l,h,v=arguments.length<4?n:arguments[3],p=i.f(f(n),r);if(!p){if(s(h=o(n)))return t(h,r,c,v);p=a(0)}if(u(p,"value")){if(!1===p.writable||!s(v))return!1;if(l=i.f(v,r)){if(l.get||l.set||!1===l.writable)return!1;l.value=c,e.f(v,r,l)}else e.f(v,r,a(0,c));return!0}return void 0!==p.set&&(p.set.call(v,c),!0)}})},function(t,n,r){var e=r(0),i=r(70);i&&e(e.S,"Reflect",{setPrototypeOf:function(t,n){i.check(t,n);try{return i.set(t,n),!0}catch(t){return!1}}})},function(t,n,r){"use strict";var e=r(0),i=r(50)(!0);e(e.P,"Array",{includes:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(31)("includes")},function(t,n,r){"use strict";var e=r(0),i=r(119),o=r(9),u=r(8),c=r(10),a=r(84);e(e.P,"Array",{flatMap:function(t){var n,r,e=o(this);return c(t),n=u(e.length),r=a(e,0),i(r,e,e,n,0,1,t,arguments[1]),r}}),r(31)("flatMap")},function(t,n,r){"use strict";var e=r(0),i=r(119),o=r(9),u=r(8),c=r(24),a=r(84);e(e.P,"Array",{flatten:function(){var t=arguments[0],n=o(this),r=u(n.length),e=a(n,0);return i(e,n,n,r,0,void 0===t?1:c(t)),e}}),r(31)("flatten")},function(t,n,r){"use strict";var e=r(0),i=r(76)(!0);e(e.P,"String",{at:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(0),i=r(120),o=r(58);e(e.P+e.F*/Version\/10\.\d+(\.\d+)? Safari\//.test(o),"String",{padStart:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!0)}})},function(t,n,r){"use strict";var e=r(0),i=r(120),o=r(58);e(e.P+e.F*/Version\/10\.\d+(\.\d+)? Safari\//.test(o),"String",{padEnd:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!1)}})},function(t,n,r){"use strict";r(43)("trimLeft",function(t){return function(){return t(this,1)}},"trimStart")},function(t,n,r){"use strict";r(43)("trimRight",function(t){return function(){return t(this,2)}},"trimEnd")},function(t,n,r){"use strict";var e=r(0),i=r(23),o=r(8),u=r(53),c=r(55),a=RegExp.prototype,f=function(t,n){this._r=t,this._s=n};r(78)(f,"RegExp String",function(){var t=this._r.exec(this._s);return{value:t,done:null===t}}),e(e.P,"String",{matchAll:function(t){if(i(this),!u(t))throw TypeError(t+" is not a regexp!");var n=String(this),r="flags"in a?String(t.flags):c.call(t),e=new RegExp(t.source,~r.indexOf("g")?r:"g"+r);return e.lastIndex=o(t.lastIndex),new f(e,n)}})},function(t,n,r){r(66)("asyncIterator")},function(t,n,r){r(66)("observable")},function(t,n,r){var e=r(0),i=r(118),o=r(15),u=r(16),c=r(82);e(e.S,"Object",{getOwnPropertyDescriptors:function(t){for(var n,r,e=o(t),a=u.f,f=i(e),s={},l=0;f.length>l;)void 0!==(r=a(e,n=f[l++]))&&c(s,n,r);return s}})},function(t,n,r){var e=r(0),i=r(121)(!1);e(e.S,"Object",{values:function(t){return i(t)}})},function(t,n,r){var e=r(0),i=r(121)(!0);e(e.S,"Object",{entries:function(t){return i(t)}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(10),u=r(7);r(6)&&e(e.P+r(61),"Object",{__defineGetter__:function(t,n){u.f(i(this),t,{get:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(10),u=r(7);r(6)&&e(e.P+r(61),"Object",{__defineSetter__:function(t,n){u.f(i(this),t,{set:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(22),u=r(17),c=r(16).f;r(6)&&e(e.P+r(61),"Object",{__lookupGetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.get}while(r=u(r))}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(22),u=r(17),c=r(16).f;r(6)&&e(e.P+r(61),"Object",{__lookupSetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.set}while(r=u(r))}})},function(t,n,r){var e=r(0);e(e.P+e.R,"Map",{toJSON:r(122)("Map")})},function(t,n,r){var e=r(0);e(e.P+e.R,"Set",{toJSON:r(122)("Set")})},function(t,n,r){r(62)("Map")},function(t,n,r){r(62)("Set")},function(t,n,r){r(62)("WeakMap")},function(t,n,r){r(62)("WeakSet")},function(t,n,r){r(63)("Map")},function(t,n,r){r(63)("Set")},function(t,n,r){r(63)("WeakMap")},function(t,n,r){r(63)("WeakSet")},function(t,n,r){var e=r(0);e(e.G,{global:r(2)})},function(t,n,r){var e=r(0);e(e.S,"System",{global:r(2)})},function(t,n,r){var e=r(0),i=r(20);e(e.S,"Error",{isError:function(t){return"Error"===i(t)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{clamp:function(t,n,r){return Math.min(r,Math.max(n,t))}})},function(t,n,r){var e=r(0);e(e.S,"Math",{DEG_PER_RAD:Math.PI/180})},function(t,n,r){var e=r(0),i=180/Math.PI;e(e.S,"Math",{degrees:function(t){return t*i}})},function(t,n,r){var e=r(0),i=r(124),o=r(104);e(e.S,"Math",{fscale:function(t,n,r,e,u){return o(i(t,n,r,e,u))}})},function(t,n,r){var e=r(0);e(e.S,"Math",{iaddh:function(t,n,r,e){var i=t>>>0,o=r>>>0;return(n>>>0)+(e>>>0)+((i&o|(i|o)&~(i+o>>>0))>>>31)|0}})},function(t,n,r){var e=r(0);e(e.S,"Math",{isubh:function(t,n,r,e){var i=t>>>0,o=r>>>0;return(n>>>0)-(e>>>0)-((~i&o|~(i^o)&i-o>>>0)>>>31)|0}})},function(t,n,r){var e=r(0);e(e.S,"Math",{imulh:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e,u=r>>16,c=e>>16,a=(u*o>>>0)+(i*o>>>16);return u*c+(a>>16)+((i*c>>>0)+(65535&a)>>16)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{RAD_PER_DEG:180/Math.PI})},function(t,n,r){var e=r(0),i=Math.PI/180;e(e.S,"Math",{radians:function(t){return t*i}})},function(t,n,r){var e=r(0);e(e.S,"Math",{scale:r(124)})},function(t,n,r){var e=r(0);e(e.S,"Math",{umulh:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e,u=r>>>16,c=e>>>16,a=(u*o>>>0)+(i*o>>>16);return u*c+(a>>>16)+((i*c>>>0)+(65535&a)>>>16)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{signbit:function(t){return(t=+t)!=t?t:0==t?1/t==1/0:t>0}})},function(t,n,r){"use strict";var e=r(0),i=r(18),o=r(2),u=r(57),c=r(111);e(e.P+e.R,"Promise",{finally:function(t){var n=u(this,i.Promise||o.Promise),r="function"==typeof t;return this.then(r?function(r){return c(n,t()).then(function(){return r})}:t,r?function(r){return c(n,t()).then(function(){throw r})}:t)}})},function(t,n,r){"use strict";var e=r(0),i=r(89),o=r(110);e(e.S,"Promise",{try:function(t){var n=i.f(this),r=o(t);return(r.e?n.reject:n.resolve)(r.v),n.promise}})},function(t,n,r){var e=r(28),i=r(1),o=e.key,u=e.set;e.exp({defineMetadata:function(t,n,r,e){u(t,n,i(r),o(e))}})},function(t,n,r){var e=r(28),i=r(1),o=e.key,u=e.map,c=e.store;e.exp({deleteMetadata:function(t,n){var r=arguments.length<3?void 0:o(arguments[2]),e=u(i(n),r,!1);if(void 0===e||!e.delete(t))return!1;if(e.size)return!0;var a=c.get(n);return a.delete(r),!!a.size||c.delete(n)}})},function(t,n,r){var e=r(28),i=r(1),o=r(17),u=e.has,c=e.get,a=e.key,f=function(t,n,r){if(u(t,n,r))return c(t,n,r);var e=o(n);return null!==e?f(t,e,r):void 0};e.exp({getMetadata:function(t,n){return f(t,i(n),arguments.length<3?void 0:a(arguments[2]))}})},function(t,n,r){var e=r(114),i=r(123),o=r(28),u=r(1),c=r(17),a=o.keys,f=o.key,s=function(t,n){var r=a(t,n),o=c(t);if(null===o)return r;var u=s(o,n);return u.length?r.length?i(new e(r.concat(u))):u:r};o.exp({getMetadataKeys:function(t){return s(u(t),arguments.length<2?void 0:f(arguments[1]))}})},function(t,n,r){var e=r(28),i=r(1),o=e.get,u=e.key;e.exp({getOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(28),i=r(1),o=e.keys,u=e.key;e.exp({getOwnMetadataKeys:function(t){return o(i(t),arguments.length<2?void 0:u(arguments[1]))}})},function(t,n,r){var e=r(28),i=r(1),o=r(17),u=e.has,c=e.key,a=function(t,n,r){if(u(t,n,r))return!0;var e=o(n);return null!==e&&a(t,e,r)};e.exp({hasMetadata:function(t,n){return a(t,i(n),arguments.length<3?void 0:c(arguments[2]))}})},function(t,n,r){var e=r(28),i=r(1),o=e.has,u=e.key;e.exp({hasOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(28),i=r(1),o=r(10),u=e.key,c=e.set;e.exp({metadata:function(t,n){return function(r,e){c(t,n,(void 0!==e?i:o)(r),u(e))}}})},function(t,n,r){var e=r(0),i=r(88)(),o=r(2).process,u="process"==r(20)(o);e(e.G,{asap:function(t){var n=u&&o.domain;i(n?n.bind(t):t)}})},function(t,n,r){"use strict";var e=r(0),i=r(2),o=r(18),u=r(88)(),c=r(5)("observable"),a=r(10),f=r(1),s=r(39),l=r(41),h=r(11),v=r(40),p=v.RETURN,g=function(t){return null==t?void 0:a(t)},d=function(t){var n=t._c;n&&(t._c=void 0,n())},y=function(t){return void 0===t._o},m=function(t){y(t)||(t._o=void 0,d(t))},b=function(t,n){f(t),this._c=void 0,this._o=t,t=new S(this);try{var r=n(t),e=r;null!=r&&("function"==typeof r.unsubscribe?r=function(){e.unsubscribe()}:a(r),this._c=r)}catch(n){return void t.error(n)}y(this)&&d(this)};b.prototype=l({},{unsubscribe:function(){m(this)}});var S=function(t){this._s=t};S.prototype=l({},{next:function(t){var n=this._s;if(!y(n)){var r=n._o;try{var e=g(r.next);if(e)return e.call(r,t)}catch(t){try{m(n)}finally{throw t}}}},error:function(t){var n=this._s;if(y(n))throw t;var r=n._o;n._o=void 0;try{var e=g(r.error);if(!e)throw t;t=e.call(r,t)}catch(t){try{d(n)}finally{throw t}}return d(n),t},complete:function(t){var n=this._s;if(!y(n)){var r=n._o;n._o=void 0;try{var e=g(r.complete);t=e?e.call(r,t):void 0}catch(t){try{d(n)}finally{throw t}}return d(n),t}}});var w=function(t){s(this,w,"Observable","_f")._f=a(t)};l(w.prototype,{subscribe:function(t){return new b(t,this._f)},forEach:function(t){var n=this;return new(o.Promise||i.Promise)(function(r,e){a(t);var i=n.subscribe({next:function(n){try{return t(n)}catch(t){e(t),i.unsubscribe()}},error:e,complete:r})})}}),l(w,{from:function(t){var n="function"==typeof this?this:w,r=g(f(t)[c]);if(r){var e=f(r.call(t));return e.constructor===n?e:new n(function(t){return e.subscribe(t)})}return new n(function(n){var r=!1;return u(function(){if(!r){try{if(v(t,!1,function(t){if(n.next(t),r)return p})===p)return}catch(t){if(r)throw t;return void n.error(t)}n.complete()}}),function(){r=!0}})},of:function(){for(var t=0,n=arguments.length,r=new Array(n);t<n;)r[t]=arguments[t++];return new("function"==typeof this?this:w)(function(t){var n=!1;return u(function(){if(!n){for(var e=0;e<r.length;++e)if(t.next(r[e]),n)return;t.complete()}}),function(){n=!0}})}}),h(w.prototype,c,function(){return this}),e(e.G,{Observable:w}),r(38)("Observable")},function(t,n,r){var e=r(2),i=r(0),o=r(58),u=[].slice,c=/MSIE .\./.test(o),a=function(t){return function(n,r){var e=arguments.length>2,i=!!e&&u.call(arguments,2);return t(e?function(){("function"==typeof n?n:Function(n)).apply(this,i)}:n,r)}};i(i.G+i.B+i.F*c,{setTimeout:a(e.setTimeout),setInterval:a(e.setInterval)})},function(t,n,r){var e=r(0),i=r(87);e(e.G+e.B,{setImmediate:i.set,clearImmediate:i.clear})},function(t,n,r){for(var e=r(86),i=r(34),o=r(12),u=r(2),c=r(11),a=r(44),f=r(5),s=f("iterator"),l=f("toStringTag"),h=a.Array,v={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},p=i(v),g=0;g<p.length;g++){var d,y=p[g],m=v[y],b=u[y],S=b&&b.prototype;if(S&&(S[s]||c(S,s,h),S[l]||c(S,l,y),a[y]=h,m))for(d in e)S[d]||o(S,d,e[d],!0)}},function(t,n,r){(function(n){!function(n){"use strict";var r,e=Object.prototype,i=e.hasOwnProperty,o="function"==typeof Symbol?Symbol:{},u=o.iterator||"@@iterator",c=o.asyncIterator||"@@asyncIterator",a=o.toStringTag||"@@toStringTag",f="object"==typeof t,s=n.regeneratorRuntime;if(s)f&&(t.exports=s);else{(s=n.regeneratorRuntime=f?t.exports:{}).wrap=S;var l="suspendedStart",h="suspendedYield",v="executing",p="completed",g={},d={};d[u]=function(){return this};var y=Object.getPrototypeOf,m=y&&y(y(j([])));m&&m!==e&&i.call(m,u)&&(d=m);var b=E.prototype=x.prototype=Object.create(d);_.prototype=b.constructor=E,E.constructor=_,E[a]=_.displayName="GeneratorFunction",s.isGeneratorFunction=function(t){var n="function"==typeof t&&t.constructor;return!!n&&(n===_||"GeneratorFunction"===(n.displayName||n.name))},s.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,E):(t.__proto__=E,a in t||(t[a]="GeneratorFunction")),t.prototype=Object.create(b),t},s.awrap=function(t){return{__await:t}},O(M.prototype),M.prototype[c]=function(){return this},s.AsyncIterator=M,s.async=function(t,n,r,e){var i=new M(S(t,n,r,e));return s.isGeneratorFunction(n)?i:i.next().then(function(t){return t.done?t.value:i.next()})},O(b),b[a]="Generator",b[u]=function(){return this},b.toString=function(){return"[object Generator]"},s.keys=function(t){var n=[];for(var r in t)n.push(r);return n.reverse(),function r(){for(;n.length;){var e=n.pop();if(e in t)return r.value=e,r.done=!1,r}return r.done=!0,r}},s.values=j,I.prototype={constructor:I,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=r,this.done=!1,this.delegate=null,this.method="next",this.arg=r,this.tryEntries.forEach(A),!t)for(var n in this)"t"===n.charAt(0)&&i.call(this,n)&&!isNaN(+n.slice(1))&&(this[n]=r)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(t){if(this.done)throw t;var n=this;function e(e,i){return c.type="throw",c.arg=t,n.next=e,i&&(n.method="next",n.arg=r),!!i}for(var o=this.tryEntries.length-1;o>=0;--o){var u=this.tryEntries[o],c=u.completion;if("root"===u.tryLoc)return e("end");if(u.tryLoc<=this.prev){var a=i.call(u,"catchLoc"),f=i.call(u,"finallyLoc");if(a&&f){if(this.prev<u.catchLoc)return e(u.catchLoc,!0);if(this.prev<u.finallyLoc)return e(u.finallyLoc)}else if(a){if(this.prev<u.catchLoc)return e(u.catchLoc,!0)}else{if(!f)throw new Error("try statement without catch or finally");if(this.prev<u.finallyLoc)return e(u.finallyLoc)}}}},abrupt:function(t,n){for(var r=this.tryEntries.length-1;r>=0;--r){var e=this.tryEntries[r];if(e.tryLoc<=this.prev&&i.call(e,"finallyLoc")&&this.prev<e.finallyLoc){var o=e;break}}o&&("break"===t||"continue"===t)&&o.tryLoc<=n&&n<=o.finallyLoc&&(o=null);var u=o?o.completion:{};return u.type=t,u.arg=n,o?(this.method="next",this.next=o.finallyLoc,g):this.complete(u)},complete:function(t,n){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&n&&(this.next=n),g},finish:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),A(r),g}},catch:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc===t){var e=r.completion;if("throw"===e.type){var i=e.arg;A(r)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(t,n,e){return this.delegate={iterator:j(t),resultName:n,nextLoc:e},"next"===this.method&&(this.arg=r),g}}}function S(t,n,r,e){var i=n&&n.prototype instanceof x?n:x,o=Object.create(i.prototype),u=new I(e||[]);return o._invoke=function(t,n,r){var e=l;return function(i,o){if(e===v)throw new Error("Generator is already running");if(e===p){if("throw"===i)throw o;return N()}for(r.method=i,r.arg=o;;){var u=r.delegate;if(u){var c=P(u,r);if(c){if(c===g)continue;return c}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(e===l)throw e=p,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);e=v;var a=w(t,n,r);if("normal"===a.type){if(e=r.done?p:h,a.arg===g)continue;return{value:a.arg,done:r.done}}"throw"===a.type&&(e=p,r.method="throw",r.arg=a.arg)}}}(t,r,u),o}function w(t,n,r){try{return{type:"normal",arg:t.call(n,r)}}catch(t){return{type:"throw",arg:t}}}function x(){}function _(){}function E(){}function O(t){["next","throw","return"].forEach(function(n){t[n]=function(t){return this._invoke(n,t)}})}function M(t){function r(n,e,o,u){var c=w(t[n],t,e);if("throw"!==c.type){var a=c.arg,f=a.value;return f&&"object"==typeof f&&i.call(f,"__await")?Promise.resolve(f.__await).then(function(t){r("next",t,o,u)},function(t){r("throw",t,o,u)}):Promise.resolve(f).then(function(t){a.value=t,o(a)},u)}u(c.arg)}var e;"object"==typeof n.process&&n.process.domain&&(r=n.process.domain.bind(r)),this._invoke=function(t,n){function i(){return new Promise(function(e,i){r(t,n,e,i)})}return e=e?e.then(i,i):i()}}function P(t,n){var e=t.iterator[n.method];if(e===r){if(n.delegate=null,"throw"===n.method){if(t.iterator.return&&(n.method="return",n.arg=r,P(t,n),"throw"===n.method))return g;n.method="throw",n.arg=new TypeError("The iterator does not provide a 'throw' method")}return g}var i=w(e,t.iterator,n.arg);if("throw"===i.type)return n.method="throw",n.arg=i.arg,n.delegate=null,g;var o=i.arg;return o?o.done?(n[t.resultName]=o.value,n.next=t.nextLoc,"return"!==n.method&&(n.method="next",n.arg=r),n.delegate=null,g):o:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,g)}function F(t){var n={tryLoc:t[0]};1 in t&&(n.catchLoc=t[1]),2 in t&&(n.finallyLoc=t[2],n.afterLoc=t[3]),this.tryEntries.push(n)}function A(t){var n=t.completion||{};n.type="normal",delete n.arg,t.completion=n}function I(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(F,this),this.reset(!0)}function j(t){if(t){var n=t[u];if(n)return n.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var e=-1,o=function n(){for(;++e<t.length;)if(i.call(t,e))return n.value=t[e],n.done=!1,n;return n.value=r,n.done=!0,n};return o.next=o}}return{next:N}}function N(){return{value:r,done:!0}}}("object"==typeof n?n:"object"==typeof window?window:"object"==typeof self?self:this)}).call(this,r(64))},function(t,n,r){r(329),t.exports=r(18).RegExp.escape},function(t,n,r){var e=r(0),i=r(330)(/[\\^$*+?.()|[\]{}]/g,"\\$&");e(e.S,"RegExp",{escape:function(t){return i(t)}})},function(t,n){t.exports=function(t,n){var r=n===Object(n)?function(t){return n[t]}:n;return function(n){return String(n).replace(t,r)}}},function(t,n,r){"use strict";r.r(n);var e=r(125),i=r.n(e),o=r(126),u=r.n(o);const c=r(332);n.default=function(t,n,r){var e,o=null,a=t.properties,f=c,s="nostate.nosalt",l=d()+"/"+u.a,h=d()+"/"+i.a,v=function(t){try{return/^(\S+?:\/\/[a-z0-9-.]+(:[0-9]+)?)/gi.exec(t)[0]+"/"}catch(t){return null}}(a.uaaLocation),p={},g=0;function d(){return r.location.href.substring(0,r.location.href.lastIndexOf("/"))}function y(t){var n=t.substring(1).split("&"),r="";return n.forEach(function(t){var n=t.split("=");"session_state"!==n[0]||(r=n[1])}),r}function m(t,n){if(t)e=!0,function(t,n){localStorage&&(localStorage.setItem(_(),JSON.stringify(t)),localStorage.setItem(E(),n))}(t,n),a.onIdentityChange(t);else{if(!1===e)return void localStorage.setItem(E(),n);e=!1,O(),localStorage.setItem(E(),n),a.onLogout()}}function b(){if(!o){var n=t.opFrame.contentWindow,r=a.clientId+" "+x();n.postMessage(r,v)}}function S(t,n,r){return a.uaaLocation+"/oauth/authorize?response_type=token&scope="+encodeURIComponent(t)+"&client_id="+a.clientId+"&prompt=none&redirect_uri="+encodeURIComponent(n+"?"+r)}function w(t,n){var r=t.split("&");for(var e in r)if(r[e].startsWith(n+"="))return r[e].substring(n.length+1);return null}function x(){var t=localStorage.getItem(E());return t||s}function _(){return a.storageKey+"-claims"}function E(){return a.storageKey+"-session_state"}function O(){localStorage&&(localStorage.removeItem(_()),localStorage.removeItem(E()))}function M(){return f}return{fetchAccessToken:function(t,n){var r=this.opFrame=document.createElement("iframe");p[g]={callback:n,frame:r},r.setAttribute("src",S(t,h,g)),document.body.appendChild(r),g++},afterAccess:function(t){var n,r=p[t],e=w(r.frame.contentWindow.location.hash.substring(1),"error");null==e?n=w(r.frame.contentWindow.location.hash.substring(1),"access_token"):e=w(r.frame.contentWindow.location.hash.substring(1),"error"),r.callback(n,e),document.body.removeChild(r.frame),delete p[t]},afterAuthorize:function(){clearTimeout(o);var r,e,i=n.location.hash;i&&i.length>0&&(r=function(n,r){var e=w(n,r);return e?t.decodeJwt(e):null}(i.substring(1),"id_token"),e=y(i)),m(r,e),o=null},receiveMessage:function(t){var r=t.data;"changed"===r?(O(),o=setTimeout(function(){n.location="about:blank",m(null,s),o=null},a.authTimeout),n.location=a.uaaLocation+"/oauth/authorize?response_type=id_token&client_id="+a.clientId+"&prompt=none&redirect_uri="+encodeURIComponent(l)):"unchanged"===r?void 0===e&&m(function(){if(!localStorage)return null;try{return JSON.parse(localStorage.getItem(_()))}catch(t){return O(),null}}(),x()):m(null,s)},startCheckingSession:function(){b(),setInterval(b,a.checkInterval)},extractSessionState:y,announceIdentity:m,getAccessTokenCallbackCount:function(){return Object.keys(p).length},getUaaValidator:M,setUaaValidator:function(t){f=t},checkClientConfig:function(){var t=S("openid",h,g);M().checkClientConfiguration(t,a.clientId)}}}},function(t,n,r){var e=r(333);function i(t,n){var r=new XMLHttpRequest;r.open("GET",t),r.setRequestHeader("Accept","application/json"),r.send(),r.onreadystatechange=function(){r.readyState==XMLHttpRequest.DONE&&n(r)}}t.exports={isValidUAA:function(t){i(t+"/info",function(n){if(200!==n.status)throw t+" does not appear to be a running UAA instance or may not have a trusted SSL certificate";var r=JSON.parse(n.response).app.version;if(!e.isGreaterThanOrEqualTo(r,"4.10.0"))throw"The UAA instance running at "+t+" has version "+r+". This uaa-singular library requires UAA version 4.10.0 or higher."})},checkClientConfiguration:function(t,n){i(t,function(r){if(400===r.status){var e=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/**";throw"Error while calling /oauth/authorize. Is the UAA client "+n+" configured to allow redirects to "+e+" ? Visit "+t+" in the browser to see the UAA's error messages."}})}}},function(t,n){t.exports={getMajor:function(t){return parseInt(t.split(".")[0])},getMinor:function(t){return parseInt(t.split(".")[1])},getPatch:function(t){return parseInt(t.split("-")[0].split(".")[2])},isGreaterThanOrEqualTo:function(t,n){var r=this.getMajor(t),e=this.getMinor(t),i=this.getPatch(t),o=this.getMajor(n),u=this.getMinor(n),c=this.getPatch(n);return r>o||(r===o&&e>u||(r===o&&e===u&&i>c||r===o&&e===u&&i===c))}}}]).default; \ No newline at end of file
diff --git a/deps/rabbitmq_management/priv/www/js/singular/singular.js b/deps/rabbitmq_management/priv/www/js/singular/singular.js
new file mode 100644
index 0000000000..0f9ef22059
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/singular/singular.js
@@ -0,0 +1 @@
+var Singular=function(t){var n={};function r(e){if(n[e])return n[e].exports;var i=n[e]={i:e,l:!1,exports:{}};return t[e].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=n,r.d=function(t,n,e){r.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:e})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,n){if(1&n&&(t=r(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var e=Object.create(null);if(r.r(e),Object.defineProperty(e,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var i in t)r.d(e,i,function(n){return t[n]}.bind(null,i));return e},r.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(n,"a",n),n},r.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},r.p="",r(r.s=127)}([function(t,n,r){var e=r(2),i=r(18),o=r(11),u=r(12),c=r(19),a=function(t,n,r){var f,s,l,h,v=t&a.F,p=t&a.G,d=t&a.S,g=t&a.P,y=t&a.B,m=p?e:d?e[n]||(e[n]={}):(e[n]||{}).prototype,b=p?i:i[n]||(i[n]={}),w=b.prototype||(b.prototype={});for(f in p&&(r=n),r)l=((s=!v&&m&&void 0!==m[f])?m:r)[f],h=y&&s?c(l,e):g&&"function"==typeof l?c(Function.call,l):l,m&&u(m,f,l,t&a.U),b[f]!=l&&o(b,f,h),g&&w[f]!=l&&(w[f]=l)};e.core=i,a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},function(t,n,r){var e=r(4);t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n,r){var e=r(49)("wks"),i=r(33),o=r(2).Symbol,u="function"==typeof o;(t.exports=function(t){return e[t]||(e[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=e},function(t,n,r){t.exports=!r(3)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(1),i=r(91),o=r(22),u=Object.defineProperty;n.f=r(6)?Object.defineProperty:function(t,n,r){if(e(t),n=o(n,!0),e(r),i)try{return u(t,n,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},function(t,n,r){var e=r(24),i=Math.min;t.exports=function(t){return t>0?i(e(t),9007199254740991):0}},function(t,n,r){var e=r(23);t.exports=function(t){return Object(e(t))}},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n,r){var e=r(7),i=r(32);t.exports=r(6)?function(t,n,r){return e.f(t,n,i(1,r))}:function(t,n,r){return t[n]=r,t}},function(t,n,r){var e=r(2),i=r(11),o=r(14),u=r(33)("src"),c=Function.toString,a=(""+c).split("toString");r(18).inspectSource=function(t){return c.call(t)},(t.exports=function(t,n,r,c){var f="function"==typeof r;f&&(o(r,"name")||i(r,"name",n)),t[n]!==r&&(f&&(o(r,u)||i(r,u,t[n]?""+t[n]:a.join(String(n)))),t===e?t[n]=r:c?t[n]?t[n]=r:i(t,n,r):(delete t[n],i(t,n,r)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[u]||c.call(this)})},function(t,n,r){var e=r(0),i=r(3),o=r(23),u=/"/g,c=function(t,n,r,e){var i=String(o(t)),c="<"+n;return""!==r&&(c+=" "+r+'="'+String(e).replace(u,"&quot;")+'"'),c+">"+i+"</"+n+">"};t.exports=function(t,n){var r={};r[t]=n(c),e(e.P+e.F*i(function(){var n=""[t]('"');return n!==n.toLowerCase()||n.split('"').length>3}),"String",r)}},function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},function(t,n,r){var e=r(46),i=r(23);t.exports=function(t){return e(i(t))}},function(t,n,r){var e=r(47),i=r(32),o=r(15),u=r(22),c=r(14),a=r(91),f=Object.getOwnPropertyDescriptor;n.f=r(6)?f:function(t,n){if(t=o(t),n=u(n,!0),a)try{return f(t,n)}catch(t){}if(c(t,n))return i(!e.f.call(t,n),t[n])}},function(t,n,r){var e=r(14),i=r(9),o=r(67)("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),e(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},function(t,n){var r=t.exports={version:"2.5.6"};"number"==typeof __e&&(__e=r)},function(t,n,r){var e=r(10);t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,i){return t.call(n,r,e,i)}}return function(){return t.apply(n,arguments)}}},function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},function(t,n,r){"use strict";var e=r(3);t.exports=function(t,n){return!!t&&e(function(){n?t.call(null,function(){},1):t.call(null)})}},function(t,n,r){var e=r(4);t.exports=function(t,n){if(!e(t))return t;var r,i;if(n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;if("function"==typeof(r=t.valueOf)&&!e(i=r.call(t)))return i;if(!n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},function(t,n,r){var e=r(0),i=r(18),o=r(3);t.exports=function(t,n){var r=(i.Object||{})[t]||Object[t],u={};u[t]=n(r),e(e.S+e.F*o(function(){r(1)}),"Object",u)}},function(t,n,r){var e=r(19),i=r(46),o=r(9),u=r(8),c=r(84);t.exports=function(t,n){var r=1==t,a=2==t,f=3==t,s=4==t,l=6==t,h=5==t||l,v=n||c;return function(n,c,p){for(var d,g,y=o(n),m=i(y),b=e(c,p,3),w=u(m.length),S=0,x=r?v(n,w):a?v(n,0):void 0;w>S;S++)if((h||S in m)&&(g=b(d=m[S],S,y),t))if(r)x[S]=g;else if(g)switch(t){case 3:return!0;case 5:return d;case 6:return S;case 2:x.push(d)}else if(s)return!1;return l?-1:f||s?s:x}}},function(t,n,r){"use strict";if(r(6)){var e=r(30),i=r(2),o=r(3),u=r(0),c=r(60),a=r(90),f=r(19),s=r(39),l=r(32),h=r(11),v=r(41),p=r(24),d=r(8),g=r(117),y=r(35),m=r(22),b=r(14),w=r(48),S=r(4),x=r(9),_=r(81),E=r(36),O=r(17),M=r(37).f,P=r(83),F=r(33),A=r(5),j=r(26),I=r(50),N=r(57),L=r(86),T=r(44),k=r(54),R=r(38),C=r(85),D=r(107),W=r(7),U=r(16),G=W.f,V=U.f,B=i.RangeError,z=i.TypeError,q=i.Uint8Array,J=Array.prototype,Y=a.ArrayBuffer,K=a.DataView,H=j(0),X=j(2),$=j(3),Z=j(4),Q=j(5),tt=j(6),nt=I(!0),rt=I(!1),et=L.values,it=L.keys,ot=L.entries,ut=J.lastIndexOf,ct=J.reduce,at=J.reduceRight,ft=J.join,st=J.sort,lt=J.slice,ht=J.toString,vt=J.toLocaleString,pt=A("iterator"),dt=A("toStringTag"),gt=F("typed_constructor"),yt=F("def_constructor"),mt=c.CONSTR,bt=c.TYPED,wt=c.VIEW,St=j(1,function(t,n){return Mt(N(t,t[yt]),n)}),xt=o(function(){return 1===new q(new Uint16Array([1]).buffer)[0]}),_t=!!q&&!!q.prototype.set&&o(function(){new q(1).set({})}),Et=function(t,n){var r=p(t);if(r<0||r%n)throw B("Wrong offset!");return r},Ot=function(t){if(S(t)&&bt in t)return t;throw z(t+" is not a typed array!")},Mt=function(t,n){if(!(S(t)&&gt in t))throw z("It is not a typed array constructor!");return new t(n)},Pt=function(t,n){return Ft(N(t,t[yt]),n)},Ft=function(t,n){for(var r=0,e=n.length,i=Mt(t,e);e>r;)i[r]=n[r++];return i},At=function(t,n,r){G(t,n,{get:function(){return this._d[r]}})},jt=function(t){var n,r,e,i,o,u,c=x(t),a=arguments.length,s=a>1?arguments[1]:void 0,l=void 0!==s,h=P(c);if(null!=h&&!_(h)){for(u=h.call(c),e=[],n=0;!(o=u.next()).done;n++)e.push(o.value);c=e}for(l&&a>2&&(s=f(s,arguments[2],2)),n=0,r=d(c.length),i=Mt(this,r);r>n;n++)i[n]=l?s(c[n],n):c[n];return i},It=function(){for(var t=0,n=arguments.length,r=Mt(this,n);n>t;)r[t]=arguments[t++];return r},Nt=!!q&&o(function(){vt.call(new q(1))}),Lt=function(){return vt.apply(Nt?lt.call(Ot(this)):Ot(this),arguments)},Tt={copyWithin:function(t,n){return D.call(Ot(this),t,n,arguments.length>2?arguments[2]:void 0)},every:function(t){return Z(Ot(this),t,arguments.length>1?arguments[1]:void 0)},fill:function(t){return C.apply(Ot(this),arguments)},filter:function(t){return Pt(this,X(Ot(this),t,arguments.length>1?arguments[1]:void 0))},find:function(t){return Q(Ot(this),t,arguments.length>1?arguments[1]:void 0)},findIndex:function(t){return tt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},forEach:function(t){H(Ot(this),t,arguments.length>1?arguments[1]:void 0)},indexOf:function(t){return rt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},includes:function(t){return nt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},join:function(t){return ft.apply(Ot(this),arguments)},lastIndexOf:function(t){return ut.apply(Ot(this),arguments)},map:function(t){return St(Ot(this),t,arguments.length>1?arguments[1]:void 0)},reduce:function(t){return ct.apply(Ot(this),arguments)},reduceRight:function(t){return at.apply(Ot(this),arguments)},reverse:function(){for(var t,n=Ot(this).length,r=Math.floor(n/2),e=0;e<r;)t=this[e],this[e++]=this[--n],this[n]=t;return this},some:function(t){return $(Ot(this),t,arguments.length>1?arguments[1]:void 0)},sort:function(t){return st.call(Ot(this),t)},subarray:function(t,n){var r=Ot(this),e=r.length,i=y(t,e);return new(N(r,r[yt]))(r.buffer,r.byteOffset+i*r.BYTES_PER_ELEMENT,d((void 0===n?e:y(n,e))-i))}},kt=function(t,n){return Pt(this,lt.call(Ot(this),t,n))},Rt=function(t){Ot(this);var n=Et(arguments[1],1),r=this.length,e=x(t),i=d(e.length),o=0;if(i+n>r)throw B("Wrong length!");for(;o<i;)this[n+o]=e[o++]},Ct={entries:function(){return ot.call(Ot(this))},keys:function(){return it.call(Ot(this))},values:function(){return et.call(Ot(this))}},Dt=function(t,n){return S(t)&&t[bt]&&"symbol"!=typeof n&&n in t&&String(+n)==String(n)},Wt=function(t,n){return Dt(t,n=m(n,!0))?l(2,t[n]):V(t,n)},Ut=function(t,n,r){return!(Dt(t,n=m(n,!0))&&S(r)&&b(r,"value"))||b(r,"get")||b(r,"set")||r.configurable||b(r,"writable")&&!r.writable||b(r,"enumerable")&&!r.enumerable?G(t,n,r):(t[n]=r.value,t)};mt||(U.f=Wt,W.f=Ut),u(u.S+u.F*!mt,"Object",{getOwnPropertyDescriptor:Wt,defineProperty:Ut}),o(function(){ht.call({})})&&(ht=vt=function(){return ft.call(this)});var Gt=v({},Tt);v(Gt,Ct),h(Gt,pt,Ct.values),v(Gt,{slice:kt,set:Rt,constructor:function(){},toString:ht,toLocaleString:Lt}),At(Gt,"buffer","b"),At(Gt,"byteOffset","o"),At(Gt,"byteLength","l"),At(Gt,"length","e"),G(Gt,dt,{get:function(){return this[bt]}}),t.exports=function(t,n,r,a){var f=t+((a=!!a)?"Clamped":"")+"Array",l="get"+t,v="set"+t,p=i[f],y=p||{},m=p&&O(p),b=!p||!c.ABV,x={},_=p&&p.prototype,P=function(t,r){G(t,r,{get:function(){return function(t,r){var e=t._d;return e.v[l](r*n+e.o,xt)}(this,r)},set:function(t){return function(t,r,e){var i=t._d;a&&(e=(e=Math.round(e))<0?0:e>255?255:255&e),i.v[v](r*n+i.o,e,xt)}(this,r,t)},enumerable:!0})};b?(p=r(function(t,r,e,i){s(t,p,f,"_d");var o,u,c,a,l=0,v=0;if(S(r)){if(!(r instanceof Y||"ArrayBuffer"==(a=w(r))||"SharedArrayBuffer"==a))return bt in r?Ft(p,r):jt.call(p,r);o=r,v=Et(e,n);var y=r.byteLength;if(void 0===i){if(y%n)throw B("Wrong length!");if((u=y-v)<0)throw B("Wrong length!")}else if((u=d(i)*n)+v>y)throw B("Wrong length!");c=u/n}else c=g(r),o=new Y(u=c*n);for(h(t,"_d",{b:o,o:v,l:u,e:c,v:new K(o)});l<c;)P(t,l++)}),_=p.prototype=E(Gt),h(_,"constructor",p)):o(function(){p(1)})&&o(function(){new p(-1)})&&k(function(t){new p,new p(null),new p(1.5),new p(t)},!0)||(p=r(function(t,r,e,i){var o;return s(t,p,f),S(r)?r instanceof Y||"ArrayBuffer"==(o=w(r))||"SharedArrayBuffer"==o?void 0!==i?new y(r,Et(e,n),i):void 0!==e?new y(r,Et(e,n)):new y(r):bt in r?Ft(p,r):jt.call(p,r):new y(g(r))}),H(m!==Function.prototype?M(y).concat(M(m)):M(y),function(t){t in p||h(p,t,y[t])}),p.prototype=_,e||(_.constructor=p));var F=_[pt],A=!!F&&("values"==F.name||null==F.name),j=Ct.values;h(p,gt,!0),h(_,bt,f),h(_,wt,!0),h(_,yt,p),(a?new p(1)[dt]==f:dt in _)||G(_,dt,{get:function(){return f}}),x[f]=p,u(u.G+u.W+u.F*(p!=y),x),u(u.S,f,{BYTES_PER_ELEMENT:n}),u(u.S+u.F*o(function(){y.of.call(p,1)}),f,{from:jt,of:It}),"BYTES_PER_ELEMENT"in _||h(_,"BYTES_PER_ELEMENT",n),u(u.P,f,Tt),R(f),u(u.P+u.F*_t,f,{set:Rt}),u(u.P+u.F*!A,f,Ct),e||_.toString==ht||(_.toString=ht),u(u.P+u.F*o(function(){new p(1).slice()}),f,{slice:kt}),u(u.P+u.F*(o(function(){return[1,2].toLocaleString()!=new p([1,2]).toLocaleString()})||!o(function(){_.toLocaleString.call([1,2])})),f,{toLocaleString:Lt}),T[f]=A?F:j,e||A||h(_,pt,j)}}else t.exports=function(){}},function(t,n,r){var e=r(112),i=r(0),o=r(49)("metadata"),u=o.store||(o.store=new(r(115))),c=function(t,n,r){var i=u.get(t);if(!i){if(!r)return;u.set(t,i=new e)}var o=i.get(n);if(!o){if(!r)return;i.set(n,o=new e)}return o};t.exports={store:u,map:c,has:function(t,n,r){var e=c(n,r,!1);return void 0!==e&&e.has(t)},get:function(t,n,r){var e=c(n,r,!1);return void 0===e?void 0:e.get(t)},set:function(t,n,r,e){c(r,e,!0).set(t,n)},keys:function(t,n){var r=c(t,n,!1),e=[];return r&&r.forEach(function(t,n){e.push(n)}),e},key:function(t){return void 0===t||"symbol"==typeof t?t:String(t)},exp:function(t){i(i.S,"Reflect",t)}}},function(t,n,r){var e=r(33)("meta"),i=r(4),o=r(14),u=r(7).f,c=0,a=Object.isExtensible||function(){return!0},f=!r(3)(function(){return a(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=t.exports={KEY:e,NEED:!1,fastKey:function(t,n){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,e)){if(!a(t))return"F";if(!n)return"E";s(t)}return t[e].i},getWeak:function(t,n){if(!o(t,e)){if(!a(t))return!0;if(!n)return!1;s(t)}return t[e].w},onFreeze:function(t){return f&&l.NEED&&a(t)&&!o(t,e)&&s(t),t}}},function(t,n){t.exports=!1},function(t,n,r){var e=r(5)("unscopables"),i=Array.prototype;null==i[e]&&r(11)(i,e,{}),t.exports=function(t){i[e][t]=!0}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},function(t,n,r){var e=r(93),i=r(68);t.exports=Object.keys||function(t){return e(t,i)}},function(t,n,r){var e=r(24),i=Math.max,o=Math.min;t.exports=function(t,n){return(t=e(t))<0?i(t+n,0):o(t,n)}},function(t,n,r){var e=r(1),i=r(94),o=r(68),u=r(67)("IE_PROTO"),c=function(){},a=function(){var t,n=r(65)("iframe"),e=o.length;for(n.style.display="none",r(69).appendChild(n),n.src="javascript:",(t=n.contentWindow.document).open(),t.write("<script>document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a.prototype[o[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c.prototype=e(t),r=new c,c.prototype=null,r[u]=t):r=a(),void 0===n?r:i(r,n)}},function(t,n,r){var e=r(93),i=r(68).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,i)}},function(t,n,r){"use strict";var e=r(2),i=r(7),o=r(6),u=r(5)("species");t.exports=function(t){var n=e[t];o&&n&&!n[u]&&i.f(n,u,{configurable:!0,get:function(){return this}})}},function(t,n){t.exports=function(t,n,r,e){if(!(t instanceof n)||void 0!==e&&e in t)throw TypeError(r+": incorrect invocation!");return t}},function(t,n,r){var e=r(19),i=r(105),o=r(81),u=r(1),c=r(8),a=r(83),f={},s={};(n=t.exports=function(t,n,r,l,h){var v,p,d,g,y=h?function(){return t}:a(t),m=e(r,l,n?2:1),b=0;if("function"!=typeof y)throw TypeError(t+" is not iterable!");if(o(y)){for(v=c(t.length);v>b;b++)if((g=n?m(u(p=t[b])[0],p[1]):m(t[b]))===f||g===s)return g}else for(d=y.call(t);!(p=d.next()).done;)if((g=i(d,m,p.value,n))===f||g===s)return g}).BREAK=f,n.RETURN=s},function(t,n,r){var e=r(12);t.exports=function(t,n,r){for(var i in n)e(t,i,n[i],r);return t}},function(t,n,r){var e=r(7).f,i=r(14),o=r(5)("toStringTag");t.exports=function(t,n,r){t&&!i(t=r?t:t.prototype,o)&&e(t,o,{configurable:!0,value:n})}},function(t,n,r){var e=r(0),i=r(23),o=r(3),u=r(71),c="["+u+"]",a=RegExp("^"+c+c+"*"),f=RegExp(c+c+"*$"),s=function(t,n,r){var i={},c=o(function(){return!!u[t]()||"​…"!="​…"[t]()}),a=i[t]=c?n(l):u[t];r&&(i[r]=a),e(e.P+e.F*c,"String",i)},l=s.trim=function(t,n){return t=String(i(t)),1&n&&(t=t.replace(a,"")),2&n&&(t=t.replace(f,"")),t};t.exports=s},function(t,n){t.exports={}},function(t,n,r){var e=r(4);t.exports=function(t,n){if(!e(t)||t._t!==n)throw TypeError("Incompatible receiver, "+n+" required!");return t}},function(t,n,r){var e=r(20);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,r){var e=r(20),i=r(5)("toStringTag"),o="Arguments"==e(function(){return arguments}());t.exports=function(t){var n,r,u;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=function(t,n){try{return t[n]}catch(t){}}(n=Object(t),i))?r:o?e(n):"Object"==(u=e(n))&&"function"==typeof n.callee?"Arguments":u}},function(t,n,r){var e=r(18),i=r(2),o=i["__core-js_shared__"]||(i["__core-js_shared__"]={});(t.exports=function(t,n){return o[t]||(o[t]=void 0!==n?n:{})})("versions",[]).push({version:e.version,mode:r(30)?"pure":"global",copyright:"© 2018 Denis Pushkarev (zloirock.ru)"})},function(t,n,r){var e=r(15),i=r(8),o=r(35);t.exports=function(t){return function(n,r,u){var c,a=e(n),f=i(a.length),s=o(u,f);if(t&&r!=r){for(;f>s;)if((c=a[s++])!=c)return!0}else for(;f>s;s++)if((t||s in a)&&a[s]===r)return t||s||0;return!t&&-1}}},function(t,n){n.f=Object.getOwnPropertySymbols},function(t,n,r){var e=r(20);t.exports=Array.isArray||function(t){return"Array"==e(t)}},function(t,n,r){var e=r(4),i=r(20),o=r(5)("match");t.exports=function(t){var n;return e(t)&&(void 0!==(n=t[o])?!!n:"RegExp"==i(t))}},function(t,n,r){var e=r(5)("iterator"),i=!1;try{var o=[7][e]();o.return=function(){i=!0},Array.from(o,function(){throw 2})}catch(t){}t.exports=function(t,n){if(!n&&!i)return!1;var r=!1;try{var o=[7],u=o[e]();u.next=function(){return{done:r=!0}},o[e]=function(){return u},t(o)}catch(t){}return r}},function(t,n,r){"use strict";var e=r(1);t.exports=function(){var t=e(this),n="";return t.global&&(n+="g"),t.ignoreCase&&(n+="i"),t.multiline&&(n+="m"),t.unicode&&(n+="u"),t.sticky&&(n+="y"),n}},function(t,n,r){"use strict";var e=r(11),i=r(12),o=r(3),u=r(23),c=r(5);t.exports=function(t,n,r){var a=c(t),f=r(u,a,""[t]),s=f[0],l=f[1];o(function(){var n={};return n[a]=function(){return 7},7!=""[t](n)})&&(i(String.prototype,t,s),e(RegExp.prototype,a,2==n?function(t,n){return l.call(t,this,n)}:function(t){return l.call(t,this)}))}},function(t,n,r){var e=r(1),i=r(10),o=r(5)("species");t.exports=function(t,n){var r,u=e(t).constructor;return void 0===u||null==(r=e(u)[o])?n:i(r)}},function(t,n,r){var e=r(2).navigator;t.exports=e&&e.userAgent||""},function(t,n,r){"use strict";var e=r(2),i=r(0),o=r(12),u=r(41),c=r(29),a=r(40),f=r(39),s=r(4),l=r(3),h=r(54),v=r(42),p=r(72);t.exports=function(t,n,r,d,g,y){var m=e[t],b=m,w=g?"set":"add",S=b&&b.prototype,x={},_=function(t){var n=S[t];o(S,t,"delete"==t?function(t){return!(y&&!s(t))&&n.call(this,0===t?0:t)}:"has"==t?function(t){return!(y&&!s(t))&&n.call(this,0===t?0:t)}:"get"==t?function(t){return y&&!s(t)?void 0:n.call(this,0===t?0:t)}:"add"==t?function(t){return n.call(this,0===t?0:t),this}:function(t,r){return n.call(this,0===t?0:t,r),this})};if("function"==typeof b&&(y||S.forEach&&!l(function(){(new b).entries().next()}))){var E=new b,O=E[w](y?{}:-0,1)!=E,M=l(function(){E.has(1)}),P=h(function(t){new b(t)}),F=!y&&l(function(){for(var t=new b,n=5;n--;)t[w](n,n);return!t.has(-0)});P||((b=n(function(n,r){f(n,b,t);var e=p(new m,n,b);return null!=r&&a(r,g,e[w],e),e})).prototype=S,S.constructor=b),(M||F)&&(_("delete"),_("has"),g&&_("get")),(F||O)&&_(w),y&&S.clear&&delete S.clear}else b=d.getConstructor(n,t,g,w),u(b.prototype,r),c.NEED=!0;return v(b,t),x[t]=b,i(i.G+i.W+i.F*(b!=m),x),y||d.setStrong(b,t,g),b}},function(t,n,r){for(var e,i=r(2),o=r(11),u=r(33),c=u("typed_array"),a=u("view"),f=!(!i.ArrayBuffer||!i.DataView),s=f,l=0,h="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");l<9;)(e=i[h[l++]])?(o(e.prototype,c,!0),o(e.prototype,a,!0)):s=!1;t.exports={ABV:f,CONSTR:s,TYPED:c,VIEW:a}},function(t,n,r){"use strict";t.exports=r(30)||!r(3)(function(){var t=Math.random();__defineSetter__.call(null,t,function(){}),delete r(2)[t]})},function(t,n,r){"use strict";var e=r(0);t.exports=function(t){e(e.S,t,{of:function(){for(var t=arguments.length,n=new Array(t);t--;)n[t]=arguments[t];return new this(n)}})}},function(t,n,r){"use strict";var e=r(0),i=r(10),o=r(19),u=r(40);t.exports=function(t){e(e.S,t,{from:function(t){var n,r,e,c,a=arguments[1];return i(this),(n=void 0!==a)&&i(a),null==t?new this:(r=[],n?(e=0,c=o(a,arguments[2],2),u(t,!1,function(t){r.push(c(t,e++))})):u(t,!1,r.push,r),new this(r))}})}},function(t,n){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(t){"object"==typeof window&&(r=window)}t.exports=r},function(t,n,r){var e=r(4),i=r(2).document,o=e(i)&&e(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n,r){var e=r(2),i=r(18),o=r(30),u=r(92),c=r(7).f;t.exports=function(t){var n=i.Symbol||(i.Symbol=o?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},function(t,n,r){var e=r(49)("keys"),i=r(33);t.exports=function(t){return e[t]||(e[t]=i(t))}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n,r){var e=r(2).document;t.exports=e&&e.documentElement},function(t,n,r){var e=r(4),i=r(1),o=function(t,n){if(i(t),!e(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,n,e){try{(e=r(19)(Function.call,r(16).f(Object.prototype,"__proto__").set,2))(t,[]),n=!(t instanceof Array)}catch(t){n=!0}return function(t,r){return o(t,r),n?t.__proto__=r:e(t,r),t}}({},!1):void 0),check:o}},function(t,n){t.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},function(t,n,r){var e=r(4),i=r(70).set;t.exports=function(t,n,r){var o,u=n.constructor;return u!==r&&"function"==typeof u&&(o=u.prototype)!==r.prototype&&e(o)&&i&&i(t,o),t}},function(t,n,r){"use strict";var e=r(24),i=r(23);t.exports=function(t){var n=String(i(this)),r="",o=e(t);if(o<0||o==1/0)throw RangeError("Count can't be negative");for(;o>0;(o>>>=1)&&(n+=n))1&o&&(r+=n);return r}},function(t,n){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},function(t,n){var r=Math.expm1;t.exports=!r||r(10)>22025.465794806718||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(t){return 0==(t=+t)?t:t>-1e-6&&t<1e-6?t+t*t/2:Math.exp(t)-1}:r},function(t,n,r){var e=r(24),i=r(23);t.exports=function(t){return function(n,r){var o,u,c=String(i(n)),a=e(r),f=c.length;return a<0||a>=f?t?"":void 0:(o=c.charCodeAt(a))<55296||o>56319||a+1===f||(u=c.charCodeAt(a+1))<56320||u>57343?t?c.charAt(a):o:t?c.slice(a,a+2):u-56320+(o-55296<<10)+65536}}},function(t,n,r){"use strict";var e=r(30),i=r(0),o=r(12),u=r(11),c=r(44),a=r(78),f=r(42),s=r(17),l=r(5)("iterator"),h=!([].keys&&"next"in[].keys()),v=function(){return this};t.exports=function(t,n,r,p,d,g,y){a(r,n,p);var m,b,w,S=function(t){if(!h&&t in O)return O[t];switch(t){case"keys":case"values":return function(){return new r(this,t)}}return function(){return new r(this,t)}},x=n+" Iterator",_="values"==d,E=!1,O=t.prototype,M=O[l]||O["@@iterator"]||d&&O[d],P=M||S(d),F=d?_?S("entries"):P:void 0,A="Array"==n&&O.entries||M;if(A&&(w=s(A.call(new t)))!==Object.prototype&&w.next&&(f(w,x,!0),e||"function"==typeof w[l]||u(w,l,v)),_&&M&&"values"!==M.name&&(E=!0,P=function(){return M.call(this)}),e&&!y||!h&&!E&&O[l]||u(O,l,P),c[n]=P,c[x]=v,d)if(m={values:_?P:S("values"),keys:g?P:S("keys"),entries:F},y)for(b in m)b in O||o(O,b,m[b]);else i(i.P+i.F*(h||E),n,m);return m}},function(t,n,r){"use strict";var e=r(36),i=r(32),o=r(42),u={};r(11)(u,r(5)("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:i(1,r)}),o(t,n+" Iterator")}},function(t,n,r){var e=r(53),i=r(23);t.exports=function(t,n,r){if(e(n))throw TypeError("String#"+r+" doesn't accept regex!");return String(i(t))}},function(t,n,r){var e=r(5)("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(r){try{return n[e]=!1,!"/./"[t](n)}catch(t){}}return!0}},function(t,n,r){var e=r(44),i=r(5)("iterator"),o=Array.prototype;t.exports=function(t){return void 0!==t&&(e.Array===t||o[i]===t)}},function(t,n,r){"use strict";var e=r(7),i=r(32);t.exports=function(t,n,r){n in t?e.f(t,n,i(0,r)):t[n]=r}},function(t,n,r){var e=r(48),i=r(5)("iterator"),o=r(44);t.exports=r(18).getIteratorMethod=function(t){if(null!=t)return t[i]||t["@@iterator"]||o[e(t)]}},function(t,n,r){var e=r(221);t.exports=function(t,n){return new(e(t))(n)}},function(t,n,r){"use strict";var e=r(9),i=r(35),o=r(8);t.exports=function(t){for(var n=e(this),r=o(n.length),u=arguments.length,c=i(u>1?arguments[1]:void 0,r),a=u>2?arguments[2]:void 0,f=void 0===a?r:i(a,r);f>c;)n[c++]=t;return n}},function(t,n,r){"use strict";var e=r(31),i=r(108),o=r(44),u=r(15);t.exports=r(77)(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,i(1)):i(0,"keys"==n?r:"values"==n?t[r]:[r,t[r]])},"values"),o.Arguments=o.Array,e("keys"),e("values"),e("entries")},function(t,n,r){var e,i,o,u=r(19),c=r(98),a=r(69),f=r(65),s=r(2),l=s.process,h=s.setImmediate,v=s.clearImmediate,p=s.MessageChannel,d=s.Dispatch,g=0,y={},m=function(){var t=+this;if(y.hasOwnProperty(t)){var n=y[t];delete y[t],n()}},b=function(t){m.call(t.data)};h&&v||(h=function(t){for(var n=[],r=1;arguments.length>r;)n.push(arguments[r++]);return y[++g]=function(){c("function"==typeof t?t:Function(t),n)},e(g),g},v=function(t){delete y[t]},"process"==r(20)(l)?e=function(t){l.nextTick(u(m,t,1))}:d&&d.now?e=function(t){d.now(u(m,t,1))}:p?(o=(i=new p).port2,i.port1.onmessage=b,e=u(o.postMessage,o,1)):s.addEventListener&&"function"==typeof postMessage&&!s.importScripts?(e=function(t){s.postMessage(t+"","*")},s.addEventListener("message",b,!1)):e="onreadystatechange"in f("script")?function(t){a.appendChild(f("script")).onreadystatechange=function(){a.removeChild(this),m.call(t)}}:function(t){setTimeout(u(m,t,1),0)}),t.exports={set:h,clear:v}},function(t,n,r){var e=r(2),i=r(87).set,o=e.MutationObserver||e.WebKitMutationObserver,u=e.process,c=e.Promise,a="process"==r(20)(u);t.exports=function(){var t,n,r,f=function(){var e,i;for(a&&(e=u.domain)&&e.exit();t;){i=t.fn,t=t.next;try{i()}catch(e){throw t?r():n=void 0,e}}n=void 0,e&&e.enter()};if(a)r=function(){u.nextTick(f)};else if(!o||e.navigator&&e.navigator.standalone)if(c&&c.resolve){var s=c.resolve(void 0);r=function(){s.then(f)}}else r=function(){i.call(e,f)};else{var l=!0,h=document.createTextNode("");new o(f).observe(h,{characterData:!0}),r=function(){h.data=l=!l}}return function(e){var i={fn:e,next:void 0};n&&(n.next=i),t||(t=i,r()),n=i}}},function(t,n,r){"use strict";var e=r(10);function i(t){var n,r;this.promise=new t(function(t,e){if(void 0!==n||void 0!==r)throw TypeError("Bad Promise constructor");n=t,r=e}),this.resolve=e(n),this.reject=e(r)}t.exports.f=function(t){return new i(t)}},function(t,n,r){"use strict";var e=r(2),i=r(6),o=r(30),u=r(60),c=r(11),a=r(41),f=r(3),s=r(39),l=r(24),h=r(8),v=r(117),p=r(37).f,d=r(7).f,g=r(85),y=r(42),m="prototype",b="Wrong index!",w=e.ArrayBuffer,S=e.DataView,x=e.Math,_=e.RangeError,E=e.Infinity,O=w,M=x.abs,P=x.pow,F=x.floor,A=x.log,j=x.LN2,I=i?"_b":"buffer",N=i?"_l":"byteLength",L=i?"_o":"byteOffset";function T(t,n,r){var e,i,o,u=new Array(r),c=8*r-n-1,a=(1<<c)-1,f=a>>1,s=23===n?P(2,-24)-P(2,-77):0,l=0,h=t<0||0===t&&1/t<0?1:0;for((t=M(t))!=t||t===E?(i=t!=t?1:0,e=a):(e=F(A(t)/j),t*(o=P(2,-e))<1&&(e--,o*=2),(t+=e+f>=1?s/o:s*P(2,1-f))*o>=2&&(e++,o/=2),e+f>=a?(i=0,e=a):e+f>=1?(i=(t*o-1)*P(2,n),e+=f):(i=t*P(2,f-1)*P(2,n),e=0));n>=8;u[l++]=255&i,i/=256,n-=8);for(e=e<<n|i,c+=n;c>0;u[l++]=255&e,e/=256,c-=8);return u[--l]|=128*h,u}function k(t,n,r){var e,i=8*r-n-1,o=(1<<i)-1,u=o>>1,c=i-7,a=r-1,f=t[a--],s=127&f;for(f>>=7;c>0;s=256*s+t[a],a--,c-=8);for(e=s&(1<<-c)-1,s>>=-c,c+=n;c>0;e=256*e+t[a],a--,c-=8);if(0===s)s=1-u;else{if(s===o)return e?NaN:f?-E:E;e+=P(2,n),s-=u}return(f?-1:1)*e*P(2,s-n)}function R(t){return t[3]<<24|t[2]<<16|t[1]<<8|t[0]}function C(t){return[255&t]}function D(t){return[255&t,t>>8&255]}function W(t){return[255&t,t>>8&255,t>>16&255,t>>24&255]}function U(t){return T(t,52,8)}function G(t){return T(t,23,4)}function V(t,n,r){d(t[m],n,{get:function(){return this[r]}})}function B(t,n,r,e){var i=v(+r);if(i+n>t[N])throw _(b);var o=t[I]._b,u=i+t[L],c=o.slice(u,u+n);return e?c:c.reverse()}function z(t,n,r,e,i,o){var u=v(+r);if(u+n>t[N])throw _(b);for(var c=t[I]._b,a=u+t[L],f=e(+i),s=0;s<n;s++)c[a+s]=f[o?s:n-s-1]}if(u.ABV){if(!f(function(){w(1)})||!f(function(){new w(-1)})||f(function(){return new w,new w(1.5),new w(NaN),"ArrayBuffer"!=w.name})){for(var q,J=(w=function(t){return s(this,w),new O(v(t))})[m]=O[m],Y=p(O),K=0;Y.length>K;)(q=Y[K++])in w||c(w,q,O[q]);o||(J.constructor=w)}var H=new S(new w(2)),X=S[m].setInt8;H.setInt8(0,2147483648),H.setInt8(1,2147483649),!H.getInt8(0)&&H.getInt8(1)||a(S[m],{setInt8:function(t,n){X.call(this,t,n<<24>>24)},setUint8:function(t,n){X.call(this,t,n<<24>>24)}},!0)}else w=function(t){s(this,w,"ArrayBuffer");var n=v(t);this._b=g.call(new Array(n),0),this[N]=n},S=function(t,n,r){s(this,S,"DataView"),s(t,w,"DataView");var e=t[N],i=l(n);if(i<0||i>e)throw _("Wrong offset!");if(i+(r=void 0===r?e-i:h(r))>e)throw _("Wrong length!");this[I]=t,this[L]=i,this[N]=r},i&&(V(w,"byteLength","_l"),V(S,"buffer","_b"),V(S,"byteLength","_l"),V(S,"byteOffset","_o")),a(S[m],{getInt8:function(t){return B(this,1,t)[0]<<24>>24},getUint8:function(t){return B(this,1,t)[0]},getInt16:function(t){var n=B(this,2,t,arguments[1]);return(n[1]<<8|n[0])<<16>>16},getUint16:function(t){var n=B(this,2,t,arguments[1]);return n[1]<<8|n[0]},getInt32:function(t){return R(B(this,4,t,arguments[1]))},getUint32:function(t){return R(B(this,4,t,arguments[1]))>>>0},getFloat32:function(t){return k(B(this,4,t,arguments[1]),23,4)},getFloat64:function(t){return k(B(this,8,t,arguments[1]),52,8)},setInt8:function(t,n){z(this,1,t,C,n)},setUint8:function(t,n){z(this,1,t,C,n)},setInt16:function(t,n){z(this,2,t,D,n,arguments[2])},setUint16:function(t,n){z(this,2,t,D,n,arguments[2])},setInt32:function(t,n){z(this,4,t,W,n,arguments[2])},setUint32:function(t,n){z(this,4,t,W,n,arguments[2])},setFloat32:function(t,n){z(this,4,t,G,n,arguments[2])},setFloat64:function(t,n){z(this,8,t,U,n,arguments[2])}});y(w,"ArrayBuffer"),y(S,"DataView"),c(S[m],u.VIEW,!0),n.ArrayBuffer=w,n.DataView=S},function(t,n,r){t.exports=!r(6)&&!r(3)(function(){return 7!=Object.defineProperty(r(65)("div"),"a",{get:function(){return 7}}).a})},function(t,n,r){n.f=r(5)},function(t,n,r){var e=r(14),i=r(15),o=r(50)(!1),u=r(67)("IE_PROTO");t.exports=function(t,n){var r,c=i(t),a=0,f=[];for(r in c)r!=u&&e(c,r)&&f.push(r);for(;n.length>a;)e(c,r=n[a++])&&(~o(f,r)||f.push(r));return f}},function(t,n,r){var e=r(7),i=r(1),o=r(34);t.exports=r(6)?Object.defineProperties:function(t,n){i(t);for(var r,u=o(n),c=u.length,a=0;c>a;)e.f(t,r=u[a++],n[r]);return t}},function(t,n,r){var e=r(15),i=r(37).f,o={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];t.exports.f=function(t){return u&&"[object Window]"==o.call(t)?function(t){try{return i(t)}catch(t){return u.slice()}}(t):i(e(t))}},function(t,n,r){"use strict";var e=r(34),i=r(51),o=r(47),u=r(9),c=r(46),a=Object.assign;t.exports=!a||r(3)(function(){var t={},n={},r=Symbol(),e="abcdefghijklmnopqrst";return t[r]=7,e.split("").forEach(function(t){n[t]=t}),7!=a({},t)[r]||Object.keys(a({},n)).join("")!=e})?function(t,n){for(var r=u(t),a=arguments.length,f=1,s=i.f,l=o.f;a>f;)for(var h,v=c(arguments[f++]),p=s?e(v).concat(s(v)):e(v),d=p.length,g=0;d>g;)l.call(v,h=p[g++])&&(r[h]=v[h]);return r}:a},function(t,n,r){"use strict";var e=r(10),i=r(4),o=r(98),u=[].slice,c={};t.exports=Function.bind||function(t){var n=e(this),r=u.call(arguments,1),a=function(){var e=r.concat(u.call(arguments));return this instanceof a?function(t,n,r){if(!(n in c)){for(var e=[],i=0;i<n;i++)e[i]="a["+i+"]";c[n]=Function("F,a","return new F("+e.join(",")+")")}return c[n](t,r)}(n,e.length,e):o(n,e,t)};return i(n.prototype)&&(a.prototype=n.prototype),a}},function(t,n){t.exports=function(t,n,r){var e=void 0===r;switch(n.length){case 0:return e?t():t.call(r);case 1:return e?t(n[0]):t.call(r,n[0]);case 2:return e?t(n[0],n[1]):t.call(r,n[0],n[1]);case 3:return e?t(n[0],n[1],n[2]):t.call(r,n[0],n[1],n[2]);case 4:return e?t(n[0],n[1],n[2],n[3]):t.call(r,n[0],n[1],n[2],n[3])}return t.apply(r,n)}},function(t,n,r){var e=r(2).parseInt,i=r(43).trim,o=r(71),u=/^[-+]?0[xX]/;t.exports=8!==e(o+"08")||22!==e(o+"0x16")?function(t,n){var r=i(String(t),3);return e(r,n>>>0||(u.test(r)?16:10))}:e},function(t,n,r){var e=r(2).parseFloat,i=r(43).trim;t.exports=1/e(r(71)+"-0")!=-1/0?function(t){var n=i(String(t),3),r=e(n);return 0===r&&"-"==n.charAt(0)?-0:r}:e},function(t,n,r){var e=r(20);t.exports=function(t,n){if("number"!=typeof t&&"Number"!=e(t))throw TypeError(n);return+t}},function(t,n,r){var e=r(4),i=Math.floor;t.exports=function(t){return!e(t)&&isFinite(t)&&i(t)===t}},function(t,n){t.exports=Math.log1p||function(t){return(t=+t)>-1e-8&&t<1e-8?t-t*t/2:Math.log(1+t)}},function(t,n,r){var e=r(74),i=Math.pow,o=i(2,-52),u=i(2,-23),c=i(2,127)*(2-u),a=i(2,-126);t.exports=Math.fround||function(t){var n,r,i=Math.abs(t),f=e(t);return i<a?f*(i/a/u+1/o-1/o)*a*u:(r=(n=(1+u/o)*i)-(n-i))>c||r!=r?f*(1/0):f*r}},function(t,n,r){var e=r(1);t.exports=function(t,n,r,i){try{return i?n(e(r)[0],r[1]):n(r)}catch(n){var o=t.return;throw void 0!==o&&e(o.call(t)),n}}},function(t,n,r){var e=r(10),i=r(9),o=r(46),u=r(8);t.exports=function(t,n,r,c,a){e(n);var f=i(t),s=o(f),l=u(f.length),h=a?l-1:0,v=a?-1:1;if(r<2)for(;;){if(h in s){c=s[h],h+=v;break}if(h+=v,a?h<0:l<=h)throw TypeError("Reduce of empty array with no initial value")}for(;a?h>=0:l>h;h+=v)h in s&&(c=n(c,s[h],h,f));return c}},function(t,n,r){"use strict";var e=r(9),i=r(35),o=r(8);t.exports=[].copyWithin||function(t,n){var r=e(this),u=o(r.length),c=i(t,u),a=i(n,u),f=arguments.length>2?arguments[2]:void 0,s=Math.min((void 0===f?u:i(f,u))-a,u-c),l=1;for(a<c&&c<a+s&&(l=-1,a+=s-1,c+=s-1);s-- >0;)a in r?r[c]=r[a]:delete r[c],c+=l,a+=l;return r}},function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},function(t,n,r){r(6)&&"g"!=/./g.flags&&r(7).f(RegExp.prototype,"flags",{configurable:!0,get:r(55)})},function(t,n){t.exports=function(t){try{return{e:!1,v:t()}}catch(t){return{e:!0,v:t}}}},function(t,n,r){var e=r(1),i=r(4),o=r(89);t.exports=function(t,n){if(e(t),i(n)&&n.constructor===t)return n;var r=o.f(t);return(0,r.resolve)(n),r.promise}},function(t,n,r){"use strict";var e=r(113),i=r(45);t.exports=r(59)("Map",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{get:function(t){var n=e.getEntry(i(this,"Map"),t);return n&&n.v},set:function(t,n){return e.def(i(this,"Map"),0===t?0:t,n)}},e,!0)},function(t,n,r){"use strict";var e=r(7).f,i=r(36),o=r(41),u=r(19),c=r(39),a=r(40),f=r(77),s=r(108),l=r(38),h=r(6),v=r(29).fastKey,p=r(45),d=h?"_s":"size",g=function(t,n){var r,e=v(n);if("F"!==e)return t._i[e];for(r=t._f;r;r=r.n)if(r.k==n)return r};t.exports={getConstructor:function(t,n,r,f){var s=t(function(t,e){c(t,s,n,"_i"),t._t=n,t._i=i(null),t._f=void 0,t._l=void 0,t[d]=0,null!=e&&a(e,r,t[f],t)});return o(s.prototype,{clear:function(){for(var t=p(this,n),r=t._i,e=t._f;e;e=e.n)e.r=!0,e.p&&(e.p=e.p.n=void 0),delete r[e.i];t._f=t._l=void 0,t[d]=0},delete:function(t){var r=p(this,n),e=g(r,t);if(e){var i=e.n,o=e.p;delete r._i[e.i],e.r=!0,o&&(o.n=i),i&&(i.p=o),r._f==e&&(r._f=i),r._l==e&&(r._l=o),r[d]--}return!!e},forEach:function(t){p(this,n);for(var r,e=u(t,arguments.length>1?arguments[1]:void 0,3);r=r?r.n:this._f;)for(e(r.v,r.k,this);r&&r.r;)r=r.p},has:function(t){return!!g(p(this,n),t)}}),h&&e(s.prototype,"size",{get:function(){return p(this,n)[d]}}),s},def:function(t,n,r){var e,i,o=g(t,n);return o?o.v=r:(t._l=o={i:i=v(n,!0),k:n,v:r,p:e=t._l,n:void 0,r:!1},t._f||(t._f=o),e&&(e.n=o),t[d]++,"F"!==i&&(t._i[i]=o)),t},getEntry:g,setStrong:function(t,n,r){f(t,n,function(t,r){this._t=p(t,n),this._k=r,this._l=void 0},function(){for(var t=this._k,n=this._l;n&&n.r;)n=n.p;return this._t&&(this._l=n=n?n.n:this._t._f)?s(0,"keys"==t?n.k:"values"==t?n.v:[n.k,n.v]):(this._t=void 0,s(1))},r?"entries":"values",!r,!0),l(n)}}},function(t,n,r){"use strict";var e=r(113),i=r(45);t.exports=r(59)("Set",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(i(this,"Set"),t=0===t?0:t,t)}},e)},function(t,n,r){"use strict";var e,i=r(26)(0),o=r(12),u=r(29),c=r(96),a=r(116),f=r(4),s=r(3),l=r(45),h=u.getWeak,v=Object.isExtensible,p=a.ufstore,d={},g=function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},y={get:function(t){if(f(t)){var n=h(t);return!0===n?p(l(this,"WeakMap")).get(t):n?n[this._i]:void 0}},set:function(t,n){return a.def(l(this,"WeakMap"),t,n)}},m=t.exports=r(59)("WeakMap",g,y,a,!0,!0);s(function(){return 7!=(new m).set((Object.freeze||Object)(d),7).get(d)})&&(c((e=a.getConstructor(g,"WeakMap")).prototype,y),u.NEED=!0,i(["delete","has","get","set"],function(t){var n=m.prototype,r=n[t];o(n,t,function(n,i){if(f(n)&&!v(n)){this._f||(this._f=new e);var o=this._f[t](n,i);return"set"==t?this:o}return r.call(this,n,i)})}))},function(t,n,r){"use strict";var e=r(41),i=r(29).getWeak,o=r(1),u=r(4),c=r(39),a=r(40),f=r(26),s=r(14),l=r(45),h=f(5),v=f(6),p=0,d=function(t){return t._l||(t._l=new g)},g=function(){this.a=[]},y=function(t,n){return h(t.a,function(t){return t[0]===n})};g.prototype={get:function(t){var n=y(this,t);if(n)return n[1]},has:function(t){return!!y(this,t)},set:function(t,n){var r=y(this,t);r?r[1]=n:this.a.push([t,n])},delete:function(t){var n=v(this.a,function(n){return n[0]===t});return~n&&this.a.splice(n,1),!!~n}},t.exports={getConstructor:function(t,n,r,o){var f=t(function(t,e){c(t,f,n,"_i"),t._t=n,t._i=p++,t._l=void 0,null!=e&&a(e,r,t[o],t)});return e(f.prototype,{delete:function(t){if(!u(t))return!1;var r=i(t);return!0===r?d(l(this,n)).delete(t):r&&s(r,this._i)&&delete r[this._i]},has:function(t){if(!u(t))return!1;var r=i(t);return!0===r?d(l(this,n)).has(t):r&&s(r,this._i)}}),f},def:function(t,n,r){var e=i(o(n),!0);return!0===e?d(t).set(n,r):e[t._i]=r,t},ufstore:d}},function(t,n,r){var e=r(24),i=r(8);t.exports=function(t){if(void 0===t)return 0;var n=e(t),r=i(n);if(n!==r)throw RangeError("Wrong length!");return r}},function(t,n,r){var e=r(37),i=r(51),o=r(1),u=r(2).Reflect;t.exports=u&&u.ownKeys||function(t){var n=e.f(o(t)),r=i.f;return r?n.concat(r(t)):n}},function(t,n,r){"use strict";var e=r(52),i=r(4),o=r(8),u=r(19),c=r(5)("isConcatSpreadable");t.exports=function t(n,r,a,f,s,l,h,v){for(var p,d,g=s,y=0,m=!!h&&u(h,v,3);y<f;){if(y in a){if(p=m?m(a[y],y,r):a[y],d=!1,i(p)&&(d=void 0!==(d=p[c])?!!d:e(p)),d&&l>0)g=t(n,r,p,o(p.length),g,l-1)-1;else{if(g>=9007199254740991)throw TypeError();n[g]=p}g++}y++}return g}},function(t,n,r){var e=r(8),i=r(73),o=r(23);t.exports=function(t,n,r,u){var c=String(o(t)),a=c.length,f=void 0===r?" ":String(r),s=e(n);if(s<=a||""==f)return c;var l=s-a,h=i.call(f,Math.ceil(l/f.length));return h.length>l&&(h=h.slice(0,l)),u?h+c:c+h}},function(t,n,r){var e=r(34),i=r(15),o=r(47).f;t.exports=function(t){return function(n){for(var r,u=i(n),c=e(u),a=c.length,f=0,s=[];a>f;)o.call(u,r=c[f++])&&s.push(t?[r,u[r]]:u[r]);return s}}},function(t,n,r){var e=r(48),i=r(123);t.exports=function(t){return function(){if(e(this)!=t)throw TypeError(t+"#toJSON isn't generic");return i(this)}}},function(t,n,r){var e=r(40);t.exports=function(t,n){var r=[];return e(t,!1,r.push,r,n),r}},function(t,n){t.exports=Math.scale||function(t,n,r,e,i){return 0===arguments.length||t!=t||n!=n||r!=r||e!=e||i!=i?NaN:t===1/0||t===-1/0?t:(t-n)*(i-e)/(r-n)+e}},function(t,n){t.exports={getMajor:function(t){return parseInt(t.split(".")[0])},getMinor:function(t){return parseInt(t.split(".")[1])},getPatch:function(t){return parseInt(t.split("-")[0].split(".")[2])},isGreaterThanOrEqualTo:function(t,n){var r=this.getMajor(t),e=this.getMinor(t),i=this.getPatch(t),o=this.getMajor(n),u=this.getMinor(n),c=this.getPatch(n);return r>o||(r===o&&e>u||(r===o&&e===u&&i>c||r===o&&e===u&&i===c))}}},function(t,n,r){t.exports=r.p+"client_frame.html"},function(t,n,r){r(128),t.exports=r(331)},function(t,n,r){"use strict";(function(t){function e(){return t._babelPolyfill||"undefined"!=typeof window&&window._babelPolyfill?null:r(129)}Object.defineProperty(n,"__esModule",{value:!0}),n.idempotentBabelPolyfill=e,n.default=e()}).call(this,r(64))},function(t,n,r){"use strict";(function(t){if(r(130),r(327),r(328),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed");t._babelPolyfill=!0;var n="defineProperty";function e(t,r,e){t[r]||Object[n](t,r,{writable:!0,configurable:!0,value:e})}e(String.prototype,"padLeft","".padStart),e(String.prototype,"padRight","".padEnd),"pop,reverse,shift,keys,values,entries,indexOf,every,some,forEach,map,filter,find,findIndex,includes,join,slice,concat,push,splice,unshift,sort,lastIndexOf,reduce,reduceRight,copyWithin,fill".split(",").forEach(function(t){[][t]&&e(Array,t,Function.call.bind([][t]))})}).call(this,r(64))},function(t,n,r){r(131),r(133),r(134),r(135),r(136),r(137),r(138),r(139),r(140),r(141),r(142),r(143),r(144),r(145),r(146),r(147),r(149),r(150),r(151),r(152),r(153),r(154),r(155),r(156),r(157),r(158),r(159),r(160),r(161),r(162),r(163),r(164),r(165),r(166),r(167),r(168),r(169),r(170),r(171),r(172),r(173),r(174),r(175),r(176),r(177),r(178),r(179),r(180),r(181),r(182),r(183),r(184),r(185),r(186),r(187),r(188),r(189),r(190),r(191),r(192),r(193),r(194),r(195),r(196),r(197),r(198),r(199),r(200),r(201),r(202),r(203),r(204),r(205),r(206),r(207),r(208),r(209),r(211),r(212),r(214),r(215),r(216),r(217),r(218),r(219),r(220),r(222),r(223),r(224),r(225),r(226),r(227),r(228),r(229),r(230),r(231),r(232),r(233),r(234),r(86),r(235),r(236),r(109),r(237),r(238),r(239),r(240),r(241),r(112),r(114),r(115),r(242),r(243),r(244),r(245),r(246),r(247),r(248),r(249),r(250),r(251),r(252),r(253),r(254),r(255),r(256),r(257),r(258),r(259),r(260),r(261),r(262),r(263),r(264),r(265),r(266),r(267),r(268),r(269),r(270),r(271),r(272),r(273),r(274),r(275),r(276),r(277),r(278),r(279),r(280),r(281),r(282),r(283),r(284),r(285),r(286),r(287),r(288),r(289),r(290),r(291),r(292),r(293),r(294),r(295),r(296),r(297),r(298),r(299),r(300),r(301),r(302),r(303),r(304),r(305),r(306),r(307),r(308),r(309),r(310),r(311),r(312),r(313),r(314),r(315),r(316),r(317),r(318),r(319),r(320),r(321),r(322),r(323),r(324),r(325),r(326),t.exports=r(18)},function(t,n,r){"use strict";var e=r(2),i=r(14),o=r(6),u=r(0),c=r(12),a=r(29).KEY,f=r(3),s=r(49),l=r(42),h=r(33),v=r(5),p=r(92),d=r(66),g=r(132),y=r(52),m=r(1),b=r(4),w=r(15),S=r(22),x=r(32),_=r(36),E=r(95),O=r(16),M=r(7),P=r(34),F=O.f,A=M.f,j=E.f,I=e.Symbol,N=e.JSON,L=N&&N.stringify,T=v("_hidden"),k=v("toPrimitive"),R={}.propertyIsEnumerable,C=s("symbol-registry"),D=s("symbols"),W=s("op-symbols"),U=Object.prototype,G="function"==typeof I,V=e.QObject,B=!V||!V.prototype||!V.prototype.findChild,z=o&&f(function(){return 7!=_(A({},"a",{get:function(){return A(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=F(U,n);e&&delete U[n],A(t,n,r),e&&t!==U&&A(U,n,e)}:A,q=function(t){var n=D[t]=_(I.prototype);return n._k=t,n},J=G&&"symbol"==typeof I.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof I},Y=function(t,n,r){return t===U&&Y(W,n,r),m(t),n=S(n,!0),m(r),i(D,n)?(r.enumerable?(i(t,T)&&t[T][n]&&(t[T][n]=!1),r=_(r,{enumerable:x(0,!1)})):(i(t,T)||A(t,T,x(1,{})),t[T][n]=!0),z(t,n,r)):A(t,n,r)},K=function(t,n){m(t);for(var r,e=g(n=w(n)),i=0,o=e.length;o>i;)Y(t,r=e[i++],n[r]);return t},H=function(t){var n=R.call(this,t=S(t,!0));return!(this===U&&i(D,t)&&!i(W,t))&&(!(n||!i(this,t)||!i(D,t)||i(this,T)&&this[T][t])||n)},X=function(t,n){if(t=w(t),n=S(n,!0),t!==U||!i(D,n)||i(W,n)){var r=F(t,n);return!r||!i(D,n)||i(t,T)&&t[T][n]||(r.enumerable=!0),r}},$=function(t){for(var n,r=j(w(t)),e=[],o=0;r.length>o;)i(D,n=r[o++])||n==T||n==a||e.push(n);return e},Z=function(t){for(var n,r=t===U,e=j(r?W:w(t)),o=[],u=0;e.length>u;)!i(D,n=e[u++])||r&&!i(U,n)||o.push(D[n]);return o};G||(c((I=function(){if(this instanceof I)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===U&&n.call(W,r),i(this,T)&&i(this[T],t)&&(this[T][t]=!1),z(this,t,x(1,r))};return o&&B&&z(U,t,{configurable:!0,set:n}),q(t)}).prototype,"toString",function(){return this._k}),O.f=X,M.f=Y,r(37).f=E.f=$,r(47).f=H,r(51).f=Z,o&&!r(30)&&c(U,"propertyIsEnumerable",H,!0),p.f=function(t){return q(v(t))}),u(u.G+u.W+u.F*!G,{Symbol:I});for(var Q="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),tt=0;Q.length>tt;)v(Q[tt++]);for(var nt=P(v.store),rt=0;nt.length>rt;)d(nt[rt++]);u(u.S+u.F*!G,"Symbol",{for:function(t){return i(C,t+="")?C[t]:C[t]=I(t)},keyFor:function(t){if(!J(t))throw TypeError(t+" is not a symbol!");for(var n in C)if(C[n]===t)return n},useSetter:function(){B=!0},useSimple:function(){B=!1}}),u(u.S+u.F*!G,"Object",{create:function(t,n){return void 0===n?_(t):K(_(t),n)},defineProperty:Y,defineProperties:K,getOwnPropertyDescriptor:X,getOwnPropertyNames:$,getOwnPropertySymbols:Z}),N&&u(u.S+u.F*(!G||f(function(){var t=I();return"[null]"!=L([t])||"{}"!=L({a:t})||"{}"!=L(Object(t))})),"JSON",{stringify:function(t){for(var n,r,e=[t],i=1;arguments.length>i;)e.push(arguments[i++]);if(r=n=e[1],(b(n)||void 0!==t)&&!J(t))return y(n)||(n=function(t,n){if("function"==typeof r&&(n=r.call(this,t,n)),!J(n))return n}),e[1]=n,L.apply(N,e)}}),I.prototype[k]||r(11)(I.prototype,k,I.prototype.valueOf),l(I,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},function(t,n,r){var e=r(34),i=r(51),o=r(47);t.exports=function(t){var n=e(t),r=i.f;if(r)for(var u,c=r(t),a=o.f,f=0;c.length>f;)a.call(t,u=c[f++])&&n.push(u);return n}},function(t,n,r){var e=r(0);e(e.S,"Object",{create:r(36)})},function(t,n,r){var e=r(0);e(e.S+e.F*!r(6),"Object",{defineProperty:r(7).f})},function(t,n,r){var e=r(0);e(e.S+e.F*!r(6),"Object",{defineProperties:r(94)})},function(t,n,r){var e=r(15),i=r(16).f;r(25)("getOwnPropertyDescriptor",function(){return function(t,n){return i(e(t),n)}})},function(t,n,r){var e=r(9),i=r(17);r(25)("getPrototypeOf",function(){return function(t){return i(e(t))}})},function(t,n,r){var e=r(9),i=r(34);r(25)("keys",function(){return function(t){return i(e(t))}})},function(t,n,r){r(25)("getOwnPropertyNames",function(){return r(95).f})},function(t,n,r){var e=r(4),i=r(29).onFreeze;r(25)("freeze",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(4),i=r(29).onFreeze;r(25)("seal",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(4),i=r(29).onFreeze;r(25)("preventExtensions",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(4);r(25)("isFrozen",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(4);r(25)("isSealed",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(4);r(25)("isExtensible",function(t){return function(n){return!!e(n)&&(!t||t(n))}})},function(t,n,r){var e=r(0);e(e.S+e.F,"Object",{assign:r(96)})},function(t,n,r){var e=r(0);e(e.S,"Object",{is:r(148)})},function(t,n){t.exports=Object.is||function(t,n){return t===n?0!==t||1/t==1/n:t!=t&&n!=n}},function(t,n,r){var e=r(0);e(e.S,"Object",{setPrototypeOf:r(70).set})},function(t,n,r){"use strict";var e=r(48),i={};i[r(5)("toStringTag")]="z",i+""!="[object z]"&&r(12)(Object.prototype,"toString",function(){return"[object "+e(this)+"]"},!0)},function(t,n,r){var e=r(0);e(e.P,"Function",{bind:r(97)})},function(t,n,r){var e=r(7).f,i=Function.prototype,o=/^\s*function ([^ (]*)/;"name"in i||r(6)&&e(i,"name",{configurable:!0,get:function(){try{return(""+this).match(o)[1]}catch(t){return""}}})},function(t,n,r){"use strict";var e=r(4),i=r(17),o=r(5)("hasInstance"),u=Function.prototype;o in u||r(7).f(u,o,{value:function(t){if("function"!=typeof this||!e(t))return!1;if(!e(this.prototype))return t instanceof this;for(;t=i(t);)if(this.prototype===t)return!0;return!1}})},function(t,n,r){var e=r(0),i=r(99);e(e.G+e.F*(parseInt!=i),{parseInt:i})},function(t,n,r){var e=r(0),i=r(100);e(e.G+e.F*(parseFloat!=i),{parseFloat:i})},function(t,n,r){"use strict";var e=r(2),i=r(14),o=r(20),u=r(72),c=r(22),a=r(3),f=r(37).f,s=r(16).f,l=r(7).f,h=r(43).trim,v=e.Number,p=v,d=v.prototype,g="Number"==o(r(36)(d)),y="trim"in String.prototype,m=function(t){var n=c(t,!1);if("string"==typeof n&&n.length>2){var r,e,i,o=(n=y?n.trim():h(n,3)).charCodeAt(0);if(43===o||45===o){if(88===(r=n.charCodeAt(2))||120===r)return NaN}else if(48===o){switch(n.charCodeAt(1)){case 66:case 98:e=2,i=49;break;case 79:case 111:e=8,i=55;break;default:return+n}for(var u,a=n.slice(2),f=0,s=a.length;f<s;f++)if((u=a.charCodeAt(f))<48||u>i)return NaN;return parseInt(a,e)}}return+n};if(!v(" 0o1")||!v("0b1")||v("+0x1")){v=function(t){var n=arguments.length<1?0:t,r=this;return r instanceof v&&(g?a(function(){d.valueOf.call(r)}):"Number"!=o(r))?u(new p(m(n)),r,v):m(n)};for(var b,w=r(6)?f(p):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),S=0;w.length>S;S++)i(p,b=w[S])&&!i(v,b)&&l(v,b,s(p,b));v.prototype=d,d.constructor=v,r(12)(e,"Number",v)}},function(t,n,r){"use strict";var e=r(0),i=r(24),o=r(101),u=r(73),c=1..toFixed,a=Math.floor,f=[0,0,0,0,0,0],s="Number.toFixed: incorrect invocation!",l=function(t,n){for(var r=-1,e=n;++r<6;)e+=t*f[r],f[r]=e%1e7,e=a(e/1e7)},h=function(t){for(var n=6,r=0;--n>=0;)r+=f[n],f[n]=a(r/t),r=r%t*1e7},v=function(){for(var t=6,n="";--t>=0;)if(""!==n||0===t||0!==f[t]){var r=String(f[t]);n=""===n?r:n+u.call("0",7-r.length)+r}return n},p=function(t,n,r){return 0===n?r:n%2==1?p(t,n-1,r*t):p(t*t,n/2,r)};e(e.P+e.F*(!!c&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!r(3)(function(){c.call({})})),"Number",{toFixed:function(t){var n,r,e,c,a=o(this,s),f=i(t),d="",g="0";if(f<0||f>20)throw RangeError(s);if(a!=a)return"NaN";if(a<=-1e21||a>=1e21)return String(a);if(a<0&&(d="-",a=-a),a>1e-21)if(r=(n=function(t){for(var n=0,r=t;r>=4096;)n+=12,r/=4096;for(;r>=2;)n+=1,r/=2;return n}(a*p(2,69,1))-69)<0?a*p(2,-n,1):a/p(2,n,1),r*=4503599627370496,(n=52-n)>0){for(l(0,r),e=f;e>=7;)l(1e7,0),e-=7;for(l(p(10,e,1),0),e=n-1;e>=23;)h(1<<23),e-=23;h(1<<e),l(1,1),h(2),g=v()}else l(0,r),l(1<<-n,0),g=v()+u.call("0",f);return g=f>0?d+((c=g.length)<=f?"0."+u.call("0",f-c)+g:g.slice(0,c-f)+"."+g.slice(c-f)):d+g}})},function(t,n,r){"use strict";var e=r(0),i=r(3),o=r(101),u=1..toPrecision;e(e.P+e.F*(i(function(){return"1"!==u.call(1,void 0)})||!i(function(){u.call({})})),"Number",{toPrecision:function(t){var n=o(this,"Number#toPrecision: incorrect invocation!");return void 0===t?u.call(n):u.call(n,t)}})},function(t,n,r){var e=r(0);e(e.S,"Number",{EPSILON:Math.pow(2,-52)})},function(t,n,r){var e=r(0),i=r(2).isFinite;e(e.S,"Number",{isFinite:function(t){return"number"==typeof t&&i(t)}})},function(t,n,r){var e=r(0);e(e.S,"Number",{isInteger:r(102)})},function(t,n,r){var e=r(0);e(e.S,"Number",{isNaN:function(t){return t!=t}})},function(t,n,r){var e=r(0),i=r(102),o=Math.abs;e(e.S,"Number",{isSafeInteger:function(t){return i(t)&&o(t)<=9007199254740991}})},function(t,n,r){var e=r(0);e(e.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},function(t,n,r){var e=r(0);e(e.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},function(t,n,r){var e=r(0),i=r(100);e(e.S+e.F*(Number.parseFloat!=i),"Number",{parseFloat:i})},function(t,n,r){var e=r(0),i=r(99);e(e.S+e.F*(Number.parseInt!=i),"Number",{parseInt:i})},function(t,n,r){var e=r(0),i=r(103),o=Math.sqrt,u=Math.acosh;e(e.S+e.F*!(u&&710==Math.floor(u(Number.MAX_VALUE))&&u(1/0)==1/0),"Math",{acosh:function(t){return(t=+t)<1?NaN:t>94906265.62425156?Math.log(t)+Math.LN2:i(t-1+o(t-1)*o(t+1))}})},function(t,n,r){var e=r(0),i=Math.asinh;e(e.S+e.F*!(i&&1/i(0)>0),"Math",{asinh:function t(n){return isFinite(n=+n)&&0!=n?n<0?-t(-n):Math.log(n+Math.sqrt(n*n+1)):n}})},function(t,n,r){var e=r(0),i=Math.atanh;e(e.S+e.F*!(i&&1/i(-0)<0),"Math",{atanh:function(t){return 0==(t=+t)?t:Math.log((1+t)/(1-t))/2}})},function(t,n,r){var e=r(0),i=r(74);e(e.S,"Math",{cbrt:function(t){return i(t=+t)*Math.pow(Math.abs(t),1/3)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{clz32:function(t){return(t>>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},function(t,n,r){var e=r(0),i=Math.exp;e(e.S,"Math",{cosh:function(t){return(i(t=+t)+i(-t))/2}})},function(t,n,r){var e=r(0),i=r(75);e(e.S+e.F*(i!=Math.expm1),"Math",{expm1:i})},function(t,n,r){var e=r(0);e(e.S,"Math",{fround:r(104)})},function(t,n,r){var e=r(0),i=Math.abs;e(e.S,"Math",{hypot:function(t,n){for(var r,e,o=0,u=0,c=arguments.length,a=0;u<c;)a<(r=i(arguments[u++]))?(o=o*(e=a/r)*e+1,a=r):o+=r>0?(e=r/a)*e:r;return a===1/0?1/0:a*Math.sqrt(o)}})},function(t,n,r){var e=r(0),i=Math.imul;e(e.S+e.F*r(3)(function(){return-5!=i(4294967295,5)||2!=i.length}),"Math",{imul:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e;return 0|i*o+((65535&r>>>16)*o+i*(65535&e>>>16)<<16>>>0)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{log10:function(t){return Math.log(t)*Math.LOG10E}})},function(t,n,r){var e=r(0);e(e.S,"Math",{log1p:r(103)})},function(t,n,r){var e=r(0);e(e.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},function(t,n,r){var e=r(0);e(e.S,"Math",{sign:r(74)})},function(t,n,r){var e=r(0),i=r(75),o=Math.exp;e(e.S+e.F*r(3)(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(i(t)-i(-t))/2:(o(t-1)-o(-t-1))*(Math.E/2)}})},function(t,n,r){var e=r(0),i=r(75),o=Math.exp;e(e.S,"Math",{tanh:function(t){var n=i(t=+t),r=i(-t);return n==1/0?1:r==1/0?-1:(n-r)/(o(t)+o(-t))}})},function(t,n,r){var e=r(0);e(e.S,"Math",{trunc:function(t){return(t>0?Math.floor:Math.ceil)(t)}})},function(t,n,r){var e=r(0),i=r(35),o=String.fromCharCode,u=String.fromCodePoint;e(e.S+e.F*(!!u&&1!=u.length),"String",{fromCodePoint:function(t){for(var n,r=[],e=arguments.length,u=0;e>u;){if(n=+arguments[u++],i(n,1114111)!==n)throw RangeError(n+" is not a valid code point");r.push(n<65536?o(n):o(55296+((n-=65536)>>10),n%1024+56320))}return r.join("")}})},function(t,n,r){var e=r(0),i=r(15),o=r(8);e(e.S,"String",{raw:function(t){for(var n=i(t.raw),r=o(n.length),e=arguments.length,u=[],c=0;r>c;)u.push(String(n[c++])),c<e&&u.push(String(arguments[c]));return u.join("")}})},function(t,n,r){"use strict";r(43)("trim",function(t){return function(){return t(this,3)}})},function(t,n,r){"use strict";var e=r(76)(!0);r(77)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},function(t,n,r){"use strict";var e=r(0),i=r(76)(!1);e(e.P,"String",{codePointAt:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(0),i=r(8),o=r(79),u="".endsWith;e(e.P+e.F*r(80)("endsWith"),"String",{endsWith:function(t){var n=o(this,t,"endsWith"),r=arguments.length>1?arguments[1]:void 0,e=i(n.length),c=void 0===r?e:Math.min(i(r),e),a=String(t);return u?u.call(n,a,c):n.slice(c-a.length,c)===a}})},function(t,n,r){"use strict";var e=r(0),i=r(79);e(e.P+e.F*r(80)("includes"),"String",{includes:function(t){return!!~i(this,t,"includes").indexOf(t,arguments.length>1?arguments[1]:void 0)}})},function(t,n,r){var e=r(0);e(e.P,"String",{repeat:r(73)})},function(t,n,r){"use strict";var e=r(0),i=r(8),o=r(79),u="".startsWith;e(e.P+e.F*r(80)("startsWith"),"String",{startsWith:function(t){var n=o(this,t,"startsWith"),r=i(Math.min(arguments.length>1?arguments[1]:void 0,n.length)),e=String(t);return u?u.call(n,e,r):n.slice(r,r+e.length)===e}})},function(t,n,r){"use strict";r(13)("anchor",function(t){return function(n){return t(this,"a","name",n)}})},function(t,n,r){"use strict";r(13)("big",function(t){return function(){return t(this,"big","","")}})},function(t,n,r){"use strict";r(13)("blink",function(t){return function(){return t(this,"blink","","")}})},function(t,n,r){"use strict";r(13)("bold",function(t){return function(){return t(this,"b","","")}})},function(t,n,r){"use strict";r(13)("fixed",function(t){return function(){return t(this,"tt","","")}})},function(t,n,r){"use strict";r(13)("fontcolor",function(t){return function(n){return t(this,"font","color",n)}})},function(t,n,r){"use strict";r(13)("fontsize",function(t){return function(n){return t(this,"font","size",n)}})},function(t,n,r){"use strict";r(13)("italics",function(t){return function(){return t(this,"i","","")}})},function(t,n,r){"use strict";r(13)("link",function(t){return function(n){return t(this,"a","href",n)}})},function(t,n,r){"use strict";r(13)("small",function(t){return function(){return t(this,"small","","")}})},function(t,n,r){"use strict";r(13)("strike",function(t){return function(){return t(this,"strike","","")}})},function(t,n,r){"use strict";r(13)("sub",function(t){return function(){return t(this,"sub","","")}})},function(t,n,r){"use strict";r(13)("sup",function(t){return function(){return t(this,"sup","","")}})},function(t,n,r){var e=r(0);e(e.S,"Date",{now:function(){return(new Date).getTime()}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(22);e(e.P+e.F*r(3)(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(t){var n=i(this),r=o(n);return"number"!=typeof r||isFinite(r)?n.toISOString():null}})},function(t,n,r){var e=r(0),i=r(210);e(e.P+e.F*(Date.prototype.toISOString!==i),"Date",{toISOString:i})},function(t,n,r){"use strict";var e=r(3),i=Date.prototype.getTime,o=Date.prototype.toISOString,u=function(t){return t>9?t:"0"+t};t.exports=e(function(){return"0385-07-25T07:06:39.999Z"!=o.call(new Date(-5e13-1))})||!e(function(){o.call(new Date(NaN))})?function(){if(!isFinite(i.call(this)))throw RangeError("Invalid time value");var t=this,n=t.getUTCFullYear(),r=t.getUTCMilliseconds(),e=n<0?"-":n>9999?"+":"";return e+("00000"+Math.abs(n)).slice(e?-6:-4)+"-"+u(t.getUTCMonth()+1)+"-"+u(t.getUTCDate())+"T"+u(t.getUTCHours())+":"+u(t.getUTCMinutes())+":"+u(t.getUTCSeconds())+"."+(r>99?r:"0"+u(r))+"Z"}:o},function(t,n,r){var e=Date.prototype,i=e.toString,o=e.getTime;new Date(NaN)+""!="Invalid Date"&&r(12)(e,"toString",function(){var t=o.call(this);return t==t?i.call(this):"Invalid Date"})},function(t,n,r){var e=r(5)("toPrimitive"),i=Date.prototype;e in i||r(11)(i,e,r(213))},function(t,n,r){"use strict";var e=r(1),i=r(22);t.exports=function(t){if("string"!==t&&"number"!==t&&"default"!==t)throw TypeError("Incorrect hint");return i(e(this),"number"!=t)}},function(t,n,r){var e=r(0);e(e.S,"Array",{isArray:r(52)})},function(t,n,r){"use strict";var e=r(19),i=r(0),o=r(9),u=r(105),c=r(81),a=r(8),f=r(82),s=r(83);i(i.S+i.F*!r(54)(function(t){Array.from(t)}),"Array",{from:function(t){var n,r,i,l,h=o(t),v="function"==typeof this?this:Array,p=arguments.length,d=p>1?arguments[1]:void 0,g=void 0!==d,y=0,m=s(h);if(g&&(d=e(d,p>2?arguments[2]:void 0,2)),null==m||v==Array&&c(m))for(r=new v(n=a(h.length));n>y;y++)f(r,y,g?d(h[y],y):h[y]);else for(l=m.call(h),r=new v;!(i=l.next()).done;y++)f(r,y,g?u(l,d,[i.value,y],!0):i.value);return r.length=y,r}})},function(t,n,r){"use strict";var e=r(0),i=r(82);e(e.S+e.F*r(3)(function(){function t(){}return!(Array.of.call(t)instanceof t)}),"Array",{of:function(){for(var t=0,n=arguments.length,r=new("function"==typeof this?this:Array)(n);n>t;)i(r,t,arguments[t++]);return r.length=n,r}})},function(t,n,r){"use strict";var e=r(0),i=r(15),o=[].join;e(e.P+e.F*(r(46)!=Object||!r(21)(o)),"Array",{join:function(t){return o.call(i(this),void 0===t?",":t)}})},function(t,n,r){"use strict";var e=r(0),i=r(69),o=r(20),u=r(35),c=r(8),a=[].slice;e(e.P+e.F*r(3)(function(){i&&a.call(i)}),"Array",{slice:function(t,n){var r=c(this.length),e=o(this);if(n=void 0===n?r:n,"Array"==e)return a.call(this,t,n);for(var i=u(t,r),f=u(n,r),s=c(f-i),l=new Array(s),h=0;h<s;h++)l[h]="String"==e?this.charAt(i+h):this[i+h];return l}})},function(t,n,r){"use strict";var e=r(0),i=r(10),o=r(9),u=r(3),c=[].sort,a=[1,2,3];e(e.P+e.F*(u(function(){a.sort(void 0)})||!u(function(){a.sort(null)})||!r(21)(c)),"Array",{sort:function(t){return void 0===t?c.call(o(this)):c.call(o(this),i(t))}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(0),o=r(21)([].forEach,!0);e(e.P+e.F*!o,"Array",{forEach:function(t){return i(this,t,arguments[1])}})},function(t,n,r){var e=r(4),i=r(52),o=r(5)("species");t.exports=function(t){var n;return i(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!i(n.prototype)||(n=void 0),e(n)&&null===(n=n[o])&&(n=void 0)),void 0===n?Array:n}},function(t,n,r){"use strict";var e=r(0),i=r(26)(1);e(e.P+e.F*!r(21)([].map,!0),"Array",{map:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(2);e(e.P+e.F*!r(21)([].filter,!0),"Array",{filter:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(3);e(e.P+e.F*!r(21)([].some,!0),"Array",{some:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(4);e(e.P+e.F*!r(21)([].every,!0),"Array",{every:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(106);e(e.P+e.F*!r(21)([].reduce,!0),"Array",{reduce:function(t){return i(this,t,arguments.length,arguments[1],!1)}})},function(t,n,r){"use strict";var e=r(0),i=r(106);e(e.P+e.F*!r(21)([].reduceRight,!0),"Array",{reduceRight:function(t){return i(this,t,arguments.length,arguments[1],!0)}})},function(t,n,r){"use strict";var e=r(0),i=r(50)(!1),o=[].indexOf,u=!!o&&1/[1].indexOf(1,-0)<0;e(e.P+e.F*(u||!r(21)(o)),"Array",{indexOf:function(t){return u?o.apply(this,arguments)||0:i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(15),o=r(24),u=r(8),c=[].lastIndexOf,a=!!c&&1/[1].lastIndexOf(1,-0)<0;e(e.P+e.F*(a||!r(21)(c)),"Array",{lastIndexOf:function(t){if(a)return c.apply(this,arguments)||0;var n=i(this),r=u(n.length),e=r-1;for(arguments.length>1&&(e=Math.min(e,o(arguments[1]))),e<0&&(e=r+e);e>=0;e--)if(e in n&&n[e]===t)return e||0;return-1}})},function(t,n,r){var e=r(0);e(e.P,"Array",{copyWithin:r(107)}),r(31)("copyWithin")},function(t,n,r){var e=r(0);e(e.P,"Array",{fill:r(85)}),r(31)("fill")},function(t,n,r){"use strict";var e=r(0),i=r(26)(5),o=!0;"find"in[]&&Array(1).find(function(){o=!1}),e(e.P+e.F*o,"Array",{find:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(31)("find")},function(t,n,r){"use strict";var e=r(0),i=r(26)(6),o="findIndex",u=!0;o in[]&&Array(1)[o](function(){u=!1}),e(e.P+e.F*u,"Array",{findIndex:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(31)(o)},function(t,n,r){r(38)("Array")},function(t,n,r){var e=r(2),i=r(72),o=r(7).f,u=r(37).f,c=r(53),a=r(55),f=e.RegExp,s=f,l=f.prototype,h=/a/g,v=/a/g,p=new f(h)!==h;if(r(6)&&(!p||r(3)(function(){return v[r(5)("match")]=!1,f(h)!=h||f(v)==v||"/a/i"!=f(h,"i")}))){f=function(t,n){var r=this instanceof f,e=c(t),o=void 0===n;return!r&&e&&t.constructor===f&&o?t:i(p?new s(e&&!o?t.source:t,n):s((e=t instanceof f)?t.source:t,e&&o?a.call(t):n),r?this:l,f)};for(var d=function(t){t in f||o(f,t,{configurable:!0,get:function(){return s[t]},set:function(n){s[t]=n}})},g=u(s),y=0;g.length>y;)d(g[y++]);l.constructor=f,f.prototype=l,r(12)(e,"RegExp",f)}r(38)("RegExp")},function(t,n,r){"use strict";r(109);var e=r(1),i=r(55),o=r(6),u=/./.toString,c=function(t){r(12)(RegExp.prototype,"toString",t,!0)};r(3)(function(){return"/a/b"!=u.call({source:"a",flags:"b"})})?c(function(){var t=e(this);return"/".concat(t.source,"/","flags"in t?t.flags:!o&&t instanceof RegExp?i.call(t):void 0)}):"toString"!=u.name&&c(function(){return u.call(this)})},function(t,n,r){r(56)("match",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=null==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(56)("replace",2,function(t,n,r){return[function(e,i){"use strict";var o=t(this),u=null==e?void 0:e[n];return void 0!==u?u.call(e,o,i):r.call(String(o),e,i)},r]})},function(t,n,r){r(56)("search",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=null==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(56)("split",2,function(t,n,e){"use strict";var i=r(53),o=e,u=[].push;if("c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length){var c=void 0===/()??/.exec("")[1];e=function(t,n){var r=String(this);if(void 0===t&&0===n)return[];if(!i(t))return o.call(r,t,n);var e,a,f,s,l,h=[],v=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),p=0,d=void 0===n?4294967295:n>>>0,g=new RegExp(t.source,v+"g");for(c||(e=new RegExp("^"+g.source+"$(?!\\s)",v));(a=g.exec(r))&&!((f=a.index+a[0].length)>p&&(h.push(r.slice(p,a.index)),!c&&a.length>1&&a[0].replace(e,function(){for(l=1;l<arguments.length-2;l++)void 0===arguments[l]&&(a[l]=void 0)}),a.length>1&&a.index<r.length&&u.apply(h,a.slice(1)),s=a[0].length,p=f,h.length>=d));)g.lastIndex===a.index&&g.lastIndex++;return p===r.length?!s&&g.test("")||h.push(""):h.push(r.slice(p)),h.length>d?h.slice(0,d):h}}else"0".split(void 0,0).length&&(e=function(t,n){return void 0===t&&0===n?[]:o.call(this,t,n)});return[function(r,i){var o=t(this),u=null==r?void 0:r[n];return void 0!==u?u.call(r,o,i):e.call(String(o),r,i)},e]})},function(t,n,r){"use strict";var e,i,o,u,c=r(30),a=r(2),f=r(19),s=r(48),l=r(0),h=r(4),v=r(10),p=r(39),d=r(40),g=r(57),y=r(87).set,m=r(88)(),b=r(89),w=r(110),S=r(58),x=r(111),_=a.TypeError,E=a.process,O=E&&E.versions,M=O&&O.v8||"",P=a.Promise,F="process"==s(E),A=function(){},j=i=b.f,I=!!function(){try{var t=P.resolve(1),n=(t.constructor={})[r(5)("species")]=function(t){t(A,A)};return(F||"function"==typeof PromiseRejectionEvent)&&t.then(A)instanceof n&&0!==M.indexOf("6.6")&&-1===S.indexOf("Chrome/66")}catch(t){}}(),N=function(t){var n;return!(!h(t)||"function"!=typeof(n=t.then))&&n},L=function(t,n){if(!t._n){t._n=!0;var r=t._c;m(function(){for(var e=t._v,i=1==t._s,o=0,u=function(n){var r,o,u,c=i?n.ok:n.fail,a=n.resolve,f=n.reject,s=n.domain;try{c?(i||(2==t._h&&R(t),t._h=1),!0===c?r=e:(s&&s.enter(),r=c(e),s&&(s.exit(),u=!0)),r===n.promise?f(_("Promise-chain cycle")):(o=N(r))?o.call(r,a,f):a(r)):f(e)}catch(t){s&&!u&&s.exit(),f(t)}};r.length>o;)u(r[o++]);t._c=[],t._n=!1,n&&!t._h&&T(t)})}},T=function(t){y.call(a,function(){var n,r,e,i=t._v,o=k(t);if(o&&(n=w(function(){F?E.emit("unhandledRejection",i,t):(r=a.onunhandledrejection)?r({promise:t,reason:i}):(e=a.console)&&e.error&&e.error("Unhandled promise rejection",i)}),t._h=F||k(t)?2:1),t._a=void 0,o&&n.e)throw n.v})},k=function(t){return 1!==t._h&&0===(t._a||t._c).length},R=function(t){y.call(a,function(){var n;F?E.emit("rejectionHandled",t):(n=a.onrejectionhandled)&&n({promise:t,reason:t._v})})},C=function(t){var n=this;n._d||(n._d=!0,(n=n._w||n)._v=t,n._s=2,n._a||(n._a=n._c.slice()),L(n,!0))},D=function(t){var n,r=this;if(!r._d){r._d=!0,r=r._w||r;try{if(r===t)throw _("Promise can't be resolved itself");(n=N(t))?m(function(){var e={_w:r,_d:!1};try{n.call(t,f(D,e,1),f(C,e,1))}catch(t){C.call(e,t)}}):(r._v=t,r._s=1,L(r,!1))}catch(t){C.call({_w:r,_d:!1},t)}}};I||(P=function(t){p(this,P,"Promise","_h"),v(t),e.call(this);try{t(f(D,this,1),f(C,this,1))}catch(t){C.call(this,t)}},(e=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1}).prototype=r(41)(P.prototype,{then:function(t,n){var r=j(g(this,P));return r.ok="function"!=typeof t||t,r.fail="function"==typeof n&&n,r.domain=F?E.domain:void 0,this._c.push(r),this._a&&this._a.push(r),this._s&&L(this,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),o=function(){var t=new e;this.promise=t,this.resolve=f(D,t,1),this.reject=f(C,t,1)},b.f=j=function(t){return t===P||t===u?new o(t):i(t)}),l(l.G+l.W+l.F*!I,{Promise:P}),r(42)(P,"Promise"),r(38)("Promise"),u=r(18).Promise,l(l.S+l.F*!I,"Promise",{reject:function(t){var n=j(this);return(0,n.reject)(t),n.promise}}),l(l.S+l.F*(c||!I),"Promise",{resolve:function(t){return x(c&&this===u?P:this,t)}}),l(l.S+l.F*!(I&&r(54)(function(t){P.all(t).catch(A)})),"Promise",{all:function(t){var n=this,r=j(n),e=r.resolve,i=r.reject,o=w(function(){var r=[],o=0,u=1;d(t,!1,function(t){var c=o++,a=!1;r.push(void 0),u++,n.resolve(t).then(function(t){a||(a=!0,r[c]=t,--u||e(r))},i)}),--u||e(r)});return o.e&&i(o.v),r.promise},race:function(t){var n=this,r=j(n),e=r.reject,i=w(function(){d(t,!1,function(t){n.resolve(t).then(r.resolve,e)})});return i.e&&e(i.v),r.promise}})},function(t,n,r){"use strict";var e=r(116),i=r(45);r(59)("WeakSet",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(i(this,"WeakSet"),t,!0)}},e,!1,!0)},function(t,n,r){"use strict";var e=r(0),i=r(60),o=r(90),u=r(1),c=r(35),a=r(8),f=r(4),s=r(2).ArrayBuffer,l=r(57),h=o.ArrayBuffer,v=o.DataView,p=i.ABV&&s.isView,d=h.prototype.slice,g=i.VIEW;e(e.G+e.W+e.F*(s!==h),{ArrayBuffer:h}),e(e.S+e.F*!i.CONSTR,"ArrayBuffer",{isView:function(t){return p&&p(t)||f(t)&&g in t}}),e(e.P+e.U+e.F*r(3)(function(){return!new h(2).slice(1,void 0).byteLength}),"ArrayBuffer",{slice:function(t,n){if(void 0!==d&&void 0===n)return d.call(u(this),t);for(var r=u(this).byteLength,e=c(t,r),i=c(void 0===n?r:n,r),o=new(l(this,h))(a(i-e)),f=new v(this),s=new v(o),p=0;e<i;)s.setUint8(p++,f.getUint8(e++));return o}}),r(38)("ArrayBuffer")},function(t,n,r){var e=r(0);e(e.G+e.W+e.F*!r(60).ABV,{DataView:r(90).DataView})},function(t,n,r){r(27)("Int8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}},!0)},function(t,n,r){r(27)("Int16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Int32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Float32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Float64",8,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){var e=r(0),i=r(10),o=r(1),u=(r(2).Reflect||{}).apply,c=Function.apply;e(e.S+e.F*!r(3)(function(){u(function(){})}),"Reflect",{apply:function(t,n,r){var e=i(t),a=o(r);return u?u(e,n,a):c.call(e,n,a)}})},function(t,n,r){var e=r(0),i=r(36),o=r(10),u=r(1),c=r(4),a=r(3),f=r(97),s=(r(2).Reflect||{}).construct,l=a(function(){function t(){}return!(s(function(){},[],t)instanceof t)}),h=!a(function(){s(function(){})});e(e.S+e.F*(l||h),"Reflect",{construct:function(t,n){o(t),u(n);var r=arguments.length<3?t:o(arguments[2]);if(h&&!l)return s(t,n,r);if(t==r){switch(n.length){case 0:return new t;case 1:return new t(n[0]);case 2:return new t(n[0],n[1]);case 3:return new t(n[0],n[1],n[2]);case 4:return new t(n[0],n[1],n[2],n[3])}var e=[null];return e.push.apply(e,n),new(f.apply(t,e))}var a=r.prototype,v=i(c(a)?a:Object.prototype),p=Function.apply.call(t,v,n);return c(p)?p:v}})},function(t,n,r){var e=r(7),i=r(0),o=r(1),u=r(22);i(i.S+i.F*r(3)(function(){Reflect.defineProperty(e.f({},1,{value:1}),1,{value:2})}),"Reflect",{defineProperty:function(t,n,r){o(t),n=u(n,!0),o(r);try{return e.f(t,n,r),!0}catch(t){return!1}}})},function(t,n,r){var e=r(0),i=r(16).f,o=r(1);e(e.S,"Reflect",{deleteProperty:function(t,n){var r=i(o(t),n);return!(r&&!r.configurable)&&delete t[n]}})},function(t,n,r){"use strict";var e=r(0),i=r(1),o=function(t){this._t=i(t),this._i=0;var n,r=this._k=[];for(n in t)r.push(n)};r(78)(o,"Object",function(){var t,n=this._k;do{if(this._i>=n.length)return{value:void 0,done:!0}}while(!((t=n[this._i++])in this._t));return{value:t,done:!1}}),e(e.S,"Reflect",{enumerate:function(t){return new o(t)}})},function(t,n,r){var e=r(16),i=r(17),o=r(14),u=r(0),c=r(4),a=r(1);u(u.S,"Reflect",{get:function t(n,r){var u,f,s=arguments.length<3?n:arguments[2];return a(n)===s?n[r]:(u=e.f(n,r))?o(u,"value")?u.value:void 0!==u.get?u.get.call(s):void 0:c(f=i(n))?t(f,r,s):void 0}})},function(t,n,r){var e=r(16),i=r(0),o=r(1);i(i.S,"Reflect",{getOwnPropertyDescriptor:function(t,n){return e.f(o(t),n)}})},function(t,n,r){var e=r(0),i=r(17),o=r(1);e(e.S,"Reflect",{getPrototypeOf:function(t){return i(o(t))}})},function(t,n,r){var e=r(0);e(e.S,"Reflect",{has:function(t,n){return n in t}})},function(t,n,r){var e=r(0),i=r(1),o=Object.isExtensible;e(e.S,"Reflect",{isExtensible:function(t){return i(t),!o||o(t)}})},function(t,n,r){var e=r(0);e(e.S,"Reflect",{ownKeys:r(118)})},function(t,n,r){var e=r(0),i=r(1),o=Object.preventExtensions;e(e.S,"Reflect",{preventExtensions:function(t){i(t);try{return o&&o(t),!0}catch(t){return!1}}})},function(t,n,r){var e=r(7),i=r(16),o=r(17),u=r(14),c=r(0),a=r(32),f=r(1),s=r(4);c(c.S,"Reflect",{set:function t(n,r,c){var l,h,v=arguments.length<4?n:arguments[3],p=i.f(f(n),r);if(!p){if(s(h=o(n)))return t(h,r,c,v);p=a(0)}if(u(p,"value")){if(!1===p.writable||!s(v))return!1;if(l=i.f(v,r)){if(l.get||l.set||!1===l.writable)return!1;l.value=c,e.f(v,r,l)}else e.f(v,r,a(0,c));return!0}return void 0!==p.set&&(p.set.call(v,c),!0)}})},function(t,n,r){var e=r(0),i=r(70);i&&e(e.S,"Reflect",{setPrototypeOf:function(t,n){i.check(t,n);try{return i.set(t,n),!0}catch(t){return!1}}})},function(t,n,r){"use strict";var e=r(0),i=r(50)(!0);e(e.P,"Array",{includes:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(31)("includes")},function(t,n,r){"use strict";var e=r(0),i=r(119),o=r(9),u=r(8),c=r(10),a=r(84);e(e.P,"Array",{flatMap:function(t){var n,r,e=o(this);return c(t),n=u(e.length),r=a(e,0),i(r,e,e,n,0,1,t,arguments[1]),r}}),r(31)("flatMap")},function(t,n,r){"use strict";var e=r(0),i=r(119),o=r(9),u=r(8),c=r(24),a=r(84);e(e.P,"Array",{flatten:function(){var t=arguments[0],n=o(this),r=u(n.length),e=a(n,0);return i(e,n,n,r,0,void 0===t?1:c(t)),e}}),r(31)("flatten")},function(t,n,r){"use strict";var e=r(0),i=r(76)(!0);e(e.P,"String",{at:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(0),i=r(120),o=r(58);e(e.P+e.F*/Version\/10\.\d+(\.\d+)? Safari\//.test(o),"String",{padStart:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!0)}})},function(t,n,r){"use strict";var e=r(0),i=r(120),o=r(58);e(e.P+e.F*/Version\/10\.\d+(\.\d+)? Safari\//.test(o),"String",{padEnd:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!1)}})},function(t,n,r){"use strict";r(43)("trimLeft",function(t){return function(){return t(this,1)}},"trimStart")},function(t,n,r){"use strict";r(43)("trimRight",function(t){return function(){return t(this,2)}},"trimEnd")},function(t,n,r){"use strict";var e=r(0),i=r(23),o=r(8),u=r(53),c=r(55),a=RegExp.prototype,f=function(t,n){this._r=t,this._s=n};r(78)(f,"RegExp String",function(){var t=this._r.exec(this._s);return{value:t,done:null===t}}),e(e.P,"String",{matchAll:function(t){if(i(this),!u(t))throw TypeError(t+" is not a regexp!");var n=String(this),r="flags"in a?String(t.flags):c.call(t),e=new RegExp(t.source,~r.indexOf("g")?r:"g"+r);return e.lastIndex=o(t.lastIndex),new f(e,n)}})},function(t,n,r){r(66)("asyncIterator")},function(t,n,r){r(66)("observable")},function(t,n,r){var e=r(0),i=r(118),o=r(15),u=r(16),c=r(82);e(e.S,"Object",{getOwnPropertyDescriptors:function(t){for(var n,r,e=o(t),a=u.f,f=i(e),s={},l=0;f.length>l;)void 0!==(r=a(e,n=f[l++]))&&c(s,n,r);return s}})},function(t,n,r){var e=r(0),i=r(121)(!1);e(e.S,"Object",{values:function(t){return i(t)}})},function(t,n,r){var e=r(0),i=r(121)(!0);e(e.S,"Object",{entries:function(t){return i(t)}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(10),u=r(7);r(6)&&e(e.P+r(61),"Object",{__defineGetter__:function(t,n){u.f(i(this),t,{get:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(10),u=r(7);r(6)&&e(e.P+r(61),"Object",{__defineSetter__:function(t,n){u.f(i(this),t,{set:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(22),u=r(17),c=r(16).f;r(6)&&e(e.P+r(61),"Object",{__lookupGetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.get}while(r=u(r))}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(22),u=r(17),c=r(16).f;r(6)&&e(e.P+r(61),"Object",{__lookupSetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.set}while(r=u(r))}})},function(t,n,r){var e=r(0);e(e.P+e.R,"Map",{toJSON:r(122)("Map")})},function(t,n,r){var e=r(0);e(e.P+e.R,"Set",{toJSON:r(122)("Set")})},function(t,n,r){r(62)("Map")},function(t,n,r){r(62)("Set")},function(t,n,r){r(62)("WeakMap")},function(t,n,r){r(62)("WeakSet")},function(t,n,r){r(63)("Map")},function(t,n,r){r(63)("Set")},function(t,n,r){r(63)("WeakMap")},function(t,n,r){r(63)("WeakSet")},function(t,n,r){var e=r(0);e(e.G,{global:r(2)})},function(t,n,r){var e=r(0);e(e.S,"System",{global:r(2)})},function(t,n,r){var e=r(0),i=r(20);e(e.S,"Error",{isError:function(t){return"Error"===i(t)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{clamp:function(t,n,r){return Math.min(r,Math.max(n,t))}})},function(t,n,r){var e=r(0);e(e.S,"Math",{DEG_PER_RAD:Math.PI/180})},function(t,n,r){var e=r(0),i=180/Math.PI;e(e.S,"Math",{degrees:function(t){return t*i}})},function(t,n,r){var e=r(0),i=r(124),o=r(104);e(e.S,"Math",{fscale:function(t,n,r,e,u){return o(i(t,n,r,e,u))}})},function(t,n,r){var e=r(0);e(e.S,"Math",{iaddh:function(t,n,r,e){var i=t>>>0,o=r>>>0;return(n>>>0)+(e>>>0)+((i&o|(i|o)&~(i+o>>>0))>>>31)|0}})},function(t,n,r){var e=r(0);e(e.S,"Math",{isubh:function(t,n,r,e){var i=t>>>0,o=r>>>0;return(n>>>0)-(e>>>0)-((~i&o|~(i^o)&i-o>>>0)>>>31)|0}})},function(t,n,r){var e=r(0);e(e.S,"Math",{imulh:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e,u=r>>16,c=e>>16,a=(u*o>>>0)+(i*o>>>16);return u*c+(a>>16)+((i*c>>>0)+(65535&a)>>16)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{RAD_PER_DEG:180/Math.PI})},function(t,n,r){var e=r(0),i=Math.PI/180;e(e.S,"Math",{radians:function(t){return t*i}})},function(t,n,r){var e=r(0);e(e.S,"Math",{scale:r(124)})},function(t,n,r){var e=r(0);e(e.S,"Math",{umulh:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e,u=r>>>16,c=e>>>16,a=(u*o>>>0)+(i*o>>>16);return u*c+(a>>>16)+((i*c>>>0)+(65535&a)>>>16)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{signbit:function(t){return(t=+t)!=t?t:0==t?1/t==1/0:t>0}})},function(t,n,r){"use strict";var e=r(0),i=r(18),o=r(2),u=r(57),c=r(111);e(e.P+e.R,"Promise",{finally:function(t){var n=u(this,i.Promise||o.Promise),r="function"==typeof t;return this.then(r?function(r){return c(n,t()).then(function(){return r})}:t,r?function(r){return c(n,t()).then(function(){throw r})}:t)}})},function(t,n,r){"use strict";var e=r(0),i=r(89),o=r(110);e(e.S,"Promise",{try:function(t){var n=i.f(this),r=o(t);return(r.e?n.reject:n.resolve)(r.v),n.promise}})},function(t,n,r){var e=r(28),i=r(1),o=e.key,u=e.set;e.exp({defineMetadata:function(t,n,r,e){u(t,n,i(r),o(e))}})},function(t,n,r){var e=r(28),i=r(1),o=e.key,u=e.map,c=e.store;e.exp({deleteMetadata:function(t,n){var r=arguments.length<3?void 0:o(arguments[2]),e=u(i(n),r,!1);if(void 0===e||!e.delete(t))return!1;if(e.size)return!0;var a=c.get(n);return a.delete(r),!!a.size||c.delete(n)}})},function(t,n,r){var e=r(28),i=r(1),o=r(17),u=e.has,c=e.get,a=e.key,f=function(t,n,r){if(u(t,n,r))return c(t,n,r);var e=o(n);return null!==e?f(t,e,r):void 0};e.exp({getMetadata:function(t,n){return f(t,i(n),arguments.length<3?void 0:a(arguments[2]))}})},function(t,n,r){var e=r(114),i=r(123),o=r(28),u=r(1),c=r(17),a=o.keys,f=o.key,s=function(t,n){var r=a(t,n),o=c(t);if(null===o)return r;var u=s(o,n);return u.length?r.length?i(new e(r.concat(u))):u:r};o.exp({getMetadataKeys:function(t){return s(u(t),arguments.length<2?void 0:f(arguments[1]))}})},function(t,n,r){var e=r(28),i=r(1),o=e.get,u=e.key;e.exp({getOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(28),i=r(1),o=e.keys,u=e.key;e.exp({getOwnMetadataKeys:function(t){return o(i(t),arguments.length<2?void 0:u(arguments[1]))}})},function(t,n,r){var e=r(28),i=r(1),o=r(17),u=e.has,c=e.key,a=function(t,n,r){if(u(t,n,r))return!0;var e=o(n);return null!==e&&a(t,e,r)};e.exp({hasMetadata:function(t,n){return a(t,i(n),arguments.length<3?void 0:c(arguments[2]))}})},function(t,n,r){var e=r(28),i=r(1),o=e.has,u=e.key;e.exp({hasOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(28),i=r(1),o=r(10),u=e.key,c=e.set;e.exp({metadata:function(t,n){return function(r,e){c(t,n,(void 0!==e?i:o)(r),u(e))}}})},function(t,n,r){var e=r(0),i=r(88)(),o=r(2).process,u="process"==r(20)(o);e(e.G,{asap:function(t){var n=u&&o.domain;i(n?n.bind(t):t)}})},function(t,n,r){"use strict";var e=r(0),i=r(2),o=r(18),u=r(88)(),c=r(5)("observable"),a=r(10),f=r(1),s=r(39),l=r(41),h=r(11),v=r(40),p=v.RETURN,d=function(t){return null==t?void 0:a(t)},g=function(t){var n=t._c;n&&(t._c=void 0,n())},y=function(t){return void 0===t._o},m=function(t){y(t)||(t._o=void 0,g(t))},b=function(t,n){f(t),this._c=void 0,this._o=t,t=new w(this);try{var r=n(t),e=r;null!=r&&("function"==typeof r.unsubscribe?r=function(){e.unsubscribe()}:a(r),this._c=r)}catch(n){return void t.error(n)}y(this)&&g(this)};b.prototype=l({},{unsubscribe:function(){m(this)}});var w=function(t){this._s=t};w.prototype=l({},{next:function(t){var n=this._s;if(!y(n)){var r=n._o;try{var e=d(r.next);if(e)return e.call(r,t)}catch(t){try{m(n)}finally{throw t}}}},error:function(t){var n=this._s;if(y(n))throw t;var r=n._o;n._o=void 0;try{var e=d(r.error);if(!e)throw t;t=e.call(r,t)}catch(t){try{g(n)}finally{throw t}}return g(n),t},complete:function(t){var n=this._s;if(!y(n)){var r=n._o;n._o=void 0;try{var e=d(r.complete);t=e?e.call(r,t):void 0}catch(t){try{g(n)}finally{throw t}}return g(n),t}}});var S=function(t){s(this,S,"Observable","_f")._f=a(t)};l(S.prototype,{subscribe:function(t){return new b(t,this._f)},forEach:function(t){var n=this;return new(o.Promise||i.Promise)(function(r,e){a(t);var i=n.subscribe({next:function(n){try{return t(n)}catch(t){e(t),i.unsubscribe()}},error:e,complete:r})})}}),l(S,{from:function(t){var n="function"==typeof this?this:S,r=d(f(t)[c]);if(r){var e=f(r.call(t));return e.constructor===n?e:new n(function(t){return e.subscribe(t)})}return new n(function(n){var r=!1;return u(function(){if(!r){try{if(v(t,!1,function(t){if(n.next(t),r)return p})===p)return}catch(t){if(r)throw t;return void n.error(t)}n.complete()}}),function(){r=!0}})},of:function(){for(var t=0,n=arguments.length,r=new Array(n);t<n;)r[t]=arguments[t++];return new("function"==typeof this?this:S)(function(t){var n=!1;return u(function(){if(!n){for(var e=0;e<r.length;++e)if(t.next(r[e]),n)return;t.complete()}}),function(){n=!0}})}}),h(S.prototype,c,function(){return this}),e(e.G,{Observable:S}),r(38)("Observable")},function(t,n,r){var e=r(2),i=r(0),o=r(58),u=[].slice,c=/MSIE .\./.test(o),a=function(t){return function(n,r){var e=arguments.length>2,i=!!e&&u.call(arguments,2);return t(e?function(){("function"==typeof n?n:Function(n)).apply(this,i)}:n,r)}};i(i.G+i.B+i.F*c,{setTimeout:a(e.setTimeout),setInterval:a(e.setInterval)})},function(t,n,r){var e=r(0),i=r(87);e(e.G+e.B,{setImmediate:i.set,clearImmediate:i.clear})},function(t,n,r){for(var e=r(86),i=r(34),o=r(12),u=r(2),c=r(11),a=r(44),f=r(5),s=f("iterator"),l=f("toStringTag"),h=a.Array,v={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},p=i(v),d=0;d<p.length;d++){var g,y=p[d],m=v[y],b=u[y],w=b&&b.prototype;if(w&&(w[s]||c(w,s,h),w[l]||c(w,l,y),a[y]=h,m))for(g in e)w[g]||o(w,g,e[g],!0)}},function(t,n,r){(function(n){!function(n){"use strict";var r,e=Object.prototype,i=e.hasOwnProperty,o="function"==typeof Symbol?Symbol:{},u=o.iterator||"@@iterator",c=o.asyncIterator||"@@asyncIterator",a=o.toStringTag||"@@toStringTag",f="object"==typeof t,s=n.regeneratorRuntime;if(s)f&&(t.exports=s);else{(s=n.regeneratorRuntime=f?t.exports:{}).wrap=w;var l="suspendedStart",h="suspendedYield",v="executing",p="completed",d={},g={};g[u]=function(){return this};var y=Object.getPrototypeOf,m=y&&y(y(I([])));m&&m!==e&&i.call(m,u)&&(g=m);var b=E.prototype=x.prototype=Object.create(g);_.prototype=b.constructor=E,E.constructor=_,E[a]=_.displayName="GeneratorFunction",s.isGeneratorFunction=function(t){var n="function"==typeof t&&t.constructor;return!!n&&(n===_||"GeneratorFunction"===(n.displayName||n.name))},s.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,E):(t.__proto__=E,a in t||(t[a]="GeneratorFunction")),t.prototype=Object.create(b),t},s.awrap=function(t){return{__await:t}},O(M.prototype),M.prototype[c]=function(){return this},s.AsyncIterator=M,s.async=function(t,n,r,e){var i=new M(w(t,n,r,e));return s.isGeneratorFunction(n)?i:i.next().then(function(t){return t.done?t.value:i.next()})},O(b),b[a]="Generator",b[u]=function(){return this},b.toString=function(){return"[object Generator]"},s.keys=function(t){var n=[];for(var r in t)n.push(r);return n.reverse(),function r(){for(;n.length;){var e=n.pop();if(e in t)return r.value=e,r.done=!1,r}return r.done=!0,r}},s.values=I,j.prototype={constructor:j,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=r,this.done=!1,this.delegate=null,this.method="next",this.arg=r,this.tryEntries.forEach(A),!t)for(var n in this)"t"===n.charAt(0)&&i.call(this,n)&&!isNaN(+n.slice(1))&&(this[n]=r)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(t){if(this.done)throw t;var n=this;function e(e,i){return c.type="throw",c.arg=t,n.next=e,i&&(n.method="next",n.arg=r),!!i}for(var o=this.tryEntries.length-1;o>=0;--o){var u=this.tryEntries[o],c=u.completion;if("root"===u.tryLoc)return e("end");if(u.tryLoc<=this.prev){var a=i.call(u,"catchLoc"),f=i.call(u,"finallyLoc");if(a&&f){if(this.prev<u.catchLoc)return e(u.catchLoc,!0);if(this.prev<u.finallyLoc)return e(u.finallyLoc)}else if(a){if(this.prev<u.catchLoc)return e(u.catchLoc,!0)}else{if(!f)throw new Error("try statement without catch or finally");if(this.prev<u.finallyLoc)return e(u.finallyLoc)}}}},abrupt:function(t,n){for(var r=this.tryEntries.length-1;r>=0;--r){var e=this.tryEntries[r];if(e.tryLoc<=this.prev&&i.call(e,"finallyLoc")&&this.prev<e.finallyLoc){var o=e;break}}o&&("break"===t||"continue"===t)&&o.tryLoc<=n&&n<=o.finallyLoc&&(o=null);var u=o?o.completion:{};return u.type=t,u.arg=n,o?(this.method="next",this.next=o.finallyLoc,d):this.complete(u)},complete:function(t,n){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&n&&(this.next=n),d},finish:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),A(r),d}},catch:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc===t){var e=r.completion;if("throw"===e.type){var i=e.arg;A(r)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(t,n,e){return this.delegate={iterator:I(t),resultName:n,nextLoc:e},"next"===this.method&&(this.arg=r),d}}}function w(t,n,r,e){var i=n&&n.prototype instanceof x?n:x,o=Object.create(i.prototype),u=new j(e||[]);return o._invoke=function(t,n,r){var e=l;return function(i,o){if(e===v)throw new Error("Generator is already running");if(e===p){if("throw"===i)throw o;return N()}for(r.method=i,r.arg=o;;){var u=r.delegate;if(u){var c=P(u,r);if(c){if(c===d)continue;return c}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(e===l)throw e=p,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);e=v;var a=S(t,n,r);if("normal"===a.type){if(e=r.done?p:h,a.arg===d)continue;return{value:a.arg,done:r.done}}"throw"===a.type&&(e=p,r.method="throw",r.arg=a.arg)}}}(t,r,u),o}function S(t,n,r){try{return{type:"normal",arg:t.call(n,r)}}catch(t){return{type:"throw",arg:t}}}function x(){}function _(){}function E(){}function O(t){["next","throw","return"].forEach(function(n){t[n]=function(t){return this._invoke(n,t)}})}function M(t){function r(n,e,o,u){var c=S(t[n],t,e);if("throw"!==c.type){var a=c.arg,f=a.value;return f&&"object"==typeof f&&i.call(f,"__await")?Promise.resolve(f.__await).then(function(t){r("next",t,o,u)},function(t){r("throw",t,o,u)}):Promise.resolve(f).then(function(t){a.value=t,o(a)},u)}u(c.arg)}var e;"object"==typeof n.process&&n.process.domain&&(r=n.process.domain.bind(r)),this._invoke=function(t,n){function i(){return new Promise(function(e,i){r(t,n,e,i)})}return e=e?e.then(i,i):i()}}function P(t,n){var e=t.iterator[n.method];if(e===r){if(n.delegate=null,"throw"===n.method){if(t.iterator.return&&(n.method="return",n.arg=r,P(t,n),"throw"===n.method))return d;n.method="throw",n.arg=new TypeError("The iterator does not provide a 'throw' method")}return d}var i=S(e,t.iterator,n.arg);if("throw"===i.type)return n.method="throw",n.arg=i.arg,n.delegate=null,d;var o=i.arg;return o?o.done?(n[t.resultName]=o.value,n.next=t.nextLoc,"return"!==n.method&&(n.method="next",n.arg=r),n.delegate=null,d):o:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,d)}function F(t){var n={tryLoc:t[0]};1 in t&&(n.catchLoc=t[1]),2 in t&&(n.finallyLoc=t[2],n.afterLoc=t[3]),this.tryEntries.push(n)}function A(t){var n=t.completion||{};n.type="normal",delete n.arg,t.completion=n}function j(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(F,this),this.reset(!0)}function I(t){if(t){var n=t[u];if(n)return n.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var e=-1,o=function n(){for(;++e<t.length;)if(i.call(t,e))return n.value=t[e],n.done=!1,n;return n.value=r,n.done=!0,n};return o.next=o}}return{next:N}}function N(){return{value:r,done:!0}}}("object"==typeof n?n:"object"==typeof window?window:"object"==typeof self?self:this)}).call(this,r(64))},function(t,n,r){r(329),t.exports=r(18).RegExp.escape},function(t,n,r){var e=r(0),i=r(330)(/[\\^$*+?.()|[\]{}]/g,"\\$&");e(e.S,"RegExp",{escape:function(t){return i(t)}})},function(t,n){t.exports=function(t,n){var r=n===Object(n)?function(t){return n[t]}:n;return function(n){return String(n).replace(t,r)}}},function(t,n,r){"use strict";r.r(n);var e=r(126),i=r.n(e);r(125);const o=r(332);function u(t,n){var r=document.createElement("iframe");return a[t]=r,r.setAttribute("style","display: none;"),r.setAttribute("src",n),r}function c(t,n){window.parent.Singular=a,document.body.appendChild(t),document.body.appendChild(n)}var a={properties:{singularLocation:"",onIdentityChange:function(){},onLogout:function(){},clientId:"",checkInterval:1e3,uaaLocation:"",storageKey:"singularUserIdentityClaims",authTimeout:2e4,appendFramesOnInit:!1},rpFrameLoaded:!1,singularLocation:function(){if(""!==a.properties.singularLocation)return a.properties.singularLocation;if(document.getElementById("singular_script").src){var t=document.getElementById("singular_script").src;return t.substring(0,t.lastIndexOf("/"))}throw"singularLocation must not be blank"},init:function(t){if(t)for(var n in t)a.properties[n]=t[n];this.validateProperties(t);var r=u("opFrame",a.properties.uaaLocation+"/session_management?clientId="+a.properties.clientId+"&messageOrigin="+encodeURIComponent(window.location.origin)),e=u("rpFrame",this.singularLocation()+"/"+i.a);e.onload=function(){a.rpFrameLoaded=!0},!0===a.properties.appendFramesOnInit?c(r,e):document.addEventListener("DOMContentLoaded",function(){c(r,e)})},validateProperties:function(t){var n=["uaaLocation","clientId"];for(var r in n){var e=n[r];if(!t[e])throw'The "'+e+'" field must be set and not empty'}this.getUaaValidator().isValidUAA(t.uaaLocation)},decodeJwt:function(t){var n=t.split(".")[1].replace("-","+").replace("_","/");return JSON.parse(window.atob(n))},access:function(t){var n=a.rpFrame;return new Promise(function(r,e){var i=function(){n.contentWindow.fetchAccessToken(t,function(t,n){n||null==t?e(n):r(t)})};a.rpFrameLoaded?i():a.rpFrame.addEventListener("load",i)})},getUaaValidator:function(){return this.validator||o},setUaaValidator:function(t){this.validator=t}};n.default=a},function(t,n,r){var e=r(125);function i(t,n){var r=new XMLHttpRequest;r.open("GET",t),r.setRequestHeader("Accept","application/json"),r.send(),r.onreadystatechange=function(){r.readyState==XMLHttpRequest.DONE&&n(r)}}t.exports={isValidUAA:function(t){i(t+"/info",function(n){if(200!==n.status)throw t+" does not appear to be a running UAA instance or may not have a trusted SSL certificate";var r=JSON.parse(n.response).app.version;if(!e.isGreaterThanOrEqualTo(r,"4.10.0"))throw"The UAA instance running at "+t+" has version "+r+". This uaa-singular library requires UAA version 4.10.0 or higher."})},checkClientConfiguration:function(t,n){i(t,function(r){if(400===r.status){var e=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/**";throw"Error while calling /oauth/authorize. Is the UAA client "+n+" configured to allow redirects to "+e+" ? Visit "+t+" in the browser to see the UAA's error messages."}})}}}]).default; \ No newline at end of file
diff --git a/deps/rabbitmq_management/priv/www/js/singular/singular.umd.js b/deps/rabbitmq_management/priv/www/js/singular/singular.umd.js
new file mode 100644
index 0000000000..01fc1536d9
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/singular/singular.umd.js
@@ -0,0 +1 @@
+!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.Singular=n():t.Singular=n()}(window,function(){return function(t){var n={};function r(e){if(n[e])return n[e].exports;var i=n[e]={i:e,l:!1,exports:{}};return t[e].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=n,r.d=function(t,n,e){r.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:e})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,n){if(1&n&&(t=r(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var e=Object.create(null);if(r.r(e),Object.defineProperty(e,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var i in t)r.d(e,i,function(n){return t[n]}.bind(null,i));return e},r.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(n,"a",n),n},r.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},r.p="",r(r.s=127)}([function(t,n,r){var e=r(2),i=r(18),o=r(11),u=r(12),c=r(19),a=function(t,n,r){var f,s,l,h,v=t&a.F,p=t&a.G,d=t&a.S,g=t&a.P,y=t&a.B,m=p?e:d?e[n]||(e[n]={}):(e[n]||{}).prototype,b=p?i:i[n]||(i[n]={}),w=b.prototype||(b.prototype={});for(f in p&&(r=n),r)l=((s=!v&&m&&void 0!==m[f])?m:r)[f],h=y&&s?c(l,e):g&&"function"==typeof l?c(Function.call,l):l,m&&u(m,f,l,t&a.U),b[f]!=l&&o(b,f,h),g&&w[f]!=l&&(w[f]=l)};e.core=i,a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},function(t,n,r){var e=r(4);t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n,r){var e=r(49)("wks"),i=r(33),o=r(2).Symbol,u="function"==typeof o;(t.exports=function(t){return e[t]||(e[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=e},function(t,n,r){t.exports=!r(3)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(1),i=r(91),o=r(22),u=Object.defineProperty;n.f=r(6)?Object.defineProperty:function(t,n,r){if(e(t),n=o(n,!0),e(r),i)try{return u(t,n,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},function(t,n,r){var e=r(24),i=Math.min;t.exports=function(t){return t>0?i(e(t),9007199254740991):0}},function(t,n,r){var e=r(23);t.exports=function(t){return Object(e(t))}},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n,r){var e=r(7),i=r(32);t.exports=r(6)?function(t,n,r){return e.f(t,n,i(1,r))}:function(t,n,r){return t[n]=r,t}},function(t,n,r){var e=r(2),i=r(11),o=r(14),u=r(33)("src"),c=Function.toString,a=(""+c).split("toString");r(18).inspectSource=function(t){return c.call(t)},(t.exports=function(t,n,r,c){var f="function"==typeof r;f&&(o(r,"name")||i(r,"name",n)),t[n]!==r&&(f&&(o(r,u)||i(r,u,t[n]?""+t[n]:a.join(String(n)))),t===e?t[n]=r:c?t[n]?t[n]=r:i(t,n,r):(delete t[n],i(t,n,r)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[u]||c.call(this)})},function(t,n,r){var e=r(0),i=r(3),o=r(23),u=/"/g,c=function(t,n,r,e){var i=String(o(t)),c="<"+n;return""!==r&&(c+=" "+r+'="'+String(e).replace(u,"&quot;")+'"'),c+">"+i+"</"+n+">"};t.exports=function(t,n){var r={};r[t]=n(c),e(e.P+e.F*i(function(){var n=""[t]('"');return n!==n.toLowerCase()||n.split('"').length>3}),"String",r)}},function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},function(t,n,r){var e=r(46),i=r(23);t.exports=function(t){return e(i(t))}},function(t,n,r){var e=r(47),i=r(32),o=r(15),u=r(22),c=r(14),a=r(91),f=Object.getOwnPropertyDescriptor;n.f=r(6)?f:function(t,n){if(t=o(t),n=u(n,!0),a)try{return f(t,n)}catch(t){}if(c(t,n))return i(!e.f.call(t,n),t[n])}},function(t,n,r){var e=r(14),i=r(9),o=r(67)("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),e(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},function(t,n){var r=t.exports={version:"2.5.6"};"number"==typeof __e&&(__e=r)},function(t,n,r){var e=r(10);t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,i){return t.call(n,r,e,i)}}return function(){return t.apply(n,arguments)}}},function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},function(t,n,r){"use strict";var e=r(3);t.exports=function(t,n){return!!t&&e(function(){n?t.call(null,function(){},1):t.call(null)})}},function(t,n,r){var e=r(4);t.exports=function(t,n){if(!e(t))return t;var r,i;if(n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;if("function"==typeof(r=t.valueOf)&&!e(i=r.call(t)))return i;if(!n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},function(t,n,r){var e=r(0),i=r(18),o=r(3);t.exports=function(t,n){var r=(i.Object||{})[t]||Object[t],u={};u[t]=n(r),e(e.S+e.F*o(function(){r(1)}),"Object",u)}},function(t,n,r){var e=r(19),i=r(46),o=r(9),u=r(8),c=r(84);t.exports=function(t,n){var r=1==t,a=2==t,f=3==t,s=4==t,l=6==t,h=5==t||l,v=n||c;return function(n,c,p){for(var d,g,y=o(n),m=i(y),b=e(c,p,3),w=u(m.length),S=0,x=r?v(n,w):a?v(n,0):void 0;w>S;S++)if((h||S in m)&&(g=b(d=m[S],S,y),t))if(r)x[S]=g;else if(g)switch(t){case 3:return!0;case 5:return d;case 6:return S;case 2:x.push(d)}else if(s)return!1;return l?-1:f||s?s:x}}},function(t,n,r){"use strict";if(r(6)){var e=r(30),i=r(2),o=r(3),u=r(0),c=r(60),a=r(90),f=r(19),s=r(39),l=r(32),h=r(11),v=r(41),p=r(24),d=r(8),g=r(117),y=r(35),m=r(22),b=r(14),w=r(48),S=r(4),x=r(9),_=r(81),E=r(36),O=r(17),M=r(37).f,P=r(83),F=r(33),A=r(5),j=r(26),I=r(50),N=r(57),L=r(86),T=r(44),k=r(54),R=r(38),C=r(85),D=r(107),W=r(7),U=r(16),G=W.f,V=U.f,B=i.RangeError,z=i.TypeError,q=i.Uint8Array,J=Array.prototype,Y=a.ArrayBuffer,K=a.DataView,H=j(0),X=j(2),$=j(3),Z=j(4),Q=j(5),tt=j(6),nt=I(!0),rt=I(!1),et=L.values,it=L.keys,ot=L.entries,ut=J.lastIndexOf,ct=J.reduce,at=J.reduceRight,ft=J.join,st=J.sort,lt=J.slice,ht=J.toString,vt=J.toLocaleString,pt=A("iterator"),dt=A("toStringTag"),gt=F("typed_constructor"),yt=F("def_constructor"),mt=c.CONSTR,bt=c.TYPED,wt=c.VIEW,St=j(1,function(t,n){return Mt(N(t,t[yt]),n)}),xt=o(function(){return 1===new q(new Uint16Array([1]).buffer)[0]}),_t=!!q&&!!q.prototype.set&&o(function(){new q(1).set({})}),Et=function(t,n){var r=p(t);if(r<0||r%n)throw B("Wrong offset!");return r},Ot=function(t){if(S(t)&&bt in t)return t;throw z(t+" is not a typed array!")},Mt=function(t,n){if(!(S(t)&&gt in t))throw z("It is not a typed array constructor!");return new t(n)},Pt=function(t,n){return Ft(N(t,t[yt]),n)},Ft=function(t,n){for(var r=0,e=n.length,i=Mt(t,e);e>r;)i[r]=n[r++];return i},At=function(t,n,r){G(t,n,{get:function(){return this._d[r]}})},jt=function(t){var n,r,e,i,o,u,c=x(t),a=arguments.length,s=a>1?arguments[1]:void 0,l=void 0!==s,h=P(c);if(null!=h&&!_(h)){for(u=h.call(c),e=[],n=0;!(o=u.next()).done;n++)e.push(o.value);c=e}for(l&&a>2&&(s=f(s,arguments[2],2)),n=0,r=d(c.length),i=Mt(this,r);r>n;n++)i[n]=l?s(c[n],n):c[n];return i},It=function(){for(var t=0,n=arguments.length,r=Mt(this,n);n>t;)r[t]=arguments[t++];return r},Nt=!!q&&o(function(){vt.call(new q(1))}),Lt=function(){return vt.apply(Nt?lt.call(Ot(this)):Ot(this),arguments)},Tt={copyWithin:function(t,n){return D.call(Ot(this),t,n,arguments.length>2?arguments[2]:void 0)},every:function(t){return Z(Ot(this),t,arguments.length>1?arguments[1]:void 0)},fill:function(t){return C.apply(Ot(this),arguments)},filter:function(t){return Pt(this,X(Ot(this),t,arguments.length>1?arguments[1]:void 0))},find:function(t){return Q(Ot(this),t,arguments.length>1?arguments[1]:void 0)},findIndex:function(t){return tt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},forEach:function(t){H(Ot(this),t,arguments.length>1?arguments[1]:void 0)},indexOf:function(t){return rt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},includes:function(t){return nt(Ot(this),t,arguments.length>1?arguments[1]:void 0)},join:function(t){return ft.apply(Ot(this),arguments)},lastIndexOf:function(t){return ut.apply(Ot(this),arguments)},map:function(t){return St(Ot(this),t,arguments.length>1?arguments[1]:void 0)},reduce:function(t){return ct.apply(Ot(this),arguments)},reduceRight:function(t){return at.apply(Ot(this),arguments)},reverse:function(){for(var t,n=Ot(this).length,r=Math.floor(n/2),e=0;e<r;)t=this[e],this[e++]=this[--n],this[n]=t;return this},some:function(t){return $(Ot(this),t,arguments.length>1?arguments[1]:void 0)},sort:function(t){return st.call(Ot(this),t)},subarray:function(t,n){var r=Ot(this),e=r.length,i=y(t,e);return new(N(r,r[yt]))(r.buffer,r.byteOffset+i*r.BYTES_PER_ELEMENT,d((void 0===n?e:y(n,e))-i))}},kt=function(t,n){return Pt(this,lt.call(Ot(this),t,n))},Rt=function(t){Ot(this);var n=Et(arguments[1],1),r=this.length,e=x(t),i=d(e.length),o=0;if(i+n>r)throw B("Wrong length!");for(;o<i;)this[n+o]=e[o++]},Ct={entries:function(){return ot.call(Ot(this))},keys:function(){return it.call(Ot(this))},values:function(){return et.call(Ot(this))}},Dt=function(t,n){return S(t)&&t[bt]&&"symbol"!=typeof n&&n in t&&String(+n)==String(n)},Wt=function(t,n){return Dt(t,n=m(n,!0))?l(2,t[n]):V(t,n)},Ut=function(t,n,r){return!(Dt(t,n=m(n,!0))&&S(r)&&b(r,"value"))||b(r,"get")||b(r,"set")||r.configurable||b(r,"writable")&&!r.writable||b(r,"enumerable")&&!r.enumerable?G(t,n,r):(t[n]=r.value,t)};mt||(U.f=Wt,W.f=Ut),u(u.S+u.F*!mt,"Object",{getOwnPropertyDescriptor:Wt,defineProperty:Ut}),o(function(){ht.call({})})&&(ht=vt=function(){return ft.call(this)});var Gt=v({},Tt);v(Gt,Ct),h(Gt,pt,Ct.values),v(Gt,{slice:kt,set:Rt,constructor:function(){},toString:ht,toLocaleString:Lt}),At(Gt,"buffer","b"),At(Gt,"byteOffset","o"),At(Gt,"byteLength","l"),At(Gt,"length","e"),G(Gt,dt,{get:function(){return this[bt]}}),t.exports=function(t,n,r,a){var f=t+((a=!!a)?"Clamped":"")+"Array",l="get"+t,v="set"+t,p=i[f],y=p||{},m=p&&O(p),b=!p||!c.ABV,x={},_=p&&p.prototype,P=function(t,r){G(t,r,{get:function(){return function(t,r){var e=t._d;return e.v[l](r*n+e.o,xt)}(this,r)},set:function(t){return function(t,r,e){var i=t._d;a&&(e=(e=Math.round(e))<0?0:e>255?255:255&e),i.v[v](r*n+i.o,e,xt)}(this,r,t)},enumerable:!0})};b?(p=r(function(t,r,e,i){s(t,p,f,"_d");var o,u,c,a,l=0,v=0;if(S(r)){if(!(r instanceof Y||"ArrayBuffer"==(a=w(r))||"SharedArrayBuffer"==a))return bt in r?Ft(p,r):jt.call(p,r);o=r,v=Et(e,n);var y=r.byteLength;if(void 0===i){if(y%n)throw B("Wrong length!");if((u=y-v)<0)throw B("Wrong length!")}else if((u=d(i)*n)+v>y)throw B("Wrong length!");c=u/n}else c=g(r),o=new Y(u=c*n);for(h(t,"_d",{b:o,o:v,l:u,e:c,v:new K(o)});l<c;)P(t,l++)}),_=p.prototype=E(Gt),h(_,"constructor",p)):o(function(){p(1)})&&o(function(){new p(-1)})&&k(function(t){new p,new p(null),new p(1.5),new p(t)},!0)||(p=r(function(t,r,e,i){var o;return s(t,p,f),S(r)?r instanceof Y||"ArrayBuffer"==(o=w(r))||"SharedArrayBuffer"==o?void 0!==i?new y(r,Et(e,n),i):void 0!==e?new y(r,Et(e,n)):new y(r):bt in r?Ft(p,r):jt.call(p,r):new y(g(r))}),H(m!==Function.prototype?M(y).concat(M(m)):M(y),function(t){t in p||h(p,t,y[t])}),p.prototype=_,e||(_.constructor=p));var F=_[pt],A=!!F&&("values"==F.name||null==F.name),j=Ct.values;h(p,gt,!0),h(_,bt,f),h(_,wt,!0),h(_,yt,p),(a?new p(1)[dt]==f:dt in _)||G(_,dt,{get:function(){return f}}),x[f]=p,u(u.G+u.W+u.F*(p!=y),x),u(u.S,f,{BYTES_PER_ELEMENT:n}),u(u.S+u.F*o(function(){y.of.call(p,1)}),f,{from:jt,of:It}),"BYTES_PER_ELEMENT"in _||h(_,"BYTES_PER_ELEMENT",n),u(u.P,f,Tt),R(f),u(u.P+u.F*_t,f,{set:Rt}),u(u.P+u.F*!A,f,Ct),e||_.toString==ht||(_.toString=ht),u(u.P+u.F*o(function(){new p(1).slice()}),f,{slice:kt}),u(u.P+u.F*(o(function(){return[1,2].toLocaleString()!=new p([1,2]).toLocaleString()})||!o(function(){_.toLocaleString.call([1,2])})),f,{toLocaleString:Lt}),T[f]=A?F:j,e||A||h(_,pt,j)}}else t.exports=function(){}},function(t,n,r){var e=r(112),i=r(0),o=r(49)("metadata"),u=o.store||(o.store=new(r(115))),c=function(t,n,r){var i=u.get(t);if(!i){if(!r)return;u.set(t,i=new e)}var o=i.get(n);if(!o){if(!r)return;i.set(n,o=new e)}return o};t.exports={store:u,map:c,has:function(t,n,r){var e=c(n,r,!1);return void 0!==e&&e.has(t)},get:function(t,n,r){var e=c(n,r,!1);return void 0===e?void 0:e.get(t)},set:function(t,n,r,e){c(r,e,!0).set(t,n)},keys:function(t,n){var r=c(t,n,!1),e=[];return r&&r.forEach(function(t,n){e.push(n)}),e},key:function(t){return void 0===t||"symbol"==typeof t?t:String(t)},exp:function(t){i(i.S,"Reflect",t)}}},function(t,n,r){var e=r(33)("meta"),i=r(4),o=r(14),u=r(7).f,c=0,a=Object.isExtensible||function(){return!0},f=!r(3)(function(){return a(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=t.exports={KEY:e,NEED:!1,fastKey:function(t,n){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,e)){if(!a(t))return"F";if(!n)return"E";s(t)}return t[e].i},getWeak:function(t,n){if(!o(t,e)){if(!a(t))return!0;if(!n)return!1;s(t)}return t[e].w},onFreeze:function(t){return f&&l.NEED&&a(t)&&!o(t,e)&&s(t),t}}},function(t,n){t.exports=!1},function(t,n,r){var e=r(5)("unscopables"),i=Array.prototype;null==i[e]&&r(11)(i,e,{}),t.exports=function(t){i[e][t]=!0}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},function(t,n,r){var e=r(93),i=r(68);t.exports=Object.keys||function(t){return e(t,i)}},function(t,n,r){var e=r(24),i=Math.max,o=Math.min;t.exports=function(t,n){return(t=e(t))<0?i(t+n,0):o(t,n)}},function(t,n,r){var e=r(1),i=r(94),o=r(68),u=r(67)("IE_PROTO"),c=function(){},a=function(){var t,n=r(65)("iframe"),e=o.length;for(n.style.display="none",r(69).appendChild(n),n.src="javascript:",(t=n.contentWindow.document).open(),t.write("<script>document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a.prototype[o[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c.prototype=e(t),r=new c,c.prototype=null,r[u]=t):r=a(),void 0===n?r:i(r,n)}},function(t,n,r){var e=r(93),i=r(68).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,i)}},function(t,n,r){"use strict";var e=r(2),i=r(7),o=r(6),u=r(5)("species");t.exports=function(t){var n=e[t];o&&n&&!n[u]&&i.f(n,u,{configurable:!0,get:function(){return this}})}},function(t,n){t.exports=function(t,n,r,e){if(!(t instanceof n)||void 0!==e&&e in t)throw TypeError(r+": incorrect invocation!");return t}},function(t,n,r){var e=r(19),i=r(105),o=r(81),u=r(1),c=r(8),a=r(83),f={},s={};(n=t.exports=function(t,n,r,l,h){var v,p,d,g,y=h?function(){return t}:a(t),m=e(r,l,n?2:1),b=0;if("function"!=typeof y)throw TypeError(t+" is not iterable!");if(o(y)){for(v=c(t.length);v>b;b++)if((g=n?m(u(p=t[b])[0],p[1]):m(t[b]))===f||g===s)return g}else for(d=y.call(t);!(p=d.next()).done;)if((g=i(d,m,p.value,n))===f||g===s)return g}).BREAK=f,n.RETURN=s},function(t,n,r){var e=r(12);t.exports=function(t,n,r){for(var i in n)e(t,i,n[i],r);return t}},function(t,n,r){var e=r(7).f,i=r(14),o=r(5)("toStringTag");t.exports=function(t,n,r){t&&!i(t=r?t:t.prototype,o)&&e(t,o,{configurable:!0,value:n})}},function(t,n,r){var e=r(0),i=r(23),o=r(3),u=r(71),c="["+u+"]",a=RegExp("^"+c+c+"*"),f=RegExp(c+c+"*$"),s=function(t,n,r){var i={},c=o(function(){return!!u[t]()||"​…"!="​…"[t]()}),a=i[t]=c?n(l):u[t];r&&(i[r]=a),e(e.P+e.F*c,"String",i)},l=s.trim=function(t,n){return t=String(i(t)),1&n&&(t=t.replace(a,"")),2&n&&(t=t.replace(f,"")),t};t.exports=s},function(t,n){t.exports={}},function(t,n,r){var e=r(4);t.exports=function(t,n){if(!e(t)||t._t!==n)throw TypeError("Incompatible receiver, "+n+" required!");return t}},function(t,n,r){var e=r(20);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,r){var e=r(20),i=r(5)("toStringTag"),o="Arguments"==e(function(){return arguments}());t.exports=function(t){var n,r,u;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=function(t,n){try{return t[n]}catch(t){}}(n=Object(t),i))?r:o?e(n):"Object"==(u=e(n))&&"function"==typeof n.callee?"Arguments":u}},function(t,n,r){var e=r(18),i=r(2),o=i["__core-js_shared__"]||(i["__core-js_shared__"]={});(t.exports=function(t,n){return o[t]||(o[t]=void 0!==n?n:{})})("versions",[]).push({version:e.version,mode:r(30)?"pure":"global",copyright:"© 2018 Denis Pushkarev (zloirock.ru)"})},function(t,n,r){var e=r(15),i=r(8),o=r(35);t.exports=function(t){return function(n,r,u){var c,a=e(n),f=i(a.length),s=o(u,f);if(t&&r!=r){for(;f>s;)if((c=a[s++])!=c)return!0}else for(;f>s;s++)if((t||s in a)&&a[s]===r)return t||s||0;return!t&&-1}}},function(t,n){n.f=Object.getOwnPropertySymbols},function(t,n,r){var e=r(20);t.exports=Array.isArray||function(t){return"Array"==e(t)}},function(t,n,r){var e=r(4),i=r(20),o=r(5)("match");t.exports=function(t){var n;return e(t)&&(void 0!==(n=t[o])?!!n:"RegExp"==i(t))}},function(t,n,r){var e=r(5)("iterator"),i=!1;try{var o=[7][e]();o.return=function(){i=!0},Array.from(o,function(){throw 2})}catch(t){}t.exports=function(t,n){if(!n&&!i)return!1;var r=!1;try{var o=[7],u=o[e]();u.next=function(){return{done:r=!0}},o[e]=function(){return u},t(o)}catch(t){}return r}},function(t,n,r){"use strict";var e=r(1);t.exports=function(){var t=e(this),n="";return t.global&&(n+="g"),t.ignoreCase&&(n+="i"),t.multiline&&(n+="m"),t.unicode&&(n+="u"),t.sticky&&(n+="y"),n}},function(t,n,r){"use strict";var e=r(11),i=r(12),o=r(3),u=r(23),c=r(5);t.exports=function(t,n,r){var a=c(t),f=r(u,a,""[t]),s=f[0],l=f[1];o(function(){var n={};return n[a]=function(){return 7},7!=""[t](n)})&&(i(String.prototype,t,s),e(RegExp.prototype,a,2==n?function(t,n){return l.call(t,this,n)}:function(t){return l.call(t,this)}))}},function(t,n,r){var e=r(1),i=r(10),o=r(5)("species");t.exports=function(t,n){var r,u=e(t).constructor;return void 0===u||null==(r=e(u)[o])?n:i(r)}},function(t,n,r){var e=r(2).navigator;t.exports=e&&e.userAgent||""},function(t,n,r){"use strict";var e=r(2),i=r(0),o=r(12),u=r(41),c=r(29),a=r(40),f=r(39),s=r(4),l=r(3),h=r(54),v=r(42),p=r(72);t.exports=function(t,n,r,d,g,y){var m=e[t],b=m,w=g?"set":"add",S=b&&b.prototype,x={},_=function(t){var n=S[t];o(S,t,"delete"==t?function(t){return!(y&&!s(t))&&n.call(this,0===t?0:t)}:"has"==t?function(t){return!(y&&!s(t))&&n.call(this,0===t?0:t)}:"get"==t?function(t){return y&&!s(t)?void 0:n.call(this,0===t?0:t)}:"add"==t?function(t){return n.call(this,0===t?0:t),this}:function(t,r){return n.call(this,0===t?0:t,r),this})};if("function"==typeof b&&(y||S.forEach&&!l(function(){(new b).entries().next()}))){var E=new b,O=E[w](y?{}:-0,1)!=E,M=l(function(){E.has(1)}),P=h(function(t){new b(t)}),F=!y&&l(function(){for(var t=new b,n=5;n--;)t[w](n,n);return!t.has(-0)});P||((b=n(function(n,r){f(n,b,t);var e=p(new m,n,b);return null!=r&&a(r,g,e[w],e),e})).prototype=S,S.constructor=b),(M||F)&&(_("delete"),_("has"),g&&_("get")),(F||O)&&_(w),y&&S.clear&&delete S.clear}else b=d.getConstructor(n,t,g,w),u(b.prototype,r),c.NEED=!0;return v(b,t),x[t]=b,i(i.G+i.W+i.F*(b!=m),x),y||d.setStrong(b,t,g),b}},function(t,n,r){for(var e,i=r(2),o=r(11),u=r(33),c=u("typed_array"),a=u("view"),f=!(!i.ArrayBuffer||!i.DataView),s=f,l=0,h="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");l<9;)(e=i[h[l++]])?(o(e.prototype,c,!0),o(e.prototype,a,!0)):s=!1;t.exports={ABV:f,CONSTR:s,TYPED:c,VIEW:a}},function(t,n,r){"use strict";t.exports=r(30)||!r(3)(function(){var t=Math.random();__defineSetter__.call(null,t,function(){}),delete r(2)[t]})},function(t,n,r){"use strict";var e=r(0);t.exports=function(t){e(e.S,t,{of:function(){for(var t=arguments.length,n=new Array(t);t--;)n[t]=arguments[t];return new this(n)}})}},function(t,n,r){"use strict";var e=r(0),i=r(10),o=r(19),u=r(40);t.exports=function(t){e(e.S,t,{from:function(t){var n,r,e,c,a=arguments[1];return i(this),(n=void 0!==a)&&i(a),null==t?new this:(r=[],n?(e=0,c=o(a,arguments[2],2),u(t,!1,function(t){r.push(c(t,e++))})):u(t,!1,r.push,r),new this(r))}})}},function(t,n){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(t){"object"==typeof window&&(r=window)}t.exports=r},function(t,n,r){var e=r(4),i=r(2).document,o=e(i)&&e(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n,r){var e=r(2),i=r(18),o=r(30),u=r(92),c=r(7).f;t.exports=function(t){var n=i.Symbol||(i.Symbol=o?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},function(t,n,r){var e=r(49)("keys"),i=r(33);t.exports=function(t){return e[t]||(e[t]=i(t))}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n,r){var e=r(2).document;t.exports=e&&e.documentElement},function(t,n,r){var e=r(4),i=r(1),o=function(t,n){if(i(t),!e(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,n,e){try{(e=r(19)(Function.call,r(16).f(Object.prototype,"__proto__").set,2))(t,[]),n=!(t instanceof Array)}catch(t){n=!0}return function(t,r){return o(t,r),n?t.__proto__=r:e(t,r),t}}({},!1):void 0),check:o}},function(t,n){t.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},function(t,n,r){var e=r(4),i=r(70).set;t.exports=function(t,n,r){var o,u=n.constructor;return u!==r&&"function"==typeof u&&(o=u.prototype)!==r.prototype&&e(o)&&i&&i(t,o),t}},function(t,n,r){"use strict";var e=r(24),i=r(23);t.exports=function(t){var n=String(i(this)),r="",o=e(t);if(o<0||o==1/0)throw RangeError("Count can't be negative");for(;o>0;(o>>>=1)&&(n+=n))1&o&&(r+=n);return r}},function(t,n){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},function(t,n){var r=Math.expm1;t.exports=!r||r(10)>22025.465794806718||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(t){return 0==(t=+t)?t:t>-1e-6&&t<1e-6?t+t*t/2:Math.exp(t)-1}:r},function(t,n,r){var e=r(24),i=r(23);t.exports=function(t){return function(n,r){var o,u,c=String(i(n)),a=e(r),f=c.length;return a<0||a>=f?t?"":void 0:(o=c.charCodeAt(a))<55296||o>56319||a+1===f||(u=c.charCodeAt(a+1))<56320||u>57343?t?c.charAt(a):o:t?c.slice(a,a+2):u-56320+(o-55296<<10)+65536}}},function(t,n,r){"use strict";var e=r(30),i=r(0),o=r(12),u=r(11),c=r(44),a=r(78),f=r(42),s=r(17),l=r(5)("iterator"),h=!([].keys&&"next"in[].keys()),v=function(){return this};t.exports=function(t,n,r,p,d,g,y){a(r,n,p);var m,b,w,S=function(t){if(!h&&t in O)return O[t];switch(t){case"keys":case"values":return function(){return new r(this,t)}}return function(){return new r(this,t)}},x=n+" Iterator",_="values"==d,E=!1,O=t.prototype,M=O[l]||O["@@iterator"]||d&&O[d],P=M||S(d),F=d?_?S("entries"):P:void 0,A="Array"==n&&O.entries||M;if(A&&(w=s(A.call(new t)))!==Object.prototype&&w.next&&(f(w,x,!0),e||"function"==typeof w[l]||u(w,l,v)),_&&M&&"values"!==M.name&&(E=!0,P=function(){return M.call(this)}),e&&!y||!h&&!E&&O[l]||u(O,l,P),c[n]=P,c[x]=v,d)if(m={values:_?P:S("values"),keys:g?P:S("keys"),entries:F},y)for(b in m)b in O||o(O,b,m[b]);else i(i.P+i.F*(h||E),n,m);return m}},function(t,n,r){"use strict";var e=r(36),i=r(32),o=r(42),u={};r(11)(u,r(5)("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:i(1,r)}),o(t,n+" Iterator")}},function(t,n,r){var e=r(53),i=r(23);t.exports=function(t,n,r){if(e(n))throw TypeError("String#"+r+" doesn't accept regex!");return String(i(t))}},function(t,n,r){var e=r(5)("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(r){try{return n[e]=!1,!"/./"[t](n)}catch(t){}}return!0}},function(t,n,r){var e=r(44),i=r(5)("iterator"),o=Array.prototype;t.exports=function(t){return void 0!==t&&(e.Array===t||o[i]===t)}},function(t,n,r){"use strict";var e=r(7),i=r(32);t.exports=function(t,n,r){n in t?e.f(t,n,i(0,r)):t[n]=r}},function(t,n,r){var e=r(48),i=r(5)("iterator"),o=r(44);t.exports=r(18).getIteratorMethod=function(t){if(null!=t)return t[i]||t["@@iterator"]||o[e(t)]}},function(t,n,r){var e=r(221);t.exports=function(t,n){return new(e(t))(n)}},function(t,n,r){"use strict";var e=r(9),i=r(35),o=r(8);t.exports=function(t){for(var n=e(this),r=o(n.length),u=arguments.length,c=i(u>1?arguments[1]:void 0,r),a=u>2?arguments[2]:void 0,f=void 0===a?r:i(a,r);f>c;)n[c++]=t;return n}},function(t,n,r){"use strict";var e=r(31),i=r(108),o=r(44),u=r(15);t.exports=r(77)(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,i(1)):i(0,"keys"==n?r:"values"==n?t[r]:[r,t[r]])},"values"),o.Arguments=o.Array,e("keys"),e("values"),e("entries")},function(t,n,r){var e,i,o,u=r(19),c=r(98),a=r(69),f=r(65),s=r(2),l=s.process,h=s.setImmediate,v=s.clearImmediate,p=s.MessageChannel,d=s.Dispatch,g=0,y={},m=function(){var t=+this;if(y.hasOwnProperty(t)){var n=y[t];delete y[t],n()}},b=function(t){m.call(t.data)};h&&v||(h=function(t){for(var n=[],r=1;arguments.length>r;)n.push(arguments[r++]);return y[++g]=function(){c("function"==typeof t?t:Function(t),n)},e(g),g},v=function(t){delete y[t]},"process"==r(20)(l)?e=function(t){l.nextTick(u(m,t,1))}:d&&d.now?e=function(t){d.now(u(m,t,1))}:p?(o=(i=new p).port2,i.port1.onmessage=b,e=u(o.postMessage,o,1)):s.addEventListener&&"function"==typeof postMessage&&!s.importScripts?(e=function(t){s.postMessage(t+"","*")},s.addEventListener("message",b,!1)):e="onreadystatechange"in f("script")?function(t){a.appendChild(f("script")).onreadystatechange=function(){a.removeChild(this),m.call(t)}}:function(t){setTimeout(u(m,t,1),0)}),t.exports={set:h,clear:v}},function(t,n,r){var e=r(2),i=r(87).set,o=e.MutationObserver||e.WebKitMutationObserver,u=e.process,c=e.Promise,a="process"==r(20)(u);t.exports=function(){var t,n,r,f=function(){var e,i;for(a&&(e=u.domain)&&e.exit();t;){i=t.fn,t=t.next;try{i()}catch(e){throw t?r():n=void 0,e}}n=void 0,e&&e.enter()};if(a)r=function(){u.nextTick(f)};else if(!o||e.navigator&&e.navigator.standalone)if(c&&c.resolve){var s=c.resolve(void 0);r=function(){s.then(f)}}else r=function(){i.call(e,f)};else{var l=!0,h=document.createTextNode("");new o(f).observe(h,{characterData:!0}),r=function(){h.data=l=!l}}return function(e){var i={fn:e,next:void 0};n&&(n.next=i),t||(t=i,r()),n=i}}},function(t,n,r){"use strict";var e=r(10);function i(t){var n,r;this.promise=new t(function(t,e){if(void 0!==n||void 0!==r)throw TypeError("Bad Promise constructor");n=t,r=e}),this.resolve=e(n),this.reject=e(r)}t.exports.f=function(t){return new i(t)}},function(t,n,r){"use strict";var e=r(2),i=r(6),o=r(30),u=r(60),c=r(11),a=r(41),f=r(3),s=r(39),l=r(24),h=r(8),v=r(117),p=r(37).f,d=r(7).f,g=r(85),y=r(42),m="prototype",b="Wrong index!",w=e.ArrayBuffer,S=e.DataView,x=e.Math,_=e.RangeError,E=e.Infinity,O=w,M=x.abs,P=x.pow,F=x.floor,A=x.log,j=x.LN2,I=i?"_b":"buffer",N=i?"_l":"byteLength",L=i?"_o":"byteOffset";function T(t,n,r){var e,i,o,u=new Array(r),c=8*r-n-1,a=(1<<c)-1,f=a>>1,s=23===n?P(2,-24)-P(2,-77):0,l=0,h=t<0||0===t&&1/t<0?1:0;for((t=M(t))!=t||t===E?(i=t!=t?1:0,e=a):(e=F(A(t)/j),t*(o=P(2,-e))<1&&(e--,o*=2),(t+=e+f>=1?s/o:s*P(2,1-f))*o>=2&&(e++,o/=2),e+f>=a?(i=0,e=a):e+f>=1?(i=(t*o-1)*P(2,n),e+=f):(i=t*P(2,f-1)*P(2,n),e=0));n>=8;u[l++]=255&i,i/=256,n-=8);for(e=e<<n|i,c+=n;c>0;u[l++]=255&e,e/=256,c-=8);return u[--l]|=128*h,u}function k(t,n,r){var e,i=8*r-n-1,o=(1<<i)-1,u=o>>1,c=i-7,a=r-1,f=t[a--],s=127&f;for(f>>=7;c>0;s=256*s+t[a],a--,c-=8);for(e=s&(1<<-c)-1,s>>=-c,c+=n;c>0;e=256*e+t[a],a--,c-=8);if(0===s)s=1-u;else{if(s===o)return e?NaN:f?-E:E;e+=P(2,n),s-=u}return(f?-1:1)*e*P(2,s-n)}function R(t){return t[3]<<24|t[2]<<16|t[1]<<8|t[0]}function C(t){return[255&t]}function D(t){return[255&t,t>>8&255]}function W(t){return[255&t,t>>8&255,t>>16&255,t>>24&255]}function U(t){return T(t,52,8)}function G(t){return T(t,23,4)}function V(t,n,r){d(t[m],n,{get:function(){return this[r]}})}function B(t,n,r,e){var i=v(+r);if(i+n>t[N])throw _(b);var o=t[I]._b,u=i+t[L],c=o.slice(u,u+n);return e?c:c.reverse()}function z(t,n,r,e,i,o){var u=v(+r);if(u+n>t[N])throw _(b);for(var c=t[I]._b,a=u+t[L],f=e(+i),s=0;s<n;s++)c[a+s]=f[o?s:n-s-1]}if(u.ABV){if(!f(function(){w(1)})||!f(function(){new w(-1)})||f(function(){return new w,new w(1.5),new w(NaN),"ArrayBuffer"!=w.name})){for(var q,J=(w=function(t){return s(this,w),new O(v(t))})[m]=O[m],Y=p(O),K=0;Y.length>K;)(q=Y[K++])in w||c(w,q,O[q]);o||(J.constructor=w)}var H=new S(new w(2)),X=S[m].setInt8;H.setInt8(0,2147483648),H.setInt8(1,2147483649),!H.getInt8(0)&&H.getInt8(1)||a(S[m],{setInt8:function(t,n){X.call(this,t,n<<24>>24)},setUint8:function(t,n){X.call(this,t,n<<24>>24)}},!0)}else w=function(t){s(this,w,"ArrayBuffer");var n=v(t);this._b=g.call(new Array(n),0),this[N]=n},S=function(t,n,r){s(this,S,"DataView"),s(t,w,"DataView");var e=t[N],i=l(n);if(i<0||i>e)throw _("Wrong offset!");if(i+(r=void 0===r?e-i:h(r))>e)throw _("Wrong length!");this[I]=t,this[L]=i,this[N]=r},i&&(V(w,"byteLength","_l"),V(S,"buffer","_b"),V(S,"byteLength","_l"),V(S,"byteOffset","_o")),a(S[m],{getInt8:function(t){return B(this,1,t)[0]<<24>>24},getUint8:function(t){return B(this,1,t)[0]},getInt16:function(t){var n=B(this,2,t,arguments[1]);return(n[1]<<8|n[0])<<16>>16},getUint16:function(t){var n=B(this,2,t,arguments[1]);return n[1]<<8|n[0]},getInt32:function(t){return R(B(this,4,t,arguments[1]))},getUint32:function(t){return R(B(this,4,t,arguments[1]))>>>0},getFloat32:function(t){return k(B(this,4,t,arguments[1]),23,4)},getFloat64:function(t){return k(B(this,8,t,arguments[1]),52,8)},setInt8:function(t,n){z(this,1,t,C,n)},setUint8:function(t,n){z(this,1,t,C,n)},setInt16:function(t,n){z(this,2,t,D,n,arguments[2])},setUint16:function(t,n){z(this,2,t,D,n,arguments[2])},setInt32:function(t,n){z(this,4,t,W,n,arguments[2])},setUint32:function(t,n){z(this,4,t,W,n,arguments[2])},setFloat32:function(t,n){z(this,4,t,G,n,arguments[2])},setFloat64:function(t,n){z(this,8,t,U,n,arguments[2])}});y(w,"ArrayBuffer"),y(S,"DataView"),c(S[m],u.VIEW,!0),n.ArrayBuffer=w,n.DataView=S},function(t,n,r){t.exports=!r(6)&&!r(3)(function(){return 7!=Object.defineProperty(r(65)("div"),"a",{get:function(){return 7}}).a})},function(t,n,r){n.f=r(5)},function(t,n,r){var e=r(14),i=r(15),o=r(50)(!1),u=r(67)("IE_PROTO");t.exports=function(t,n){var r,c=i(t),a=0,f=[];for(r in c)r!=u&&e(c,r)&&f.push(r);for(;n.length>a;)e(c,r=n[a++])&&(~o(f,r)||f.push(r));return f}},function(t,n,r){var e=r(7),i=r(1),o=r(34);t.exports=r(6)?Object.defineProperties:function(t,n){i(t);for(var r,u=o(n),c=u.length,a=0;c>a;)e.f(t,r=u[a++],n[r]);return t}},function(t,n,r){var e=r(15),i=r(37).f,o={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];t.exports.f=function(t){return u&&"[object Window]"==o.call(t)?function(t){try{return i(t)}catch(t){return u.slice()}}(t):i(e(t))}},function(t,n,r){"use strict";var e=r(34),i=r(51),o=r(47),u=r(9),c=r(46),a=Object.assign;t.exports=!a||r(3)(function(){var t={},n={},r=Symbol(),e="abcdefghijklmnopqrst";return t[r]=7,e.split("").forEach(function(t){n[t]=t}),7!=a({},t)[r]||Object.keys(a({},n)).join("")!=e})?function(t,n){for(var r=u(t),a=arguments.length,f=1,s=i.f,l=o.f;a>f;)for(var h,v=c(arguments[f++]),p=s?e(v).concat(s(v)):e(v),d=p.length,g=0;d>g;)l.call(v,h=p[g++])&&(r[h]=v[h]);return r}:a},function(t,n,r){"use strict";var e=r(10),i=r(4),o=r(98),u=[].slice,c={};t.exports=Function.bind||function(t){var n=e(this),r=u.call(arguments,1),a=function(){var e=r.concat(u.call(arguments));return this instanceof a?function(t,n,r){if(!(n in c)){for(var e=[],i=0;i<n;i++)e[i]="a["+i+"]";c[n]=Function("F,a","return new F("+e.join(",")+")")}return c[n](t,r)}(n,e.length,e):o(n,e,t)};return i(n.prototype)&&(a.prototype=n.prototype),a}},function(t,n){t.exports=function(t,n,r){var e=void 0===r;switch(n.length){case 0:return e?t():t.call(r);case 1:return e?t(n[0]):t.call(r,n[0]);case 2:return e?t(n[0],n[1]):t.call(r,n[0],n[1]);case 3:return e?t(n[0],n[1],n[2]):t.call(r,n[0],n[1],n[2]);case 4:return e?t(n[0],n[1],n[2],n[3]):t.call(r,n[0],n[1],n[2],n[3])}return t.apply(r,n)}},function(t,n,r){var e=r(2).parseInt,i=r(43).trim,o=r(71),u=/^[-+]?0[xX]/;t.exports=8!==e(o+"08")||22!==e(o+"0x16")?function(t,n){var r=i(String(t),3);return e(r,n>>>0||(u.test(r)?16:10))}:e},function(t,n,r){var e=r(2).parseFloat,i=r(43).trim;t.exports=1/e(r(71)+"-0")!=-1/0?function(t){var n=i(String(t),3),r=e(n);return 0===r&&"-"==n.charAt(0)?-0:r}:e},function(t,n,r){var e=r(20);t.exports=function(t,n){if("number"!=typeof t&&"Number"!=e(t))throw TypeError(n);return+t}},function(t,n,r){var e=r(4),i=Math.floor;t.exports=function(t){return!e(t)&&isFinite(t)&&i(t)===t}},function(t,n){t.exports=Math.log1p||function(t){return(t=+t)>-1e-8&&t<1e-8?t-t*t/2:Math.log(1+t)}},function(t,n,r){var e=r(74),i=Math.pow,o=i(2,-52),u=i(2,-23),c=i(2,127)*(2-u),a=i(2,-126);t.exports=Math.fround||function(t){var n,r,i=Math.abs(t),f=e(t);return i<a?f*(i/a/u+1/o-1/o)*a*u:(r=(n=(1+u/o)*i)-(n-i))>c||r!=r?f*(1/0):f*r}},function(t,n,r){var e=r(1);t.exports=function(t,n,r,i){try{return i?n(e(r)[0],r[1]):n(r)}catch(n){var o=t.return;throw void 0!==o&&e(o.call(t)),n}}},function(t,n,r){var e=r(10),i=r(9),o=r(46),u=r(8);t.exports=function(t,n,r,c,a){e(n);var f=i(t),s=o(f),l=u(f.length),h=a?l-1:0,v=a?-1:1;if(r<2)for(;;){if(h in s){c=s[h],h+=v;break}if(h+=v,a?h<0:l<=h)throw TypeError("Reduce of empty array with no initial value")}for(;a?h>=0:l>h;h+=v)h in s&&(c=n(c,s[h],h,f));return c}},function(t,n,r){"use strict";var e=r(9),i=r(35),o=r(8);t.exports=[].copyWithin||function(t,n){var r=e(this),u=o(r.length),c=i(t,u),a=i(n,u),f=arguments.length>2?arguments[2]:void 0,s=Math.min((void 0===f?u:i(f,u))-a,u-c),l=1;for(a<c&&c<a+s&&(l=-1,a+=s-1,c+=s-1);s-- >0;)a in r?r[c]=r[a]:delete r[c],c+=l,a+=l;return r}},function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},function(t,n,r){r(6)&&"g"!=/./g.flags&&r(7).f(RegExp.prototype,"flags",{configurable:!0,get:r(55)})},function(t,n){t.exports=function(t){try{return{e:!1,v:t()}}catch(t){return{e:!0,v:t}}}},function(t,n,r){var e=r(1),i=r(4),o=r(89);t.exports=function(t,n){if(e(t),i(n)&&n.constructor===t)return n;var r=o.f(t);return(0,r.resolve)(n),r.promise}},function(t,n,r){"use strict";var e=r(113),i=r(45);t.exports=r(59)("Map",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{get:function(t){var n=e.getEntry(i(this,"Map"),t);return n&&n.v},set:function(t,n){return e.def(i(this,"Map"),0===t?0:t,n)}},e,!0)},function(t,n,r){"use strict";var e=r(7).f,i=r(36),o=r(41),u=r(19),c=r(39),a=r(40),f=r(77),s=r(108),l=r(38),h=r(6),v=r(29).fastKey,p=r(45),d=h?"_s":"size",g=function(t,n){var r,e=v(n);if("F"!==e)return t._i[e];for(r=t._f;r;r=r.n)if(r.k==n)return r};t.exports={getConstructor:function(t,n,r,f){var s=t(function(t,e){c(t,s,n,"_i"),t._t=n,t._i=i(null),t._f=void 0,t._l=void 0,t[d]=0,null!=e&&a(e,r,t[f],t)});return o(s.prototype,{clear:function(){for(var t=p(this,n),r=t._i,e=t._f;e;e=e.n)e.r=!0,e.p&&(e.p=e.p.n=void 0),delete r[e.i];t._f=t._l=void 0,t[d]=0},delete:function(t){var r=p(this,n),e=g(r,t);if(e){var i=e.n,o=e.p;delete r._i[e.i],e.r=!0,o&&(o.n=i),i&&(i.p=o),r._f==e&&(r._f=i),r._l==e&&(r._l=o),r[d]--}return!!e},forEach:function(t){p(this,n);for(var r,e=u(t,arguments.length>1?arguments[1]:void 0,3);r=r?r.n:this._f;)for(e(r.v,r.k,this);r&&r.r;)r=r.p},has:function(t){return!!g(p(this,n),t)}}),h&&e(s.prototype,"size",{get:function(){return p(this,n)[d]}}),s},def:function(t,n,r){var e,i,o=g(t,n);return o?o.v=r:(t._l=o={i:i=v(n,!0),k:n,v:r,p:e=t._l,n:void 0,r:!1},t._f||(t._f=o),e&&(e.n=o),t[d]++,"F"!==i&&(t._i[i]=o)),t},getEntry:g,setStrong:function(t,n,r){f(t,n,function(t,r){this._t=p(t,n),this._k=r,this._l=void 0},function(){for(var t=this._k,n=this._l;n&&n.r;)n=n.p;return this._t&&(this._l=n=n?n.n:this._t._f)?s(0,"keys"==t?n.k:"values"==t?n.v:[n.k,n.v]):(this._t=void 0,s(1))},r?"entries":"values",!r,!0),l(n)}}},function(t,n,r){"use strict";var e=r(113),i=r(45);t.exports=r(59)("Set",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(i(this,"Set"),t=0===t?0:t,t)}},e)},function(t,n,r){"use strict";var e,i=r(26)(0),o=r(12),u=r(29),c=r(96),a=r(116),f=r(4),s=r(3),l=r(45),h=u.getWeak,v=Object.isExtensible,p=a.ufstore,d={},g=function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},y={get:function(t){if(f(t)){var n=h(t);return!0===n?p(l(this,"WeakMap")).get(t):n?n[this._i]:void 0}},set:function(t,n){return a.def(l(this,"WeakMap"),t,n)}},m=t.exports=r(59)("WeakMap",g,y,a,!0,!0);s(function(){return 7!=(new m).set((Object.freeze||Object)(d),7).get(d)})&&(c((e=a.getConstructor(g,"WeakMap")).prototype,y),u.NEED=!0,i(["delete","has","get","set"],function(t){var n=m.prototype,r=n[t];o(n,t,function(n,i){if(f(n)&&!v(n)){this._f||(this._f=new e);var o=this._f[t](n,i);return"set"==t?this:o}return r.call(this,n,i)})}))},function(t,n,r){"use strict";var e=r(41),i=r(29).getWeak,o=r(1),u=r(4),c=r(39),a=r(40),f=r(26),s=r(14),l=r(45),h=f(5),v=f(6),p=0,d=function(t){return t._l||(t._l=new g)},g=function(){this.a=[]},y=function(t,n){return h(t.a,function(t){return t[0]===n})};g.prototype={get:function(t){var n=y(this,t);if(n)return n[1]},has:function(t){return!!y(this,t)},set:function(t,n){var r=y(this,t);r?r[1]=n:this.a.push([t,n])},delete:function(t){var n=v(this.a,function(n){return n[0]===t});return~n&&this.a.splice(n,1),!!~n}},t.exports={getConstructor:function(t,n,r,o){var f=t(function(t,e){c(t,f,n,"_i"),t._t=n,t._i=p++,t._l=void 0,null!=e&&a(e,r,t[o],t)});return e(f.prototype,{delete:function(t){if(!u(t))return!1;var r=i(t);return!0===r?d(l(this,n)).delete(t):r&&s(r,this._i)&&delete r[this._i]},has:function(t){if(!u(t))return!1;var r=i(t);return!0===r?d(l(this,n)).has(t):r&&s(r,this._i)}}),f},def:function(t,n,r){var e=i(o(n),!0);return!0===e?d(t).set(n,r):e[t._i]=r,t},ufstore:d}},function(t,n,r){var e=r(24),i=r(8);t.exports=function(t){if(void 0===t)return 0;var n=e(t),r=i(n);if(n!==r)throw RangeError("Wrong length!");return r}},function(t,n,r){var e=r(37),i=r(51),o=r(1),u=r(2).Reflect;t.exports=u&&u.ownKeys||function(t){var n=e.f(o(t)),r=i.f;return r?n.concat(r(t)):n}},function(t,n,r){"use strict";var e=r(52),i=r(4),o=r(8),u=r(19),c=r(5)("isConcatSpreadable");t.exports=function t(n,r,a,f,s,l,h,v){for(var p,d,g=s,y=0,m=!!h&&u(h,v,3);y<f;){if(y in a){if(p=m?m(a[y],y,r):a[y],d=!1,i(p)&&(d=void 0!==(d=p[c])?!!d:e(p)),d&&l>0)g=t(n,r,p,o(p.length),g,l-1)-1;else{if(g>=9007199254740991)throw TypeError();n[g]=p}g++}y++}return g}},function(t,n,r){var e=r(8),i=r(73),o=r(23);t.exports=function(t,n,r,u){var c=String(o(t)),a=c.length,f=void 0===r?" ":String(r),s=e(n);if(s<=a||""==f)return c;var l=s-a,h=i.call(f,Math.ceil(l/f.length));return h.length>l&&(h=h.slice(0,l)),u?h+c:c+h}},function(t,n,r){var e=r(34),i=r(15),o=r(47).f;t.exports=function(t){return function(n){for(var r,u=i(n),c=e(u),a=c.length,f=0,s=[];a>f;)o.call(u,r=c[f++])&&s.push(t?[r,u[r]]:u[r]);return s}}},function(t,n,r){var e=r(48),i=r(123);t.exports=function(t){return function(){if(e(this)!=t)throw TypeError(t+"#toJSON isn't generic");return i(this)}}},function(t,n,r){var e=r(40);t.exports=function(t,n){var r=[];return e(t,!1,r.push,r,n),r}},function(t,n){t.exports=Math.scale||function(t,n,r,e,i){return 0===arguments.length||t!=t||n!=n||r!=r||e!=e||i!=i?NaN:t===1/0||t===-1/0?t:(t-n)*(i-e)/(r-n)+e}},function(t,n){t.exports={getMajor:function(t){return parseInt(t.split(".")[0])},getMinor:function(t){return parseInt(t.split(".")[1])},getPatch:function(t){return parseInt(t.split("-")[0].split(".")[2])},isGreaterThanOrEqualTo:function(t,n){var r=this.getMajor(t),e=this.getMinor(t),i=this.getPatch(t),o=this.getMajor(n),u=this.getMinor(n),c=this.getPatch(n);return r>o||(r===o&&e>u||(r===o&&e===u&&i>c||r===o&&e===u&&i===c))}}},function(t,n,r){t.exports=r.p+"client_frame.html"},function(t,n,r){r(128),t.exports=r(331)},function(t,n,r){"use strict";(function(t){function e(){return t._babelPolyfill||"undefined"!=typeof window&&window._babelPolyfill?null:r(129)}Object.defineProperty(n,"__esModule",{value:!0}),n.idempotentBabelPolyfill=e,n.default=e()}).call(this,r(64))},function(t,n,r){"use strict";(function(t){if(r(130),r(327),r(328),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed");t._babelPolyfill=!0;var n="defineProperty";function e(t,r,e){t[r]||Object[n](t,r,{writable:!0,configurable:!0,value:e})}e(String.prototype,"padLeft","".padStart),e(String.prototype,"padRight","".padEnd),"pop,reverse,shift,keys,values,entries,indexOf,every,some,forEach,map,filter,find,findIndex,includes,join,slice,concat,push,splice,unshift,sort,lastIndexOf,reduce,reduceRight,copyWithin,fill".split(",").forEach(function(t){[][t]&&e(Array,t,Function.call.bind([][t]))})}).call(this,r(64))},function(t,n,r){r(131),r(133),r(134),r(135),r(136),r(137),r(138),r(139),r(140),r(141),r(142),r(143),r(144),r(145),r(146),r(147),r(149),r(150),r(151),r(152),r(153),r(154),r(155),r(156),r(157),r(158),r(159),r(160),r(161),r(162),r(163),r(164),r(165),r(166),r(167),r(168),r(169),r(170),r(171),r(172),r(173),r(174),r(175),r(176),r(177),r(178),r(179),r(180),r(181),r(182),r(183),r(184),r(185),r(186),r(187),r(188),r(189),r(190),r(191),r(192),r(193),r(194),r(195),r(196),r(197),r(198),r(199),r(200),r(201),r(202),r(203),r(204),r(205),r(206),r(207),r(208),r(209),r(211),r(212),r(214),r(215),r(216),r(217),r(218),r(219),r(220),r(222),r(223),r(224),r(225),r(226),r(227),r(228),r(229),r(230),r(231),r(232),r(233),r(234),r(86),r(235),r(236),r(109),r(237),r(238),r(239),r(240),r(241),r(112),r(114),r(115),r(242),r(243),r(244),r(245),r(246),r(247),r(248),r(249),r(250),r(251),r(252),r(253),r(254),r(255),r(256),r(257),r(258),r(259),r(260),r(261),r(262),r(263),r(264),r(265),r(266),r(267),r(268),r(269),r(270),r(271),r(272),r(273),r(274),r(275),r(276),r(277),r(278),r(279),r(280),r(281),r(282),r(283),r(284),r(285),r(286),r(287),r(288),r(289),r(290),r(291),r(292),r(293),r(294),r(295),r(296),r(297),r(298),r(299),r(300),r(301),r(302),r(303),r(304),r(305),r(306),r(307),r(308),r(309),r(310),r(311),r(312),r(313),r(314),r(315),r(316),r(317),r(318),r(319),r(320),r(321),r(322),r(323),r(324),r(325),r(326),t.exports=r(18)},function(t,n,r){"use strict";var e=r(2),i=r(14),o=r(6),u=r(0),c=r(12),a=r(29).KEY,f=r(3),s=r(49),l=r(42),h=r(33),v=r(5),p=r(92),d=r(66),g=r(132),y=r(52),m=r(1),b=r(4),w=r(15),S=r(22),x=r(32),_=r(36),E=r(95),O=r(16),M=r(7),P=r(34),F=O.f,A=M.f,j=E.f,I=e.Symbol,N=e.JSON,L=N&&N.stringify,T=v("_hidden"),k=v("toPrimitive"),R={}.propertyIsEnumerable,C=s("symbol-registry"),D=s("symbols"),W=s("op-symbols"),U=Object.prototype,G="function"==typeof I,V=e.QObject,B=!V||!V.prototype||!V.prototype.findChild,z=o&&f(function(){return 7!=_(A({},"a",{get:function(){return A(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=F(U,n);e&&delete U[n],A(t,n,r),e&&t!==U&&A(U,n,e)}:A,q=function(t){var n=D[t]=_(I.prototype);return n._k=t,n},J=G&&"symbol"==typeof I.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof I},Y=function(t,n,r){return t===U&&Y(W,n,r),m(t),n=S(n,!0),m(r),i(D,n)?(r.enumerable?(i(t,T)&&t[T][n]&&(t[T][n]=!1),r=_(r,{enumerable:x(0,!1)})):(i(t,T)||A(t,T,x(1,{})),t[T][n]=!0),z(t,n,r)):A(t,n,r)},K=function(t,n){m(t);for(var r,e=g(n=w(n)),i=0,o=e.length;o>i;)Y(t,r=e[i++],n[r]);return t},H=function(t){var n=R.call(this,t=S(t,!0));return!(this===U&&i(D,t)&&!i(W,t))&&(!(n||!i(this,t)||!i(D,t)||i(this,T)&&this[T][t])||n)},X=function(t,n){if(t=w(t),n=S(n,!0),t!==U||!i(D,n)||i(W,n)){var r=F(t,n);return!r||!i(D,n)||i(t,T)&&t[T][n]||(r.enumerable=!0),r}},$=function(t){for(var n,r=j(w(t)),e=[],o=0;r.length>o;)i(D,n=r[o++])||n==T||n==a||e.push(n);return e},Z=function(t){for(var n,r=t===U,e=j(r?W:w(t)),o=[],u=0;e.length>u;)!i(D,n=e[u++])||r&&!i(U,n)||o.push(D[n]);return o};G||(c((I=function(){if(this instanceof I)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===U&&n.call(W,r),i(this,T)&&i(this[T],t)&&(this[T][t]=!1),z(this,t,x(1,r))};return o&&B&&z(U,t,{configurable:!0,set:n}),q(t)}).prototype,"toString",function(){return this._k}),O.f=X,M.f=Y,r(37).f=E.f=$,r(47).f=H,r(51).f=Z,o&&!r(30)&&c(U,"propertyIsEnumerable",H,!0),p.f=function(t){return q(v(t))}),u(u.G+u.W+u.F*!G,{Symbol:I});for(var Q="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),tt=0;Q.length>tt;)v(Q[tt++]);for(var nt=P(v.store),rt=0;nt.length>rt;)d(nt[rt++]);u(u.S+u.F*!G,"Symbol",{for:function(t){return i(C,t+="")?C[t]:C[t]=I(t)},keyFor:function(t){if(!J(t))throw TypeError(t+" is not a symbol!");for(var n in C)if(C[n]===t)return n},useSetter:function(){B=!0},useSimple:function(){B=!1}}),u(u.S+u.F*!G,"Object",{create:function(t,n){return void 0===n?_(t):K(_(t),n)},defineProperty:Y,defineProperties:K,getOwnPropertyDescriptor:X,getOwnPropertyNames:$,getOwnPropertySymbols:Z}),N&&u(u.S+u.F*(!G||f(function(){var t=I();return"[null]"!=L([t])||"{}"!=L({a:t})||"{}"!=L(Object(t))})),"JSON",{stringify:function(t){for(var n,r,e=[t],i=1;arguments.length>i;)e.push(arguments[i++]);if(r=n=e[1],(b(n)||void 0!==t)&&!J(t))return y(n)||(n=function(t,n){if("function"==typeof r&&(n=r.call(this,t,n)),!J(n))return n}),e[1]=n,L.apply(N,e)}}),I.prototype[k]||r(11)(I.prototype,k,I.prototype.valueOf),l(I,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},function(t,n,r){var e=r(34),i=r(51),o=r(47);t.exports=function(t){var n=e(t),r=i.f;if(r)for(var u,c=r(t),a=o.f,f=0;c.length>f;)a.call(t,u=c[f++])&&n.push(u);return n}},function(t,n,r){var e=r(0);e(e.S,"Object",{create:r(36)})},function(t,n,r){var e=r(0);e(e.S+e.F*!r(6),"Object",{defineProperty:r(7).f})},function(t,n,r){var e=r(0);e(e.S+e.F*!r(6),"Object",{defineProperties:r(94)})},function(t,n,r){var e=r(15),i=r(16).f;r(25)("getOwnPropertyDescriptor",function(){return function(t,n){return i(e(t),n)}})},function(t,n,r){var e=r(9),i=r(17);r(25)("getPrototypeOf",function(){return function(t){return i(e(t))}})},function(t,n,r){var e=r(9),i=r(34);r(25)("keys",function(){return function(t){return i(e(t))}})},function(t,n,r){r(25)("getOwnPropertyNames",function(){return r(95).f})},function(t,n,r){var e=r(4),i=r(29).onFreeze;r(25)("freeze",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(4),i=r(29).onFreeze;r(25)("seal",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(4),i=r(29).onFreeze;r(25)("preventExtensions",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(4);r(25)("isFrozen",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(4);r(25)("isSealed",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(4);r(25)("isExtensible",function(t){return function(n){return!!e(n)&&(!t||t(n))}})},function(t,n,r){var e=r(0);e(e.S+e.F,"Object",{assign:r(96)})},function(t,n,r){var e=r(0);e(e.S,"Object",{is:r(148)})},function(t,n){t.exports=Object.is||function(t,n){return t===n?0!==t||1/t==1/n:t!=t&&n!=n}},function(t,n,r){var e=r(0);e(e.S,"Object",{setPrototypeOf:r(70).set})},function(t,n,r){"use strict";var e=r(48),i={};i[r(5)("toStringTag")]="z",i+""!="[object z]"&&r(12)(Object.prototype,"toString",function(){return"[object "+e(this)+"]"},!0)},function(t,n,r){var e=r(0);e(e.P,"Function",{bind:r(97)})},function(t,n,r){var e=r(7).f,i=Function.prototype,o=/^\s*function ([^ (]*)/;"name"in i||r(6)&&e(i,"name",{configurable:!0,get:function(){try{return(""+this).match(o)[1]}catch(t){return""}}})},function(t,n,r){"use strict";var e=r(4),i=r(17),o=r(5)("hasInstance"),u=Function.prototype;o in u||r(7).f(u,o,{value:function(t){if("function"!=typeof this||!e(t))return!1;if(!e(this.prototype))return t instanceof this;for(;t=i(t);)if(this.prototype===t)return!0;return!1}})},function(t,n,r){var e=r(0),i=r(99);e(e.G+e.F*(parseInt!=i),{parseInt:i})},function(t,n,r){var e=r(0),i=r(100);e(e.G+e.F*(parseFloat!=i),{parseFloat:i})},function(t,n,r){"use strict";var e=r(2),i=r(14),o=r(20),u=r(72),c=r(22),a=r(3),f=r(37).f,s=r(16).f,l=r(7).f,h=r(43).trim,v=e.Number,p=v,d=v.prototype,g="Number"==o(r(36)(d)),y="trim"in String.prototype,m=function(t){var n=c(t,!1);if("string"==typeof n&&n.length>2){var r,e,i,o=(n=y?n.trim():h(n,3)).charCodeAt(0);if(43===o||45===o){if(88===(r=n.charCodeAt(2))||120===r)return NaN}else if(48===o){switch(n.charCodeAt(1)){case 66:case 98:e=2,i=49;break;case 79:case 111:e=8,i=55;break;default:return+n}for(var u,a=n.slice(2),f=0,s=a.length;f<s;f++)if((u=a.charCodeAt(f))<48||u>i)return NaN;return parseInt(a,e)}}return+n};if(!v(" 0o1")||!v("0b1")||v("+0x1")){v=function(t){var n=arguments.length<1?0:t,r=this;return r instanceof v&&(g?a(function(){d.valueOf.call(r)}):"Number"!=o(r))?u(new p(m(n)),r,v):m(n)};for(var b,w=r(6)?f(p):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),S=0;w.length>S;S++)i(p,b=w[S])&&!i(v,b)&&l(v,b,s(p,b));v.prototype=d,d.constructor=v,r(12)(e,"Number",v)}},function(t,n,r){"use strict";var e=r(0),i=r(24),o=r(101),u=r(73),c=1..toFixed,a=Math.floor,f=[0,0,0,0,0,0],s="Number.toFixed: incorrect invocation!",l=function(t,n){for(var r=-1,e=n;++r<6;)e+=t*f[r],f[r]=e%1e7,e=a(e/1e7)},h=function(t){for(var n=6,r=0;--n>=0;)r+=f[n],f[n]=a(r/t),r=r%t*1e7},v=function(){for(var t=6,n="";--t>=0;)if(""!==n||0===t||0!==f[t]){var r=String(f[t]);n=""===n?r:n+u.call("0",7-r.length)+r}return n},p=function(t,n,r){return 0===n?r:n%2==1?p(t,n-1,r*t):p(t*t,n/2,r)};e(e.P+e.F*(!!c&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!r(3)(function(){c.call({})})),"Number",{toFixed:function(t){var n,r,e,c,a=o(this,s),f=i(t),d="",g="0";if(f<0||f>20)throw RangeError(s);if(a!=a)return"NaN";if(a<=-1e21||a>=1e21)return String(a);if(a<0&&(d="-",a=-a),a>1e-21)if(r=(n=function(t){for(var n=0,r=t;r>=4096;)n+=12,r/=4096;for(;r>=2;)n+=1,r/=2;return n}(a*p(2,69,1))-69)<0?a*p(2,-n,1):a/p(2,n,1),r*=4503599627370496,(n=52-n)>0){for(l(0,r),e=f;e>=7;)l(1e7,0),e-=7;for(l(p(10,e,1),0),e=n-1;e>=23;)h(1<<23),e-=23;h(1<<e),l(1,1),h(2),g=v()}else l(0,r),l(1<<-n,0),g=v()+u.call("0",f);return g=f>0?d+((c=g.length)<=f?"0."+u.call("0",f-c)+g:g.slice(0,c-f)+"."+g.slice(c-f)):d+g}})},function(t,n,r){"use strict";var e=r(0),i=r(3),o=r(101),u=1..toPrecision;e(e.P+e.F*(i(function(){return"1"!==u.call(1,void 0)})||!i(function(){u.call({})})),"Number",{toPrecision:function(t){var n=o(this,"Number#toPrecision: incorrect invocation!");return void 0===t?u.call(n):u.call(n,t)}})},function(t,n,r){var e=r(0);e(e.S,"Number",{EPSILON:Math.pow(2,-52)})},function(t,n,r){var e=r(0),i=r(2).isFinite;e(e.S,"Number",{isFinite:function(t){return"number"==typeof t&&i(t)}})},function(t,n,r){var e=r(0);e(e.S,"Number",{isInteger:r(102)})},function(t,n,r){var e=r(0);e(e.S,"Number",{isNaN:function(t){return t!=t}})},function(t,n,r){var e=r(0),i=r(102),o=Math.abs;e(e.S,"Number",{isSafeInteger:function(t){return i(t)&&o(t)<=9007199254740991}})},function(t,n,r){var e=r(0);e(e.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},function(t,n,r){var e=r(0);e(e.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},function(t,n,r){var e=r(0),i=r(100);e(e.S+e.F*(Number.parseFloat!=i),"Number",{parseFloat:i})},function(t,n,r){var e=r(0),i=r(99);e(e.S+e.F*(Number.parseInt!=i),"Number",{parseInt:i})},function(t,n,r){var e=r(0),i=r(103),o=Math.sqrt,u=Math.acosh;e(e.S+e.F*!(u&&710==Math.floor(u(Number.MAX_VALUE))&&u(1/0)==1/0),"Math",{acosh:function(t){return(t=+t)<1?NaN:t>94906265.62425156?Math.log(t)+Math.LN2:i(t-1+o(t-1)*o(t+1))}})},function(t,n,r){var e=r(0),i=Math.asinh;e(e.S+e.F*!(i&&1/i(0)>0),"Math",{asinh:function t(n){return isFinite(n=+n)&&0!=n?n<0?-t(-n):Math.log(n+Math.sqrt(n*n+1)):n}})},function(t,n,r){var e=r(0),i=Math.atanh;e(e.S+e.F*!(i&&1/i(-0)<0),"Math",{atanh:function(t){return 0==(t=+t)?t:Math.log((1+t)/(1-t))/2}})},function(t,n,r){var e=r(0),i=r(74);e(e.S,"Math",{cbrt:function(t){return i(t=+t)*Math.pow(Math.abs(t),1/3)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{clz32:function(t){return(t>>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},function(t,n,r){var e=r(0),i=Math.exp;e(e.S,"Math",{cosh:function(t){return(i(t=+t)+i(-t))/2}})},function(t,n,r){var e=r(0),i=r(75);e(e.S+e.F*(i!=Math.expm1),"Math",{expm1:i})},function(t,n,r){var e=r(0);e(e.S,"Math",{fround:r(104)})},function(t,n,r){var e=r(0),i=Math.abs;e(e.S,"Math",{hypot:function(t,n){for(var r,e,o=0,u=0,c=arguments.length,a=0;u<c;)a<(r=i(arguments[u++]))?(o=o*(e=a/r)*e+1,a=r):o+=r>0?(e=r/a)*e:r;return a===1/0?1/0:a*Math.sqrt(o)}})},function(t,n,r){var e=r(0),i=Math.imul;e(e.S+e.F*r(3)(function(){return-5!=i(4294967295,5)||2!=i.length}),"Math",{imul:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e;return 0|i*o+((65535&r>>>16)*o+i*(65535&e>>>16)<<16>>>0)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{log10:function(t){return Math.log(t)*Math.LOG10E}})},function(t,n,r){var e=r(0);e(e.S,"Math",{log1p:r(103)})},function(t,n,r){var e=r(0);e(e.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},function(t,n,r){var e=r(0);e(e.S,"Math",{sign:r(74)})},function(t,n,r){var e=r(0),i=r(75),o=Math.exp;e(e.S+e.F*r(3)(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(i(t)-i(-t))/2:(o(t-1)-o(-t-1))*(Math.E/2)}})},function(t,n,r){var e=r(0),i=r(75),o=Math.exp;e(e.S,"Math",{tanh:function(t){var n=i(t=+t),r=i(-t);return n==1/0?1:r==1/0?-1:(n-r)/(o(t)+o(-t))}})},function(t,n,r){var e=r(0);e(e.S,"Math",{trunc:function(t){return(t>0?Math.floor:Math.ceil)(t)}})},function(t,n,r){var e=r(0),i=r(35),o=String.fromCharCode,u=String.fromCodePoint;e(e.S+e.F*(!!u&&1!=u.length),"String",{fromCodePoint:function(t){for(var n,r=[],e=arguments.length,u=0;e>u;){if(n=+arguments[u++],i(n,1114111)!==n)throw RangeError(n+" is not a valid code point");r.push(n<65536?o(n):o(55296+((n-=65536)>>10),n%1024+56320))}return r.join("")}})},function(t,n,r){var e=r(0),i=r(15),o=r(8);e(e.S,"String",{raw:function(t){for(var n=i(t.raw),r=o(n.length),e=arguments.length,u=[],c=0;r>c;)u.push(String(n[c++])),c<e&&u.push(String(arguments[c]));return u.join("")}})},function(t,n,r){"use strict";r(43)("trim",function(t){return function(){return t(this,3)}})},function(t,n,r){"use strict";var e=r(76)(!0);r(77)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},function(t,n,r){"use strict";var e=r(0),i=r(76)(!1);e(e.P,"String",{codePointAt:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(0),i=r(8),o=r(79),u="".endsWith;e(e.P+e.F*r(80)("endsWith"),"String",{endsWith:function(t){var n=o(this,t,"endsWith"),r=arguments.length>1?arguments[1]:void 0,e=i(n.length),c=void 0===r?e:Math.min(i(r),e),a=String(t);return u?u.call(n,a,c):n.slice(c-a.length,c)===a}})},function(t,n,r){"use strict";var e=r(0),i=r(79);e(e.P+e.F*r(80)("includes"),"String",{includes:function(t){return!!~i(this,t,"includes").indexOf(t,arguments.length>1?arguments[1]:void 0)}})},function(t,n,r){var e=r(0);e(e.P,"String",{repeat:r(73)})},function(t,n,r){"use strict";var e=r(0),i=r(8),o=r(79),u="".startsWith;e(e.P+e.F*r(80)("startsWith"),"String",{startsWith:function(t){var n=o(this,t,"startsWith"),r=i(Math.min(arguments.length>1?arguments[1]:void 0,n.length)),e=String(t);return u?u.call(n,e,r):n.slice(r,r+e.length)===e}})},function(t,n,r){"use strict";r(13)("anchor",function(t){return function(n){return t(this,"a","name",n)}})},function(t,n,r){"use strict";r(13)("big",function(t){return function(){return t(this,"big","","")}})},function(t,n,r){"use strict";r(13)("blink",function(t){return function(){return t(this,"blink","","")}})},function(t,n,r){"use strict";r(13)("bold",function(t){return function(){return t(this,"b","","")}})},function(t,n,r){"use strict";r(13)("fixed",function(t){return function(){return t(this,"tt","","")}})},function(t,n,r){"use strict";r(13)("fontcolor",function(t){return function(n){return t(this,"font","color",n)}})},function(t,n,r){"use strict";r(13)("fontsize",function(t){return function(n){return t(this,"font","size",n)}})},function(t,n,r){"use strict";r(13)("italics",function(t){return function(){return t(this,"i","","")}})},function(t,n,r){"use strict";r(13)("link",function(t){return function(n){return t(this,"a","href",n)}})},function(t,n,r){"use strict";r(13)("small",function(t){return function(){return t(this,"small","","")}})},function(t,n,r){"use strict";r(13)("strike",function(t){return function(){return t(this,"strike","","")}})},function(t,n,r){"use strict";r(13)("sub",function(t){return function(){return t(this,"sub","","")}})},function(t,n,r){"use strict";r(13)("sup",function(t){return function(){return t(this,"sup","","")}})},function(t,n,r){var e=r(0);e(e.S,"Date",{now:function(){return(new Date).getTime()}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(22);e(e.P+e.F*r(3)(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(t){var n=i(this),r=o(n);return"number"!=typeof r||isFinite(r)?n.toISOString():null}})},function(t,n,r){var e=r(0),i=r(210);e(e.P+e.F*(Date.prototype.toISOString!==i),"Date",{toISOString:i})},function(t,n,r){"use strict";var e=r(3),i=Date.prototype.getTime,o=Date.prototype.toISOString,u=function(t){return t>9?t:"0"+t};t.exports=e(function(){return"0385-07-25T07:06:39.999Z"!=o.call(new Date(-5e13-1))})||!e(function(){o.call(new Date(NaN))})?function(){if(!isFinite(i.call(this)))throw RangeError("Invalid time value");var t=this,n=t.getUTCFullYear(),r=t.getUTCMilliseconds(),e=n<0?"-":n>9999?"+":"";return e+("00000"+Math.abs(n)).slice(e?-6:-4)+"-"+u(t.getUTCMonth()+1)+"-"+u(t.getUTCDate())+"T"+u(t.getUTCHours())+":"+u(t.getUTCMinutes())+":"+u(t.getUTCSeconds())+"."+(r>99?r:"0"+u(r))+"Z"}:o},function(t,n,r){var e=Date.prototype,i=e.toString,o=e.getTime;new Date(NaN)+""!="Invalid Date"&&r(12)(e,"toString",function(){var t=o.call(this);return t==t?i.call(this):"Invalid Date"})},function(t,n,r){var e=r(5)("toPrimitive"),i=Date.prototype;e in i||r(11)(i,e,r(213))},function(t,n,r){"use strict";var e=r(1),i=r(22);t.exports=function(t){if("string"!==t&&"number"!==t&&"default"!==t)throw TypeError("Incorrect hint");return i(e(this),"number"!=t)}},function(t,n,r){var e=r(0);e(e.S,"Array",{isArray:r(52)})},function(t,n,r){"use strict";var e=r(19),i=r(0),o=r(9),u=r(105),c=r(81),a=r(8),f=r(82),s=r(83);i(i.S+i.F*!r(54)(function(t){Array.from(t)}),"Array",{from:function(t){var n,r,i,l,h=o(t),v="function"==typeof this?this:Array,p=arguments.length,d=p>1?arguments[1]:void 0,g=void 0!==d,y=0,m=s(h);if(g&&(d=e(d,p>2?arguments[2]:void 0,2)),null==m||v==Array&&c(m))for(r=new v(n=a(h.length));n>y;y++)f(r,y,g?d(h[y],y):h[y]);else for(l=m.call(h),r=new v;!(i=l.next()).done;y++)f(r,y,g?u(l,d,[i.value,y],!0):i.value);return r.length=y,r}})},function(t,n,r){"use strict";var e=r(0),i=r(82);e(e.S+e.F*r(3)(function(){function t(){}return!(Array.of.call(t)instanceof t)}),"Array",{of:function(){for(var t=0,n=arguments.length,r=new("function"==typeof this?this:Array)(n);n>t;)i(r,t,arguments[t++]);return r.length=n,r}})},function(t,n,r){"use strict";var e=r(0),i=r(15),o=[].join;e(e.P+e.F*(r(46)!=Object||!r(21)(o)),"Array",{join:function(t){return o.call(i(this),void 0===t?",":t)}})},function(t,n,r){"use strict";var e=r(0),i=r(69),o=r(20),u=r(35),c=r(8),a=[].slice;e(e.P+e.F*r(3)(function(){i&&a.call(i)}),"Array",{slice:function(t,n){var r=c(this.length),e=o(this);if(n=void 0===n?r:n,"Array"==e)return a.call(this,t,n);for(var i=u(t,r),f=u(n,r),s=c(f-i),l=new Array(s),h=0;h<s;h++)l[h]="String"==e?this.charAt(i+h):this[i+h];return l}})},function(t,n,r){"use strict";var e=r(0),i=r(10),o=r(9),u=r(3),c=[].sort,a=[1,2,3];e(e.P+e.F*(u(function(){a.sort(void 0)})||!u(function(){a.sort(null)})||!r(21)(c)),"Array",{sort:function(t){return void 0===t?c.call(o(this)):c.call(o(this),i(t))}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(0),o=r(21)([].forEach,!0);e(e.P+e.F*!o,"Array",{forEach:function(t){return i(this,t,arguments[1])}})},function(t,n,r){var e=r(4),i=r(52),o=r(5)("species");t.exports=function(t){var n;return i(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!i(n.prototype)||(n=void 0),e(n)&&null===(n=n[o])&&(n=void 0)),void 0===n?Array:n}},function(t,n,r){"use strict";var e=r(0),i=r(26)(1);e(e.P+e.F*!r(21)([].map,!0),"Array",{map:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(2);e(e.P+e.F*!r(21)([].filter,!0),"Array",{filter:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(3);e(e.P+e.F*!r(21)([].some,!0),"Array",{some:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(26)(4);e(e.P+e.F*!r(21)([].every,!0),"Array",{every:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(106);e(e.P+e.F*!r(21)([].reduce,!0),"Array",{reduce:function(t){return i(this,t,arguments.length,arguments[1],!1)}})},function(t,n,r){"use strict";var e=r(0),i=r(106);e(e.P+e.F*!r(21)([].reduceRight,!0),"Array",{reduceRight:function(t){return i(this,t,arguments.length,arguments[1],!0)}})},function(t,n,r){"use strict";var e=r(0),i=r(50)(!1),o=[].indexOf,u=!!o&&1/[1].indexOf(1,-0)<0;e(e.P+e.F*(u||!r(21)(o)),"Array",{indexOf:function(t){return u?o.apply(this,arguments)||0:i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(0),i=r(15),o=r(24),u=r(8),c=[].lastIndexOf,a=!!c&&1/[1].lastIndexOf(1,-0)<0;e(e.P+e.F*(a||!r(21)(c)),"Array",{lastIndexOf:function(t){if(a)return c.apply(this,arguments)||0;var n=i(this),r=u(n.length),e=r-1;for(arguments.length>1&&(e=Math.min(e,o(arguments[1]))),e<0&&(e=r+e);e>=0;e--)if(e in n&&n[e]===t)return e||0;return-1}})},function(t,n,r){var e=r(0);e(e.P,"Array",{copyWithin:r(107)}),r(31)("copyWithin")},function(t,n,r){var e=r(0);e(e.P,"Array",{fill:r(85)}),r(31)("fill")},function(t,n,r){"use strict";var e=r(0),i=r(26)(5),o=!0;"find"in[]&&Array(1).find(function(){o=!1}),e(e.P+e.F*o,"Array",{find:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(31)("find")},function(t,n,r){"use strict";var e=r(0),i=r(26)(6),o="findIndex",u=!0;o in[]&&Array(1)[o](function(){u=!1}),e(e.P+e.F*u,"Array",{findIndex:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(31)(o)},function(t,n,r){r(38)("Array")},function(t,n,r){var e=r(2),i=r(72),o=r(7).f,u=r(37).f,c=r(53),a=r(55),f=e.RegExp,s=f,l=f.prototype,h=/a/g,v=/a/g,p=new f(h)!==h;if(r(6)&&(!p||r(3)(function(){return v[r(5)("match")]=!1,f(h)!=h||f(v)==v||"/a/i"!=f(h,"i")}))){f=function(t,n){var r=this instanceof f,e=c(t),o=void 0===n;return!r&&e&&t.constructor===f&&o?t:i(p?new s(e&&!o?t.source:t,n):s((e=t instanceof f)?t.source:t,e&&o?a.call(t):n),r?this:l,f)};for(var d=function(t){t in f||o(f,t,{configurable:!0,get:function(){return s[t]},set:function(n){s[t]=n}})},g=u(s),y=0;g.length>y;)d(g[y++]);l.constructor=f,f.prototype=l,r(12)(e,"RegExp",f)}r(38)("RegExp")},function(t,n,r){"use strict";r(109);var e=r(1),i=r(55),o=r(6),u=/./.toString,c=function(t){r(12)(RegExp.prototype,"toString",t,!0)};r(3)(function(){return"/a/b"!=u.call({source:"a",flags:"b"})})?c(function(){var t=e(this);return"/".concat(t.source,"/","flags"in t?t.flags:!o&&t instanceof RegExp?i.call(t):void 0)}):"toString"!=u.name&&c(function(){return u.call(this)})},function(t,n,r){r(56)("match",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=null==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(56)("replace",2,function(t,n,r){return[function(e,i){"use strict";var o=t(this),u=null==e?void 0:e[n];return void 0!==u?u.call(e,o,i):r.call(String(o),e,i)},r]})},function(t,n,r){r(56)("search",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=null==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(56)("split",2,function(t,n,e){"use strict";var i=r(53),o=e,u=[].push;if("c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length){var c=void 0===/()??/.exec("")[1];e=function(t,n){var r=String(this);if(void 0===t&&0===n)return[];if(!i(t))return o.call(r,t,n);var e,a,f,s,l,h=[],v=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),p=0,d=void 0===n?4294967295:n>>>0,g=new RegExp(t.source,v+"g");for(c||(e=new RegExp("^"+g.source+"$(?!\\s)",v));(a=g.exec(r))&&!((f=a.index+a[0].length)>p&&(h.push(r.slice(p,a.index)),!c&&a.length>1&&a[0].replace(e,function(){for(l=1;l<arguments.length-2;l++)void 0===arguments[l]&&(a[l]=void 0)}),a.length>1&&a.index<r.length&&u.apply(h,a.slice(1)),s=a[0].length,p=f,h.length>=d));)g.lastIndex===a.index&&g.lastIndex++;return p===r.length?!s&&g.test("")||h.push(""):h.push(r.slice(p)),h.length>d?h.slice(0,d):h}}else"0".split(void 0,0).length&&(e=function(t,n){return void 0===t&&0===n?[]:o.call(this,t,n)});return[function(r,i){var o=t(this),u=null==r?void 0:r[n];return void 0!==u?u.call(r,o,i):e.call(String(o),r,i)},e]})},function(t,n,r){"use strict";var e,i,o,u,c=r(30),a=r(2),f=r(19),s=r(48),l=r(0),h=r(4),v=r(10),p=r(39),d=r(40),g=r(57),y=r(87).set,m=r(88)(),b=r(89),w=r(110),S=r(58),x=r(111),_=a.TypeError,E=a.process,O=E&&E.versions,M=O&&O.v8||"",P=a.Promise,F="process"==s(E),A=function(){},j=i=b.f,I=!!function(){try{var t=P.resolve(1),n=(t.constructor={})[r(5)("species")]=function(t){t(A,A)};return(F||"function"==typeof PromiseRejectionEvent)&&t.then(A)instanceof n&&0!==M.indexOf("6.6")&&-1===S.indexOf("Chrome/66")}catch(t){}}(),N=function(t){var n;return!(!h(t)||"function"!=typeof(n=t.then))&&n},L=function(t,n){if(!t._n){t._n=!0;var r=t._c;m(function(){for(var e=t._v,i=1==t._s,o=0,u=function(n){var r,o,u,c=i?n.ok:n.fail,a=n.resolve,f=n.reject,s=n.domain;try{c?(i||(2==t._h&&R(t),t._h=1),!0===c?r=e:(s&&s.enter(),r=c(e),s&&(s.exit(),u=!0)),r===n.promise?f(_("Promise-chain cycle")):(o=N(r))?o.call(r,a,f):a(r)):f(e)}catch(t){s&&!u&&s.exit(),f(t)}};r.length>o;)u(r[o++]);t._c=[],t._n=!1,n&&!t._h&&T(t)})}},T=function(t){y.call(a,function(){var n,r,e,i=t._v,o=k(t);if(o&&(n=w(function(){F?E.emit("unhandledRejection",i,t):(r=a.onunhandledrejection)?r({promise:t,reason:i}):(e=a.console)&&e.error&&e.error("Unhandled promise rejection",i)}),t._h=F||k(t)?2:1),t._a=void 0,o&&n.e)throw n.v})},k=function(t){return 1!==t._h&&0===(t._a||t._c).length},R=function(t){y.call(a,function(){var n;F?E.emit("rejectionHandled",t):(n=a.onrejectionhandled)&&n({promise:t,reason:t._v})})},C=function(t){var n=this;n._d||(n._d=!0,(n=n._w||n)._v=t,n._s=2,n._a||(n._a=n._c.slice()),L(n,!0))},D=function(t){var n,r=this;if(!r._d){r._d=!0,r=r._w||r;try{if(r===t)throw _("Promise can't be resolved itself");(n=N(t))?m(function(){var e={_w:r,_d:!1};try{n.call(t,f(D,e,1),f(C,e,1))}catch(t){C.call(e,t)}}):(r._v=t,r._s=1,L(r,!1))}catch(t){C.call({_w:r,_d:!1},t)}}};I||(P=function(t){p(this,P,"Promise","_h"),v(t),e.call(this);try{t(f(D,this,1),f(C,this,1))}catch(t){C.call(this,t)}},(e=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1}).prototype=r(41)(P.prototype,{then:function(t,n){var r=j(g(this,P));return r.ok="function"!=typeof t||t,r.fail="function"==typeof n&&n,r.domain=F?E.domain:void 0,this._c.push(r),this._a&&this._a.push(r),this._s&&L(this,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),o=function(){var t=new e;this.promise=t,this.resolve=f(D,t,1),this.reject=f(C,t,1)},b.f=j=function(t){return t===P||t===u?new o(t):i(t)}),l(l.G+l.W+l.F*!I,{Promise:P}),r(42)(P,"Promise"),r(38)("Promise"),u=r(18).Promise,l(l.S+l.F*!I,"Promise",{reject:function(t){var n=j(this);return(0,n.reject)(t),n.promise}}),l(l.S+l.F*(c||!I),"Promise",{resolve:function(t){return x(c&&this===u?P:this,t)}}),l(l.S+l.F*!(I&&r(54)(function(t){P.all(t).catch(A)})),"Promise",{all:function(t){var n=this,r=j(n),e=r.resolve,i=r.reject,o=w(function(){var r=[],o=0,u=1;d(t,!1,function(t){var c=o++,a=!1;r.push(void 0),u++,n.resolve(t).then(function(t){a||(a=!0,r[c]=t,--u||e(r))},i)}),--u||e(r)});return o.e&&i(o.v),r.promise},race:function(t){var n=this,r=j(n),e=r.reject,i=w(function(){d(t,!1,function(t){n.resolve(t).then(r.resolve,e)})});return i.e&&e(i.v),r.promise}})},function(t,n,r){"use strict";var e=r(116),i=r(45);r(59)("WeakSet",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(i(this,"WeakSet"),t,!0)}},e,!1,!0)},function(t,n,r){"use strict";var e=r(0),i=r(60),o=r(90),u=r(1),c=r(35),a=r(8),f=r(4),s=r(2).ArrayBuffer,l=r(57),h=o.ArrayBuffer,v=o.DataView,p=i.ABV&&s.isView,d=h.prototype.slice,g=i.VIEW;e(e.G+e.W+e.F*(s!==h),{ArrayBuffer:h}),e(e.S+e.F*!i.CONSTR,"ArrayBuffer",{isView:function(t){return p&&p(t)||f(t)&&g in t}}),e(e.P+e.U+e.F*r(3)(function(){return!new h(2).slice(1,void 0).byteLength}),"ArrayBuffer",{slice:function(t,n){if(void 0!==d&&void 0===n)return d.call(u(this),t);for(var r=u(this).byteLength,e=c(t,r),i=c(void 0===n?r:n,r),o=new(l(this,h))(a(i-e)),f=new v(this),s=new v(o),p=0;e<i;)s.setUint8(p++,f.getUint8(e++));return o}}),r(38)("ArrayBuffer")},function(t,n,r){var e=r(0);e(e.G+e.W+e.F*!r(60).ABV,{DataView:r(90).DataView})},function(t,n,r){r(27)("Int8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}},!0)},function(t,n,r){r(27)("Int16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Int32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Uint32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Float32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(27)("Float64",8,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){var e=r(0),i=r(10),o=r(1),u=(r(2).Reflect||{}).apply,c=Function.apply;e(e.S+e.F*!r(3)(function(){u(function(){})}),"Reflect",{apply:function(t,n,r){var e=i(t),a=o(r);return u?u(e,n,a):c.call(e,n,a)}})},function(t,n,r){var e=r(0),i=r(36),o=r(10),u=r(1),c=r(4),a=r(3),f=r(97),s=(r(2).Reflect||{}).construct,l=a(function(){function t(){}return!(s(function(){},[],t)instanceof t)}),h=!a(function(){s(function(){})});e(e.S+e.F*(l||h),"Reflect",{construct:function(t,n){o(t),u(n);var r=arguments.length<3?t:o(arguments[2]);if(h&&!l)return s(t,n,r);if(t==r){switch(n.length){case 0:return new t;case 1:return new t(n[0]);case 2:return new t(n[0],n[1]);case 3:return new t(n[0],n[1],n[2]);case 4:return new t(n[0],n[1],n[2],n[3])}var e=[null];return e.push.apply(e,n),new(f.apply(t,e))}var a=r.prototype,v=i(c(a)?a:Object.prototype),p=Function.apply.call(t,v,n);return c(p)?p:v}})},function(t,n,r){var e=r(7),i=r(0),o=r(1),u=r(22);i(i.S+i.F*r(3)(function(){Reflect.defineProperty(e.f({},1,{value:1}),1,{value:2})}),"Reflect",{defineProperty:function(t,n,r){o(t),n=u(n,!0),o(r);try{return e.f(t,n,r),!0}catch(t){return!1}}})},function(t,n,r){var e=r(0),i=r(16).f,o=r(1);e(e.S,"Reflect",{deleteProperty:function(t,n){var r=i(o(t),n);return!(r&&!r.configurable)&&delete t[n]}})},function(t,n,r){"use strict";var e=r(0),i=r(1),o=function(t){this._t=i(t),this._i=0;var n,r=this._k=[];for(n in t)r.push(n)};r(78)(o,"Object",function(){var t,n=this._k;do{if(this._i>=n.length)return{value:void 0,done:!0}}while(!((t=n[this._i++])in this._t));return{value:t,done:!1}}),e(e.S,"Reflect",{enumerate:function(t){return new o(t)}})},function(t,n,r){var e=r(16),i=r(17),o=r(14),u=r(0),c=r(4),a=r(1);u(u.S,"Reflect",{get:function t(n,r){var u,f,s=arguments.length<3?n:arguments[2];return a(n)===s?n[r]:(u=e.f(n,r))?o(u,"value")?u.value:void 0!==u.get?u.get.call(s):void 0:c(f=i(n))?t(f,r,s):void 0}})},function(t,n,r){var e=r(16),i=r(0),o=r(1);i(i.S,"Reflect",{getOwnPropertyDescriptor:function(t,n){return e.f(o(t),n)}})},function(t,n,r){var e=r(0),i=r(17),o=r(1);e(e.S,"Reflect",{getPrototypeOf:function(t){return i(o(t))}})},function(t,n,r){var e=r(0);e(e.S,"Reflect",{has:function(t,n){return n in t}})},function(t,n,r){var e=r(0),i=r(1),o=Object.isExtensible;e(e.S,"Reflect",{isExtensible:function(t){return i(t),!o||o(t)}})},function(t,n,r){var e=r(0);e(e.S,"Reflect",{ownKeys:r(118)})},function(t,n,r){var e=r(0),i=r(1),o=Object.preventExtensions;e(e.S,"Reflect",{preventExtensions:function(t){i(t);try{return o&&o(t),!0}catch(t){return!1}}})},function(t,n,r){var e=r(7),i=r(16),o=r(17),u=r(14),c=r(0),a=r(32),f=r(1),s=r(4);c(c.S,"Reflect",{set:function t(n,r,c){var l,h,v=arguments.length<4?n:arguments[3],p=i.f(f(n),r);if(!p){if(s(h=o(n)))return t(h,r,c,v);p=a(0)}if(u(p,"value")){if(!1===p.writable||!s(v))return!1;if(l=i.f(v,r)){if(l.get||l.set||!1===l.writable)return!1;l.value=c,e.f(v,r,l)}else e.f(v,r,a(0,c));return!0}return void 0!==p.set&&(p.set.call(v,c),!0)}})},function(t,n,r){var e=r(0),i=r(70);i&&e(e.S,"Reflect",{setPrototypeOf:function(t,n){i.check(t,n);try{return i.set(t,n),!0}catch(t){return!1}}})},function(t,n,r){"use strict";var e=r(0),i=r(50)(!0);e(e.P,"Array",{includes:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(31)("includes")},function(t,n,r){"use strict";var e=r(0),i=r(119),o=r(9),u=r(8),c=r(10),a=r(84);e(e.P,"Array",{flatMap:function(t){var n,r,e=o(this);return c(t),n=u(e.length),r=a(e,0),i(r,e,e,n,0,1,t,arguments[1]),r}}),r(31)("flatMap")},function(t,n,r){"use strict";var e=r(0),i=r(119),o=r(9),u=r(8),c=r(24),a=r(84);e(e.P,"Array",{flatten:function(){var t=arguments[0],n=o(this),r=u(n.length),e=a(n,0);return i(e,n,n,r,0,void 0===t?1:c(t)),e}}),r(31)("flatten")},function(t,n,r){"use strict";var e=r(0),i=r(76)(!0);e(e.P,"String",{at:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(0),i=r(120),o=r(58);e(e.P+e.F*/Version\/10\.\d+(\.\d+)? Safari\//.test(o),"String",{padStart:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!0)}})},function(t,n,r){"use strict";var e=r(0),i=r(120),o=r(58);e(e.P+e.F*/Version\/10\.\d+(\.\d+)? Safari\//.test(o),"String",{padEnd:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!1)}})},function(t,n,r){"use strict";r(43)("trimLeft",function(t){return function(){return t(this,1)}},"trimStart")},function(t,n,r){"use strict";r(43)("trimRight",function(t){return function(){return t(this,2)}},"trimEnd")},function(t,n,r){"use strict";var e=r(0),i=r(23),o=r(8),u=r(53),c=r(55),a=RegExp.prototype,f=function(t,n){this._r=t,this._s=n};r(78)(f,"RegExp String",function(){var t=this._r.exec(this._s);return{value:t,done:null===t}}),e(e.P,"String",{matchAll:function(t){if(i(this),!u(t))throw TypeError(t+" is not a regexp!");var n=String(this),r="flags"in a?String(t.flags):c.call(t),e=new RegExp(t.source,~r.indexOf("g")?r:"g"+r);return e.lastIndex=o(t.lastIndex),new f(e,n)}})},function(t,n,r){r(66)("asyncIterator")},function(t,n,r){r(66)("observable")},function(t,n,r){var e=r(0),i=r(118),o=r(15),u=r(16),c=r(82);e(e.S,"Object",{getOwnPropertyDescriptors:function(t){for(var n,r,e=o(t),a=u.f,f=i(e),s={},l=0;f.length>l;)void 0!==(r=a(e,n=f[l++]))&&c(s,n,r);return s}})},function(t,n,r){var e=r(0),i=r(121)(!1);e(e.S,"Object",{values:function(t){return i(t)}})},function(t,n,r){var e=r(0),i=r(121)(!0);e(e.S,"Object",{entries:function(t){return i(t)}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(10),u=r(7);r(6)&&e(e.P+r(61),"Object",{__defineGetter__:function(t,n){u.f(i(this),t,{get:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(10),u=r(7);r(6)&&e(e.P+r(61),"Object",{__defineSetter__:function(t,n){u.f(i(this),t,{set:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(22),u=r(17),c=r(16).f;r(6)&&e(e.P+r(61),"Object",{__lookupGetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.get}while(r=u(r))}})},function(t,n,r){"use strict";var e=r(0),i=r(9),o=r(22),u=r(17),c=r(16).f;r(6)&&e(e.P+r(61),"Object",{__lookupSetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.set}while(r=u(r))}})},function(t,n,r){var e=r(0);e(e.P+e.R,"Map",{toJSON:r(122)("Map")})},function(t,n,r){var e=r(0);e(e.P+e.R,"Set",{toJSON:r(122)("Set")})},function(t,n,r){r(62)("Map")},function(t,n,r){r(62)("Set")},function(t,n,r){r(62)("WeakMap")},function(t,n,r){r(62)("WeakSet")},function(t,n,r){r(63)("Map")},function(t,n,r){r(63)("Set")},function(t,n,r){r(63)("WeakMap")},function(t,n,r){r(63)("WeakSet")},function(t,n,r){var e=r(0);e(e.G,{global:r(2)})},function(t,n,r){var e=r(0);e(e.S,"System",{global:r(2)})},function(t,n,r){var e=r(0),i=r(20);e(e.S,"Error",{isError:function(t){return"Error"===i(t)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{clamp:function(t,n,r){return Math.min(r,Math.max(n,t))}})},function(t,n,r){var e=r(0);e(e.S,"Math",{DEG_PER_RAD:Math.PI/180})},function(t,n,r){var e=r(0),i=180/Math.PI;e(e.S,"Math",{degrees:function(t){return t*i}})},function(t,n,r){var e=r(0),i=r(124),o=r(104);e(e.S,"Math",{fscale:function(t,n,r,e,u){return o(i(t,n,r,e,u))}})},function(t,n,r){var e=r(0);e(e.S,"Math",{iaddh:function(t,n,r,e){var i=t>>>0,o=r>>>0;return(n>>>0)+(e>>>0)+((i&o|(i|o)&~(i+o>>>0))>>>31)|0}})},function(t,n,r){var e=r(0);e(e.S,"Math",{isubh:function(t,n,r,e){var i=t>>>0,o=r>>>0;return(n>>>0)-(e>>>0)-((~i&o|~(i^o)&i-o>>>0)>>>31)|0}})},function(t,n,r){var e=r(0);e(e.S,"Math",{imulh:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e,u=r>>16,c=e>>16,a=(u*o>>>0)+(i*o>>>16);return u*c+(a>>16)+((i*c>>>0)+(65535&a)>>16)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{RAD_PER_DEG:180/Math.PI})},function(t,n,r){var e=r(0),i=Math.PI/180;e(e.S,"Math",{radians:function(t){return t*i}})},function(t,n,r){var e=r(0);e(e.S,"Math",{scale:r(124)})},function(t,n,r){var e=r(0);e(e.S,"Math",{umulh:function(t,n){var r=+t,e=+n,i=65535&r,o=65535&e,u=r>>>16,c=e>>>16,a=(u*o>>>0)+(i*o>>>16);return u*c+(a>>>16)+((i*c>>>0)+(65535&a)>>>16)}})},function(t,n,r){var e=r(0);e(e.S,"Math",{signbit:function(t){return(t=+t)!=t?t:0==t?1/t==1/0:t>0}})},function(t,n,r){"use strict";var e=r(0),i=r(18),o=r(2),u=r(57),c=r(111);e(e.P+e.R,"Promise",{finally:function(t){var n=u(this,i.Promise||o.Promise),r="function"==typeof t;return this.then(r?function(r){return c(n,t()).then(function(){return r})}:t,r?function(r){return c(n,t()).then(function(){throw r})}:t)}})},function(t,n,r){"use strict";var e=r(0),i=r(89),o=r(110);e(e.S,"Promise",{try:function(t){var n=i.f(this),r=o(t);return(r.e?n.reject:n.resolve)(r.v),n.promise}})},function(t,n,r){var e=r(28),i=r(1),o=e.key,u=e.set;e.exp({defineMetadata:function(t,n,r,e){u(t,n,i(r),o(e))}})},function(t,n,r){var e=r(28),i=r(1),o=e.key,u=e.map,c=e.store;e.exp({deleteMetadata:function(t,n){var r=arguments.length<3?void 0:o(arguments[2]),e=u(i(n),r,!1);if(void 0===e||!e.delete(t))return!1;if(e.size)return!0;var a=c.get(n);return a.delete(r),!!a.size||c.delete(n)}})},function(t,n,r){var e=r(28),i=r(1),o=r(17),u=e.has,c=e.get,a=e.key,f=function(t,n,r){if(u(t,n,r))return c(t,n,r);var e=o(n);return null!==e?f(t,e,r):void 0};e.exp({getMetadata:function(t,n){return f(t,i(n),arguments.length<3?void 0:a(arguments[2]))}})},function(t,n,r){var e=r(114),i=r(123),o=r(28),u=r(1),c=r(17),a=o.keys,f=o.key,s=function(t,n){var r=a(t,n),o=c(t);if(null===o)return r;var u=s(o,n);return u.length?r.length?i(new e(r.concat(u))):u:r};o.exp({getMetadataKeys:function(t){return s(u(t),arguments.length<2?void 0:f(arguments[1]))}})},function(t,n,r){var e=r(28),i=r(1),o=e.get,u=e.key;e.exp({getOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(28),i=r(1),o=e.keys,u=e.key;e.exp({getOwnMetadataKeys:function(t){return o(i(t),arguments.length<2?void 0:u(arguments[1]))}})},function(t,n,r){var e=r(28),i=r(1),o=r(17),u=e.has,c=e.key,a=function(t,n,r){if(u(t,n,r))return!0;var e=o(n);return null!==e&&a(t,e,r)};e.exp({hasMetadata:function(t,n){return a(t,i(n),arguments.length<3?void 0:c(arguments[2]))}})},function(t,n,r){var e=r(28),i=r(1),o=e.has,u=e.key;e.exp({hasOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(28),i=r(1),o=r(10),u=e.key,c=e.set;e.exp({metadata:function(t,n){return function(r,e){c(t,n,(void 0!==e?i:o)(r),u(e))}}})},function(t,n,r){var e=r(0),i=r(88)(),o=r(2).process,u="process"==r(20)(o);e(e.G,{asap:function(t){var n=u&&o.domain;i(n?n.bind(t):t)}})},function(t,n,r){"use strict";var e=r(0),i=r(2),o=r(18),u=r(88)(),c=r(5)("observable"),a=r(10),f=r(1),s=r(39),l=r(41),h=r(11),v=r(40),p=v.RETURN,d=function(t){return null==t?void 0:a(t)},g=function(t){var n=t._c;n&&(t._c=void 0,n())},y=function(t){return void 0===t._o},m=function(t){y(t)||(t._o=void 0,g(t))},b=function(t,n){f(t),this._c=void 0,this._o=t,t=new w(this);try{var r=n(t),e=r;null!=r&&("function"==typeof r.unsubscribe?r=function(){e.unsubscribe()}:a(r),this._c=r)}catch(n){return void t.error(n)}y(this)&&g(this)};b.prototype=l({},{unsubscribe:function(){m(this)}});var w=function(t){this._s=t};w.prototype=l({},{next:function(t){var n=this._s;if(!y(n)){var r=n._o;try{var e=d(r.next);if(e)return e.call(r,t)}catch(t){try{m(n)}finally{throw t}}}},error:function(t){var n=this._s;if(y(n))throw t;var r=n._o;n._o=void 0;try{var e=d(r.error);if(!e)throw t;t=e.call(r,t)}catch(t){try{g(n)}finally{throw t}}return g(n),t},complete:function(t){var n=this._s;if(!y(n)){var r=n._o;n._o=void 0;try{var e=d(r.complete);t=e?e.call(r,t):void 0}catch(t){try{g(n)}finally{throw t}}return g(n),t}}});var S=function(t){s(this,S,"Observable","_f")._f=a(t)};l(S.prototype,{subscribe:function(t){return new b(t,this._f)},forEach:function(t){var n=this;return new(o.Promise||i.Promise)(function(r,e){a(t);var i=n.subscribe({next:function(n){try{return t(n)}catch(t){e(t),i.unsubscribe()}},error:e,complete:r})})}}),l(S,{from:function(t){var n="function"==typeof this?this:S,r=d(f(t)[c]);if(r){var e=f(r.call(t));return e.constructor===n?e:new n(function(t){return e.subscribe(t)})}return new n(function(n){var r=!1;return u(function(){if(!r){try{if(v(t,!1,function(t){if(n.next(t),r)return p})===p)return}catch(t){if(r)throw t;return void n.error(t)}n.complete()}}),function(){r=!0}})},of:function(){for(var t=0,n=arguments.length,r=new Array(n);t<n;)r[t]=arguments[t++];return new("function"==typeof this?this:S)(function(t){var n=!1;return u(function(){if(!n){for(var e=0;e<r.length;++e)if(t.next(r[e]),n)return;t.complete()}}),function(){n=!0}})}}),h(S.prototype,c,function(){return this}),e(e.G,{Observable:S}),r(38)("Observable")},function(t,n,r){var e=r(2),i=r(0),o=r(58),u=[].slice,c=/MSIE .\./.test(o),a=function(t){return function(n,r){var e=arguments.length>2,i=!!e&&u.call(arguments,2);return t(e?function(){("function"==typeof n?n:Function(n)).apply(this,i)}:n,r)}};i(i.G+i.B+i.F*c,{setTimeout:a(e.setTimeout),setInterval:a(e.setInterval)})},function(t,n,r){var e=r(0),i=r(87);e(e.G+e.B,{setImmediate:i.set,clearImmediate:i.clear})},function(t,n,r){for(var e=r(86),i=r(34),o=r(12),u=r(2),c=r(11),a=r(44),f=r(5),s=f("iterator"),l=f("toStringTag"),h=a.Array,v={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},p=i(v),d=0;d<p.length;d++){var g,y=p[d],m=v[y],b=u[y],w=b&&b.prototype;if(w&&(w[s]||c(w,s,h),w[l]||c(w,l,y),a[y]=h,m))for(g in e)w[g]||o(w,g,e[g],!0)}},function(t,n,r){(function(n){!function(n){"use strict";var r,e=Object.prototype,i=e.hasOwnProperty,o="function"==typeof Symbol?Symbol:{},u=o.iterator||"@@iterator",c=o.asyncIterator||"@@asyncIterator",a=o.toStringTag||"@@toStringTag",f="object"==typeof t,s=n.regeneratorRuntime;if(s)f&&(t.exports=s);else{(s=n.regeneratorRuntime=f?t.exports:{}).wrap=w;var l="suspendedStart",h="suspendedYield",v="executing",p="completed",d={},g={};g[u]=function(){return this};var y=Object.getPrototypeOf,m=y&&y(y(I([])));m&&m!==e&&i.call(m,u)&&(g=m);var b=E.prototype=x.prototype=Object.create(g);_.prototype=b.constructor=E,E.constructor=_,E[a]=_.displayName="GeneratorFunction",s.isGeneratorFunction=function(t){var n="function"==typeof t&&t.constructor;return!!n&&(n===_||"GeneratorFunction"===(n.displayName||n.name))},s.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,E):(t.__proto__=E,a in t||(t[a]="GeneratorFunction")),t.prototype=Object.create(b),t},s.awrap=function(t){return{__await:t}},O(M.prototype),M.prototype[c]=function(){return this},s.AsyncIterator=M,s.async=function(t,n,r,e){var i=new M(w(t,n,r,e));return s.isGeneratorFunction(n)?i:i.next().then(function(t){return t.done?t.value:i.next()})},O(b),b[a]="Generator",b[u]=function(){return this},b.toString=function(){return"[object Generator]"},s.keys=function(t){var n=[];for(var r in t)n.push(r);return n.reverse(),function r(){for(;n.length;){var e=n.pop();if(e in t)return r.value=e,r.done=!1,r}return r.done=!0,r}},s.values=I,j.prototype={constructor:j,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=r,this.done=!1,this.delegate=null,this.method="next",this.arg=r,this.tryEntries.forEach(A),!t)for(var n in this)"t"===n.charAt(0)&&i.call(this,n)&&!isNaN(+n.slice(1))&&(this[n]=r)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(t){if(this.done)throw t;var n=this;function e(e,i){return c.type="throw",c.arg=t,n.next=e,i&&(n.method="next",n.arg=r),!!i}for(var o=this.tryEntries.length-1;o>=0;--o){var u=this.tryEntries[o],c=u.completion;if("root"===u.tryLoc)return e("end");if(u.tryLoc<=this.prev){var a=i.call(u,"catchLoc"),f=i.call(u,"finallyLoc");if(a&&f){if(this.prev<u.catchLoc)return e(u.catchLoc,!0);if(this.prev<u.finallyLoc)return e(u.finallyLoc)}else if(a){if(this.prev<u.catchLoc)return e(u.catchLoc,!0)}else{if(!f)throw new Error("try statement without catch or finally");if(this.prev<u.finallyLoc)return e(u.finallyLoc)}}}},abrupt:function(t,n){for(var r=this.tryEntries.length-1;r>=0;--r){var e=this.tryEntries[r];if(e.tryLoc<=this.prev&&i.call(e,"finallyLoc")&&this.prev<e.finallyLoc){var o=e;break}}o&&("break"===t||"continue"===t)&&o.tryLoc<=n&&n<=o.finallyLoc&&(o=null);var u=o?o.completion:{};return u.type=t,u.arg=n,o?(this.method="next",this.next=o.finallyLoc,d):this.complete(u)},complete:function(t,n){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&n&&(this.next=n),d},finish:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),A(r),d}},catch:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc===t){var e=r.completion;if("throw"===e.type){var i=e.arg;A(r)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(t,n,e){return this.delegate={iterator:I(t),resultName:n,nextLoc:e},"next"===this.method&&(this.arg=r),d}}}function w(t,n,r,e){var i=n&&n.prototype instanceof x?n:x,o=Object.create(i.prototype),u=new j(e||[]);return o._invoke=function(t,n,r){var e=l;return function(i,o){if(e===v)throw new Error("Generator is already running");if(e===p){if("throw"===i)throw o;return N()}for(r.method=i,r.arg=o;;){var u=r.delegate;if(u){var c=P(u,r);if(c){if(c===d)continue;return c}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(e===l)throw e=p,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);e=v;var a=S(t,n,r);if("normal"===a.type){if(e=r.done?p:h,a.arg===d)continue;return{value:a.arg,done:r.done}}"throw"===a.type&&(e=p,r.method="throw",r.arg=a.arg)}}}(t,r,u),o}function S(t,n,r){try{return{type:"normal",arg:t.call(n,r)}}catch(t){return{type:"throw",arg:t}}}function x(){}function _(){}function E(){}function O(t){["next","throw","return"].forEach(function(n){t[n]=function(t){return this._invoke(n,t)}})}function M(t){function r(n,e,o,u){var c=S(t[n],t,e);if("throw"!==c.type){var a=c.arg,f=a.value;return f&&"object"==typeof f&&i.call(f,"__await")?Promise.resolve(f.__await).then(function(t){r("next",t,o,u)},function(t){r("throw",t,o,u)}):Promise.resolve(f).then(function(t){a.value=t,o(a)},u)}u(c.arg)}var e;"object"==typeof n.process&&n.process.domain&&(r=n.process.domain.bind(r)),this._invoke=function(t,n){function i(){return new Promise(function(e,i){r(t,n,e,i)})}return e=e?e.then(i,i):i()}}function P(t,n){var e=t.iterator[n.method];if(e===r){if(n.delegate=null,"throw"===n.method){if(t.iterator.return&&(n.method="return",n.arg=r,P(t,n),"throw"===n.method))return d;n.method="throw",n.arg=new TypeError("The iterator does not provide a 'throw' method")}return d}var i=S(e,t.iterator,n.arg);if("throw"===i.type)return n.method="throw",n.arg=i.arg,n.delegate=null,d;var o=i.arg;return o?o.done?(n[t.resultName]=o.value,n.next=t.nextLoc,"return"!==n.method&&(n.method="next",n.arg=r),n.delegate=null,d):o:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,d)}function F(t){var n={tryLoc:t[0]};1 in t&&(n.catchLoc=t[1]),2 in t&&(n.finallyLoc=t[2],n.afterLoc=t[3]),this.tryEntries.push(n)}function A(t){var n=t.completion||{};n.type="normal",delete n.arg,t.completion=n}function j(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(F,this),this.reset(!0)}function I(t){if(t){var n=t[u];if(n)return n.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var e=-1,o=function n(){for(;++e<t.length;)if(i.call(t,e))return n.value=t[e],n.done=!1,n;return n.value=r,n.done=!0,n};return o.next=o}}return{next:N}}function N(){return{value:r,done:!0}}}("object"==typeof n?n:"object"==typeof window?window:"object"==typeof self?self:this)}).call(this,r(64))},function(t,n,r){r(329),t.exports=r(18).RegExp.escape},function(t,n,r){var e=r(0),i=r(330)(/[\\^$*+?.()|[\]{}]/g,"\\$&");e(e.S,"RegExp",{escape:function(t){return i(t)}})},function(t,n){t.exports=function(t,n){var r=n===Object(n)?function(t){return n[t]}:n;return function(n){return String(n).replace(t,r)}}},function(t,n,r){"use strict";r.r(n);var e=r(126),i=r.n(e);r(125);const o=r(332);function u(t,n){var r=document.createElement("iframe");return a[t]=r,r.setAttribute("style","display: none;"),r.setAttribute("src",n),r}function c(t,n){window.parent.Singular=a,document.body.appendChild(t),document.body.appendChild(n)}var a={properties:{singularLocation:"",onIdentityChange:function(){},onLogout:function(){},clientId:"",checkInterval:1e3,uaaLocation:"",storageKey:"singularUserIdentityClaims",authTimeout:2e4,appendFramesOnInit:!1},rpFrameLoaded:!1,singularLocation:function(){if(""!==a.properties.singularLocation)return a.properties.singularLocation;if(document.getElementById("singular_script").src){var t=document.getElementById("singular_script").src;return t.substring(0,t.lastIndexOf("/"))}throw"singularLocation must not be blank"},init:function(t){if(t)for(var n in t)a.properties[n]=t[n];this.validateProperties(t);var r=u("opFrame",a.properties.uaaLocation+"/session_management?clientId="+a.properties.clientId+"&messageOrigin="+encodeURIComponent(window.location.origin)),e=u("rpFrame",this.singularLocation()+"/"+i.a);e.onload=function(){a.rpFrameLoaded=!0},!0===a.properties.appendFramesOnInit?c(r,e):document.addEventListener("DOMContentLoaded",function(){c(r,e)})},validateProperties:function(t){var n=["uaaLocation","clientId"];for(var r in n){var e=n[r];if(!t[e])throw'The "'+e+'" field must be set and not empty'}this.getUaaValidator().isValidUAA(t.uaaLocation)},decodeJwt:function(t){var n=t.split(".")[1].replace("-","+").replace("_","/");return JSON.parse(window.atob(n))},access:function(t){var n=a.rpFrame;return new Promise(function(r,e){var i=function(){n.contentWindow.fetchAccessToken(t,function(t,n){n||null==t?e(n):r(t)})};a.rpFrameLoaded?i():a.rpFrame.addEventListener("load",i)})},getUaaValidator:function(){return this.validator||o},setUaaValidator:function(t){this.validator=t}};n.default=a},function(t,n,r){var e=r(125);function i(t,n){var r=new XMLHttpRequest;r.open("GET",t),r.setRequestHeader("Accept","application/json"),r.send(),r.onreadystatechange=function(){r.readyState==XMLHttpRequest.DONE&&n(r)}}t.exports={isValidUAA:function(t){i(t+"/info",function(n){if(200!==n.status)throw t+" does not appear to be a running UAA instance or may not have a trusted SSL certificate";var r=JSON.parse(n.response).app.version;if(!e.isGreaterThanOrEqualTo(r,"4.10.0"))throw"The UAA instance running at "+t+" has version "+r+". This uaa-singular library requires UAA version 4.10.0 or higher."})},checkClientConfiguration:function(t,n){i(t,function(r){if(400===r.status){var e=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/**";throw"Error while calling /oauth/authorize. Is the UAA client "+n+" configured to allow redirects to "+e+" ? Visit "+t+" in the browser to see the UAA's error messages."}})}}}]).default}); \ No newline at end of file
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/404.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/404.ejs
new file mode 100644
index 0000000000..d6279481c8
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/404.ejs
@@ -0,0 +1,3 @@
+<h1>Not found</h1>
+
+<p>The object you clicked on was not found; it may have been deleted on the server.</p>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/add-binding.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/add-binding.ejs
new file mode 100644
index 0000000000..3f5b83205b
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/add-binding.ejs
@@ -0,0 +1,49 @@
+<% if (mode == 'queue') { %>
+ <h3 style="padding-top: 20px;">Add binding to this queue</h3>
+<% } else { %>
+ <h3 style="padding-top: 20px;">Add binding from this exchange</h3>
+<% } %>
+ <form action="#/bindings" method="post">
+ <input type="hidden" name="vhost" value="<%= fmt_string(parent.vhost) %>"/>
+<% if (mode == 'queue') { %>
+ <input type="hidden" name="destination" value="<%= fmt_string(parent.name) %>"/>
+<% } else { %>
+ <input type="hidden" name="source" value="<%= fmt_string(parent.name) %>"/>
+<% } %>
+ <table class="form">
+<% if (mode == 'queue') { %>
+ <tr>
+ <th>
+ <label>From exchange:</label>
+ </th>
+ <td>
+ <input type="hidden" name="destination_type" value="q"/>
+ <input type="text" name="source" value=""/>
+ <span class="mand">*</span>
+ </td>
+ </tr>
+<% } else { %>
+ <tr>
+ <th>
+ <select name="destination_type" class="narrow">
+ <option value="e">To exchange</option>
+ <option value="q" selected="selected">To queue</option>
+ </select>:
+ </th>
+ <td>
+ <input type="text" name="destination" value=""/>
+ <span class="mand">*</span>
+ </td>
+ </tr>
+<% } %>
+ <tr>
+ <th><label>Routing key:</label></th>
+ <td><input type="text" name="routing_key" value=""/></td>
+ </tr>
+ <tr>
+ <th><label>Arguments:</label></th>
+ <td><div class="multifield" id="arguments"></div></td>
+ </tr>
+ </table>
+ <input type="submit" value="Bind"/>
+ </form>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/binary.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/binary.ejs
new file mode 100644
index 0000000000..e19921e12d
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/binary.ejs
@@ -0,0 +1,61 @@
+<%
+ if (binary == "not_available") {
+%>
+<p class="warning">
+ Binary statistics not available.
+</p>
+<% } else { %>
+<%
+ var sections = {'queue_procs' : ['queue', 'Classic queues (masters)'],
+ 'queue_slave_procs' : ['queue', 'Classic queues (mirrors)'],
+ 'quorum_queue_procs' : ['queue', 'Quorum queues'],
+ 'stream_queue_procs' : ['queue', 'Stream queues'],
+ 'stream_queue_replica_reader_procs' : ['queue', 'Stream queues (replica reader)'],
+ 'stream_queue_coordinator_procs' : ['queue', 'Stream queues (coordinator)'],
+ 'connection_readers' : ['conn', 'Connection readers'],
+ 'connection_writers' : ['conn', 'Connection writers'],
+ 'connection_channels' : ['conn', 'Connection channels'],
+ 'connection_other' : ['conn', 'Connections (other)'],
+ 'msg_index' : ['table', 'Message store index'],
+ 'mgmt_db' : ['table', 'Management database'],
+ 'plugins' : ['proc', 'Plugins'],
+ 'other' : ['system', 'Other binary references']};
+ var total_out = [];
+%>
+<%= format('memory-bar', {sections: sections, memory: binary, total_out: total_out}) %>
+<span class="clear">&nbsp;</span>
+<div class="box">
+<%
+var key = [[{name: 'Queues', colour: 'queue',
+ keys: [['queue_procs', 'queues'],
+ ['queue_slave_procs', 'mirrors'],
+ ['quorum_queue_procs', 'quorum'],
+ ['stream_queue_procs', 'stream'],
+ ['stream_queue_replica_reader_procs', 'stream replica reader'],
+ ['stream_queue_coordinator_procs', 'stream coordinator']]}],
+
+ [{name: 'Connections', colour: 'conn',
+ keys: [['connection_readers', 'readers'],
+ ['connection_writers', 'writers'],
+ ['connection_channels', 'channels'],
+ ['connection_other', 'other']]}],
+
+ [{name: 'Tables', colour: 'table',
+ keys: [['msg_index', 'message store index'],
+ ['mgmt_db', 'management database']]}],
+
+ [{name: 'Processes', colour: 'proc',
+ keys: [['plugins', 'plugins']]},
+ {name: 'System', colour: 'system',
+ keys: [['other', 'other']]}]];
+%>
+<%= format('memory-table', {key: key, memory: binary}) %>
+</div>
+
+<div class="memory-info">
+ Last updated: <b><%= fmt_date(new Date()) %></b>.<br/>
+ Total referenced binaries at last update: <b><%= fmt_bytes(total_out[0]) %></b>
+ <span class="help" id="binary-use"></span>
+</div>
+
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/bindings.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/bindings.ejs
new file mode 100644
index 0000000000..a4e77e4446
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/bindings.ejs
@@ -0,0 +1,66 @@
+<%= maybe_truncate(bindings) %>
+<% if (bindings.length > 0) { %>
+ <table class="list updatable">
+ <thead>
+ <tr>
+<% if (mode == 'exchange_source') { %>
+ <th>To</th>
+<% } else { %>
+ <th>From</th>
+<% } %>
+ <th>Routing key</th>
+ <th>Arguments</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <%
+ for (var i = 0; i < bindings.length; i++) {
+ var binding = bindings[i];
+ %>
+ <tr<%= alt_rows(i)%>>
+<% if (binding.source == '') { %>
+ <td colspan="4">
+ (Default exchange binding)
+ </td>
+<% } else { %>
+<% if (mode == 'queue' || mode == 'exchange_destination') { %>
+ <td>
+ <span class="exchange">
+ <%= link_exchange(binding.vhost, binding.source) %>
+ </span>
+ </td>
+<% } else if (binding.destination_type == 'exchange') { %>
+ <td>
+ <span class="exchange" title="Exchange">
+ <%= link_exchange(binding.vhost, binding.destination) %>
+ </span>
+ </td>
+<% } else { %>
+ <td>
+ <span class="queue" title="Queue">
+ <%= link_queue(binding.vhost, binding.destination) %>
+ </span>
+ </td>
+<% } %>
+ <td><%= fmt_string(binding.routing_key) %></td>
+ <td><%= fmt_table_short(binding.arguments) %></td>
+ <td class="c">
+ <form action="#/bindings" method="delete" class="confirm">
+ <input type="hidden" name="vhost" value="<%= fmt_string(binding.vhost) %>"/>
+ <input type="hidden" name="source" value="<%= fmt_exchange_url(binding.source) %>"/>
+ <input type="hidden" name="destination" value="<%= fmt_string(binding.destination) %>"/>
+ <input type="hidden" name="destination_type" value="<%= binding.destination_type.substring(0, 1) %>"/>
+ <input type="hidden" name="properties_key" value="<%= fmt_string(binding.properties_key) %>"/>
+ <input type="submit" value="Unbind"/>
+ </form>
+ </td>
+ <% } %>
+ </tr>
+ <% } %>
+ </tbody>
+ </table>
+
+<% } else { %>
+ <p>... no bindings ...</p>
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/channel.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/channel.ejs
new file mode 100644
index 0000000000..5ecd7f801e
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/channel.ejs
@@ -0,0 +1,144 @@
+<h1>Channel: <b><%= fmt_escape_html(channel.name) %></b><%= fmt_maybe_vhost(channel.vhost) %></h1>
+
+<div class="section">
+<h2>Overview</h2>
+<div class="hider updatable">
+<% if (rates_mode != 'none') { %>
+ <%= message_rates('msg-rates-ch', channel.message_stats) %>
+<% } %>
+
+<h3>Details</h3>
+<table class="facts facts-l">
+ <tr>
+ <th>Connection</th>
+ <td><%= link_conn(channel.connection_details.name) %></td>
+ </tr>
+<% if (nodes_interesting) { %>
+ <tr>
+ <th>Node</th>
+ <td><%= fmt_node(channel.node) %></td>
+ </tr>
+<% } %>
+ <tr>
+ <th>Username</th>
+ <td><%= fmt_string(channel.user) %></td>
+ </tr>
+ <tr>
+ <th>Mode <span class="help" id="channel-mode"></span></th>
+ <td><%= fmt_channel_mode(channel) %></td>
+ </tr>
+</table>
+
+<table class="facts facts-l">
+ <tr>
+ <th>State</th>
+ <td><%= fmt_object_state(channel) %></td>
+ </tr>
+ <tr>
+ <th>Prefetch count</th>
+ <td><%= channel.prefetch_count %></td>
+ </tr>
+ <tr>
+ <th>Global prefetch count</th>
+ <td><%= channel.global_prefetch_count %></td>
+ </tr>
+</table>
+
+<table class="facts">
+ <tr>
+ <th>Messages unacknowledged</th>
+ <td><%= channel.messages_unacknowledged %></td>
+ </tr>
+ <tr>
+ <th>Messages unconfirmed</th>
+ <td><%= channel.messages_unconfirmed %></td>
+ </tr>
+ <tr>
+ <th>Messages uncommitted</th>
+ <td><%= channel.messages_uncommitted %></td>
+ </tr>
+ <tr>
+ <th>Acks uncommitted</th>
+ <td><%= channel.acks_uncommitted %></td>
+ </tr>
+</table>
+
+<table class="facts">
+ <tr>
+ <th>Pending Raft commands</th>
+ <td><%= channel.pending_raft_commands %></td>
+ </tr>
+</table>
+
+</div>
+</div>
+
+<div class="section">
+ <h2>Consumers</h2>
+ <div class="hider updatable">
+<%= format('consumers', {'mode': 'channel', 'consumers': channel.consumer_details}) %>
+ </div>
+</div>
+
+<% if (rates_mode == 'detailed') { %>
+<div class="section">
+<h2>Message rates breakdown</h2>
+<div class="hider updatable">
+<table class="two-col-layout">
+ <tr>
+ <td>
+ <%= format('msg-detail-publishes',
+ {'mode': 'channel',
+ 'object': channel.publishes,
+ 'label': 'Publishes'}) %>
+ </td>
+ <td>
+ <%= format('msg-detail-deliveries',
+ {'mode': 'channel',
+ 'object': channel.deliveries}) %>
+ </td>
+ </tr>
+</table>
+</div>
+</div>
+<% } %>
+
+<% if(channel.reductions || channel.garbage_collection) { %>
+<div class="section-hidden">
+<h2>Runtime Metrics (Advanced)</h2>
+ <div class="hider updatable">
+ <%= data_reductions('reductions-rates-conn', channel) %>
+ <table class="facts">
+ <% if (channel.garbage_collection.min_bin_vheap_size) { %>
+ <tr>
+ <th>Minimum binary virtual heap size in words (min_bin_vheap_size)</th>
+ <td><%= channel.garbage_collection.min_bin_vheap_size %></td>
+ </tr>
+ <% } %>
+
+ <% if (channel.garbage_collection.min_heap_size) { %>
+ <tr>
+ <th>Minimum heap size in words (min_heap_size)</th>
+ <td><%= channel.garbage_collection.min_heap_size %></td>
+ </tr>
+ <% } %>
+
+ <% if (channel.garbage_collection.fullsweep_after) { %>
+ <tr>
+ <th>Maximum generational collections before fullsweep (fullsweep_after)</th>
+ <td><%= channel.garbage_collection.fullsweep_after %></td>
+ </tr>
+ <% } %>
+
+ <% if (channel.garbage_collection.minor_gcs) { %>
+ <tr>
+ <th>Number of minor GCs (minor_gcs)</th>
+ <td><%= channel.garbage_collection.minor_gcs %></td>
+ </tr>
+ <% } %>
+ </table>
+ </div>
+</div>
+
+<% } %>
+
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/channels-list.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/channels-list.ejs
new file mode 100644
index 0000000000..8acf57ab4d
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/channels-list.ejs
@@ -0,0 +1,203 @@
+<% if (channels.length > 0) { %>
+<table class="list">
+ <thead>
+ <tr>
+<% if (mode == 'standalone') { %>
+ <%= group_heading('channels', 'Overview', [true, vhosts_interesting, nodes_interesting]) %>
+<% } else { %>
+ <%= group_heading('channels', 'Overview', [true]) %>
+<% } %>
+ <%= group_heading('channels', 'Details', []) %>
+ <%= group_heading('channels', 'Transactions', []) %>
+<% if (rates_mode != 'none') { %>
+ <%= group_heading('channels', 'Message rates', []) %>
+<% } %>
+ <th class="plus-minus"><span class="popup-options-link" title="Click to change columns" type="columns" for="channels">+/-</span></th>
+ </tr>
+ <tr>
+<% if (mode == 'standalone') { %>
+ <th><%= fmt_sort('Channel', 'name') %></th>
+<% if (nodes_interesting) { %>
+ <th><%= fmt_sort('Node', 'node') %></th>
+<% } %>
+<% if (vhosts_interesting) { %>
+ <th><%= fmt_sort('Virtual host', 'vhost') %></th>
+<% } %>
+<% if (show_column('channels', 'user')) { %>
+ <th><%= fmt_sort('User name', 'user') %></th>
+<% } %>
+<% if (show_column('channels', 'mode')) { %>
+ <th>Mode <span class="help" id="channel-mode"></span></th>
+<% } %>
+<% if (show_column('channels', 'state')) { %>
+ <th><%= fmt_sort('State', 'state') %></th>
+<% } %>
+<% if (show_column('channels', 'msgs-unconfirmed')) { %>
+ <th><%= fmt_sort('Unconfirmed', 'messages_unconfirmed') %></th>
+<% } %>
+<% if (show_column('channels', 'prefetch')) { %>
+ <th>Prefetch <span class="help" id="channel-prefetch"></span></th>
+<% } %>
+<% if (show_column('channels', 'msgs-unacked')) { %>
+ <th><%= fmt_sort('Unacked', 'messages_unacknowledged') %></th>
+<% } %>
+<% if (show_column('channels', 'msgs-uncommitted')) { %>
+ <th><%= fmt_sort('Uncommitted msgs', 'messages_uncommitted') %></th>
+<% } %>
+<% if (show_column('channels', 'acks-uncommitted')) { %>
+ <th><%= fmt_sort('Uncommitted acks', 'acks_uncommitted') %></th>
+<% } %>
+<% if (rates_mode != 'none') { %>
+<% if (show_column('channels', 'rate-publish')) { %>
+ <th><%= fmt_sort('publish', 'message_stats.publish_details.rate') %></th>
+<% } %>
+<% if (show_column('channels', 'rate-confirm')) { %>
+ <th><%= fmt_sort('confirm', 'message_stats.confirm_details.rate') %></th>
+<% } %>
+<% if (show_column('channels', 'rate-unroutable-drop')) { %>
+ <th><%= fmt_sort('unroutable (drop)', 'message_stats.drop_unroutable_details.rate') %></th>
+<% } %>
+<% if (show_column('channels', 'rate-unroutable-return')) { %>
+ <th><%= fmt_sort('unroutable (return)', 'message_stats.return_unroutable_details.rate') %></th>
+<% } %>
+<% if (show_column('channels', 'rate-deliver')) { %>
+ <th><%= fmt_sort('deliver / get', 'message_stats.deliver_get_details.rate') %></th>
+<% } %>
+<% if (show_column('channels', 'rate-redeliver')) { %>
+ <th><%= fmt_sort('redelivered', 'message_stats.redeliver_details.rate') %></th>
+<% } %>
+<% if (show_column('channels', 'rate-ack')) { %>
+ <th><%= fmt_sort('ack', 'message_stats.ack_details.rate') %></th>
+<% } %>
+<% } %>
+<% } else { %>
+<!-- TODO make sortable after bug 23401 -->
+ <th>Channel</th>
+<% if (show_column('channels', 'user')) { %>
+ <th>User name</th>
+<% } %>
+<% if (show_column('channels', 'mode')) { %>
+ <th>Mode <span class="help" id="channel-mode"></span></th>
+<% } %>
+<% if (show_column('channels', 'state')) { %>
+ <th>State</th>
+<% } %>
+<% if (show_column('channels', 'msgs-unconfirmed')) { %>
+ <th>Unconfirmed</th>
+<% } %>
+<% if (show_column('channels', 'prefetch')) { %>
+ <th>Prefetch <span class="help" id="channel-prefetch"></span></th>
+<% } %>
+<% if (show_column('channels', 'msgs-unacked')) { %>
+ <th>Unacked</th>
+<% } %>
+<% if (show_column('channels', 'msgs-uncommitted')) { %>
+ <th>Uncommitted msgs</th>
+<% } %>
+<% if (show_column('channels', 'acks-uncommitted')) { %>
+ <th>Uncommitted acks</th>
+<% } %>
+<% if (rates_mode != 'none') { %>
+<% if (show_column('channels', 'rate-publish')) { %>
+ <th>publish</th>
+<% } %>
+<% if (show_column('channels', 'rate-confirm')) { %>
+ <th>confirm</th>
+<% } %>
+<% if (show_column('channels', 'rate-unroutable-drop')) { %>
+ <th>unroutable (drop)</th>
+<% } %>
+<% if (show_column('channels', 'rate-unroutable-return')) { %>
+ <th>unroutable (return)</th>
+<% } %>
+<% if (show_column('channels', 'rate-deliver')) { %>
+ <th>deliver / get</th>
+<% } %>
+<% if (show_column('channels', 'rate-redeliver')) { %>
+ <th>redelivered</th>
+<% } %>
+<% if (show_column('channels', 'rate-ack')) { %>
+ <th>ack</th>
+<% } %>
+<% } %>
+<% } %>
+ </tr>
+ </thead>
+ <tbody>
+<%
+ for (var i = 0; i < channels.length; i++) {
+ var channel = channels[i];
+%>
+ <tr<%= alt_rows(i)%>>
+ <td>
+ <%= link_channel(channel.name) %>
+ </td>
+<% if (mode == 'standalone' && nodes_interesting) { %>
+ <td><%= fmt_node(channel.node) %></td>
+<% } %>
+<% if (mode == 'standalone' && vhosts_interesting) { %>
+ <td class="c"><%= fmt_string(channel.vhost) %></td>
+<% } %>
+<% if (show_column('channels', 'user')) { %>
+ <td class="c"><%= fmt_string(channel.user) %></td>
+<% } %>
+<% if (show_column('channels', 'mode')) { %>
+ <td class="c">
+ <%= fmt_channel_mode(channel) %>
+ </td>
+<% } %>
+<% if (show_column('channels', 'state')) { %>
+ <td class="c"><%= fmt_object_state(channel) %></td>
+<% } %>
+<% if (show_column('channels', 'msgs-unconfirmed')) { %>
+ <td class="c"><%= channel.messages_unconfirmed %></td>
+<% } %>
+<% if (show_column('channels', 'prefetch')) { %>
+ <td class="c">
+ <% if (channel.prefetch_count != 0) { %>
+ <%= channel.prefetch_count %><br/>
+ <% } %>
+ <% if (channel.global_prefetch_count != 0) { %>
+ <%= channel.global_prefetch_count %> (global)
+ <% } %>
+ </td>
+<% } %>
+<% if (show_column('channels', 'msgs-unacked')) { %>
+ <td class="c"><%= channel.messages_unacknowledged %></td>
+<% } %>
+<% if (show_column('channels', 'msgs-uncommitted')) { %>
+ <td class="c"><%= channel.messages_uncommitted %></td>
+<% } %>
+<% if (show_column('channels', 'acks-uncommitted')) { %>
+ <td class="c"><%= channel.acks_uncommitted %></td>
+<% } %>
+<% if (rates_mode != 'none') { %>
+<% if (show_column('channels', 'rate-publish')) { %>
+ <td class="r"><%= fmt_detail_rate(channel.message_stats, 'publish') %></td>
+<% } %>
+<% if (show_column('channels', 'rate-confirm')) { %>
+ <td class="r"><%= fmt_detail_rate(channel.message_stats, 'confirm') %></td>
+<% } %>
+<% if (show_column('channels', 'rate-unroutable-drop')) { %>
+ <td class="r"><%= fmt_detail_rate(channel.message_stats, 'drop_unroutable') %></td>
+<% } %>
+<% if (show_column('channels', 'rate-unroutable-return')) { %>
+ <td class="r"><%= fmt_detail_rate(channel.message_stats, 'return_unroutable') %></td>
+<% } %>
+<% if (show_column('channels', 'rate-deliver')) { %>
+ <td class="r"><%= fmt_detail_rate(channel.message_stats, 'deliver_get') %></td>
+<% } %>
+<% if (show_column('channels', 'rate-redeliver')) { %>
+ <td class="r"><%= fmt_detail_rate(channel.message_stats, 'redeliver') %></td>
+<% } %>
+<% if (show_column('channels', 'rate-ack')) { %>
+ <td class="r"><%= fmt_detail_rate(channel.message_stats, 'ack') %></td>
+<% } %>
+<% } %>
+ </tr>
+ <% } %>
+ </tbody>
+</table>
+<% } else { %>
+ <p>... no channels ...</p>
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/channels.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/channels.ejs
new file mode 100644
index 0000000000..a29553f96c
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/channels.ejs
@@ -0,0 +1,7 @@
+<h1>Channels</h1>
+<div class="section">
+ <%= paginate_ui(channels, 'channels') %>
+</div>
+<div class="updatable">
+ <%= format('channels-list', {'channels': channels.items, 'mode': 'standalone'}) %>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/cluster-name.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/cluster-name.ejs
new file mode 100644
index 0000000000..fbaa3cd24e
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/cluster-name.ejs
@@ -0,0 +1,31 @@
+<h1>Cluster name: <b><%= fmt_string(cluster_name.name) %></b></h1>
+
+<p>
+ The cluster name can be used by clients to identify clusters over
+ AMQP connections, and is used by the shovel and federation plugins
+ to identify which clusters a message has been routed through.
+</p>
+<p>
+ Note that the cluster name is announced to clients in the AMQP
+ server properties; i.e. before authentication has taken
+ place. Therefore it should not be considered secret.
+</p>
+<p>
+ The cluster name is generated by default from the name of the first
+ node in the cluster, but can be changed.
+</p>
+
+<div class="section-hidden">
+ <h2>Change name</h2>
+ <div class="hider">
+ <form action="#/cluster-name" method="put">
+ <table class="form">
+ <tr>
+ <th><label>Name:</label></th>
+ <td><input type="text" name="name" value="<%= fmt_string(cluster_name.name) %>"/><span class="mand">*</span></td>
+ </tr>
+ </table>
+ <input type="submit" value="Change name"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/columns-options.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/columns-options.ejs
new file mode 100644
index 0000000000..20594603c0
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/columns-options.ejs
@@ -0,0 +1,25 @@
+<%
+ var mode = span.attr('for');
+%>
+
+<form action="#/column-options" method="put" class="auto-submit">
+ <input type="hidden" name="mode" value="<%= mode %>"/>
+ <table class="form" width="100%">
+ <tr>
+ <td colspan="2">
+ <h3>Columns for this table</h3>
+ </td>
+ </tr>
+<% for (var group in COLUMNS[mode]) {
+ var options = COLUMNS[mode][group]; %>
+ <tr>
+ <th><label><%= group %>:</label></th>
+ <td>
+ <% for (var i = 0; i < options.length; i++) { %>
+ <%= fmt_checkbox(mode + '-' + options[i][0], options[i][1], get_pref('column-' + mode + '-' + options[i][0]) == 'true') %>
+ <% } %>
+ </td>
+<% } %>
+ </tr>
+ </table>
+</form>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/connection.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/connection.ejs
new file mode 100644
index 0000000000..9a09f89b72
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/connection.ejs
@@ -0,0 +1,194 @@
+<h1>Connection <%= fmt_string(connection.name) %> <%= fmt_maybe_vhost(connection.vhost) %></h1>
+
+<% if (!disable_stats) { %>
+<div class="section">
+<h2>Overview</h2>
+<div class="hider updatable">
+ <%= data_rates('data-rates-conn', connection, 'Data rates') %>
+
+<h3>Details</h3>
+<table class="facts facts-l">
+<% if (nodes_interesting) { %>
+<tr>
+ <th>Node</th>
+ <td><%= fmt_node(connection.node) %></td>
+</tr>
+<% } %>
+
+<% if (connection.client_properties.connection_name) { %>
+<tr>
+ <th>Client-provided name</th>
+ <td><%= fmt_string(connection.client_properties.connection_name) %></td>
+</tr>
+<% } %>
+
+<tr>
+ <th>Username</th>
+ <td><%= fmt_string(connection.user) %></td>
+</tr>
+<tr>
+ <th>Protocol</th>
+ <td><%= connection.protocol %></td>
+</tr>
+<tr>
+ <th>Connected at</th>
+ <td><%= fmt_timestamp(connection.connected_at) %></td>
+</tr>
+
+<% if (connection.ssl) { %>
+<tr>
+ <th>SSL</th>
+ <td><%= fmt_boolean(connection.ssl) %></td>
+</tr>
+<% } %>
+
+<% if (connection.auth_mechanism) { %>
+<tr>
+ <th>Authentication</th>
+ <td><%= connection.auth_mechanism %></td>
+</tr>
+<% } %>
+</table>
+
+<% if (connection.state) { %>
+<table class="facts">
+<tr>
+ <th>State</th>
+ <td><%= fmt_object_state(connection) %></td>
+</tr>
+<tr>
+ <th>Heartbeat</th>
+ <td><%= fmt_time(connection.timeout, 's') %></td>
+</tr>
+<tr>
+ <th>Frame max</th>
+ <td><%= connection.frame_max %> bytes</td>
+</tr>
+<tr>
+ <th>Channel limit</th>
+ <td><%= connection.channel_max %> channels</td>
+</tr>
+</table>
+
+<% } %>
+
+</div>
+</div>
+
+<div class="section">
+ <h2>Channels</h2>
+ <div class="hider updatable">
+ <%= format('channels-list', {'channels': channels, 'mode': 'connection'}) %>
+ </div>
+</div>
+
+<% if (connection.ssl) { %>
+<div class="section">
+<h2>SSL</h2>
+<div class="hider">
+
+<table class="facts">
+ <tr>
+ <th>Protocol Version</th>
+ <td><%= connection.ssl_protocol %></td>
+ </tr>
+ <tr>
+ <th>Key Exchange Algorithm</th>
+ <td><%= connection.ssl_key_exchange %></td>
+ </tr>
+ <tr>
+ <th>Cipher Algorithm</th>
+ <td><%= connection.ssl_cipher %></td>
+ </tr>
+ <tr>
+ <th>Hash Algorithm</th>
+ <td><%= connection.ssl_hash %></td>
+ </tr>
+</table>
+
+<% if (connection.peer_cert_issuer != '') { %>
+<table class="facts">
+ <tr>
+ <th>Peer Certificate Issuer</th>
+ <td><%= connection.peer_cert_issuer %></td>
+ </tr>
+ <tr>
+ <th>Peer Certificate Subject</th>
+ <td><%= connection.peer_cert_subject %></td>
+ </tr>
+ <tr>
+ <th>Peer Certificate Validity</th>
+ <td><%= connection.peer_cert_validity %></td>
+ </tr>
+</table>
+<% } %>
+</div>
+</div>
+<% } %>
+
+<% if (properties_size(connection.client_properties) > 0) { %>
+<div class="section-hidden">
+<h2>Client properties</h2>
+<div class="hider">
+<%= fmt_table_long(connection.client_properties) %>
+</div>
+</div>
+<% } %>
+
+<% if(connection.reductions || connection.garbage_collection) { %>
+<div class="section-hidden">
+<h2>Runtime Metrics (Advanced)</h2>
+ <div class="hider updatable">
+ <%= data_reductions('reductions-rates-conn', connection) %>
+ <table class="facts">
+ <% if (connection.garbage_collection.min_bin_vheap_size) { %>
+ <tr>
+ <th>Minimum binary virtual heap size in words (min_bin_vheap_size)</th>
+ <td><%= connection.garbage_collection.min_bin_vheap_size %></td>
+ </tr>
+ <% } %>
+
+ <% if (connection.garbage_collection.min_heap_size) { %>
+ <tr>
+ <th>Minimum heap size in words (min_heap_size)</th>
+ <td><%= connection.garbage_collection.min_heap_size %></td>
+ </tr>
+ <% } %>
+
+ <% if (connection.garbage_collection.fullsweep_after) { %>
+ <tr>
+ <th>Maximum generational collections before fullsweep (fullsweep_after)</th>
+ <td><%= connection.garbage_collection.fullsweep_after %></td>
+ </tr>
+ <% } %>
+
+ <% if (connection.garbage_collection.minor_gcs) { %>
+ <tr>
+ <th>Number of minor GCs (minor_gcs)</th>
+ <td><%= connection.garbage_collection.minor_gcs %></td>
+ </tr>
+ <% } %>
+ </table>
+ </div>
+</div>
+
+<% } %>
+<% } %>
+
+<div class="section-hidden">
+ <h2>Close this connection</h2>
+ <div class="hider">
+ <form action="#/connections" method="delete" class="confirm">
+ <input type="hidden" name="name" value="<%= fmt_string(connection.name) %>"/>
+ <table class="form">
+ <tr>
+ <th><label>Reason:</label></th>
+ <td>
+ <input type="text" name="reason" value="Closed via management plugin" class="wide"/>
+ </td>
+ </tr>
+ </table>
+ <input type="submit" value="Force Close"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/connections.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/connections.ejs
new file mode 100644
index 0000000000..a75fa7f560
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/connections.ejs
@@ -0,0 +1,155 @@
+<h1>Connections</h1>
+<div class="section">
+ <%= paginate_ui(connections, 'connections') %>
+</div>
+<div class="updatable">
+<% if (connections.items.length > 0) { %>
+<table class="list">
+ <thead>
+ <tr>
+ <%= group_heading('connections', 'Overview', [vhosts_interesting, nodes_interesting, true]) %>
+ <% if (!disable_stats) { %>
+ <%= group_heading('connections', 'Details', []) %>
+ <%= group_heading('connections', 'Network', []) %>
+ <% } %>
+ <th class="plus-minus"><span class="popup-options-link" title="Click to change columns" type="columns" for="connections">+/-</span></th>
+ </tr>
+ <tr>
+<% if (vhosts_interesting) { %>
+ <th><%= fmt_sort('Virtual host', 'vhost') %></th>
+<% } %>
+<% if(disable_stats) { %>
+ <th><%= fmt_sort('Name', 'name') %></th>
+<% } else { %>
+ <th><%= fmt_sort('Name', 'client_properties.connection_name') %></th>
+<% } %>
+<% if (nodes_interesting) { %>
+ <th><%= fmt_sort('Node', 'node') %></th>
+<% } %>
+<% if (show_column('connections', 'user')) { %>
+ <th><%= fmt_sort('User name', 'user') %></th>
+<% } %>
+<% if (!disable_stats) { %>
+<% if (show_column('connections', 'state')) { %>
+ <th><%= fmt_sort('State', 'state') %></th>
+<% } %>
+<% if (show_column('connections', 'ssl')) { %>
+ <th><%= fmt_sort('SSL / TLS', 'ssl') %></th>
+<% } %>
+<% if (show_column('connections', 'ssl_info')) { %>
+ <th>SSL Details</th>
+<% } %>
+<% if (show_column('connections', 'protocol')) { %>
+ <th><%= fmt_sort('Protocol', 'protocol') %></th>
+<% } %>
+<% if (show_column('connections', 'channels')) { %>
+ <th><%= fmt_sort('Channels', 'channels') %></th>
+<% } %>
+<% if (show_column('connections', 'channel_max')) { %>
+ <th><%= fmt_sort('Channel max', 'channel_max') %></th>
+<% } %>
+<% if (show_column('connections', 'frame_max')) { %>
+ <th><%= fmt_sort('Frame max', 'frame_max') %></th>
+<% } %>
+<% if (show_column('connections', 'auth_mechanism')) { %>
+ <th><%= fmt_sort('Auth mechanism', 'auth_mechanism') %></th>
+<% } %>
+<% if (show_column('connections', 'client')) { %>
+ <th><%= fmt_sort('Client', 'properties') %></th>
+<% } %>
+<% if (show_column('connections', 'from_client')) { %>
+ <th><%= fmt_sort('From client', 'recv_oct_details.rate') %></th>
+<% } %>
+<% if (show_column('connections', 'to_client')) { %>
+ <th><%= fmt_sort('To client', 'send_oct_details.rate') %></th>
+<% } %>
+<% if (show_column('connections', 'heartbeat')) { %>
+ <th><%= fmt_sort('Heartbeat', 'timeout') %></th>
+<% } %>
+<% if (show_column('connections', 'connected_at')) { %>
+ <th><%= fmt_sort('Connected at', 'connected_at') %></th>
+<% } %>
+<% } %>
+ </tr>
+ </thead>
+ <tbody>
+<%
+ for (var i = 0; i < connections.items.length; i++) {
+ var connection = connections.items[i];
+%>
+ <tr<%= alt_rows(i)%>>
+<% if (vhosts_interesting) { %>
+ <td><%= fmt_string(connection.vhost) %></td>
+<% } %>
+<% if(connection.client_properties) { %>
+ <td>
+ <%= link_conn(connection.name) %>
+ <sub><%= fmt_string(short_conn(connection.client_properties.connection_name)) %></sub>
+ </td>
+<% } else { %>
+ <td><%= link_conn(connection.name) %></td>
+<% } %>
+<% if (nodes_interesting) { %>
+ <td><%= fmt_node(connection.node) %></td>
+<% } %>
+<% if (show_column('connections', 'user')) { %>
+ <td class="c"><%= fmt_string(connection.user) %></td>
+<% } %>
+<% if (!disable_stats) { %>
+<% if (show_column('connections', 'state')) { %>
+ <td><%= fmt_object_state(connection) %></td>
+<% } %>
+<% if (show_column('connections', 'ssl')) { %>
+ <td class="c"><%= fmt_boolean(connection.ssl, '') %></td>
+<% } %>
+<% if (show_column('connections', 'ssl_info')) { %>
+ <td>
+ <% if (connection.ssl) { %>
+ <%= connection.ssl_protocol %>
+ <sub>
+ <%= connection.ssl_key_exchange %>
+ <%= connection.ssl_cipher %>
+ <%= connection.ssl_hash %>
+ </sub>
+ <% } %>
+ </td>
+<% } %>
+<% if (show_column('connections', 'protocol')) { %>
+ <td class="c"><%= connection.protocol %></td>
+<% } %>
+<% if (show_column('connections', 'channels')) { %>
+ <td class="r"><%= fmt_string(connection.channels, '') %></td>
+<% } %>
+<% if (show_column('connections', 'channel_max')) { %>
+ <td class="r"><%= fmt_string(connection.channel_max, '') %></td>
+<% } %>
+<% if (show_column('connections', 'frame_max')) { %>
+ <td class="r"><%= fmt_string(connection.frame_max, '') %></td>
+<% } %>
+<% if (show_column('connections', 'auth_mechanism')) { %>
+ <td class="c"><%= fmt_string(connection.auth_mechanism, '') %></td>
+<% } %>
+<% if (show_column('connections', 'client')) { %>
+ <td><%= fmt_client_name(connection.client_properties) %></td>
+<% } %>
+<% if (show_column('connections', 'from_client')) { %>
+ <td><%= fmt_detail_rate_bytes(connection, 'recv_oct') %></td>
+<% } %>
+<% if (show_column('connections', 'to_client')) { %>
+ <td><%= fmt_detail_rate_bytes(connection, 'send_oct') %></td>
+<% } %>
+<% if (show_column('connections', 'heartbeat')) { %>
+ <td class="r"><%= fmt_time(connection.timeout, 's') %></td>
+<% } %>
+<% if (show_column('connections', 'connected_at')) { %>
+ <td><%= fmt_timestamp_mini(connection.connected_at) %></td>
+<% } %>
+ <% } %>
+ </tr>
+ <% } %>
+ </tbody>
+</table>
+<% } else { %>
+ <p>... no connections ...</p>
+<% } %>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/consumers.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/consumers.ejs
new file mode 100644
index 0000000000..ffd7b7b603
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/consumers.ejs
@@ -0,0 +1,43 @@
+<% if (consumers.length > 0) { %>
+ <table class="list">
+ <thead>
+ <tr>
+<% if (mode == 'queue') { %>
+ <th>Channel</th>
+ <th>Consumer tag</th>
+<% } else { %>
+ <th>Consumer tag</th>
+ <th>Queue</th>
+<% } %>
+ <th>Ack required</th>
+ <th>Exclusive</th>
+ <th>Prefetch count</th>
+ <th>Active <span class="help" id="consumer-active"></span></th>
+ <th>Activity status</th>
+ <th>Arguments</th>
+ </tr>
+ </thead>
+<%
+ for (var i = 0; i < consumers.length; i++) {
+ var consumer = consumers[i];
+%>
+ <tr<%= alt_rows(i) %>>
+<% if (mode == 'queue') { %>
+ <td><%= link_channel(consumer.channel_details.name) %></td>
+ <td><%= fmt_string(consumer.consumer_tag) %></td>
+<% } else { %>
+ <td><%= fmt_string(consumer.consumer_tag) %></td>
+ <td><%= link_queue(consumer.queue.vhost, consumer.queue.name) %></td>
+<% } %>
+ <td class="c"><%= fmt_boolean(consumer.ack_required) %></td>
+ <td class="c"><%= fmt_boolean(consumer.exclusive) %></td>
+ <td class="c"><%= consumer.prefetch_count %></td>
+ <td class="c"><%= fmt_boolean(consumer.active) %></td>
+ <td class="c"><%= fmt_activity_status(consumer.activity_status) %></td>
+ <td class="c"><%= fmt_table_short(consumer.arguments) %></td>
+ </tr>
+<% } %>
+ </table>
+<% } else { %>
+ <p>... no consumers ...</p>
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/exchange.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/exchange.ejs
new file mode 100644
index 0000000000..bff57441ec
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/exchange.ejs
@@ -0,0 +1,97 @@
+<h1>Exchange: <b><%= fmt_exchange(highlight_extra_whitespace(exchange.name)) %></b><%= fmt_maybe_vhost(exchange.vhost) %></h1>
+
+<div class="section">
+ <h2>Overview</h2>
+ <div class="hider updatable">
+<% if (!disable_stats) { %>
+<% if (rates_mode != 'none') { %>
+ <%= message_rates('msg-rates-x', exchange.message_stats) %>
+<% } %>
+<% } %>
+ <h3>Details</h3>
+ <table class="facts">
+ <tr>
+ <th>Type</th>
+ <td class="l"><%= fmt_exchange_type(exchange.type) %></td>
+ </tr>
+ <tr>
+ <th>Features</th>
+ <td><%= fmt_features(exchange) %></td>
+ </tr>
+ <tr>
+ <th>Policy</th>
+ <td><%= link_policy(exchange.vhost, exchange.policy) %></td>
+ </tr>
+ </table>
+ </div>
+</div>
+
+<% if (!disable_stats) { %>
+<% if (rates_mode == 'detailed') { %>
+<div class="section-hidden">
+<h2>Message rates breakdown</h2>
+<div class="hider updatable">
+<table class="two-col-layout">
+ <tr>
+ <td>
+ <%= format('msg-detail-publishes',
+ {'mode': 'exchange-incoming',
+ 'object': exchange.incoming,
+ 'label': 'Incoming <span class="help" id="exchange-rates-incoming"></span>'}) %>
+ </td>
+ <td>
+ <%= format('msg-detail-publishes',
+ {'mode': 'exchange-outgoing',
+ 'object': exchange.outgoing,
+ 'label': 'Outgoing <span class="help" id="exchange-rates-outgoing"></span>'}) %>
+ </td>
+ </tr>
+</table>
+</div>
+</div>
+<% } %>
+<% } %>
+
+
+<div class="section-hidden">
+ <h2>Bindings</h2>
+ <div class="hider">
+<% if (exchange.name == "") { %>
+ <h3>Default exchange</h3>
+ <p>
+ The default exchange is implicitly bound to every queue, with a
+ routing key equal to the queue name. It is not possible to
+ explicitly bind to, or unbind from the default exchange. It also
+ cannot be deleted.
+ </p>
+<% } else { %>
+<div class="bindings-wrapper">
+<% if (bindings_destination.length > 0) { %>
+ <%= format('bindings', {'mode': 'exchange_destination', 'bindings': bindings_destination}) %>
+ <p class="arrow">&dArr;</p>
+<% } %>
+ <p><span class="exchange">This exchange</span></p>
+ <p class="arrow">&dArr;</p>
+ <%= format('bindings', {'mode': 'exchange_source', 'bindings': bindings_source}) %>
+</div>
+ <%= format('add-binding', {'mode': 'exchange_source', 'parent': exchange}) %>
+<% } %>
+</div>
+</div>
+
+<% if (!exchange.internal) { %>
+<%= format('publish', {'mode': 'exchange', 'exchange': exchange}) %>
+<% } %>
+
+<% if (exchange.name != "") { %>
+<div class="section-hidden">
+ <h2>Delete this exchange</h2>
+ <div class="hider">
+ <form action="#/exchanges" method="delete" class="confirm">
+ <input type="hidden" name="vhost" value="<%= fmt_string(exchange.vhost) %>"/>
+ <input type="hidden" name="name" value="<%= fmt_exchange_url(exchange.name) %>"/>
+ <input type="submit" value="Delete"/>
+ </form>
+ </div>
+</div>
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/exchanges.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/exchanges.ejs
new file mode 100644
index 0000000000..f4fd5c4c8d
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/exchanges.ejs
@@ -0,0 +1,169 @@
+<h1>Exchanges</h1>
+<div class="section">
+ <%= paginate_ui(exchanges, 'exchanges') %>
+</div>
+<div class="updatable">
+<% if (exchanges.items.length > 0) { %>
+<table class="list">
+ <thead>
+ <tr>
+<% if (vhosts_interesting) { %>
+ <th><%= fmt_sort('Virtual host', 'vhost') %></th>
+<% } %>
+ <th><%= fmt_sort('Name', 'name') %></th>
+<% if (show_column('exchanges', 'type')) { %>
+ <th><%= fmt_sort('Type', 'type') %></th>
+<% } %>
+<% if (show_column('exchanges', 'features')) { %>
+ <th>Features</th>
+<% } %>
+<% if (show_column('exchanges', 'features_no_policy')) { %>
+ <th>Features</th>
+<% } %>
+<% if (show_column('exchanges', 'policy')) { %>
+ <th><%= fmt_sort('Policy','policy') %></th>
+<% } %>
+<% if (!disable_stats) { %>
+<% if (rates_mode != 'none') { %>
+<% if (show_column('exchanges', 'rate-in')) { %>
+ <th><%= fmt_sort('Message rate in', 'message_stats.publish_in_details.rate') %></th>
+<% } %>
+<% if (show_column('exchanges', 'rate-out')) { %>
+ <th><%= fmt_sort('Message rate out', 'message_stats.publish_out_details.rate') %></th>
+<% } %>
+<% } %>
+<% } %>
+ <th class="plus-minus"><span class="popup-options-link" title="Click to change columns" type="columns" for="exchanges">+/-</span></th>
+ </tr>
+ </thead>
+ <tbody>
+<%
+ for (var i = 0; i < exchanges.items.length; i++) {
+ var exchange = exchanges.items[i];
+%>
+ <tr<%= alt_rows(i, exchange.arguments)%>>
+<% if (vhosts_interesting) { %>
+ <td><%= fmt_string(exchange.vhost) %></td>
+<% } %>
+ <td><%= link_exchange(exchange.vhost, exchange.name, exchange.arguments) %></td>
+ <td class="c"><%= fmt_exchange_type(exchange.type) %></td>
+<% if (show_column('exchanges', 'features')) { %>
+ <td class="c">
+ <%= fmt_features_short(exchange) %>
+ <%= fmt_policy_short(exchange) %>
+ </td>
+<% } %>
+<% if (show_column('exchanges', 'features_no_policy')) { %>
+ <td class="c">
+ <%= fmt_features_short(exchange) %>
+ </td>
+<% } %>
+<% if (show_column('exchanges', 'policy')) { %>
+ <td class="c">
+ <%= link_policy(exchange.vhost, exchange.policy) %>
+ </td>
+<% } %>
+<% if (!disable_stats) { %>
+<% if (rates_mode != 'none') { %>
+<% if (show_column('exchanges', 'rate-in')) { %>
+ <td class="r"><%= fmt_detail_rate(exchange.message_stats, 'publish_in') %></td>
+<% } %>
+<% if (show_column('exchanges', 'rate-out')) { %>
+ <td class="r"><%= fmt_detail_rate(exchange.message_stats, 'publish_out') %></td>
+<% } %>
+<% } %>
+<% } %>
+ </tr>
+ <% } %>
+ </tbody>
+</table>
+<% } else { %>
+ <p>... no exchanges ...</p>
+<% } %>
+ </div>
+ </div>
+</div>
+
+<div class="section-hidden">
+ <h2>Add a new exchange</h2>
+ <div class="hider">
+ <form action="#/exchanges" method="put">
+ <table class="form">
+<% if (vhosts_interesting) { %>
+ <tr>
+ <th><label>Virtual host:</label></th>
+ <td>
+ <select name="vhost">
+ <% for (var i = 0; i < vhosts.length; i++) { %>
+ <option value="<%= fmt_string(vhosts[i].name) %>" <%= (vhosts[i].name === current_vhost) ? 'selected="selected"' : '' %>><%= fmt_string(vhosts[i].name) %></option>
+ <% } %>
+ </select>
+ </td>
+ </tr>
+<% } else { %>
+ <tr><td><input type="hidden" name="vhost" value="<%= fmt_string(vhosts[0].name) %>"/></td></tr>
+<% } %>
+ <tr>
+ <th><label>Name:</label></th>
+ <td><input type="text" name="name"/><span class="mand">*</span></td>
+ </tr>
+ <tr>
+ <th><label>Type:</label></th>
+ <td>
+ <select name="type">
+ <% for (var i = 0; i < exchange_types.length; i++) {
+ var type = exchange_types[i];
+ if (type.internal_purpose == undefined) { %>
+ <option value="<%= fmt_string(type.name) %>"><%= fmt_string(type.name) %></option>
+ <% }
+ } %>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Durability:</label></th>
+ <td>
+ <select name="durable">
+ <option value="true">Durable</option>
+ <option value="false">Transient</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Auto delete: <span class="help" id="exchange-auto-delete"></span></label></th>
+ <td>
+ <select name="auto_delete">
+ <option value="false">No</option>
+ <option value="true">Yes</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Internal: <span class="help" id="exchange-internal"></span></label></th>
+ <td>
+ <select name="internal">
+ <option value="false">No</option>
+ <option value="true">Yes</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Arguments:</label></th>
+ <td>
+ <div class="multifield" id="arguments"></div>
+ <table class="argument-links">
+ <tr>
+ <td>Add</td>
+ <td>
+ <span class="argument-link" field="arguments" key="alternate-exchange" type="string">Alternate exchange</span>
+ <span class="help" id="exchange-alternate"></span>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <input type="submit" value="Add exchange"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs
new file mode 100644
index 0000000000..23b0b73071
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs
@@ -0,0 +1,60 @@
+<h1>Feature Flags</h1>
+<div class="section">
+ <h2>All Feature Flags</h2>
+ <div class="hider">
+<%= filter_ui(feature_flags) %>
+ <div class="updatable">
+<% if (feature_flags.length > 0) { %>
+<table class="list">
+ <thead>
+ <tr>
+ <th><%= fmt_sort('Name', 'name') %></th>
+ <th class="c"><%= fmt_sort('State', 'state') %></th>
+ <th>Description</th>
+ </tr>
+ </thead>
+ <tbody>
+ <%
+ for (var i = 0; i < feature_flags.length; i++) {
+ var feature_flag = feature_flags[i];
+ var state_color = "grey";
+ if (feature_flag.state == "enabled") {
+ state_color = "green";
+ } else if (feature_flag.state == "disabled") {
+ state_color = "yellow";
+ } else if (feature_flag.state == "unsupported") {
+ state_color = "red";
+ }
+ %>
+ <tr<%= alt_rows(i)%>>
+ <td><%= fmt_string(feature_flag.name) %></td>
+ <td class="c">
+ <% if (feature_flag.state == "disabled") { %>
+ <form action="#/feature-flags-enable" method="put" style="display: inline-block">
+ <input type="hidden" name="name" value="<%= fmt_string(feature_flag.name) %>"/>
+ <input type="submit" value="Enable" class="c"/>
+ </form>
+ <% } else { %>
+ <abbr class="status-<%= fmt_string(state_color) %>"
+ style="text-transform: capitalize"
+ title="Feature flag state: <%= fmt_string(feature_flag.state) %>">
+ <%= fmt_string(feature_flag.state) %>
+ </abbr>
+ <% } %>
+ </td>
+ <td>
+ <p><%= fmt_string(feature_flag.desc) %></p>
+ <% if (feature_flag.doc_url) { %>
+ <p><a href="<%= fmt_string(feature_flag.doc_url) %>">[Learn more]</a></p>
+ <% } %>
+ </td>
+ </tr>
+ <% } %>
+ </tbody>
+</table>
+<% } else { %>
+ <p>... no feature_flags ...</p>
+<% } %>
+ </div>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/layout.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/layout.ejs
new file mode 100644
index 0000000000..e10a71553b
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/layout.ejs
@@ -0,0 +1,58 @@
+<div id="header">
+ <ul id="topnav">
+ <li id="interval">
+ <label for="update-every" id="status"></label>
+ <select id="update-every">
+ <% if(disable_stats) { %>
+ <option value="">Do not refresh</option>
+ <option value="5000">Refresh every 5 seconds</option>
+ <% } else { %>
+ <option value="5000">Refresh every 5 seconds</option>
+ <% } %>
+ <option value="10000">Refresh every 10 seconds</option>
+ <option value="30000">Refresh every 30 seconds</option>
+ <% if(!disable_stats) { %>
+ <option value="">Do not refresh</option>
+ <% } %>
+ </select>
+ </li>
+ <li id="vhost">
+ <label for="show-vhost">Virtual host </label>
+ <select id="show-vhost">
+ <option value="">All</option>
+ </select>
+ </li>
+ <li id="logout">
+ <% if (enable_uaa) { %>
+ <input type="submit" id="loginWindow" onclick="uaa_logout_window()" value="Log out"/>
+ <% } else { %>
+ <form action="#/logout" method="put">
+ <input type="submit" value="Log out"/>
+ </form>
+ <% } %>
+ </li>
+ </ul>
+ <div id="logo">
+ <a href="#/"><img src="img/rabbitmqlogo.svg" alt="RabbitMQ logo" width="204" height="37"/></a>
+ <span id="versions"></span>
+ </div>
+ <div id="menu">
+ <ul id="tabs">
+ </ul>
+ </div>
+</div>
+<div id="rhs"></div>
+<div id="main"></div>
+<div id="footer">
+ <ul>
+ <li><a href="api/" target="_blank">HTTP API</a></li>
+ <li><a href="https://www.rabbitmq.com/admin-guide.html" target="_blank">Server Docs</a></li>
+ <li><a href="https://www.rabbitmq.com/getstarted.html" target="_blank">Tutorials</a></li>
+ <li><a href="https://groups.google.com/forum/#!forum/rabbitmq-users" target="_blank">Community Support</a></li>
+ <li><a href="https://rabbitmq-slack.herokuapp.com/" target="_blank">Community Slack</a></li>
+ <li><a href="https://www.rabbitmq.com/services.html" target="_blank">Commercial Support</a></li>
+ <li><a href="https://www.rabbitmq.com/plugins.html" target="_blank">Plugins</a></li>
+ <li><a href="https://www.rabbitmq.com/github.html" target="_blank">GitHub</a></li>
+ <li><a href="https://www.rabbitmq.com/changelog.html" target="_blank">Changelog</a></li>
+ </ul>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/limits.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/limits.ejs
new file mode 100644
index 0000000000..7b0534e5b1
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/limits.ejs
@@ -0,0 +1,177 @@
+<h1>Limits</h1>
+
+<div class="section">
+ <h2>Virtual host Limits</h2>
+ <div class="hider">
+ <div class="updatable">
+
+ <% if (limits.length > 0) { %>
+ <table class="list">
+ <thead>
+ <tr>
+ <th>Virtual Host</th>
+ <th>Limit</th>
+ <th>Value</th>
+ <th class="administrator-only"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <% for (var i = 0; i < limits.length; i++) {
+ var limit = limits[i];
+ var limit_values = Object.keys(limit.value).sort().map(
+ function(k) { return {name: k, value: limit.value[k]};});
+ %>
+
+ <% for (var j = 0; j < limit_values.length; j++) {
+ var limit_value = limit_values[j];
+ %>
+
+ <tr<%= alt_rows(j+1)%>>
+ <% if(j == 0) { %>
+ <td rowspan="<%= limit_values.length %>"> <%= fmt_string(limit.vhost) %> </td>
+ <% } %>
+ <td><%= limit_value.name %></td>
+ <td><%= limit_value.value %></td>
+ <td class="administrator-only">
+ <form action="#/limits" method="delete" class="confirm">
+ <input type="hidden" name="name" value="<%= fmt_string(limit_value.name) %>"/>
+ <input type="hidden" name="vhost" value="<%= fmt_string(limit.vhost) %>"/>
+ <input type="submit" value="Clear"/>
+ </form>
+ </td>
+ </tr>
+ <% } %>
+ <% } %>
+ </tbody>
+ </table>
+ <% } %>
+ </div>
+ </div>
+</div>
+
+<div class="section administrator-only">
+ <h2>Set / update a virtual host limit</h2>
+ <div class="hider">
+ <form action="#/limits" method="put">
+ <table class="form">
+ <tr>
+ <th><label>Virtual host:</label></th>
+ <td>
+ <select name="vhost">
+ <% for (var i = 0; i < vhosts.length; i++) { %>
+ <option value="<%= fmt_string(vhosts[i].name) %>">
+ <%= fmt_string(vhosts[i].name) %>
+ </option>
+ <% } %>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Limit:</label></th>
+ <td>
+ <select name="name">
+ <option value="max-connections">max-connections</option>
+ <option value="max-queues">max-queues</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Value:</label></th>
+ <td>
+ <input type="text" name="value"/>
+ <span class="mand">*</span>
+ </td>
+ </tr>
+ </table>
+ <input type="submit" value="Set / update limit"/>
+ </form>
+ </div>
+</div>
+
+<div class="section">
+ <h2>User Limits</h2>
+ <div class="hider">
+ <div class="updatable">
+
+ <% if (user_limits.length > 0) { %>
+ <table class="list">
+ <thead>
+ <tr>
+ <th>User</th>
+ <th>Limit</th>
+ <th>Value</th>
+ <th class="administrator-only"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <% for (var i = 0; i < user_limits.length; i++) {
+ var user_limit = user_limits[i];
+ var user_limit_values = Object.keys(user_limit.value).sort().map(
+ function(k) { return {name: k, value: user_limit.value[k]};});
+ %>
+
+ <% for (var j = 0; j < user_limit_values.length; j++) {
+ var user_limit_value = user_limit_values[j];
+ %>
+
+ <tr<%= alt_rows(j+1)%>>
+ <% if(j == 0) { %>
+ <td rowspan="<%= user_limit_values.length %>"> <%= fmt_string(user_limit.user) %> </td>
+ <% } %>
+ <td><%= user_limit_value.name %></td>
+ <td><%= user_limit_value.value %></td>
+ <td class="administrator-only">
+ <form action="#/user-limits" method="delete" class="confirm">
+ <input type="hidden" name="name" value="<%= fmt_string(user_limit_value.name) %>"/>
+ <input type="hidden" name="user" value="<%= fmt_string(user_limit.user) %>"/>
+ <input type="submit" value="Clear"/>
+ </form>
+ </td>
+ </tr>
+ <% } %>
+ <% } %>
+ </tbody>
+ </table>
+ <% } %>
+ </div>
+ </div>
+</div>
+
+<div class="section administrator-only">
+ <h2>Set / update a user limit</h2>
+ <div class="hider">
+ <form action="#/user-limits" method="put">
+ <table class="form">
+ <tr>
+ <th><label>User:</label></th>
+ <td>
+ <select name="user">
+ <% for (var i = 0; i < users.length; i++) { %>
+ <option value="<%= fmt_string(users[i].name) %>">
+ <%= fmt_string(users[i].name) %>
+ </option>
+ <% } %>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Limit:</label></th>
+ <td>
+ <select name="name">
+ <option value="max-connections">max-connections</option>
+ <option value="max-channels">max-channels</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Value:</label></th>
+ <td>
+ <input type="text" name="value"/>
+ <span class="mand">*</span>
+ </td>
+ </tr>
+ </table>
+ <input type="submit" value="Set / update limit"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/list-exchanges.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/list-exchanges.ejs
new file mode 100644
index 0000000000..f449dfe001
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/list-exchanges.ejs
@@ -0,0 +1,5 @@
+<select name="exchange">
+ <% for (var i = 0; i < exchanges.length; i++) { %>
+ <option value="<%= fmt_string(exchanges[i].name) %>"><%= fmt_exchange(exchanges[i].name) %></option>
+ <% } %>
+</select>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/login.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/login.ejs
new file mode 100644
index 0000000000..d22262fc3e
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/login.ejs
@@ -0,0 +1,21 @@
+<div id="login">
+ <p><img src="img/rabbitmqlogo.svg" alt="RabbitMQ logo" width="204" height="37"/></p>
+
+ <form action="#/login" method="put">
+ <div id="login-status"></div>
+ <table class="form">
+ <tr>
+ <th><label>Username:</label></th>
+ <td><input type="text" name="username"/><span class="mand">*</span></td>
+ </tr>
+ <tr>
+ <th><label>Password:</label></th>
+ <td><input type="password" name="password"/><span class="mand">*</span></td>
+ </tr>
+ <tr>
+ <th>&nbsp;</th>
+ <td><input type="submit" value="Login"/></td>
+ </tr>
+ </table>
+ </form>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/login_uaa.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/login_uaa.ejs
new file mode 100644
index 0000000000..c4bba22ce7
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/login_uaa.ejs
@@ -0,0 +1,5 @@
+<div id="login">
+ <p><img src="img/rabbitmqlogo.svg" alt="RabbitMQ logo" width="204" height="37"/></p>
+
+ <div id="login-status"></div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/memory-bar.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/memory-bar.ejs
new file mode 100644
index 0000000000..a6d6ab631a
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/memory-bar.ejs
@@ -0,0 +1,24 @@
+<div class="memory-bar">
+<%
+ var width = 800;
+
+ var pseudo_total = 0
+ for (var section in sections) {
+ pseudo_total += memory[section];
+ }
+
+ total_out[0] = pseudo_total;
+
+ for (var section in sections) {
+ if (memory[section] > 0) {
+ var section_width = Math.round(width * memory[section] / pseudo_total);
+%>
+ <div class="memory-section memory_<%= sections[section][0] %>"
+ style="width: <%= section_width %>px;"
+ title="<%= sections[section][1] %> <%= fmt_bytes(memory[section]) %>">
+ </div>
+<%
+ }
+ }
+%>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/memory-table.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/memory-table.ejs
new file mode 100644
index 0000000000..7f90ab82b5
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/memory-table.ejs
@@ -0,0 +1,28 @@
+<%
+ for (var i in key) {
+%>
+<table class="facts">
+<%
+ for (var j in key[i]) {
+ var group = key[i][j];
+%>
+ <tr>
+ <th><div class="colour-key memory_<%= group.colour %>"></div><%= group.name %></th>
+ <td>
+ <table class="mini">
+<%
+ for (var k in group.keys) {
+ var name = group.keys[k][0];
+ var label = group.keys[k][1];
+%>
+ <tr>
+ <td class="r"><%= fmt_bytes(memory[name]) %></td>
+ <td><%= label %></td>
+ </tr>
+<% } %>
+ </table>
+ </td>
+ </tr>
+<% } %>
+</table>
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/memory.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/memory.ejs
new file mode 100644
index 0000000000..9bb129f446
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/memory.ejs
@@ -0,0 +1,97 @@
+<%
+ if (memory == "not_available") {
+%>
+<p class="warning">
+ Memory statistics not available.
+</p>
+<% } else { %>
+<%
+ var sections = {'queue_procs' : ['queue', 'Classic queues (masters)'],
+ 'queue_slave_procs' : ['queue', 'Classic queues (mirrors)'],
+ 'quorum_queue_procs' : ['quorum', 'Quorum queues'],
+ 'stream_queue_procs' : ['stream', 'Stream queues'],
+ 'stream_queue_replica_reader_procs' : ['stream', 'Stream queues (replica reader)'],
+ 'stream_queue_coordinator_procs' : ['stream', 'Stream queues (coordinator)'],
+ 'binary' : ['binary', 'Binaries'],
+ 'connection_readers' : ['conn', 'Connection readers'],
+ 'connection_writers' : ['conn', 'Connection writers'],
+ 'connection_channels' : ['conn', 'Connection channels'],
+ 'connection_other' : ['conn', 'Connections (other)'],
+ 'mnesia' : ['table', 'Mnesia'],
+ 'msg_index' : ['table', 'Message store index'],
+ 'mgmt_db' : ['table', 'Management database'],
+ 'quorum_ets' : ['table', 'Quorum queue ETS tables'],
+ 'other_ets' : ['table', 'Other ETS tables'],
+ 'plugins' : ['proc', 'Plugins'],
+ 'other_proc' : ['proc', 'Other process memory'],
+ 'code' : ['system', 'Code'],
+ 'atom' : ['system', 'Atoms'],
+ 'other_system' : ['system', 'Other system'],
+ 'allocated_unused' : ['unused', 'Allocated unused'],
+ 'reserved_unallocated': ['unused', 'Unallocated reserved by the OS']};
+%>
+<%= format('memory-bar', {sections: sections, memory: memory, total_out: []}) %>
+<span class="clear">&nbsp;</span>
+<div class="box">
+<%
+var key = [[{name: 'Queues', colour: 'queue',
+ keys: [['queue_procs', 'queues'],
+ ['queue_slave_procs', 'mirrors'],
+ ['quorum_queue_procs', 'quorum'],
+ ['stream_queue_procs', 'stream'],
+ ['stream_queue_replica_reader_procs', 'stream replica reader'],
+ ['stream_queue_coordinator_procs', 'stream coordinator']]},
+
+ {name: 'Binaries', colour: 'binary',
+ keys: [['binary', '']]}],
+
+ [{name: 'Connections', colour: 'conn',
+ keys: [['connection_readers', 'readers'],
+ ['connection_writers', 'writers'],
+ ['connection_channels', 'channels'],
+ ['connection_other', 'other']]}],
+
+ [{name: 'Tables', colour: 'table',
+ keys: [['mnesia', 'internal database tables'],
+ ['msg_index', 'message store index'],
+ ['mgmt_db', 'management database'],
+ ['quorum_ets', 'quorum queue tables'],
+ ['other_ets', 'other']]}],
+
+ [{name: 'Processes', colour: 'proc',
+ keys: [['plugins', 'plugins'],
+ ['other_proc', 'other']]},
+ {name: 'System', colour: 'system',
+ keys: [['code', 'code'],
+ ['atom', 'atoms'],
+ ['other_system', 'other']
+ ]}],
+
+ [{name: 'Preallocated memory', colour: 'unused',
+ keys: [['allocated_unused', 'preallocated by runtime, unused'],
+ ['reserved_unallocated', 'unallocated, reserved by the OS']]}]];
+%>
+<%= format('memory-table', {key: key, memory: memory}) %>
+</div>
+
+<div class="memory-info">
+ Last updated: <b><%= fmt_date(new Date()) %></b>.<br/>
+ Memory calculation strategy: <b><%= memory.strategy %></b>. <span class="help" id="memory-calculation-strategy-breakdown"></span><br/><br/>
+ Amount of memory used vs. allocated during last update: <span class="help" id="memory-use"></span><br/>
+ <table class="facts">
+ <tr>
+ <th>Runtime Used</th>
+ <td><%= fmt_bytes(memory.total.erlang) %></td>
+ </tr>
+ <tr>
+ <th>Runtime Allocated</th>
+ <td><%= fmt_bytes(memory.total.allocated) %></td>
+ </tr>
+ <tr>
+ <th>Resident Set Size (RSS) reported by the OS</th>
+ <td><%= fmt_bytes(memory.total.rss) %></td>
+ </tr>
+ </table>
+</div>
+
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/messages.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/messages.ejs
new file mode 100644
index 0000000000..06836a1e23
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/messages.ejs
@@ -0,0 +1,39 @@
+<%
+ for (var i = 0; i < msgs.length; i++) {
+ var msg = msgs[i];
+%>
+<div class="box">
+<h3>Message <%= i+1 %></h3>
+<p>The server reported <b><%= msg.message_count %></b> messages remaining.</p>
+<table class="facts">
+ <tr>
+ <th>Exchange</th>
+ <td><%= fmt_exchange(msg.exchange) %></td>
+ </tr>
+ <tr>
+ <th>Routing Key</th>
+ <td><%= fmt_string(msg.routing_key) %></td>
+ </tr>
+ <tr>
+ <th>Redelivered</th>
+ <td><%= fmt_boolean(msg.redelivered) %></td>
+ </tr>
+ <tr>
+ <th>Properties</th>
+ <td><%= fmt_table_short(msg.properties) %></td>
+ </tr>
+ <tr>
+ <th>
+ Payload
+ <sub><%= msg.payload_bytes %> bytes</sub>
+ <sub>Encoding: <%= msg.payload_encoding %></sub>
+ </th>
+ <td>
+ <pre class="msg-payload"><%= fmt_maybe_wrap(msg.payload, msg.payload_encoding) %></pre>
+ </td>
+ </tr>
+</table>
+</div>
+<%
+ }
+%>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/msg-detail-deliveries.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/msg-detail-deliveries.ejs
new file mode 100644
index 0000000000..ee9e6f6500
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/msg-detail-deliveries.ejs
@@ -0,0 +1,30 @@
+<h3>Deliveries</h3>
+<% if (object && object.length > 0) { %>
+<table class="list">
+ <tr>
+<% if (mode == 'queue') { %>
+ <th>Channel</th>
+<% } else { %>
+ <th>Queue</th>
+<% } %>
+ <th>deliver / get</th>
+ <th>ack</th>
+ </tr>
+<%
+ for (var i = 0; i < object.length; i++) {
+ var del = object[i];
+%>
+ <tr<%= alt_rows(i)%>>
+<% if (mode == 'queue') { %>
+ <td><%= link_channel(del.channel_details.name) %></td>
+<% } else { %>
+ <td><%= link_queue(del.queue.vhost, del.queue.name) %></td>
+<% } %>
+ <td class="r"><%= fmt_detail_rate(del.stats, 'deliver_get') %></td>
+ <td class="r"><%= fmt_detail_rate(del.stats, 'ack') %></td>
+ </tr>
+<% } %>
+</table>
+<% } else { %>
+<p> ... no deliveries ...</p>
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/msg-detail-publishes.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/msg-detail-publishes.ejs
new file mode 100644
index 0000000000..4ea959ac52
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/msg-detail-publishes.ejs
@@ -0,0 +1,46 @@
+<h3><%= label %></h3>
+<% if (object && object.length > 0) { %>
+<%
+ var col_confirm = mode != 'exchange-outgoing';
+%>
+<table class="list">
+ <tr>
+<% if (mode == 'channel') { %>
+ <th>Exchange</th>
+<% } else if (mode == 'exchange-incoming') { %>
+ <th>Channel</th>
+<% } else if (mode == 'exchange-outgoing') { %>
+ <th>Queue</th>
+<% } else { %>
+ <th>Exchange</th>
+<% } %>
+ <th>publish</th>
+<% if (col_confirm) { %>
+ <th>confirm</th>
+<% } %>
+ </tr>
+<%
+ for (var i = 0; i < object.length; i++) {
+ var pub = object[i];
+%>
+ <tr<%= alt_rows(i)%>>
+
+<% if (mode == 'channel') { %>
+ <td><%= link_exchange(pub.exchange.vhost, pub.exchange.name) %></td>
+<% } else if (mode == 'exchange-incoming') { %>
+ <td><%= link_channel(pub.channel_details.name) %></td>
+<% } else if (mode == 'exchange-outgoing') { %>
+ <td><%= link_queue(pub.queue.vhost, pub.queue.name) %></td>
+<% } else { %>
+ <td><%= link_exchange(pub.exchange.vhost, pub.exchange.name) %></td>
+<% } %>
+ <td class="r"><%= fmt_detail_rate(pub.stats, 'publish') %></td>
+<% if (col_confirm) { %>
+ <td class="r"><%= fmt_detail_rate(pub.stats, 'confirm') %></td>
+<% } %>
+ </tr>
+<% } %>
+</table>
+<% } else { %>
+<p> ... no publishes ...</p>
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/node.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/node.ejs
new file mode 100644
index 0000000000..884ccd6e88
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/node.ejs
@@ -0,0 +1,416 @@
+<h1>Node <b><%= node.name %></b></h1>
+<div class="updatable">
+
+<% if (!node.running) { %>
+<p class="warning">Node not running</p>
+<% } else if ((node.os_pid == undefined) && (!disable_stats)) { %>
+<p class="warning">Node statistics not available</p>
+<% } else { %>
+
+<% if(!disable_stats) { %>
+<div class="section">
+<h2>Overview</h2>
+<div class="hider">
+ <div class="box">
+ <table class="facts facts-l">
+ <tr>
+ <th>Uptime</th>
+ <td><%= fmt_uptime(node.uptime) %></td>
+ </tr>
+<% if (rabbit_versions_interesting) { %>
+ <tr>
+ <th>RabbitMQ Version</th>
+ <td><%= fmt_rabbit_version(node.applications) %></td>
+ </tr>
+<% } %>
+ <tr>
+ <th>Type</th>
+ <td>
+ <% if (node.type == 'disc') { %>
+ <abbr title="Broker definitions are held on disc.">Disc</abbr>
+ <% } else { %>
+ <abbr title="Broker definitions are held in RAM. Messages will still be written to disc if necessary.">RAM</abbr>
+ <% } %>
+ </td>
+ </tr>
+ <tr>
+ <th>
+ <a href="https://www.rabbitmq.com/configure.html" target="_blank">Config file</a>
+ </th>
+ <td>
+ <%
+ for (var i = 0; i < node.config_files.length; i++) {
+ var config = fmt_escape_html(node.config_files[i]);
+ %>
+ <code><%= config %></code>
+ <% } %>
+ </td>
+ </tr>
+ <tr>
+ <th>Database directory</th>
+ <td>
+ <code><%= node.db_dir %></code>
+ </td>
+ </tr>
+ <tr>
+ <th>
+<% if (node.log_files.length == 1) { %>
+ Log file
+<% } else { %>
+ Log files
+<% } %>
+ </th>
+ <td>
+ <pre style="margin-top: 0px; margin-bottom: 0px;"><%
+ for (var i = 0; i < node.log_files.length; i++) {
+ var config = fmt_escape_html(node.log_files[i]);
+%><%= config %>
+<% } %></pre>
+ </td>
+ </tr>
+ </table>
+ </div>
+</div>
+</div>
+
+<% if(!disable_stats) { %>
+<div class="section">
+<h2>Process statistics</h2>
+<div class="hider">
+ <%= node_stats_prefs() %>
+ <table class="facts">
+ <tr>
+ <th>
+ File descriptors <span class="help" id="file-descriptors"></span>
+ </th>
+ <td>
+<% if (node.fd_used != 'install_handle_from_sysinternals') { %>
+ <%= node_stat_count('fd_used', 'fd_total', node, FD_THRESHOLDS) %>
+<% } else { %>
+ <p class="c">handle.exe missing <span class="help" id="handle-exe"></span><sub><%= node.fd_total %> available</sub></p>
+
+<% } %>
+ </td>
+ </tr>
+ <tr>
+ <th>
+ Socket descriptors <span class="help" id="socket-descriptors"></span>
+ </th>
+ <td>
+ <%= node_stat_count('sockets_used', 'sockets_total', node, FD_THRESHOLDS) %>
+ </td>
+ </tr>
+ <tr>
+ <th>
+ Erlang processes
+ </th>
+ <td>
+ <%= node_stat_count('proc_used', 'proc_total', node, PROCESS_THRESHOLDS) %>
+ </td>
+ </tr>
+ <tr>
+ <th>
+ Memory <span class="help" id="memory-calculation-strategy"></span>
+ </th>
+ <td>
+<% if (node.mem_limit != 'memory_monitoring_disabled') { %>
+ <%= node_stat('mem_used', 'Used', 'mem_limit', 'high watermark', node,
+ fmt_bytes, fmt_bytes_axis,
+ node.mem_alarm ? 'red' : 'green',
+ node.mem_alarm ? 'memory-alarm' : null) %>
+<% } else { %>
+ <%= fmt_bytes(node.mem_used) %>
+<% } %>
+ </td>
+ </tr>
+ <tr>
+ <th>
+ Disk space
+ </th>
+ <td>
+<% if (node.disk_free_limit != 'disk_free_monitoring_disabled') { %>
+ <%= node_stat('disk_free', 'Free', 'disk_free_limit', 'low watermark', node,
+ fmt_bytes, fmt_bytes_axis,
+ node.disk_free_alarm ? 'red' : 'green',
+ node.disk_free_alarm ? 'disk_free-alarm' : null,
+ true) %>
+<% } else { %>
+ (not available)
+<% } %>
+ </td>
+ </tr>
+ </table>
+</div>
+</div>
+
+<div class="section-hidden">
+<h2>Persistence statistics</h2>
+<div class="hider">
+ <%= rates_chart_or_text('mnesia-stats-count', node,
+ [['RAM only', 'mnesia_ram_tx_count'],
+ ['Disk', 'mnesia_disk_tx_count']],
+ fmt_rate, fmt_rate_axis, true, 'Schema data store transactions', 'mnesia-transactions') %>
+
+ <%= rates_chart_or_text('persister-msg-stats-count', node,
+ [['QI Journal', 'queue_index_journal_write_count'],
+ ['Store Read', 'msg_store_read_count'],
+ ['Store Write', 'msg_store_write_count']],
+ fmt_rate, fmt_rate_axis, true, 'Persistence operations (messages)', 'persister-operations-msg') %>
+
+ <%= rates_chart_or_text('persister-bulk-stats-count', node,
+ [['QI Read', 'queue_index_read_count'],
+ ['QI Write', 'queue_index_write_count']],
+ fmt_rate, fmt_rate_axis, true, 'Persistence operations (bulk)', 'persister-operations-bulk') %>
+</div>
+</div>
+
+<div class="section-hidden">
+<h2>I/O statistics</h2>
+<div class="hider">
+ <%= rates_chart_or_text('persister-io-stats-count', node,
+ [['Read', 'io_read_count'],
+ ['Write', 'io_write_count'],
+ ['Seek', 'io_seek_count'],
+ ['Sync', 'io_sync_count'],
+ ['File handle reopen', 'io_reopen_count'],
+ ['File handle open attempt', 'io_file_handle_open_attempt_count']],
+ fmt_rate, fmt_rate_axis, true, 'I/O operations', 'io-operations') %>
+
+ <%= rates_chart_or_text('persister-io-stats-bytes', node,
+ [['Read', 'io_read_bytes'],
+ ['Write', 'io_write_bytes']],
+ fmt_rate_bytes, fmt_rate_bytes_axis, true, 'I/O data rates') %>
+
+ <%= rates_chart_or_text('persister-io-stats-time', node,
+ [['Read', 'io_read_avg_time'],
+ ['Write', 'io_write_avg_time'],
+ ['Seek', 'io_seek_avg_time'],
+ ['Sync', 'io_sync_avg_time'],
+ ['File handle open attempt', 'io_file_handle_open_attempt_avg_time']],
+ fmt_ms, fmt_ms, false, 'I/O average time per operation') %>
+</div>
+</div>
+
+<div class="section-hidden">
+<h2>Churn statistics</h2>
+<div class="hider">
+ <%= rates_chart_or_text('connection-churn', node,
+ [['Created', 'connection_created'],
+ ['Closed', 'connection_closed']],
+ fmt_rate, fmt_rate_axis, true, 'Connection operations', 'connection-operations') %>
+
+ <%= rates_chart_or_text('channel-churn', node,
+ [['Created', 'channel_created'],
+ ['Closed', 'channel_closed']],
+ fmt_rate, fmt_rate_axis, true, 'Channel operations', 'channel-operations') %>
+
+ <%= rates_chart_or_text('queue-churn', node,
+ [['Declared', 'queue_declared'],
+ ['Created', 'queue_created'],
+ ['Deleted', 'queue_deleted']],
+ fmt_rate, fmt_rate_axis, true, 'Queue operations', 'queue-operations') %>
+
+</div>
+</div>
+<% } %>
+
+<div class="section-hidden">
+<h2>Cluster links</h2>
+<div class="hider">
+<% if (node.cluster_links.length > 0) { %>
+<table class="list">
+ <tr>
+ <th>Remote node</th>
+ <th>Local address</th>
+ <th>Local port</th>
+ <th>Remote address</th>
+ <th>Remote port</th>
+ <th class="plain">
+ <%= chart_h3('cluster-link-data-rates', 'Data rates') %>
+ </th>
+ </tr>
+ <%
+ for (var i = 0; i < node.cluster_links.length; i++) {
+ var link = node.cluster_links[i];
+ %>
+ <tr<%= alt_rows(i)%>>
+ <td><%= link_node(link.name) %></td>
+ <td><%= fmt_string(link.sock_addr) %></td>
+ <td><%= fmt_string(link.sock_port) %></td>
+ <td><%= fmt_string(link.peer_addr) %></td>
+ <td><%= fmt_string(link.peer_port) %></td>
+ <td class="plain">
+ <%= rates_chart_or_text_no_heading(
+ 'cluster-link-data-rates', 'cluster-link-data-rates' + link.name,
+ link.stats,
+ [['Recv', 'recv_bytes'],
+ ['Send', 'send_bytes']],
+ fmt_rate_bytes, fmt_rate_bytes_axis, true) %>
+ </td>
+ </tr>
+<% } %>
+</table>
+<% } else { %>
+ <p>... no cluster links ...</p>
+<% } %>
+</div>
+</div>
+
+<% } %>
+
+</div>
+<% } %>
+
+<!--
+ The next two need to be non-updatable or we will wipe the memory details
+ as soon as we have drawn it.
+ -->
+
+<% if (node.running && (node.os_pid != undefined || disable_stats)) { %>
+
+<div class="section">
+<h2>Memory details</h2>
+<div class="hider">
+ <div id="memory-details"></div>
+ <button class="update-manual memory-button" for="memory-details" query="memory">Update</button>
+</div>
+</div>
+
+<div class="section-hidden">
+<h2>Binary references</h2>
+<div class="hider">
+ <p>
+ <b>Warning:</b> Calculating binary memory use can be expensive if
+ there are many small binaries in the system.
+ </p>
+ <div id="binary-details"></div>
+ <button class="update-manual memory-button" for="binary-details" query="binary">Update</button>
+</div>
+</div>
+
+<% } %>
+
+<% if(!disable_stats) { %>
+<div class="updatable">
+<% if (node.running && node.os_pid != undefined) { %>
+
+<div class="section-hidden">
+<h2>Advanced</h2>
+<div class="hider">
+ <div class="box">
+ <h3>VM</h3>
+ <table class="facts">
+ <tr>
+ <th>OS pid</th>
+ <td><%= node.os_pid %></td>
+ </tr>
+ <tr>
+ <th>Rates mode</th>
+ <td><%= node.rates_mode %></td>
+ </tr>
+ <tr>
+ <th>Net ticktime</th>
+ <td><%= node.net_ticktime %>s</td>
+ </tr>
+ </table>
+
+ <table class="facts">
+ <tr>
+ <th>Run queue</th>
+ <td><%= node.run_queue %></td>
+ </tr>
+ <tr>
+ <th>Processors</th>
+ <td><%= node.processors %></td>
+ </tr>
+ </table>
+ </div>
+
+ <%= rates_chart_or_text('advanced-gc-stats-count', node,
+ [['GC', 'gc_num']],
+ fmt_rate, fmt_rate_axis, true, 'GC operations', 'gc-operations') %>
+
+ <%= rates_chart_or_text('advanced-gc-bytes-stats-count', node,
+ [['GC bytes reclaimed', 'gc_bytes_reclaimed']],
+ fmt_rate, fmt_rate_axis, true, 'GC bytes reclaimed', 'gc-bytes') %>
+
+ <%= rates_chart_or_text('advanced-context-switches-stats-count', node,
+ [['Context switches', 'context_switches']],
+ fmt_rate, fmt_rate_axis, true, 'Context switch operations', 'context-switches-operations') %>
+
+<div class="box">
+ <h3>Management GC queue length</h3>
+ <table class="facts">
+ <% for(var k in node.metrics_gc_queue_length) {
+ if(node.metrics_gc_queue_length.hasOwnProperty(k)) { %>
+ <tr>
+ <th><%= k %></th>
+ <td><%= node.metrics_gc_queue_length[k] %></td>
+ </tr>
+ <% } } %>
+ </table>
+</div>
+
+<div class="box">
+ <h3>Quorum queue open file metrics</h3>
+ <table class="facts">
+ <% for(var k in node.ra_open_file_metrics) { %>
+ <tr>
+ <th><%= k %></th>
+ <td><%= node.ra_open_file_metrics[k] %></td>
+ </tr>
+ <% } %>
+ </table>
+</div>
+
+<h3>Plugins <span class="help" id="plugins"></span></h3>
+<table class="list">
+ <tr>
+ <th>Name</th>
+ <th>Version</th>
+ <th>Description</th>
+ </tr>
+ <%
+ var plugins = get_plugins_list(node);
+ for (var j = 0; j < plugins.length; j++) {
+ var application = plugins[j];
+ %>
+ <tr<%= alt_rows(j)%>>
+ <td><%= application.name %></td>
+ <td><%= application.version %></td>
+ <td><%= application.description %></td>
+ </tr>
+ <% } %>
+</table>
+
+<h3>All applications</h3>
+<table class="list">
+ <tr>
+ <th>Name</th>
+ <th>Version</th>
+ <th>Description</th>
+ </tr>
+ <%
+ for (var j = 0; j < node.applications.length; j++) {
+ var application = node.applications[j];
+ %>
+ <tr<%= alt_rows(j)%>>
+ <td><%= application.name %></td>
+ <td><%= application.version %></td>
+ <td><%= application.description %></td>
+ </tr>
+ <% } %>
+</table>
+
+<h3>Exchange types</h3>
+<%= format('registry', {'list': node.exchange_types, 'node': node, 'show_enabled': false} ) %>
+<h3>Authentication mechanisms</h3>
+<%= format('registry', {'list': node.auth_mechanisms, 'node': node, 'show_enabled': true} ) %>
+
+</div>
+</div>
+
+<% } %>
+<% } %>
+
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/overview.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/overview.ejs
new file mode 100644
index 0000000000..4b6dce1a99
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/overview.ejs
@@ -0,0 +1,399 @@
+<% if(disable_stats) { %>
+ <h1>Overview: Management only mode</h1>
+<% } else { %>
+ <h1>Overview</h1>
+<% } %>
+<% if (user_monitor) { %>
+<%= format('partition', {'nodes': nodes}) %>
+<% } %>
+
+<div class="updatable">
+<% if (overview.statistics_db_event_queue > 1000) { %>
+<p class="warning">
+ The management statistics database currently has a queue
+ of <b><%= overview.statistics_db_event_queue %></b> events to
+ process. If this number keeps increasing, so will the memory used by
+ the management plugin.
+
+ <% if (overview.rates_mode != 'none') { %>
+ You may find it useful to set the <code>rates_mode</code> config item
+ to <code>none</code>.
+ <% } %>
+</p>
+<% } %>
+<% for (i = 0; i < vhosts.length; i++)
+{
+ for (var vhost_status_node in vhosts[i].cluster_state) {
+ if (vhosts[i].cluster_state[vhost_status_node] != 'running') {
+%>
+<p class="warning">
+ Virtual host <b><%= vhosts[i].name %></b> experienced an error on node <b><%= vhost_status_node %></b> and may be inaccessible
+</p>
+<% }}} %>
+</div>
+
+<div class="section">
+<h2>Totals</h2>
+<div class="hider updatable">
+<% if(!disable_stats) { %>
+<%= queue_lengths('lengths-over', overview.queue_totals) %>
+<% if (rates_mode != 'none') { %>
+ <%= message_rates('msg-rates-over', overview.message_stats) %>
+<% } %>
+<% } %>
+
+<% if (overview.object_totals) { %>
+ <h3>Global counts <span class="help" id="resource-counts"></span></h3>
+
+ <ul id="global-counts">
+ <li>
+ <a href="#/connections" class="button">Connections: <strong><%= overview.object_totals.connections %></strong></a>
+ </li>
+<% if(!disable_stats) { %>
+ <li>
+ <a href="#/channels" class="button">Channels: <strong><%= overview.object_totals.channels %></strong></a>
+ </li>
+<% } %>
+ <li>
+ <a href="#/exchanges" class="button">Exchanges: <strong><%= overview.object_totals.exchanges %></strong></a>
+ </li>
+ <li>
+ <a href="#/queues" class="button">Queues: <strong><%= overview.object_totals.queues %></strong></a>
+ </li>
+<% if (overview.object_totals['consumers'] != undefined) { %>
+ <li>
+ <a href="#" class="button disabled">Consumers: <strong><%= overview.object_totals.consumers %></strong></a>
+ </li>
+<% } %>
+ </ul>
+<% } %>
+
+</div>
+</div>
+
+<% if (user_monitor) { %>
+<div class="section">
+<h2>Nodes</h2>
+
+<div class="hider updatable">
+
+<table class="list">
+ <tr>
+ <th>Name</th>
+ <% if(!disable_stats) { %>
+ <% if (show_column('overview', 'file_descriptors')) { %>
+ <th>File descriptors <span class="help" id="file-descriptors"></span></th>
+ <% } %>
+ <% if (show_column('overview', 'socket_descriptors')) { %>
+ <th>Socket descriptors <span class="help" id="socket-descriptors"></span></th>
+ <% } %>
+ <% if (show_column('overview', 'erlang_processes')) { %>
+ <th>Erlang processes</th>
+ <% } %>
+ <% if (show_column('overview', 'memory')) { %>
+ <th>Memory <span class="help" id="memory-calculation-strategy"></span></th>
+ <% } %>
+ <% if (show_column('overview', 'disk_space')) { %>
+ <th>Disk space</th>
+ <% } %>
+ <% if (show_column('overview', 'uptime')) { %>
+ <th>Uptime</th>
+ <% } %>
+ <% if (show_column('overview', 'info')) { %>
+ <th>Info</th>
+ <% } %>
+ <% if (user_administrator && show_column('overview', 'reset_stats')) { %>
+ <th>Reset stats</th>
+ <% } %>
+ <th class="plus-minus"><span class="popup-options-link" title="Click to change columns" type="columns" for="overview">+/-</span></th>
+ <% } %>
+ </tr>
+<%
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ if(!disable_stats) {
+ var colspan = group_count('overview', 'Statistics', []) +
+ group_count('overview', 'General', []);
+ } else {
+ var colspan = [];
+ }
+%>
+ <tr<%= alt_rows(i)%>>
+ <td>
+ <a href="#/nodes/<%= esc(node.name) %>" class="button"><%= fmt_node(node.name) %></a>
+ <% if (rabbit_versions_interesting) { %>
+ <sub>RabbitMQ <%= fmt_rabbit_version(node.applications) %></sub>
+ <% } %>
+ </td>
+<% if(!disable_stats) { %>
+<% if (!node.running) { %>
+ <td colspan="<%= colspan %>">
+ <div class="status-red">
+ Node not running
+ </div>
+ </td>
+<% } else if (node.os_pid == undefined) { %>
+ <td colspan="<%= colspan %>">
+ <div class="status-yellow">
+ <abbr title="The rabbitmq_management_agent plugin should be enabled on this node. If it is not, various statistics will be inaccurate.">
+ Node statistics not available</abbr>
+ </div>
+ </td>
+<% } else { %>
+ <% if (show_column('overview', 'file_descriptors')) { %>
+ <td>
+ <% if (node.fd_used != 'install_handle_from_sysinternals') { %>
+ <%= node_stat_count_bar('fd_used', 'fd_total', node, FD_THRESHOLDS) %>
+ <% } else { %>
+ <p class="c">handle.exe missing <span class="help" id="handle-exe"></span><sub><%= node.fd_total %> available</sub></p>
+
+ <% } %>
+ </td>
+ <% } %>
+ <% if (show_column('overview', 'socket_descriptors')) { %>
+ <td>
+ <%= node_stat_count_bar('sockets_used', 'sockets_total', node, FD_THRESHOLDS) %>
+ </td>
+ <% } %>
+ <% if (show_column('overview', 'erlang_processes')) { %>
+ <td>
+
+ <%= node_stat_count_bar('proc_used', 'proc_total', node, PROCESS_THRESHOLDS) %>
+ </td>
+ <% } %>
+ <% if (show_column('overview', 'memory')) { %>
+ <td>
+
+ <% if (node.mem_limit != 'memory_monitoring_disabled') { %>
+ <%= node_stat_bar('mem_used', 'mem_limit', 'high watermark', node, fmt_bytes_axis,
+ node.mem_alarm ? 'red' : 'green',
+ node.mem_alarm ? 'memory-alarm' : null) %>
+ <% } else { %>
+ <%= fmt_bytes(node.mem_used) %>
+ <% } %>
+ </td>
+ <% } %>
+ <% if (show_column('overview', 'disk_space')) { %>
+ <td>
+
+ <% if (node.disk_free_limit != 'disk_free_monitoring_disabled') { %>
+ <%= node_stat_bar('disk_free', 'disk_free_limit', 'low watermark', node, fmt_bytes_axis,
+ node.disk_free_alarm ? 'red' : 'green',
+ node.disk_free_alarm ? 'disk_free-alarm' : null, true) %>
+ <% } else { %>
+ (not available)
+ <% } %>
+ </td>
+ <% } %>
+ <% if (show_column('overview', 'uptime')) { %>
+ <td><span><%= fmt_uptime(node.uptime) %></span></td>
+ <% } %>
+ <% if (show_column('overview', 'info')) { %>
+ <td>
+ <abbr title="Message rates"><%= fmt_string(node.rates_mode) %></abbr>
+ <% if (node.type == 'disc') { %>
+ <abbr title="Broker definitions are held on disc.">disc</abbr>
+ <% } else { %>
+ <abbr title="Broker definitions are held in RAM. Messages will still be written to disc if necessary.">RAM</abbr>
+ <% } %>
+ <%= fmt_plugins_small(node) %>
+ <abbr title="Memory calculation strategy"><%= fmt_string(node.mem_calculation_strategy) %></abbr>
+ </td>
+ <% } %>
+ <% if(user_administrator && show_column('overview', 'reset_stats')) { %>
+ <td>
+ <form action="#/reset_node" method="delete" class="confirm inline-form">
+ <input type="hidden" name="node" value="<%= node.name %>"/>
+ <input type="submit" value="This node"/>
+ </form>
+ <form action="#/reset" method="delete" class="confirm inline-form-right">
+ <input type="submit" value="All nodes"/>
+ </form>
+ <% } %>
+<% } %>
+<% } %>
+ </tr>
+<% } %>
+</table>
+
+</div>
+</div>
+
+<% if(!disable_stats) { %>
+<div class="section-hidden">
+<h2>Churn statistics</h2>
+<div class="hider updatable">
+ <%= rates_chart_or_text('connection-churn', overview.churn_rates,
+ [['Created', 'connection_created'],
+ ['Closed', 'connection_closed']],
+ fmt_rate, fmt_rate_axis, true, 'Connection operations', 'connection-operations') %>
+
+ <%= rates_chart_or_text('channel-churn', overview.churn_rates,
+ [['Created', 'channel_created'],
+ ['Closed', 'channel_closed']],
+ fmt_rate, fmt_rate_axis, true, 'Channel operations', 'channel-operations') %>
+
+ <%= rates_chart_or_text('queue-churn', overview.churn_rates,
+ [['Declared', 'queue_declared'],
+ ['Created', 'queue_created'],
+ ['Deleted', 'queue_deleted']],
+ fmt_rate, fmt_rate_axis, true, 'Queue operations', 'queue-operations') %>
+
+</div>
+</div>
+<% } %>
+
+<% if(!disable_stats) { %>
+<div class="section-hidden">
+<h2>Ports and contexts</h2>
+<div class="hider updatable">
+<h3>Listening ports</h3>
+<table class="list">
+ <tr>
+ <th>Protocol</th>
+<% if (nodes_interesting) { %>
+ <th>Node</th>
+<% } %>
+ <th>Bound to</th>
+ <th>Port</th>
+ </tr>
+ <%
+ for (var i = 0; i < overview.listeners.length; i++) {
+ var listener = overview.listeners[i];
+ %>
+ <tr<%= alt_rows(i)%>>
+ <td><%= listener.protocol %></td>
+<% if (nodes_interesting) { %>
+ <td><%= fmt_node(listener.node) %></td>
+<% } %>
+ <td><%= listener.ip_address %></td>
+ <td><%= listener.port %></td>
+ </tr>
+ <% } %>
+</table>
+<h3>Web contexts</h3>
+<table class="list">
+ <tr>
+ <th>Context</th>
+<% if (nodes_interesting) { %>
+ <th>Node</th>
+<% } %>
+ <th>Bound to</th>
+ <th>Port</th>
+ <th>SSL</th>
+ <th>Path</th>
+ </tr>
+ <%
+ for (var i = 0; i < overview.contexts.length; i++) {
+ var context = overview.contexts[i];
+ %>
+ <tr<%= alt_rows(i)%>>
+ <td><%= context.description %></td>
+<% if (nodes_interesting) { %>
+ <td><%= fmt_node(context.node) %></td>
+<% } %>
+ <td><%= (context.ip != undefined) ? context.ip : "0.0.0.0" %></td>
+ <td><%= context.port %></td>
+ <td class="c"><%= fmt_boolean(context.ssl || false) %></td>
+ <td><%= context.path %></td>
+ </tr>
+ <% } %>
+</table>
+</div>
+</div>
+<% } %>
+
+<div class="section-hidden administrator-only">
+<h2>Export definitions</h2>
+<div class="hider">
+ <table class="two-col-layout">
+ <tr>
+ <td>
+ <p>
+ <label for="download-filename">Filename for download:</label><br/>
+ <input type="text" id="download-filename" value="<%= fmt_download_filename(overview.node) %>" class="wide" />
+ </p>
+ </td>
+ <td>
+ <p>
+ <button id="download-definitions">Download broker definitions</button>
+ <span class="help" id="export-definitions"></span>
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <% if (vhosts_interesting) { %>
+ <label>Virtual host:</label>
+ <select name="vhost-download">
+ <option value="all">All</option>
+ <% for (var i = 0; i < vhosts.length; i++) { %>
+ <option value="<%= fmt_string(vhosts[i].name) %>"><%= fmt_string(vhosts[i].name) %></option>
+ <% } %>
+ </select> <span class="help" id="export-definitions-vhost"></span>
+<% } else { %>
+ <input type="hidden" name="vhost" value="all"/>
+<% } %>
+ </td>
+ </tr>
+ </table>
+</div>
+</div>
+
+<div class="section-hidden administrator-only">
+<h2>Import definitions</h2>
+<div class="hider">
+ <form method="post" enctype="multipart/form-data">
+ <table class="two-col-layout">
+ <tr>
+ <td>
+ <p>
+ <label>Definitions file:</label><br/>
+ <input type="file" name="file"/>
+ </p>
+ </td>
+ <td>
+ <p>
+ <input type="submit" value="Upload broker definitions" onclick="submit_import($(this).closest('form')[0]); return false"/>
+ <span class="help" id="import-definitions"></span>
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <% if (vhosts_interesting) { %>
+ <label>Virtual host:</label>
+ <select name="vhost-upload">
+ <option value="all">All</option>
+ <% for (var i = 0; i < vhosts.length; i++) { %>
+ <option value="<%= fmt_string(vhosts[i].name) %>"><%= fmt_string(vhosts[i].name) %></option>
+ <% } %>
+ </select> <span class="help" id="import-definitions-vhost"></span>
+
+<% } else { %>
+ <input type="hidden" name="vhost" value="all"/>
+<% } %>
+ </td>
+ </tr>
+ </table>
+ </form>
+</div>
+</div>
+
+<% if (overview.rates_mode == 'none') { %>
+<div class="section-hidden">
+<h2>Message rates disabled</h2>
+<div class="hider">
+<p>
+ Message rates are currently disabled.
+</p>
+<p>
+ To re-enable message rates, edit your configuration file and
+ set <code>rates_mode</code> to <code>basic</code>
+ or <code>detailed</code> in the <code>rabbitmq_management</code>
+ application
+</p>
+</div>
+</div>
+<% } %>
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/partition.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/partition.ejs
new file mode 100644
index 0000000000..7c40fa88f4
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/partition.ejs
@@ -0,0 +1,105 @@
+<div class="updatable">
+<%
+ var partitions = [];
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ if (node.partitions != undefined && node.partitions.length != 0) {
+ partitions.push({'node': node.name,
+ 'others': node.partitions});
+ }
+ }
+ if (partitions.length > 0) {
+%>
+<p class="status-error">
+ Network partition detected<br/><br/>
+ Mnesia reports that this RabbitMQ cluster has experienced a
+ network partition. There is a risk of losing data. Please read
+ <a href="https://www.rabbitmq.com/partitions.html">RabbitMQ
+ documentation about network partitions and the possible solutions</a>.
+</p>
+<p>
+ The nature of the partition is as follows:
+</p>
+ <table class="list">
+ <tr>
+ <th>Node</th><th>Was partitioned from</th>
+ </tr>
+
+<%
+ for (var i = 0; i < partitions.length; i++) {
+ var partition = partitions[i];
+%>
+ <tr<%= alt_rows(i)%>>
+ <td><%= fmt_node(partition.node) %></td>
+ <td>
+<%
+ for (var j = 0; j < partition.others.length; j++) {
+ var other = partition.others[j];
+%>
+ <%= other %><br/>
+<% } %>
+ </td>
+ </tr>
+<% } %>
+ </table>
+<p>
+ While running in this partitioned state, changes (such as queue or
+ exchange declaration and binding) which take place in one partition
+ will not be visible to other partition(s). Other behaviour is not
+ guaranteed.
+</p>
+<p>
+ <a target="_blank"
+ href="https://www.rabbitmq.com/partitions.html">More information on
+ network partitions.</a>
+</p>
+<% } %>
+<%
+ var ticktime = null;
+ var ticktimes_unequal = false;
+ for (var i = 0; i < nodes.length; i++) {
+ var node_ticktime = nodes[i].net_ticktime;
+ if (node_ticktime != undefined) {
+
+ if (ticktime != null && node_ticktime != ticktime) {
+ ticktimes_unequal = true;
+ }
+ ticktime = nodes[i].net_ticktime;
+ }
+ }
+ if (ticktimes_unequal) {
+%>
+<p class="status-error">
+ The <code>kernel</code> <code>net_ticktime</code> values are set
+ differently for different nodes in this cluster.
+</p>
+<p>
+ The values are:
+</p>
+ <table class="list">
+ <tr><th>Node</th><th>net_ticktime</th></tr>
+<%
+ for (var i = 0; i < nodes.length; i++) {
+%>
+ <tr<%= alt_rows(i)%>>
+ <td><%= nodes[i].name %></td>
+ <td><%= nodes[i].net_ticktime %></td>
+ </tr>
+<%
+ }
+%>
+ </table>
+<p>
+ This is a dangerous configuration; use of substantially
+ unequal <code>net_ticktime</code> values can lead to partitions
+ being falsely detected.
+</p>
+<p>
+ <a target="_blank"
+ href="https://www.rabbitmq.com/nettick.html">More information on
+ <code>net_ticktime</code>.</a>
+</p>
+<%
+ }
+%>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/permissions.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/permissions.ejs
new file mode 100644
index 0000000000..ccb066bc44
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/permissions.ejs
@@ -0,0 +1,91 @@
+<div class="section">
+ <h2>Permissions</h2>
+ <div class="hider">
+ <h3>Current permissions</h3>
+ <% if (permissions.length > 0) { %>
+ <table class="list">
+ <thead>
+ <tr>
+<% if (mode == 'vhost') { %>
+ <th>User</th>
+<% } else { %>
+ <th>Virtual host</th>
+<% } %>
+ <th>Configure regexp</th>
+ <th>Write regexp</th>
+ <th>Read regexp</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+<%
+for (var i = 0; i < permissions.length; i++) {
+ var permission = permissions[i];
+%>
+ <tr<%= alt_rows(i)%>>
+<% if (mode == 'vhost') { %>
+ <td><%= link_user(permission.user) %></td>
+<% } else { %>
+ <td><%= link_vhost(permission.vhost) %></td>
+<% } %>
+ <td><%= fmt_string(permission.configure) %></td>
+ <td><%= fmt_string(permission.write) %></td>
+ <td><%= fmt_string(permission.read) %></td>
+ <td class="c">
+ <form action="#/permissions" method="delete" class="confirm">
+ <input type="hidden" name="username" value="<%= fmt_string(permission.user) %>"/>
+ <input type="hidden" name="vhost" value="<%= fmt_string(permission.vhost) %>"/>
+ <input type="submit" value="Clear"/>
+ </form>
+ </td>
+ </tr>
+ <% } %>
+ </tbody>
+ </table>
+ <% } else { %>
+ <p>... no permissions ...</p>
+ <% } %>
+
+ <h3>Set permission</h3>
+ <form action="#/permissions" method="put">
+ <table class="form">
+ <tr>
+<% if (mode == 'vhost') { %>
+ <th>User</th>
+ <td>
+ <input type="hidden" name="vhost" value="<%= fmt_string(parent.name) %>"/>
+ <select name="username">
+ <% for (var i = 0; i < users.length; i++) { %>
+ <option value="<%= fmt_string(users[i].name) %>"><%= fmt_string(users[i].name) %></option>
+ <% } %>
+ </select>
+ </td>
+<% } else { %>
+ <th><label>Virtual Host:</label></th>
+ <td>
+ <input type="hidden" name="username" value="<%= fmt_string(parent.name) %>"/>
+ <select name="vhost">
+ <% for (var i = 0; i < vhosts.length; i++) { %>
+ <option value="<%= fmt_string(vhosts[i].name) %>"><%= fmt_string(vhosts[i].name) %></option>
+ <% } %>
+ </select>
+ </td>
+<% } %>
+ </tr>
+ <tr>
+ <th><label>Configure regexp:</label></th>
+ <td><input type="text" name="configure" value=".*"/></td>
+ </tr>
+ <tr>
+ <th><label>Write regexp:</label></th>
+ <td><input type="text" name="write" value=".*"/></td>
+ </tr>
+ <tr>
+ <th><label>Read regexp:</label></th>
+ <td><input type="text" name="read" value=".*"/></td>
+ </tr>
+ </table>
+ <input type="submit" value="Set permission"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/policies.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/policies.ejs
new file mode 100644
index 0000000000..59c922ad46
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/policies.ejs
@@ -0,0 +1,305 @@
+<h1>Policies</h1>
+<div class="section">
+ <h2>User policies</h2>
+ <div class="hider">
+<%= filter_ui(policies) %>
+ <div class="updatable">
+<% if (policies.length > 0) { %>
+<table class="list">
+ <thead>
+ <tr>
+<% if (vhosts_interesting) { %>
+ <th>Virtual Host</th>
+<% } %>
+ <th>Name</th>
+ <th>Pattern</th>
+ <th>Apply to</th>
+ <th>Definition</th>
+ <th>Priority</th>
+ </tr>
+ </thead>
+ <tbody>
+<%
+ for (var i = 0; i < policies.length; i++) {
+ var policy = policies[i];
+%>
+ <tr<%= alt_rows(i)%>>
+<% if (vhosts_interesting) { %>
+ <td><%= fmt_string(policy.vhost) %></td>
+<% } %>
+<% if (is_user_policymaker) { %>
+ <td><%= link_policy(policy.vhost, policy.name) %></td>
+<% } else { %>
+ <td><%= fmt_string(policy.name) %></td>
+<% } %>
+ <td><%= fmt_string(policy.pattern) %></td>
+ <td><%= fmt_string(policy['apply-to']) %></td>
+ <td><%= fmt_table_short(policy.definition) %></td>
+ <td><%= fmt_string(policy.priority) %></td>
+ </tr>
+<% } %>
+ </tbody>
+</table>
+<% } else { %>
+ <p>... no policies ...</p>
+<% } %>
+ </div>
+ </div>
+</div>
+
+<% if (is_user_policymaker && vhosts.length > 0) { %>
+
+<div class="section-hidden">
+ <h2>Add / update a policy</h2>
+ <div class="hider">
+ <form action="#/policies" method="put">
+ <table class="form">
+<% if (vhosts_interesting) { %>
+ <tr>
+ <th><label>Virtual host:</label></th>
+ <td>
+ <select name="vhost">
+ <% for (var i = 0; i < vhosts.length; i++) { %>
+ <option value="<%= fmt_string(vhosts[i].name) %>" <%= (vhosts[i].name === current_vhost) ? 'selected="selected"' : '' %>><%= fmt_string(vhosts[i].name) %></option>
+ <% } %>
+ </select>
+ </td>
+ </tr>
+<% } else { %>
+ <tr><td><input type="hidden" name="vhost" value="<%= fmt_string(vhosts[0].name) %>"/></td></tr>
+<% } %>
+ <tr>
+ <th><label>Name:</label></th>
+ <td><input type="text" name="name"/><span class="mand">*</span></td>
+ </tr>
+ <tr>
+ <th><label>Pattern:</label></th>
+ <td><input type="text" name="pattern"/><span class="mand">*</span></td>
+ </tr>
+ <tr>
+ <th><label>Apply to:</label></th>
+ <td>
+ <select name="apply-to">
+ <option value="all">Exchanges and queues</option>
+ <option value="exchanges">Exchanges</option>
+ <option value="queues">Queues</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Priority:</label></th>
+ <td><input type="text" name="priority"/></td>
+ </tr>
+ <tr>
+ <th><label>Definition:</label></th>
+ <td>
+ <div class="multifield" id="definition"></div>
+ <table class="argument-links">
+ <tr>
+ <td>Queues [All types]</td>
+ <td>
+ <span class="argument-link" field="definition" key="max-length" type="number">Max length</span> |
+ <span class="argument-link" field="definition" key="max-length-bytes" type="number">Max length bytes</span> |
+ <span class="argument-link" field="definition" key="overflow" type="string">Overflow behaviour</span>
+ <span class="argument-link" field="definition" key="expires" type="number">Auto expire</span> |
+ <span class="help" id="queue-overflow"></span> </br>
+ <span class="argument-link" field="definition" key="dead-letter-exchange" type="string">Dead letter exchange</span> |
+ <span class="argument-link" field="definition" key="dead-letter-routing-key" type="string">Dead letter routing key</span><br />
+ </td>
+ <tr>
+ <td>Queues [Classic]</td>
+ <td>
+ <span class="argument-link" field="definition" key="ha-mode" type="string">HA mode</span> <span class="help" id="policy-ha-mode"></span> |
+ <span class="argument-link" field="definition" key="ha-params" type="number">HA params</span> <span class="help" id="policy-ha-params"></span> |
+ <span class="argument-link" field="definition" key="ha-sync-mode" type="string">HA sync mode</span> <span class="help" id="policy-ha-sync-mode"></span> </br>
+ <span class="argument-link" field="definition" key="ha-promote-on-shutdown" type="string" value="">HA mirror promotion on shutdown</span> <span class="help" id="policy-ha-promote-on-shutdown"></span> |
+ <span class="argument-link" field="definition" key="ha-promote-on-failure" type="string" value="">HA mirror promotion on failure</span> <span class="help" id="policy-ha-promote-on-failure"></span>
+ </br>
+ <span class="argument-link" field="definition" key="message-ttl" type="number">Message TTL</span> |
+ <span class="argument-link" field="definition" key="queue-mode" type="string" value="lazy">Lazy mode</span> |
+ <span class="argument-link" field="definition" key="queue-master-locator" type="string">Master Locator</span></br>
+ </td>
+ </tr>
+ <tr>
+ <td>Queues [Quorum]</td>
+ <td>
+ <span class="argument-link" field="definition" key="max-in-memory-length" type="number">Max in memory length</span>
+ <span class="help" id="queue-max-in-memory-length"></span> |
+ <span class="argument-link" field="definition" key="max-in-memory-bytes" type="number">Max in memory bytes</span>
+ <span class="help" id="queue-max-in-memory-bytes"></span> |
+ <span class="argument-link" field="definition" key="delivery-limit" type="number">Delivery limit</span>
+ <span class="help" id="delivery-limit"></span>
+ </td>
+ </tr>
+ <tr>
+ <td>Queues [Stream]</td>
+ <td>
+ <span class="argument-link" field="definition" key="max-age" type="string">Max age</span>
+ <span class="help" id="queue-max-age"></span> |
+ <span class="argument-link" field="definition" key="max-segment-size" type="number">Max segment size</span>
+ <span class="help" id="queue-max-segment-size"></span>
+ </td>
+ </tr>
+ <tr>
+ <td>Exchanges</td>
+ <td>
+ <span class="argument-link" field="definition" key="alternate-exchange" type="string">Alternate exchange</span>
+ <span class="help" id="exchange-alternate"></span>
+ </td>
+ </tr>
+ <tr>
+ <td>Federation</td>
+ <td>
+ <span class="argument-link" field="definition" key="federation-upstream-set" type="string">Federation upstream set</span> <span class="help" id="policy-federation-upstream-set"></span> |
+ <span class="argument-link" field="definition" key="federation-upstream" type="string">Federation upstream</span>
+ <span class="help" id="policy-federation-upstream"></span>
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td class="t"><span class="mand">*</span></td>
+ </tr>
+ </table>
+ <input type="submit" value="Add / update policy"/>
+ </form>
+ </div>
+</div>
+<% } %>
+
+
+<div class="section">
+ <h2>Operator policies</h2>
+ <div class="hider">
+<%= filter_ui(operator_policies) %>
+ <div class="updatable">
+<% if (operator_policies.length > 0) { %>
+<table class="list">
+ <thead>
+ <tr>
+<% if (vhosts_interesting) { %>
+ <th>Virtual Host</th>
+<% } %>
+ <th>Name</th>
+ <th>Pattern</th>
+ <th>Apply to</th>
+ <th>Definition</th>
+ <th>Priority</th>
+ <th class="administrator-only">Clear</th>
+ </tr>
+ </thead>
+ <tbody>
+<%
+ for (var i = 0; i < operator_policies.length; i++) {
+ var policy = operator_policies[i];
+%>
+ <tr<%= alt_rows(i)%>>
+<% if (vhosts_interesting) { %>
+ <td><%= fmt_string(policy.vhost) %></td>
+<% } %>
+ <td><%= fmt_string(policy.name) %></td>
+ <td><%= fmt_string(policy.pattern) %></td>
+ <td><%= fmt_string(policy['apply-to']) %></td>
+ <td><%= fmt_table_short(policy.definition) %></td>
+ <td><%= fmt_string(policy.priority) %></td>
+ <td class="administrator-only">
+ <form action="#/operator_policies" method="delete" class="confirm">
+ <input type="hidden" name="name" value="<%= fmt_string(policy.name) %>"/>
+ <input type="hidden" name="vhost" value="<%= fmt_string(policy.vhost) %>"/>
+ <input type="submit" value="Clear"/>
+ </form>
+ </td>
+ </tr>
+<% } %>
+ </tbody>
+</table>
+<% } else { %>
+ <p>... no policies ...</p>
+<% } %>
+ </div>
+ </div>
+</div>
+
+<% if (user_administrator && vhosts.length > 0) { %>
+
+<div class="section-hidden">
+ <h2>Add / update an operator policy</h2>
+ <div class="hider">
+ <form action="#/operator_policies" method="put">
+ <table class="form">
+<% if (vhosts_interesting) { %>
+ <tr>
+ <th><label>Virtual host:</label></th>
+ <td>
+ <select name="vhost">
+ <% for (var i = 0; i < vhosts.length; i++) { %>
+ <option value="<%= fmt_string(vhosts[i].name) %>"><%= fmt_string(vhosts[i].name) %></option>
+ <% } %>
+ </select>
+ </td>
+ </tr>
+<% } else { %>
+ <tr><td><input type="hidden" name="vhost" value="<%= fmt_string(vhosts[0].name) %>"/></td></tr>
+<% } %>
+ <tr>
+ <th><label>Name:</label></th>
+ <td><input type="text" name="name"/><span class="mand">*</span></td>
+ </tr>
+ <tr>
+ <th><label>Pattern:</label></th>
+ <td><input type="text" name="pattern"/><span class="mand">*</span></td>
+ </tr>
+ <tr>
+ <th><label>Apply to:</label></th>
+ <td>
+ <select name="apply-to">
+ <option value="queues">Queues</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Priority:</label></th>
+ <td><input type="text" name="priority"/></td>
+ </tr>
+ <tr>
+ <th><label>Definition:</label></th>
+ <td>
+ <div class="multifield" id="definitionop"></div>
+ <table class="argument-links">
+ <tr>
+ <td>Queues [All types]</td>
+ <td>
+ <span class="argument-link" field="definitionop" key="max-length" type="number">Max length</span> |
+ <span class="argument-link" field="definitionop" key="max-length-bytes" type="number">Max length bytes</span> |
+ <span class="argument-link" field="definitionop" key="overflow" type="string">Overflow behaviour</span>
+ <span class="help" id="queue-overflow"></span>
+ </td>
+ </tr>
+ <tr>
+ <td>Queues [Classic]</td>
+ <td>
+ <span class="argument-link" field="definitionop" key="message-ttl" type="number">Message TTL</span> |
+ <span class="argument-link" field="definitionop" key="expires" type="number">Auto expire</span>
+ </td>
+ </tr>
+ <tr>
+ <td>Queues [Quorum]</td>
+ <td>
+ <span class="argument-link" field="definitionop" key="max-in-memory-length" type="number">Max in memory length</span>
+ <span class="help" id="queue-max-in-memory-length"></span> |
+ <span class="argument-link" field="definitionop" key="max-in-memory-bytes" type="number">Max in memory bytes</span>
+ <span class="help" id="queue-max-in-memory-bytes"></span> |
+ <span class="argument-link" field="definitionop" key="delivery-limit" type="string">Delivery limit</span>
+ <span class="help" id="delivery-limit"></span>
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td class="t"><span class="mand">*</span></td>
+ </tr>
+ </table>
+ <input type="submit" value="Add / update operator policy"/>
+ </form>
+ </div>
+</div>
+<% } %>
+
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/policy.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/policy.ejs
new file mode 100644
index 0000000000..c4a58c44b6
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/policy.ejs
@@ -0,0 +1,37 @@
+<h1>Policy: <b><%= fmt_string(policy.name) %></b><%= fmt_maybe_vhost(policy.vhost) %></h1>
+
+<div class="section">
+ <h2>Overview</h2>
+ <div class="hider">
+ <table class="facts">
+ <tr>
+ <th>Pattern</th>
+ <td><%= fmt_string(policy.pattern) %></td>
+ </tr>
+ <tr>
+ <th>Apply to</th>
+ <td><%= fmt_string(policy['apply-to']) %></td>
+ </tr>
+ <tr>
+ <th>Definition</th>
+ <td><%= fmt_table_short(policy.definition) %></td>
+ </tr>
+ <tr>
+ <th>Priority</th>
+ <td><%= fmt_string(policy.priority) %></td>
+ </tr>
+ </table>
+ </div>
+</div>
+
+<div class="section-hidden">
+ <h2>Delete this policy</h2>
+ <div class="hider">
+ <form action="#/policies" method="delete" class="confirm">
+ <input type="hidden" name="component" value="policy"/>
+ <input type="hidden" name="vhost" value="<%= fmt_string(policy.vhost) %>"/>
+ <input type="hidden" name="name" value="<%= fmt_string(policy.name) %>"/>
+ <input type="submit" value="Delete this policy"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/popup.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/popup.ejs
new file mode 100644
index 0000000000..bf9081fab6
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/popup.ejs
@@ -0,0 +1,6 @@
+<div class="form-popup-<%= type %>">
+ <%= text %>
+ <br/>
+ <br/>
+ <span>Close</span>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/publish.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/publish.ejs
new file mode 100644
index 0000000000..d57724e12a
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/publish.ejs
@@ -0,0 +1,67 @@
+<div class="section-hidden">
+ <h2>Publish message</h2>
+ <div class="hider">
+ <form action="#/exchanges/publish" method="post">
+<% if (mode == 'queue') { %>
+ <input type="hidden" name="vhost" value="<%= fmt_string(queue.vhost) %>"/>
+ <input type="hidden" name="name" value="amq.default"/>
+<% } else { %>
+ <input type="hidden" name="vhost" value="<%= fmt_string(exchange.vhost) %>"/>
+ <input type="hidden" name="name" value="<%= fmt_exchange_url(exchange.name) %>"/>
+<% } %>
+ <input type="hidden" name="properties" value=""/>
+ <table class="form">
+<% if (mode == 'queue') { %>
+ <tr>
+ <td colspan="2"><input type="hidden" name="routing_key" value="<%= fmt_string(queue.name) %>"/> Message will be published to the default exchange with routing key <strong><%= fmt_string(queue.name) %></strong>, routing it to this queue.</td>
+ </tr>
+<% } else { %>
+ <tr>
+ <th><label>Routing key:</label></th>
+ <td><input type="text" name="routing_key" value=""/></td>
+ </tr>
+<% } %>
+ <% if (mode == 'queue' && is_classic(queue)) { %>
+ <tr>
+ <th><label>Delivery mode:</label></th>
+ <td>
+ <select name="delivery_mode">
+ <option value="1">1 - Non-persistent</option>
+ <option value="2">2 - Persistent</option>
+ </select>
+ </td>
+ </tr>
+ <% } else { %>
+ <input type="hidden" name="delivery_mode" value="2">
+ <% } %>
+ <tr>
+ <th>
+ <label>
+ Headers:
+ <span class="help" id="message-publish-headers"></span>
+ </label>
+ </th>
+ <td>
+ <div class="multifield" id="headers"></div>
+ </td>
+ </tr>
+ <tr>
+ <th>
+ <label>
+ Properties:
+ <span class="help" id="message-publish-properties"></span>
+ </label>
+ </th>
+ <td>
+ <div class="multifield string-only" id="props"></div>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Payload:</label></th>
+ <td><textarea name="payload"></textarea></td>
+ </tr>
+ </table>
+ <input type="submit" value="Publish message" />
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/queue.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/queue.ejs
new file mode 100644
index 0000000000..27297b3757
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/queue.ejs
@@ -0,0 +1,431 @@
+<h1>Queue <b><%= fmt_string(highlight_extra_whitespace(queue.name)) %></b><%= fmt_maybe_vhost(queue.vhost) %></h1>
+
+<div class="section">
+ <h2>Overview</h2>
+<% if(!disable_stats) { %>
+ <div class="hider updatable">
+ <%= queue_lengths('lengths-q', queue) %>
+<% if (rates_mode != 'none') { %>
+ <%= message_rates('msg-rates-q', queue.message_stats) %>
+<% } %>
+<% } %>
+
+ <h3>Details</h3>
+ <table class="facts facts-l">
+ <tr>
+ <th>Features</th>
+ <td><%= fmt_features(queue) %></td>
+ </tr>
+<% if(!disable_stats) { %>
+ <tr>
+ <th>Policy</th>
+ <td><%= link_policy(queue.vhost, queue.policy) %></td>
+ </tr>
+ <tr>
+ <th>Operator policy</th>
+ <td><%= fmt_string(queue.operator_policy, '') %></td>
+ </tr>
+ <% if (queue.owner_pid_details != undefined) { %>
+ <tr>
+ <th>Exclusive owner</th>
+ <td><%= link_conn(queue.owner_pid_details.name) %></td>
+ </tr>
+ <% } %>
+ <tr>
+ <th>Effective policy definition</th>
+ <td><%= fmt_table_short(queue.effective_policy_definition) %></td>
+ </tr>
+<% } %>
+<% if (nodes_interesting) { %>
+ <tr>
+ <% if (is_quorum(queue) || is_stream(queue)) { %>
+ <th>Leader</th>
+ <% } else { %>
+ <th>Node</th>
+ <% } %>
+ <% if (queue.leader) { %>
+ <td><%= fmt_node(queue.leader) %></td>
+ <% } else { %>
+ <td><%= fmt_node(queue.node) %></td>
+ <% } %>
+ </tr>
+ <% if (is_quorum(queue) || is_stream(queue)) { %>
+ <tr>
+ <th>Online</th>
+ <td>
+ <%
+ for (var i in queue.online) {
+ %>
+ <%= fmt_node(queue.online[i]) %>
+ <br/>
+ <% } %>
+ </td>
+ </tr>
+ <th>Members</th>
+ <td>
+ <%
+ for (var i in queue.members) {
+ %>
+ <%= fmt_node(queue.members[i]) %>
+ <br/>
+ <% } %>
+ </td>
+ </tr>
+ <% } else { %>
+ <% if (!queue.exclusive) { %>
+ <tr>
+ <th>Mirrors</th>
+ <td>
+ <%
+ var has_unsynced_node = false;
+ for (var i in queue.slave_nodes) {
+ var node = queue.slave_nodes[i];
+ %>
+ <%
+ if (jQuery.inArray(node, queue.synchronised_slave_nodes) == -1) {
+ has_unsynced_node = true;
+ %>
+ <%= fmt_node(node) %> <b>(unsynchronised)</b>
+ <% } else { %>
+ <%= fmt_node(node) %>
+ <% } %>
+ <br/>
+ <% } %>
+ <% if (queue.state == 'syncing') { %>
+ <table>
+ <tr>
+ <td>
+ <%= fmt_sync_state(queue) %>
+ </td>
+ <td>
+ <form action="#/queues/actions" method="post">
+ <input type="hidden" name="vhost" value="<%= fmt_string(queue.vhost) %>"/>
+ <input type="hidden" name="name" value="<%= fmt_string(queue.name) %>"/>
+ <input type="hidden" name="action" value="cancel_sync"/>
+ <input type="submit" value="Cancel" id="action-button" />
+ </form>
+ </td>
+ </tr>
+ </table>
+ <% } else if (has_unsynced_node) { %>
+ <form action="#/queues/actions" method="post">
+ <input type="hidden" name="vhost" value="<%= fmt_string(queue.vhost) %>"/>
+ <input type="hidden" name="name" value="<%= fmt_string(queue.name) %>"/>
+ <input type="hidden" name="action" value="sync"/>
+ <input type="submit" value="Synchronise" id="action-button" />
+ </form>
+ <% } %>
+ </td>
+ </tr>
+ <% } %>
+ <% } %>
+<% } %>
+ </table>
+
+<% if(!disable_stats) { %>
+ <table class="facts facts-l">
+ <tr>
+ <th>State</th>
+ <td><%= fmt_object_state(queue) %></td>
+ </tr>
+ <tr>
+ <th>Consumers</th>
+ <td><%= fmt_string(queue.consumers) %></td>
+ </tr>
+ <tr>
+ <th>Consumer utilisation <span class="help" id="queue-consumer-utilisation"></th>
+ <td><%= fmt_percent(queue.consumer_utilisation) %></td>
+ </tr>
+ <% if (is_quorum(queue) || is_stream(queue)) { %>
+ <tr>
+ <th>Open files</th>
+ <td><%= fmt_table_short(queue.open_files) %></td>
+ </tr>
+ <% } %>
+ </table>
+
+ <table class="facts">
+ <tr>
+ <td></td>
+ <th class="horizontal">Total</th>
+ <th class="horizontal">Ready</th>
+ <th class="horizontal">Unacked</th>
+ <% if (is_quorum(queue)) { %>
+ <th class="horizontal">In memory ready</th>
+ <% } %>
+ <% if (is_classic(queue)) { %>
+ <th class="horizontal">In memory</th>
+ <th class="horizontal">Persistent</th>
+ <th class="horizontal">Transient, Paged Out</th>
+ <% } %>
+ </tr>
+ <tr>
+ <th>
+ Messages
+ <span class="help" id="queue-messages"></span>
+ </th>
+ <td class="r">
+ <%= fmt_num_thousands(queue.messages) %>
+ </td>
+ <td class="r">
+ <%= fmt_num_thousands(queue.messages_ready) %>
+ </td>
+ <td class="r">
+ <%= fmt_num_thousands(queue.messages_unacknowledged) %>
+ </td>
+ <td class="r">
+ <%= fmt_num_thousands(queue.messages_ram) %>
+ </td>
+ <% if (is_classic(queue)) { %>
+ <td class="r">
+ <%= fmt_num_thousands(queue.messages_persistent) %>
+ </td>
+ <td class="r">
+ <%= fmt_num_thousands(queue.messages_paged_out) %>
+ </td>
+ <% } %>
+ </tr>
+ <tr>
+ <th>
+ Message body bytes
+ <span class="help" id="queue-message-body-bytes"></span>
+ </th>
+ <td class="r">
+ <%= fmt_bytes(queue.message_bytes) %>
+ </td>
+ <td class="r">
+ <%= fmt_bytes(queue.message_bytes_ready) %>
+ </td>
+ <td class="r">
+ <%= fmt_bytes(queue.message_bytes_unacknowledged) %>
+ </td>
+ <td class="r">
+ <%= fmt_bytes(queue.message_bytes_ram) %>
+ </td>
+ <% if (is_classic(queue)) { %>
+ <td class="r">
+ <%= fmt_bytes(queue.message_bytes_persistent) %>
+ </td>
+ <td class="r">
+ <%= fmt_bytes(queue.message_bytes_paged_out) %>
+ </td>
+ <% } %>
+ </tr>
+ <tr>
+ <th>
+ Process memory
+ <span class="help" id="queue-process-memory"></span>
+ </th>
+ <td class="r"><%= fmt_bytes(queue.memory) %></td>
+ </tr>
+ </table>
+ <% } %>
+ </div>
+</div>
+
+<% if (rates_mode == 'detailed') { %>
+<div class="section-hidden">
+<h2>Message rates breakdown</h2>
+<div class="hider updatable">
+<table class="two-col-layout">
+ <tr>
+ <td>
+ <%= format('msg-detail-publishes',
+ {'mode': 'queue',
+ 'object': queue.incoming,
+ 'label': 'Incoming'}) %>
+
+ </td>
+ <td>
+ <%= format('msg-detail-deliveries',
+ {'mode': 'queue',
+ 'object': queue.deliveries}) %>
+ </td>
+ </tr>
+</table>
+</div>
+</div>
+
+<% } %>
+
+
+<% if(!disable_stats) { %>
+<div class="section-hidden">
+ <h2>Consumers</h2>
+ <div class="hider updatable">
+<%= format('consumers', {'mode': 'queue', 'consumers': queue.consumer_details}) %>
+ </div>
+</div>
+<% } %>
+
+<div class="section-hidden">
+ <h2>Bindings</h2>
+ <div class="hider">
+ <div class="bindings-wrapper">
+ <%= format('bindings', {'mode': 'queue', 'bindings': bindings}) %>
+ <p class="arrow">&dArr;</p>
+ <p><span class="queue">This queue</span></p>
+
+ <%= format('add-binding', {'mode': 'queue', 'parent': queue}) %>
+ </div>
+ </div>
+</div>
+
+<%= format('publish', {'mode': 'queue', 'queue': queue}) %>
+
+<% if (!is_stream(queue)) { %>
+<div class="section-hidden">
+ <h2>Get messages</h2>
+ <div class="hider">
+ <p>
+ Warning: getting messages from a queue is a destructive action.
+ <span class="help" id="message-get-requeue"></span>
+ </p>
+ <form action="#/queues/get" method="post">
+ <input type="hidden" name="vhost" value="<%= fmt_string(queue.vhost) %>"/>
+ <input type="hidden" name="name" value="<%= fmt_string(queue.name) %>"/>
+ <input type="hidden" name="truncate" value="50000"/>
+ <table class="form">
+ <tr>
+ <th><label>Ack Mode:</label></th>
+ <td>
+ <select name="ackmode">
+ <option value="ack_requeue_true" selected>Nack message requeue true</option>
+ <option value="ack_requeue_false">Ack message requeue false</option>
+ <option value="reject_requeue_true">Reject requeue true</option>
+ <option value="reject_requeue_false">Reject requeue false</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Encoding:</label></th>
+ <td>
+ <select name="encoding">
+ <option value="auto">Auto string / base64</option>
+ <option value="base64">base64</option>
+ </select>
+ <span class="help" id="string-base64"></span>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Messages:</label></th>
+ <td><input type="text" name="count" value="1"/></td>
+ </tr>
+ </table>
+ <input type="submit" value="Get Message(s)" />
+ </form>
+ <div id="msg-wrapper"></div>
+ </div>
+</div>
+<% } %>
+
+<% if (is_user_policymaker) { %>
+<div class="section-hidden">
+ <h2>Move messages</h2>
+ <div class="hider">
+ <% if (NAVIGATION['Admin'][0]['Shovel Management'] == undefined) { %>
+ <p>To move messages, the shovel plugin must be enabled, try:</p>
+ <pre>$ rabbitmq-plugins enable rabbitmq_shovel rabbitmq_shovel_management</pre>
+ <% } else { %>
+ <p>
+ The shovel plugin can be used to move messages from this queue
+ to another one. The form below will create a temporary shovel to
+ move messages to another queue on the same virtual host, with
+ default settings.
+ </p>
+ <p>
+ For more options <a href="#/dynamic-shovels">see the shovel
+ interface</a>.
+ </p>
+ <form action="#/shovel-parameters-move-messages" method="put">
+ <input type="hidden" name="component" value="shovel"/>
+ <input type="hidden" name="vhost" value="<%= fmt_string(queue.vhost) %>"/>
+ <input type="hidden" name="name" value="Move from <%= fmt_string(queue.name) %>"/>
+ <input type="hidden" name="src-uri" value="amqp:///<%= esc(queue.vhost) %>"/>
+ <input type="hidden" name="src-queue" value="<%= fmt_string(queue.name) %>"/>
+ <input type="hidden" name="src-protocol" value="amqp091"/>
+ <input type="hidden" name="src-prefetch-count" value="1000"/>
+ <input type="hidden" name="src-delete-after" value="queue-length"/>
+ <input type="hidden" name="dest-protocol" value="amqp091"/>
+ <input type="hidden" name="dest-uri" value="amqp:///<%= esc(queue.vhost) %>"/>
+ <input type="hidden" name="dest-add-forward-headers" value="false"/>
+ <input type="hidden" name="ack-mode" value="on-confirm"/>
+ <input type="hidden" name="redirect" value="#/queues"/>
+
+ <table class="form">
+ <tr>
+ <th>Destination queue:</th>
+ <td><input type="text" name="dest-queue"/></td>
+ </tr>
+ </table>
+ <input type="submit" value="Move messages"/>
+ </form>
+ <% } %>
+ </div>
+</div>
+<% } %>
+
+<div class="section-hidden">
+ <h2>Delete</h2>
+ <div class="hider">
+ <form action="#/queues" method="delete" class="confirm-queue inline-form">
+ <input type="hidden" name="vhost" value="<%= fmt_string(queue.vhost) %>"/>
+ <input type="hidden" name="name" value="<%= fmt_string(queue.name) %>"/>
+ <input type="hidden" name="mode" value="delete"/>
+ <input type="submit" value="Delete Queue" />
+ </form>
+ </div>
+</div>
+
+<% if (!is_stream(queue)) { %>
+<div class="section-hidden">
+ <h2>Purge</h2>
+ <div class="hider">
+ <form action="#/queues" method="delete" class="confirm-purge-queue inline-form">
+ <input type="hidden" name="vhost" value="<%= fmt_string(queue.vhost) %>"/>
+ <input type="hidden" name="name" value="<%= fmt_string(queue.name) %>"/>
+ <input type="hidden" name="mode" value="purge"/>
+ <input type="submit" value="Purge Messages" />
+ </form>
+ </div>
+</div>
+<% } %>
+
+<% if(queue.reductions || queue.garbage_collection) { %>
+<div class="section-hidden">
+<h2>Runtime Metrics (Advanced)</h2>
+ <div class="hider updatable">
+ <%= data_reductions('reductions-rates-queue', queue) %>
+ <table class="facts">
+ <% if (queue.garbage_collection.min_bin_vheap_size) { %>
+ <tr>
+ <th>Minimum binary virtual heap size in words (min_bin_vheap_size)</th>
+ <td><%= queue.garbage_collection.min_bin_vheap_size %></td>
+ </tr>
+ <% } %>
+
+ <% if (queue.garbage_collection.min_heap_size) { %>
+ <tr>
+ <th>Minimum heap size in words (min_heap_size)</th>
+ <td><%= queue.garbage_collection.min_heap_size %></td>
+ </tr>
+ <% } %>
+
+ <% if (queue.garbage_collection.fullsweep_after) { %>
+ <tr>
+ <th>Maximum generational collections before fullsweep (fullsweep_after)</th>
+ <td><%= queue.garbage_collection.fullsweep_after %></td>
+ </tr>
+ <% } %>
+
+ <% if (queue.garbage_collection.minor_gcs) { %>
+ <tr>
+ <th>Number of minor GCs (minor_gcs)</th>
+ <td><%= queue.garbage_collection.minor_gcs %></td>
+ </tr>
+ <% } %>
+ </table>
+ </div>
+</div>
+
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/queues.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/queues.ejs
new file mode 100644
index 0000000000..6e13f84361
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/queues.ejs
@@ -0,0 +1,353 @@
+
+<h1>Queues</h1>
+<div class="section">
+ <%= paginate_ui(queues, 'queues') %>
+</div>
+<div class="updatable">
+<% if (queues.items.length > 0) { %>
+<table class="list">
+ <thead>
+ <tr>
+ <%= group_heading('queues', 'Overview', [vhosts_interesting, nodes_interesting, true]) %>
+<% if(disable_stats && enable_queue_totals) { %>
+ <%= group_heading('queues', 'Messages', []) %>
+<% } else { %>
+<% if(!disable_stats) { %>
+ <%= group_heading('queues', 'Messages', []) %>
+ <%= group_heading('queues', 'Message bytes', []) %>
+<% if (rates_mode != 'none') { %>
+ <%= group_heading('queues', 'Message rates', []) %>
+<% } %>
+<% } %>
+<% } %>
+ <th class="plus-minus"><span class="popup-options-link" title="Click to change columns" type="columns" for="queues">+/-</span></th>
+ </tr>
+ <tr>
+<% if (vhosts_interesting) { %>
+ <th><%= fmt_sort('Virtual host', 'vhost') %></th>
+<% } %>
+ <th><%= fmt_sort('Name', 'name') %></th>
+<% if (nodes_interesting) { %>
+ <th><%= fmt_sort('Node', 'node') %></th>
+<% } %>
+<% if (show_column('queues', 'type')) { %>
+ <th><%= fmt_sort('Type', 'type') %></th>
+<% } %>
+<% if (show_column('queues', 'features')) { %>
+ <th>Features</th>
+<% } %>
+<% if (show_column('queues', 'features_no_policy')) { %>
+ <th>Features</th>
+<% } %>
+<% if (show_column('queues', 'policy')) { %>
+ <th><%= fmt_sort('Policy','policy') %></th>
+<% } %>
+<% if (show_column('queues', 'consumers')) { %>
+ <th><%= fmt_sort('Consumers', 'consumers') %></th>
+<% } %>
+<% if (show_column('queues', 'consumer_utilisation')) { %>
+ <th><%= fmt_sort('Consumer utilisation', 'consumer_utilisation') %></th>
+<% } %>
+<% if (show_column('queues', 'state')) { %>
+ <th><%= fmt_sort('State', 'state') %></th>
+<% } %>
+<% if(disable_stats && enable_queue_totals) { %>
+<% if (show_column('queues', 'msgs-ready')) { %>
+ <th><%= fmt_sort('Ready', 'messages_ready') %></th>
+<% } %>
+<% if (show_column('queues', 'msgs-unacked')) { %>
+ <th><%= fmt_sort('Unacked', 'messages_unacknowledged') %></th>
+<% } %>
+<% if (show_column('queues', 'msgs-total')) { %>
+ <th><%= fmt_sort('Total', 'messages') %></th>
+<% } %>
+<% } %>
+<% if(!disable_stats) { %>
+<% if (show_column('queues', 'msgs-ready')) { %>
+ <th><%= fmt_sort('Ready', 'messages_ready') %></th>
+<% } %>
+<% if (show_column('queues', 'msgs-unacked')) { %>
+ <th><%= fmt_sort('Unacked', 'messages_unacknowledged') %></th>
+<% } %>
+<% if (show_column('queues', 'msgs-ram')) { %>
+ <th><%= fmt_sort('In Memory', 'messages_ram') %></th>
+<% } %>
+<% if (show_column('queues', 'msgs-persistent')) { %>
+ <th><%= fmt_sort('Persistent', 'messages_persistent') %></th>
+<% } %>
+<% if (show_column('queues', 'msgs-total')) { %>
+ <th><%= fmt_sort('Total', 'messages') %></th>
+<% } %>
+<% if (show_column('queues', 'msg-bytes-ready')) { %>
+ <th><%= fmt_sort('Ready', 'message_bytes_ready') %></th>
+<% } %>
+<% if (show_column('queues', 'msg-bytes-unacked')) { %>
+ <th><%= fmt_sort('Unacked', 'message_bytes_unacknowledged') %></th>
+<% } %>
+<% if (show_column('queues', 'msg-bytes-ram')) { %>
+ <th><%= fmt_sort('In Memory', 'message_bytes_ram') %></th>
+<% } %>
+<% if (show_column('queues', 'msg-bytes-persistent')) { %>
+ <th><%= fmt_sort('Persistent', 'message_bytes_persistent') %></th>
+<% } %>
+<% if (show_column('queues', 'msg-bytes-total')) { %>
+ <th><%= fmt_sort('Total', 'message_bytes') %></th>
+<% } %>
+<% if (rates_mode != 'none') { %>
+ <% if (show_column('queues', 'rate-incoming')) { %>
+ <th><%= fmt_sort('incoming', 'message_stats.publish_details.rate') %></th>
+ <% } %>
+ <% if (show_column('queues', 'rate-deliver')) { %>
+ <th><%= fmt_sort('deliver / get', 'message_stats.deliver_get_details.rate') %></th>
+ <% } %>
+ <% if (show_column('queues', 'rate-redeliver')) { %>
+ <th><%= fmt_sort('redelivered', 'message_stats.redeliver_details.rate') %></th>
+ <% } %>
+ <% if (show_column('queues', 'rate-ack')) { %>
+ <th><%= fmt_sort('ack', 'message_stats.ack_details.rate') %></th>
+ <% } %>
+<% } %>
+<% } %>
+ </tr>
+ </thead>
+ <tbody>
+<%
+ for (var i = 0; i < queues.items.length; i++) {
+ var queue = queues.items[i];
+%>
+ <tr<%= alt_rows(i, queue.arguments) %>>
+<% if (vhosts_interesting) { %>
+ <td><%= fmt_string(queue.vhost) %></td>
+<% } %>
+ <td><%= link_queue(queue.vhost, queue.name, queue.arguments) %></td>
+<% if (nodes_interesting) { %>
+ <td>
+ <% if (queue.node) { %>
+ <%= fmt_node(queue.node) %>
+ <% } else { %>
+ <%= fmt_node(queue.leader) %>
+ <% } %>
+ <% if (queue.hasOwnProperty('members')) { %>
+ <%= fmt_members(queue) %>
+ <% } else { %>
+ <%= fmt_mirrors(queue) %>
+ <% if (queue.state == 'syncing') { %>
+ <%= fmt_sync_state(queue) %>
+ <% } %>
+ <% } %>
+ </td>
+<% } %>
+<% if (show_column('queues', 'type')) { %>
+ <td><%= fmt_string(queue.type, '') %></td>
+<% } %>
+<% if (show_column('queues', 'features')) { %>
+ <td class="c">
+ <%= fmt_features_short(queue) %>
+ <%= fmt_policy_short(queue) %>
+ <%= fmt_op_policy_short(queue) %>
+ </td>
+<% } %>
+<% if (show_column('queues', 'features_no_policy')) { %>
+ <td class="c"><%= fmt_features_short(queue) %></td>
+<% } %>
+<% if (show_column('queues', 'policy')) { %>
+ <td class="c"><%= link_policy(queue.vhost, queue.policy) %>
+ <%= fmt_string(queue.operator_policy) %></td>
+<% } %>
+<% if (show_column('queues', 'consumers')) { %>
+ <td class="c"><%= fmt_string(queue.consumers) %></td>
+<% } %>
+<% if (show_column('queues', 'consumer_utilisation')) { %>
+ <td class="c"><%= fmt_percent(queue.consumer_utilisation) %></td>
+<% } %>
+<% if (show_column('queues', 'state')) { %>
+ <td class="c"><%= fmt_object_state(queue) %></td>
+<% } %>
+<% if(!disable_stats || (disable_stats && enable_queue_totals)) { %>
+<% if (show_column('queues', 'msgs-ready')) { %>
+ <td class="r"><%= fmt_num_thousands(queue.messages_ready) %></td>
+<% } %>
+<% if (show_column('queues', 'msgs-unacked')) { %>
+ <td class="r"><%= fmt_num_thousands(queue.messages_unacknowledged) %></td>
+<% } %>
+<% } %>
+<% if(!disable_stats) { %>
+<% if (show_column('queues', 'msgs-ram')) { %>
+ <td class="r"><%= fmt_num_thousands(queue.messages_ram) %></td>
+<% } %>
+<% if (show_column('queues', 'msgs-persistent')) { %>
+ <td class="r"><%= fmt_num_thousands(queue.messages_persistent) %></td>
+<% } %>
+<% } %>
+<% if(!disable_stats || (disable_stats && enable_queue_totals)) { %>
+<% if (show_column('queues', 'msgs-total')) { %>
+ <td class="r"><%= fmt_num_thousands(queue.messages) %></td>
+<% } %>
+<% } %>
+<% if(!disable_stats) { %>
+<% if (show_column('queues', 'msg-bytes-ready')) { %>
+ <td class="r"><%= fmt_bytes(queue.message_bytes_ready) %></td>
+<% } %>
+<% if (show_column('queues', 'msg-bytes-unacked')) { %>
+ <td class="r"><%= fmt_bytes(queue.message_bytes_unacknowledged) %></td>
+<% } %>
+<% if (show_column('queues', 'msg-bytes-ram')) { %>
+ <td class="r"><%= fmt_bytes(queue.message_bytes_ram) %></td>
+<% } %>
+<% if (show_column('queues', 'msg-bytes-persistent')) { %>
+ <td class="r"><%= fmt_bytes(queue.message_bytes_persistent) %></td>
+<% } %>
+<% if (show_column('queues', 'msg-bytes-total')) { %>
+ <td class="r"><%= fmt_bytes(queue.message_bytes) %></td>
+<% } %>
+<% if (rates_mode != 'none') { %>
+ <% if (show_column('queues', 'rate-incoming')) { %>
+ <td class="r"><%= fmt_detail_rate(queue.message_stats, 'publish') %></td>
+ <% } %>
+ <% if (show_column('queues', 'rate-deliver')) { %>
+ <td class="r"><%= fmt_detail_rate(queue.message_stats, 'deliver_get') %></td>
+ <% } %>
+ <% if (show_column('queues', 'rate-redeliver')) { %>
+ <td class="r"><%= fmt_detail_rate(queue.message_stats, 'redeliver') %></td>
+ <% } %>
+ <% if (show_column('queues', 'rate-ack')) { %>
+ <td class="r"><%= fmt_detail_rate(queue.message_stats, 'ack') %></td>
+ <% } %>
+<% } %>
+<% } %>
+ </tr>
+ <% } %>
+ </tbody>
+</table>
+<% } else { %>
+ <p>... no queues ...</p>
+<% } %>
+ </div>
+ </div>
+</div>
+
+<div class="section-hidden">
+ <h2>Add a new queue</h2>
+ <div class="hider">
+ <form action="#/queues" method="put">
+ <table class="form">
+<% if (vhosts_interesting) { %>
+ <tr>
+ <th><label>Virtual host:</label></th>
+ <td>
+ <select name="vhost">
+ <% for (var i = 0; i < vhosts.length; i++) { %>
+ <option value="<%= fmt_string(vhosts[i].name) %>" <%= (vhosts[i].name === current_vhost) ? 'selected="selected"' : '' %>><%= fmt_string(vhosts[i].name) %></option>
+ <% } %>
+ </select>
+ </td>
+ </tr>
+<% } else { %>
+ <tr><td><input type="hidden" name="vhost" value="<%= fmt_string(vhosts[0].name) %>"/></td></tr>
+<% } %>
+ <tr>
+ <th><label>Type:</label></th>
+ <td>
+ <select name="queuetype" onchange="select_queue_type(queuetype)">
+ <option value="classic">Classic</option>
+ <% if (queue_type == "quorum") { %>
+ <option value="quorum" selected>Quorum</option>
+ <% } else { %>
+ <option value="quorum">Quorum</option>
+ <% } %>
+ <% if (queue_type == "stream") { %>
+ <option value="stream" selected>Stream</option>
+ <% } else { %>
+ <option value="stream">Stream</option>
+ <% } %>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Name:</label></th>
+ <td><input type="text" name="name"/><span class="mand">*</span></td>
+ </tr>
+<% if (queue_type == "classic") { %>
+ <tr>
+ <th><label>Durability:</label></th>
+ <td>
+ <select name="durable">
+ <option value="true">Durable</option>
+ <option value="false">Transient</option>
+ </select>
+ </td>
+ </tr>
+<% } %>
+<%
+ if (nodes_interesting) {
+ var nodes = JSON.parse(sync_get('/nodes'));
+%>
+ <tr>
+ <th><label>Node:</label></th>
+ <td>
+ <select name="node">
+ <% for (var i = 0; i < nodes.length; i++) { %>
+ <option value="<%= fmt_string(nodes[i].name) %>"><%= fmt_node(nodes[i].name) %></option>
+ <% } %>
+ </select>
+ </td>
+ </tr>
+<% } %>
+<% if (queue_type == "classic") { %>
+ <tr>
+ <th><label>Auto delete: <span class="help" id="queue-auto-delete"></span></label></th>
+ <td>
+ <select name="auto_delete">
+ <option value="false">No</option>
+ <option value="true">Yes</option>
+ </select>
+ </td>
+ </tr>
+ <% } %>
+ <tr>
+ <th><label>Arguments:</label></th>
+ <td>
+ <div class="multifield" id="arguments"></div>
+ <table class="argument-links">
+ <tr>
+ <td>Add</td>
+ <td>
+ <% if (queue_type == "classic") { %>
+ <span class="argument-link" field="arguments" key="x-message-ttl" type="number">Message TTL</span> <span class="help" id="queue-message-ttl"></span> |
+ <% } %>
+ <% if (queue_type != "stream") { %>
+ <span class="argument-link" field="arguments" key="x-expires" type="number">Auto expire</span> <span class="help" id="queue-expires"></span> |
+ <span class="argument-link" field="arguments" key="x-overflow" type="string">Overflow behaviour</span> <span class="help" id="queue-overflow"></span> |
+ <span class="argument-link" field="arguments" key="x-single-active-consumer" type="boolean">Single active consumer</span> <span class="help" id="queue-single-active-consumer"></span><br/>
+ <span class="argument-link" field="arguments" key="x-dead-letter-exchange" type="string">Dead letter exchange</span> <span class="help" id="queue-dead-letter-exchange"></span> |
+ <span class="argument-link" field="arguments" key="x-dead-letter-routing-key" type="string">Dead letter routing key</span> <span class="help" id="queue-dead-letter-routing-key"></span><br/>
+ <span class="argument-link" field="arguments" key="x-max-length" type="number">Max length</span> <span class="help" id="queue-max-length"></span> |
+ <% } %>
+ <span class="argument-link" field="arguments" key="x-max-length-bytes" type="number">Max length bytes</span> <span class="help" id="queue-max-length-bytes"></span><br/>
+ <% if (queue_type == "classic") { %>
+ <span class="argument-link" field="arguments" key="x-max-priority" type="number">Maximum priority</span> <span class="help" id="queue-max-priority"></span>
+ | <span class="argument-link" field="arguments" key="x-queue-mode" type="string" value="lazy">Lazy mode</span> <span class="help" id="queue-lazy"></span>
+ | <span class="argument-link" field="arguments" key="x-queue-master-locator" type="string" value="">Master locator</span> <span class="help" id="queue-master-locator"></span>
+ <% } %>
+ <% if (queue_type == "quorum") { %>
+ <span class="argument-link" field="arguments" key="x-delivery-limit" type="number">Delivery limit</span><span class="help" id="delivery-limit"></span>
+ | <span class="argument-link" field="arguments" key="x-max-in-memory-length" type="number">Max in memory length</span><span class="help" id="queue-max-in-memory-length"></span>
+ | <span class="argument-link" field="arguments" key="x-max-in-memory-bytes" type="number">Max in memory bytes</span><span class="help" id="queue-max-in-memory-bytes"></span>
+ | <span class="argument-link" field="arguments" key="x-quorum-initial-group-size" type="number">Initial cluster size</span><span class="help" id="queue-quorum-initial-group-size"></span><br/>
+ <% } %>
+ <% if (queue_type == "stream") { %>
+ <span class="argument-link" field="arguments" key="x-max-age" type="string">Max time retention</span><span class="help" id="queue-max-age"></span>
+ | <span class="argument-link" field="arguments" key="x-max-segment-size" type="number">Max segment size</span><span class="help" id="queue-max-segment-size"></span>
+ | <span class="argument-link" field="arguments" key="x-initial-cluster-size" type="number">Initial cluster size</span><span class="help" id="queue-initial-cluster-size"></span>
+ | <span class="argument-link" field="arguments" key="x-queue-leader-locator" type="string">Leader locator</span><span class="help" id="queue-leader-locator"></span>
+ <% } %>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <input type="submit" value="Add queue"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/rate-options.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/rate-options.ejs
new file mode 100644
index 0000000000..6ec5c75c94
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/rate-options.ejs
@@ -0,0 +1,56 @@
+<%
+ var id = span.attr('for');
+ var mode = get_pref('rate-mode-' + id);
+ var size = get_pref('chart-size-' + id);
+ var range_pref = get_pref('chart-range');
+%>
+
+<form action="#/rate-options" method="put" class="auto-submit">
+ <input type="hidden" name="id" value="<%= id %>"/>
+ <table class="form" width="100%">
+ <tr>
+ <td colspan="2">
+ <h3>This time series</h3>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Display:</label></th>
+ <td>
+ <%= fmt_radio('mode', 'Chart', 'chart', mode) %>
+ <%= fmt_radio('mode', 'Current value', 'curr', mode) %>
+ <% if (id != 'node-stats') { %>
+ <%= fmt_radio('mode', 'Moving average', 'avg', mode) %>
+ <% } %>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Chart size:</label></th>
+ <td>
+ <%= fmt_radio('size', 'Small', 'small', size) %>
+ <%= fmt_radio('size', 'Medium', 'medium', size) %>
+ <%= fmt_radio('size', 'Large', 'large', size) %>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <h3>All time series</h3>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Chart range:</label></th>
+ <td>
+<%
+ var range_type = get_chart_range_type(id);
+ for (var i = 0; i < CHART_RANGES[range_type].length; ++i) {
+ var data = CHART_RANGES[range_type][i];
+ var range = data[0];
+ var desc = data[1];
+%>
+ <%= fmt_radio('range', desc, range, range_pref) %>
+<%
+ }
+%>
+ </td>
+ </tr>
+ </table>
+</form>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/registry.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/registry.ejs
new file mode 100644
index 0000000000..38d98e1ebb
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/registry.ejs
@@ -0,0 +1,25 @@
+<% if (node.running) { %>
+<table class="list">
+ <tr>
+ <th>Name</th>
+ <th>Description</th>
+<% if (show_enabled) { %>
+ <th>Enabled</th>
+<% } %>
+ </tr>
+ <%
+ for (var i = 0; i < list.length; i++) {
+ var item = list[i];
+ %>
+ <tr<%= alt_rows(i)%>>
+ <td><%= fmt_string(item.name) %></td>
+ <td><%= fmt_string(item.description) %></td>
+<% if (show_enabled) { %>
+ <td class="c"><%= fmt_boolean(item.enabled) %></td>
+<% } %>
+ </tr>
+ <% } %>
+</table>
+<% } else {%>
+<p>...node not running...</p>
+<% } %>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/status.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/status.ejs
new file mode 100644
index 0000000000..d5da971f8b
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/status.ejs
@@ -0,0 +1 @@
+<span class="status-<%= fmt_string(status) %>"><%= text %></span>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/topic-permissions.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/topic-permissions.ejs
new file mode 100644
index 0000000000..02f5459516
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/topic-permissions.ejs
@@ -0,0 +1,96 @@
+<div class="section">
+ <h2>Topic permissions</h2>
+ <div class="hider">
+ <h3>Current topic permissions</h3>
+ <% if (topic_permissions.length > 0) { %>
+ <table class="list">
+ <thead>
+ <tr>
+<% if (mode == 'vhost') { %>
+ <th>User</th>
+<% } else { %>
+ <th>Virtual host</th>
+<% } %>
+ <th>Exchange</th>
+ <th>Write regexp</th>
+ <th>Read regexp</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+<%
+for (var i = 0; i < topic_permissions.length; i++) {
+ var permission = topic_permissions[i];
+%>
+ <tr<%= alt_rows(i)%>>
+<% if (mode == 'vhost') { %>
+ <td><%= link_user(permission.user) %></td>
+<% } else { %>
+ <td><%= link_vhost(permission.vhost) %></td>
+<% } %>
+ <td><%= fmt_exchange(permission.exchange) %></td>
+ <td><%= fmt_string(permission.write) %></td>
+ <td><%= fmt_string(permission.read) %></td>
+ <td class="c">
+ <form action="#/topic-permissions" method="delete" class="confirm">
+ <input type="hidden" name="username" value="<%= fmt_string(permission.user) %>"/>
+ <input type="hidden" name="vhost" value="<%= fmt_string(permission.vhost) %>"/>
+ <input type="hidden" name="exchange" value="<%= fmt_exchange_url(permission.exchange) %>"/>
+ <input type="submit" value="Clear"/>
+ </form>
+ </td>
+ </tr>
+ <% } %>
+ </tbody>
+ </table>
+ <% } else { %>
+ <p>... no topic permissions ...</p>
+ <% } %>
+
+<h3>Set topic permission</h3>
+ <form action="#/topic-permissions" method="put">
+ <table class="form">
+ <tr>
+<% if (mode == 'vhost') { %>
+ <th>User</th>
+ <td>
+ <input type="hidden" name="vhost" value="<%= fmt_string(parent.name) %>"/>
+ <select name="username">
+ <% for (var i = 0; i < users.length; i++) { %>
+ <option value="<%= fmt_string(users[i].name) %>"><%= fmt_string(users[i].name) %></option>
+ <% } %>
+ </select>
+ </td>
+<% } else { %>
+ <th><label>Virtual Host:</label></th>
+ <td>
+ <input type="hidden" name="username" value="<%= fmt_string(parent.name) %>"/>
+ <select name="vhost" class="list-exchanges">
+ <% for (var i = 0; i < vhosts.length; i++) { %>
+ <option value="<%= fmt_string(vhosts[i].name) %>"><%= fmt_string(vhosts[i].name) %></option>
+ <% } %>
+ </select>
+ </td>
+<% } %>
+ </tr>
+ <tr>
+ <th><label>Exchange:</label></th>
+ <td>
+ <div id='list-exchanges'>
+ <%= format('list-exchanges', {'exchanges': exchanges}) %>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Write regexp:</label></th>
+ <td><input type="text" name="write" value=".*"/></td>
+ </tr>
+ <tr>
+ <th><label>Read regexp:</label></th>
+ <td><input type="text" name="read" value=".*"/></td>
+ </tr>
+ </table>
+ <input type="submit" value="Set topic permission"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/user.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/user.ejs
new file mode 100644
index 0000000000..e4f49b104e
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/user.ejs
@@ -0,0 +1,100 @@
+<h1>User: <b><%= fmt_string(user.name) %></b></h1>
+
+<% if (permissions.length == 0) { %>
+<p class="warning">
+ This user does not have permission to access any virtual hosts.<br/>
+ Use "Set Permission" below to grant permission to access virtual hosts.
+</p>
+<% } %>
+
+<div class="section">
+ <h2>Overview</h2>
+ <div class="hider">
+<table class="facts">
+ <tr>
+ <th>Tags</th>
+ <td><%= fmt_string(user.tags) %></td>
+ </tr>
+ <tr>
+ <th>Can log in with password</th>
+ <td><%= fmt_boolean(user.password_hash.length > 0) %></td>
+ </tr>
+</table>
+ </div>
+</div>
+
+<%= format('permissions', {'mode': 'user', 'permissions': permissions, 'vhosts': vhosts, 'parent': user}) %>
+
+<%= format('topic-permissions', {'mode': 'user', 'topic_permissions': topic_permissions, 'vhosts': vhosts, 'parent': user, 'exchanges': exchanges}) %>
+
+<div class="section-hidden">
+ <h2>Update this user</h2>
+ <div class="hider">
+ <form action="#/users-modify" method="put">
+ <input type="hidden" name="username" value="<%= fmt_string(user.name) %>"/>
+ <table class="form">
+ <tr>
+ <th>
+ <label>
+ <select name="has-password" class="narrow controls-appearance">
+ <% if (user.password_hash.length > 0) { %>
+ <option value="password" selected="selected">Password:</option>
+ <option value="no-password">No password</option>
+ <% } else { %>
+ <option value="password">Password:</option>
+ <option value="no-password" selected="selected">No password</option>
+ <% } %>
+ </select>
+ </label>
+ </th>
+ <td>
+ <% if (user.password_hash.length > 0) { %>
+ <div id="password-div">
+ <% } else { %>
+ <div id="password-div" style="display: none;">
+ <% } %>
+ <input type="password" name="password" />
+ <span class="mand">*</span><br/>
+ <input type="password" name="password_confirm" />
+ <span class="mand">*</span>
+ (confirm)
+ </div>
+ <% if (user.password_hash.length > 0) { %>
+ <div id="no-password-div" style="display: none;">
+ <% } else { %>
+ <div id="no-password-div">
+ <% } %>
+ User cannot log in using password.
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Tags:</label></th>
+ <td>
+ <input type="text" name="tags" id="tags" value="<%= fmt_string(user.tags) %>" />
+ <span class="help" id="user-tags"/>
+ <sub>
+ [<span class="tag-link" tag="administrator">Admin</span>]
+ [<span class="tag-link" tag="monitoring">Monitoring</span>]
+ [<span class="tag-link" tag="policymaker">Policymaker</span>]
+ [<span class="tag-link" tag="management">Management</span>]
+ [<span class="tag-link" tag="impersonator">Impersonator</span>]
+ [<span class="tag-link" tag="">None</span>]
+ </sub>
+ </td>
+ </tr>
+ </table>
+ <input type="submit" value="Update user"/>
+ </form>
+ </div>
+</div>
+
+<div class="section-hidden">
+ <h2>Delete this user</h2>
+ <div class="hider">
+ <form action="#/users" method="delete" class="confirm">
+ <input type="hidden" name="username" value="<%= fmt_string(user.name) %>"/>
+ <input type="submit" value="Delete"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/users.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/users.ejs
new file mode 100644
index 0000000000..fb6ddc5f92
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/users.ejs
@@ -0,0 +1,98 @@
+<h1>Users</h1>
+<div class="section">
+ <h2>All users</h2>
+ <div class="hider">
+<%= filter_ui(users) %>
+ <div class="updatable">
+<% if (users.length > 0) { %>
+<table class="list">
+ <thead>
+ <tr>
+ <th><%= fmt_sort('Name', 'name') %></th>
+ <th><%= fmt_sort('Tags', 'tags') %></th>
+ <th>Can access virtual hosts</th>
+ <th>Has password</th>
+ </tr>
+ </thead>
+ <tbody>
+ <%
+ for (var i = 0; i < users.length; i++) {
+ var user = users[i];
+ %>
+ <tr<%= alt_rows(i)%>>
+ <td><%= link_user(user.name) %></td>
+ <td class="c"><%= fmt_string(user.tags) %></td>
+ <td class="c"><%= fmt_permissions(user, permissions, 'user', 'vhost',
+ '<p class="warning">No access</p>') %></td>
+ <td class="c"><%= fmt_boolean(user.password_hash.length > 0) %></td>
+ </tr>
+ <% } %>
+ </tbody>
+</table>
+<% } else { %>
+ <p>... no users ...</p>
+<% } %>
+ <p><span class="help" id="internal-users-only"></span></p>
+ </div>
+ </div>
+</div>
+
+<div class="section-hidden">
+ <h2>Add a user</h2>
+ <div class="hider">
+ <form action="#/users-add" method="put">
+ <table class="form">
+ <tr>
+ <th><label>Username:</label></th>
+ <td>
+ <input type="text" name="username"/>
+ <span class="mand">*</span>
+ </td>
+ </tr>
+ <tr>
+ <th>
+ <label>
+ <select name="has-password" class="narrow controls-appearance">
+ <option value="password">Password:</option>
+ <option value="no-password">No password</option>
+ </select>
+ </label>
+ </th>
+ <td>
+ <div id="password-div">
+ <input type="password" name="password" />
+ <span class="mand">*</span><br/>
+ <input type="password" name="password_confirm" />
+ <span class="mand">*</span>
+ (confirm)
+ </div>
+ <div id="no-password-div" style="display: none;">
+ User cannot log in using password.
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <th><label>Tags:</label></th>
+ <td>
+ <input type="text" name="tags" id="tags" />
+ <span class="help" id="user-tags"/>
+ <table class="argument-links">
+ <tr>
+ <td>Set</td>
+ <td>
+ <span class="tag-link" tag="administrator">Admin</span> |
+ <span class="tag-link" tag="monitoring">Monitoring</span> |
+ <span class="tag-link" tag="policymaker">Policymaker</span><br />
+ <span class="tag-link" tag="management">Management</span> |
+ <span class="tag-link" tag="impersonator">Impersonator</span> |
+ <span class="tag-link" tag="">None</span>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <input type="submit" value="Add user"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/vhost.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/vhost.ejs
new file mode 100644
index 0000000000..2639224954
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/vhost.ejs
@@ -0,0 +1,62 @@
+<h1>Virtual Host: <b><%= fmt_string(vhost.name) %></b></h1>
+
+<% if (permissions.length == 0) { %>
+<p class="warning">
+ No users have permission to access this virtual host.<br/>
+ Use "Set Permission" below to grant users permission to access this virtual host.
+</p>
+<% } %>
+
+<% if (!disable_stats) { %>
+<div class="section">
+ <h2>Overview</h2>
+ <div class="hider updatable">
+ <%= queue_lengths('lengths-vhost', vhost) %>
+<% if (rates_mode != 'none') { %>
+ <%= message_rates('msg-rates-vhost', vhost.message_stats) %>
+<% } %>
+ <%= data_rates('data-rates-vhost', vhost, 'Data rates') %>
+ <h3>Details</h3>
+ <table class="facts">
+ <tr>
+ <th>Tracing enabled:</th>
+ <td><%= fmt_boolean(vhost.tracing) %></td>
+ <tr>
+ <th>State:</th>
+ <td>
+ <table class="mini">
+ <% for (var node in vhost.cluster_state) { %>
+ <tr>
+ <th><%= fmt_escape_html(node) %> :</th>
+ <td><%= vhost.cluster_state[node] %>
+ <% if (vhost.cluster_state[node] == "stopped"){ %>
+ <form action="#/restart_vhost" method="post">
+ <input type="hidden" name="node" value="<%= node %>"/>
+ <input type="hidden" name="vhost" value="<%= vhost.name %>"/>
+ <input type="submit" value="Restart"/>
+ </form>
+ <% } %>
+ </td>
+ </tr>
+ <% } %>
+ </table>
+ </td>
+ </tr>
+ </table>
+</div>
+</div>
+<% } %>
+
+<%= format('permissions', {'mode': 'vhost', 'permissions': permissions, 'users': users, 'parent': vhost}) %>
+
+<%= format('topic-permissions', {'mode': 'vhost', 'topic_permissions': topic_permissions, 'users':users, 'parent': vhost, 'exchanges': exchanges}) %>
+
+<div class="section-hidden">
+<h2>Delete this vhost</h2>
+<div class="hider">
+<form action="#/vhosts" method="delete" class="confirm">
+<input type="hidden" name="name" value="<%= fmt_string(vhost.name) %>"/>
+<input type="submit" value="Delete this virtual host"/>
+</form>
+</div>
+</div>
diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/vhosts.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/vhosts.ejs
new file mode 100644
index 0000000000..ef1b7206f2
--- /dev/null
+++ b/deps/rabbitmq_management/priv/www/js/tmpl/vhosts.ejs
@@ -0,0 +1,163 @@
+<h1>Virtual Hosts</h1>
+
+<div class="section">
+ <h2>All virtual hosts</h2>
+ <div class="hider">
+<%= filter_ui(vhosts) %>
+ <div class="updatable">
+<% if (vhosts.length > 0) { %>
+<table class="list">
+ <thead>
+ <tr>
+ <%= group_heading('vhosts', 'Overview', [true, true, true]) %>
+ <% if (!disable_stats) { %>
+ <%= group_heading('vhosts', 'Messages', []) %>
+ <%= group_heading('vhosts', 'Network', []) %>
+<% if (rates_mode != 'none') { %>
+ <%= group_heading('vhosts', 'Message rates', []) %>
+<% } %>
+ <% } %>
+ <th class="plus-minus"><span class="popup-options-link" title="Click to change columns" type="columns" for="vhosts">+/-</span></th>
+ </tr>
+ <tr>
+ <th><%= fmt_sort('Name', 'name') %></th>
+ <th>Users <span class="help" id="internal-users-only"></span></th>
+ <th>State</th>
+<% if (show_column('vhosts', 'cluster-state')) { %>
+ <th>Cluster state</th>
+<% } %>
+<% if (show_column('vhosts', 'description')) { %>
+ <th>Description</th>
+<% } %>
+<% if (show_column('vhosts', 'tags')) { %>
+ <th>Tags</th>
+<% } %>
+<% if (!disable_stats) { %>
+<% if (show_column('vhosts', 'msgs-ready')) { %>
+ <th><%= fmt_sort('Ready', 'messages_ready') %></th>
+<% } %>
+<% if (show_column('vhosts', 'msgs-unacked')) { %>
+ <th><%= fmt_sort('Unacked', 'messages_unacknowledged') %></th>
+<% } %>
+<% if (show_column('vhosts', 'msgs-total')) { %>
+ <th><%= fmt_sort('Total', 'messages') %></th>
+<% } %>
+<% if (show_column('vhosts', 'from_client')) { %>
+ <th><%= fmt_sort('From client', 'recv_oct_details.rate') %></th>
+<% } %>
+<% if (show_column('vhosts', 'to_client')) { %>
+ <th><%= fmt_sort('To client', 'send_oct_details.rate') %></th>
+<% } %>
+<% if (rates_mode != 'none') { %>
+ <% if (show_column('vhosts', 'rate-publish')) { %>
+ <th><%= fmt_sort('publish', 'message_stats.publish_details.rate') %></th>
+ <% } %>
+ <% if (show_column('vhosts', 'rate-deliver')) { %>
+ <th><%= fmt_sort('deliver / get','message_stats.deliver_get_details.rate') %></th>
+ <% } %>
+<% } %>
+<% } %>
+ </tr>
+ </thead>
+ <tbody>
+ <%
+ for (var i = 0; i < vhosts.length; i++) {
+ var vhost = vhosts[i];
+ %>
+ <tr<%= alt_rows(i)%>>
+ <td><%= link_vhost(vhost.name) %></td>
+ <td class="c"><%= fmt_permissions(vhost, permissions, 'vhost', 'user',
+ '<p class="warning">No users</p>') %></td>
+ <td><%= fmt_vhost_state(vhost) %></td>
+<% if (show_column('vhosts', 'cluster-state')) { %>
+ <td>
+ <table>
+ <tbody>
+ <%
+ for (var node in vhost.cluster_state) {
+ var state = vhost.cluster_state[node];
+ %>
+ <tr>
+ <td><%= node %></td>
+ <td>
+ <%= state %>
+ <% if (state == "stopped"){ %>
+ <form action="#/restart_vhost" method="post" class="confirm">
+ <input type="hidden" name="node" value="<%= node %>"/>
+ <input type="hidden" name="vhost" value="<%= vhost.name %>"/>
+ <input type="submit" value="Restart"/>
+ </form>
+ <% } %>
+ </td>
+ </tr>
+ <%
+ }
+ %>
+ </tbody>
+ </table>
+ </td>
+<% } %>
+<% if (show_column('vhosts', 'description')) { %>
+ <td class="r"><%= fmt_string(vhost.description) %></td>
+<% } %>
+<% if (show_column('vhosts', 'tags')) { %>
+ <td class="r"><%= fmt_string(vhost.tags) %></td>
+<% } %>
+<% if (!disable_stats) { %>
+<% if (show_column('vhosts', 'msgs-ready')) { %>
+ <td class="r"><%= fmt_num_thousands(vhost.messages_ready) %></td>
+<% } %>
+<% if (show_column('vhosts', 'msgs-unacked')) { %>
+ <td class="r"><%= fmt_num_thousands(vhost.messages_unacknowledged) %></td>
+<% } %>
+<% if (show_column('vhosts', 'msgs-total')) { %>
+ <td class="r"><%= fmt_num_thousands(vhost.messages) %></td>
+<% } %>
+<% if (show_column('vhosts', 'from_client')) { %>
+ <td><%= fmt_detail_rate_bytes(vhost, 'recv_oct') %></td>
+<% } %>
+<% if (show_column('vhosts', 'to_client')) { %>
+ <td><%= fmt_detail_rate_bytes(vhost, 'send_oct') %></td>
+<% } %>
+<% if (rates_mode != 'none') { %>
+ <% if (show_column('vhosts', 'rate-publish')) { %>
+ <td class="r"><%= fmt_detail_rate(vhost.message_stats, 'publish') %></td>
+ <% } %>
+ <% if (show_column('vhosts', 'rate-deliver')) { %>
+ <td class="r"><%= fmt_detail_rate(vhost.message_stats, 'deliver_get') %></td>
+ <% } %>
+<% } %>
+<% } %>
+ </tr>
+ <% } %>
+ </tbody>
+</table>
+<% } else { %>
+ <p>... no vhosts ...</p>
+<% } %>
+ </div>
+ </div>
+</div>
+
+<div class="section-hidden">
+ <h2>Add a new virtual host</h2>
+ <div class="hider">
+ <form action="#/vhosts" method="put">
+ <table class="form">
+ <tr>
+ <th><label>Name:</label></th>
+ <td><input type="text" name="name"/><span class="mand">*</span></td>
+ </tr>
+ <tr>
+ <th><label>Description:</label></th>
+ <td><input type="text" name="description"/></td>
+ </tr>
+ <tr>
+ <th><label>Tags:</label></th>
+ <td><input type="text" name="tags"/></td>
+ </tr>
+ </table>
+ <input type="submit" value="Add virtual host"/>
+ </form>
+ </div>
+</div>
diff --git a/deps/rabbitmq_management/rabbitmq-components.mk b/deps/rabbitmq_management/rabbitmq-components.mk
new file mode 100644
index 0000000000..b2a3be8b35
--- /dev/null
+++ b/deps/rabbitmq_management/rabbitmq-components.mk
@@ -0,0 +1,359 @@
+ifeq ($(.DEFAULT_GOAL),)
+# Define default goal to `all` because this file defines some targets
+# before the inclusion of erlang.mk leading to the wrong target becoming
+# the default.
+.DEFAULT_GOAL = all
+endif
+
+# PROJECT_VERSION defaults to:
+# 1. the version exported by rabbitmq-server-release;
+# 2. the version stored in `git-revisions.txt`, if it exists;
+# 3. a version based on git-describe(1), if it is a Git clone;
+# 4. 0.0.0
+
+PROJECT_VERSION := $(RABBITMQ_VERSION)
+
+ifeq ($(PROJECT_VERSION),)
+PROJECT_VERSION := $(shell \
+if test -f git-revisions.txt; then \
+ head -n1 git-revisions.txt | \
+ awk '{print $$$(words $(PROJECT_DESCRIPTION) version);}'; \
+else \
+ (git describe --dirty --abbrev=7 --tags --always --first-parent \
+ 2>/dev/null || echo rabbitmq_v0_0_0) | \
+ sed -e 's/^rabbitmq_v//' -e 's/^v//' -e 's/_/./g' -e 's/-/+/' \
+ -e 's/-/./g'; \
+fi)
+endif
+
+# --------------------------------------------------------------------
+# RabbitMQ components.
+# --------------------------------------------------------------------
+
+# For RabbitMQ repositories, we want to checkout branches which match
+# the parent project. For instance, if the parent project is on a
+# release tag, dependencies must be on the same release tag. If the
+# parent project is on a topic branch, dependencies must be on the same
+# topic branch or fallback to `stable` or `master` whichever was the
+# base of the topic branch.
+
+dep_amqp_client = git_rmq rabbitmq-erlang-client $(current_rmq_ref) $(base_rmq_ref) master
+dep_amqp10_client = git_rmq rabbitmq-amqp1.0-client $(current_rmq_ref) $(base_rmq_ref) master
+dep_amqp10_common = git_rmq rabbitmq-amqp1.0-common $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbit = git_rmq rabbitmq-server $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbit_common = git_rmq rabbitmq-common $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_amqp1_0 = git_rmq rabbitmq-amqp1.0 $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_auth_backend_amqp = git_rmq rabbitmq-auth-backend-amqp $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_auth_backend_cache = git_rmq rabbitmq-auth-backend-cache $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_auth_backend_http = git_rmq rabbitmq-auth-backend-http $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_auth_backend_ldap = git_rmq rabbitmq-auth-backend-ldap $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_auth_backend_oauth2 = git_rmq rabbitmq-auth-backend-oauth2 $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_auth_mechanism_ssl = git_rmq rabbitmq-auth-mechanism-ssl $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_aws = git_rmq rabbitmq-aws $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_boot_steps_visualiser = git_rmq rabbitmq-boot-steps-visualiser $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_cli = git_rmq rabbitmq-cli $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_codegen = git_rmq rabbitmq-codegen $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_consistent_hash_exchange = git_rmq rabbitmq-consistent-hash-exchange $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_ct_client_helpers = git_rmq rabbitmq-ct-client-helpers $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_ct_helpers = git_rmq rabbitmq-ct-helpers $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_delayed_message_exchange = git_rmq rabbitmq-delayed-message-exchange $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_dotnet_client = git_rmq rabbitmq-dotnet-client $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_event_exchange = git_rmq rabbitmq-event-exchange $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_federation = git_rmq rabbitmq-federation $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_federation_management = git_rmq rabbitmq-federation-management $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_java_client = git_rmq rabbitmq-java-client $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_jms_client = git_rmq rabbitmq-jms-client $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_jms_cts = git_rmq rabbitmq-jms-cts $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_jms_topic_exchange = git_rmq rabbitmq-jms-topic-exchange $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_lvc_exchange = git_rmq rabbitmq-lvc-exchange $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_management = git_rmq rabbitmq-management $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_management_agent = git_rmq rabbitmq-management-agent $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_management_exchange = git_rmq rabbitmq-management-exchange $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_management_themes = git_rmq rabbitmq-management-themes $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_message_timestamp = git_rmq rabbitmq-message-timestamp $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_metronome = git_rmq rabbitmq-metronome $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_mqtt = git_rmq rabbitmq-mqtt $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_objc_client = git_rmq rabbitmq-objc-client $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_peer_discovery_aws = git_rmq rabbitmq-peer-discovery-aws $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_peer_discovery_common = git_rmq rabbitmq-peer-discovery-common $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_peer_discovery_consul = git_rmq rabbitmq-peer-discovery-consul $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_peer_discovery_etcd = git_rmq rabbitmq-peer-discovery-etcd $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_peer_discovery_k8s = git_rmq rabbitmq-peer-discovery-k8s $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_prometheus = git_rmq rabbitmq-prometheus $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_random_exchange = git_rmq rabbitmq-random-exchange $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_recent_history_exchange = git_rmq rabbitmq-recent-history-exchange $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_routing_node_stamp = git_rmq rabbitmq-routing-node-stamp $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_rtopic_exchange = git_rmq rabbitmq-rtopic-exchange $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_server_release = git_rmq rabbitmq-server-release $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_sharding = git_rmq rabbitmq-sharding $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_shovel = git_rmq rabbitmq-shovel $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_shovel_management = git_rmq rabbitmq-shovel-management $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_stomp = git_rmq rabbitmq-stomp $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_stream = git_rmq rabbitmq-stream $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_toke = git_rmq rabbitmq-toke $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_top = git_rmq rabbitmq-top $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_tracing = git_rmq rabbitmq-tracing $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_trust_store = git_rmq rabbitmq-trust-store $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_test = git_rmq rabbitmq-test $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_web_dispatch = git_rmq rabbitmq-web-dispatch $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_web_stomp = git_rmq rabbitmq-web-stomp $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_web_stomp_examples = git_rmq rabbitmq-web-stomp-examples $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_web_mqtt = git_rmq rabbitmq-web-mqtt $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_web_mqtt_examples = git_rmq rabbitmq-web-mqtt-examples $(current_rmq_ref) $(base_rmq_ref) master
+dep_rabbitmq_website = git_rmq rabbitmq-website $(current_rmq_ref) $(base_rmq_ref) live master
+dep_toke = git_rmq toke $(current_rmq_ref) $(base_rmq_ref) master
+
+dep_rabbitmq_public_umbrella = git_rmq rabbitmq-public-umbrella $(current_rmq_ref) $(base_rmq_ref) master
+
+# Third-party dependencies version pinning.
+#
+# We do that in this file, which is copied in all projects, to ensure
+# all projects use the same versions. It avoids conflicts and makes it
+# possible to work with rabbitmq-public-umbrella.
+
+dep_accept = hex 0.3.5
+dep_cowboy = hex 2.8.0
+dep_cowlib = hex 2.9.1
+dep_jsx = hex 2.11.0
+dep_lager = hex 3.8.0
+dep_prometheus = git https://github.com/deadtrickster/prometheus.erl.git master
+dep_ra = git https://github.com/rabbitmq/ra.git master
+dep_ranch = hex 1.7.1
+dep_recon = hex 2.5.1
+dep_observer_cli = hex 1.5.4
+dep_stdout_formatter = hex 0.2.4
+dep_sysmon_handler = hex 1.3.0
+
+RABBITMQ_COMPONENTS = amqp_client \
+ amqp10_common \
+ amqp10_client \
+ rabbit \
+ rabbit_common \
+ rabbitmq_amqp1_0 \
+ rabbitmq_auth_backend_amqp \
+ rabbitmq_auth_backend_cache \
+ rabbitmq_auth_backend_http \
+ rabbitmq_auth_backend_ldap \
+ rabbitmq_auth_backend_oauth2 \
+ rabbitmq_auth_mechanism_ssl \
+ rabbitmq_aws \
+ rabbitmq_boot_steps_visualiser \
+ rabbitmq_cli \
+ rabbitmq_codegen \
+ rabbitmq_consistent_hash_exchange \
+ rabbitmq_ct_client_helpers \
+ rabbitmq_ct_helpers \
+ rabbitmq_delayed_message_exchange \
+ rabbitmq_dotnet_client \
+ rabbitmq_event_exchange \
+ rabbitmq_federation \
+ rabbitmq_federation_management \
+ rabbitmq_java_client \
+ rabbitmq_jms_client \
+ rabbitmq_jms_cts \
+ rabbitmq_jms_topic_exchange \
+ rabbitmq_lvc_exchange \
+ rabbitmq_management \
+ rabbitmq_management_agent \
+ rabbitmq_management_exchange \
+ rabbitmq_management_themes \
+ rabbitmq_message_timestamp \
+ rabbitmq_metronome \
+ rabbitmq_mqtt \
+ rabbitmq_objc_client \
+ rabbitmq_peer_discovery_aws \
+ rabbitmq_peer_discovery_common \
+ rabbitmq_peer_discovery_consul \
+ rabbitmq_peer_discovery_etcd \
+ rabbitmq_peer_discovery_k8s \
+ rabbitmq_prometheus \
+ rabbitmq_random_exchange \
+ rabbitmq_recent_history_exchange \
+ rabbitmq_routing_node_stamp \
+ rabbitmq_rtopic_exchange \
+ rabbitmq_server_release \
+ rabbitmq_sharding \
+ rabbitmq_shovel \
+ rabbitmq_shovel_management \
+ rabbitmq_stomp \
+ rabbitmq_stream \
+ rabbitmq_toke \
+ rabbitmq_top \
+ rabbitmq_tracing \
+ rabbitmq_trust_store \
+ rabbitmq_web_dispatch \
+ rabbitmq_web_mqtt \
+ rabbitmq_web_mqtt_examples \
+ rabbitmq_web_stomp \
+ rabbitmq_web_stomp_examples \
+ rabbitmq_website
+
+# Erlang.mk does not rebuild dependencies by default, once they were
+# compiled once, except for those listed in the `$(FORCE_REBUILD)`
+# variable.
+#
+# We want all RabbitMQ components to always be rebuilt: this eases
+# the work on several components at the same time.
+
+FORCE_REBUILD = $(RABBITMQ_COMPONENTS)
+
+# Several components have a custom erlang.mk/build.config, mainly
+# to disable eunit. Therefore, we can't use the top-level project's
+# erlang.mk copy.
+NO_AUTOPATCH += $(RABBITMQ_COMPONENTS)
+
+ifeq ($(origin current_rmq_ref),undefined)
+ifneq ($(wildcard .git),)
+current_rmq_ref := $(shell (\
+ ref=$$(LANG=C git branch --list | awk '/^\* \(.*detached / {ref=$$0; sub(/.*detached [^ ]+ /, "", ref); sub(/\)$$/, "", ref); print ref; exit;} /^\* / {ref=$$0; sub(/^\* /, "", ref); print ref; exit}');\
+ if test "$$(git rev-parse --short HEAD)" != "$$ref"; then echo "$$ref"; fi))
+else
+current_rmq_ref := master
+endif
+endif
+export current_rmq_ref
+
+ifeq ($(origin base_rmq_ref),undefined)
+ifneq ($(wildcard .git),)
+possible_base_rmq_ref := master
+ifeq ($(possible_base_rmq_ref),$(current_rmq_ref))
+base_rmq_ref := $(current_rmq_ref)
+else
+base_rmq_ref := $(shell \
+ (git rev-parse --verify -q master >/dev/null && \
+ git rev-parse --verify -q $(possible_base_rmq_ref) >/dev/null && \
+ git merge-base --is-ancestor $$(git merge-base master HEAD) $(possible_base_rmq_ref) && \
+ echo $(possible_base_rmq_ref)) || \
+ echo master)
+endif
+else
+base_rmq_ref := master
+endif
+endif
+export base_rmq_ref
+
+# Repository URL selection.
+#
+# First, we infer other components' location from the current project
+# repository URL, if it's a Git repository:
+# - We take the "origin" remote URL as the base
+# - The current project name and repository name is replaced by the
+# target's properties:
+# eg. rabbitmq-common is replaced by rabbitmq-codegen
+# eg. rabbit_common is replaced by rabbitmq_codegen
+#
+# If cloning from this computed location fails, we fallback to RabbitMQ
+# upstream which is GitHub.
+
+# Macro to transform eg. "rabbit_common" to "rabbitmq-common".
+rmq_cmp_repo_name = $(word 2,$(dep_$(1)))
+
+# Upstream URL for the current project.
+RABBITMQ_COMPONENT_REPO_NAME := $(call rmq_cmp_repo_name,$(PROJECT))
+RABBITMQ_UPSTREAM_FETCH_URL ?= https://github.com/rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git
+RABBITMQ_UPSTREAM_PUSH_URL ?= git@github.com:rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git
+
+# Current URL for the current project. If this is not a Git clone,
+# default to the upstream Git repository.
+ifneq ($(wildcard .git),)
+git_origin_fetch_url := $(shell git config remote.origin.url)
+git_origin_push_url := $(shell git config remote.origin.pushurl || git config remote.origin.url)
+RABBITMQ_CURRENT_FETCH_URL ?= $(git_origin_fetch_url)
+RABBITMQ_CURRENT_PUSH_URL ?= $(git_origin_push_url)
+else
+RABBITMQ_CURRENT_FETCH_URL ?= $(RABBITMQ_UPSTREAM_FETCH_URL)
+RABBITMQ_CURRENT_PUSH_URL ?= $(RABBITMQ_UPSTREAM_PUSH_URL)
+endif
+
+# Macro to replace the following pattern:
+# 1. /foo.git -> /bar.git
+# 2. /foo -> /bar
+# 3. /foo/ -> /bar/
+subst_repo_name = $(patsubst %/$(1)/%,%/$(2)/%,$(patsubst %/$(1),%/$(2),$(patsubst %/$(1).git,%/$(2).git,$(3))))
+
+# Macro to replace both the project's name (eg. "rabbit_common") and
+# repository name (eg. "rabbitmq-common") by the target's equivalent.
+#
+# This macro is kept on one line because we don't want whitespaces in
+# the returned value, as it's used in $(dep_fetch_git_rmq) in a shell
+# single-quoted string.
+dep_rmq_repo = $(if $(dep_$(2)),$(call subst_repo_name,$(PROJECT),$(2),$(call subst_repo_name,$(RABBITMQ_COMPONENT_REPO_NAME),$(call rmq_cmp_repo_name,$(2)),$(1))),$(pkg_$(1)_repo))
+
+dep_rmq_commits = $(if $(dep_$(1)), \
+ $(wordlist 3,$(words $(dep_$(1))),$(dep_$(1))), \
+ $(pkg_$(1)_commit))
+
+define dep_fetch_git_rmq
+ fetch_url1='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_FETCH_URL),$(1))'; \
+ fetch_url2='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_FETCH_URL),$(1))'; \
+ if test "$$$$fetch_url1" != '$(RABBITMQ_CURRENT_FETCH_URL)' && \
+ git clone -q -n -- "$$$$fetch_url1" $(DEPS_DIR)/$(call dep_name,$(1)); then \
+ fetch_url="$$$$fetch_url1"; \
+ push_url='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_PUSH_URL),$(1))'; \
+ elif git clone -q -n -- "$$$$fetch_url2" $(DEPS_DIR)/$(call dep_name,$(1)); then \
+ fetch_url="$$$$fetch_url2"; \
+ push_url='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_PUSH_URL),$(1))'; \
+ fi; \
+ cd $(DEPS_DIR)/$(call dep_name,$(1)) && ( \
+ $(foreach ref,$(call dep_rmq_commits,$(1)), \
+ git checkout -q $(ref) >/dev/null 2>&1 || \
+ ) \
+ (echo "error: no valid pathspec among: $(call dep_rmq_commits,$(1))" \
+ 1>&2 && false) ) && \
+ (test "$$$$fetch_url" = "$$$$push_url" || \
+ git remote set-url --push origin "$$$$push_url")
+endef
+
+# --------------------------------------------------------------------
+# Component distribution.
+# --------------------------------------------------------------------
+
+list-dist-deps::
+ @:
+
+prepare-dist::
+ @:
+
+# --------------------------------------------------------------------
+# Umbrella-specific settings.
+# --------------------------------------------------------------------
+
+# If the top-level project is a RabbitMQ component, we override
+# $(DEPS_DIR) for this project to point to the top-level's one.
+#
+# We also verify that the guessed DEPS_DIR is actually named `deps`,
+# to rule out any situation where it is a coincidence that we found a
+# `rabbitmq-components.mk` up upper directories.
+
+possible_deps_dir_1 = $(abspath ..)
+possible_deps_dir_2 = $(abspath ../../..)
+
+ifeq ($(notdir $(possible_deps_dir_1)),deps)
+ifneq ($(wildcard $(possible_deps_dir_1)/../rabbitmq-components.mk),)
+deps_dir_overriden = 1
+DEPS_DIR ?= $(possible_deps_dir_1)
+DISABLE_DISTCLEAN = 1
+endif
+endif
+
+ifeq ($(deps_dir_overriden),)
+ifeq ($(notdir $(possible_deps_dir_2)),deps)
+ifneq ($(wildcard $(possible_deps_dir_2)/../rabbitmq-components.mk),)
+deps_dir_overriden = 1
+DEPS_DIR ?= $(possible_deps_dir_2)
+DISABLE_DISTCLEAN = 1
+endif
+endif
+endif
+
+ifneq ($(wildcard UMBRELLA.md),)
+DISABLE_DISTCLEAN = 1
+endif
+
+# We disable `make distclean` so $(DEPS_DIR) is not accidentally removed.
+
+ifeq ($(DISABLE_DISTCLEAN),1)
+ifneq ($(filter distclean distclean-deps,$(MAKECMDGOALS)),)
+SKIP_DEPS = 1
+endif
+endif
diff --git a/deps/rabbitmq_management/scripts/seed.sh b/deps/rabbitmq_management/scripts/seed.sh
new file mode 100755
index 0000000000..0b62d3d983
--- /dev/null
+++ b/deps/rabbitmq_management/scripts/seed.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env sh
+
+# use admin context to add user and client
+uaac token client get admin -s adminsecret
+
+uaac user add rabbit_user -p rabbit_password --email rabbit_user@example.com
+
+# these groups will end up in the scope of the users
+uaac group add "rabbitmq.read:*/*"
+uaac group add "rabbitmq.write:*/*"
+uaac group add "rabbitmq.configure:*/*"
+uaac group add "rabbitmq.tag:management"
+uaac group add "rabbitmq.tag:administrator"
+
+uaac member add "rabbitmq.read:*/*" rabbit_user
+uaac member add "rabbitmq.write:*/*" rabbit_user
+uaac member add "rabbitmq.configure:*/*" rabbit_user
+uaac member add "rabbitmq.tag:management" rabbit_user
+uaac member add "rabbitmq.tag:administrator" rabbit_user
+
+# add the client for the management plugin. It has the implicit grant type.
+# add e.g. --access_token_validity 60 --refresh_token_validity 3600 to experiment with token validity
+uaac client add rabbit_user_client \
+ --name rabbit_user_client \
+ --secret '' \
+ --scope 'rabbitmq.* openid' \
+ --authorized_grant_types implicit \
+ --autoapprove true \
+ --redirect_uri 'http://localhost:15672/**'
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_app.erl b/deps/rabbitmq_management/src/rabbit_mgmt_app.erl
new file mode 100644
index 0000000000..256ae56cc6
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_app.erl
@@ -0,0 +1,206 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_app).
+
+-behaviour(application).
+-export([start/2, stop/1, reset_dispatcher/1]).
+
+-ifdef(TEST).
+-export([get_listeners_config/0]).
+-endif.
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+-define(TCP_CONTEXT, rabbitmq_management_tcp).
+-define(TLS_CONTEXT, rabbitmq_management_tls).
+-define(DEFAULT_PORT, 15672).
+-define(DEFAULT_TLS_PORT, 15671).
+
+-rabbit_boot_step({rabbit_management_load_definitions,
+ [{description, "Imports definition file at management.load_definitions"},
+ {mfa, {rabbit_mgmt_load_definitions, boot, []}},
+ {enables, empty_db_check}]}).
+
+start(_Type, _StartArgs) ->
+ case application:get_env(rabbitmq_management_agent, disable_metrics_collector, false) of
+ false ->
+ start();
+ true ->
+ rabbit_log:warning("Metrics collection disabled in management agent, "
+ "management only interface started", []),
+ start()
+ end.
+
+stop(_State) ->
+ unregister_all_contexts(),
+ ok.
+
+%% At the point at which this is invoked we have both newly enabled
+%% apps and about-to-disable apps running (so that
+%% rabbit_mgmt_reset_handler can look at all of them to find
+%% extensions). Therefore we have to explicitly exclude
+%% about-to-disable apps from our new dispatcher.
+reset_dispatcher(IgnoreApps) ->
+ unregister_all_contexts(),
+ start_configured_listeners(IgnoreApps, false).
+
+-spec start_configured_listeners([atom()], boolean()) -> ok.
+start_configured_listeners(IgnoreApps, NeedLogStartup) ->
+ [start_listener(Listener, IgnoreApps, NeedLogStartup)
+ || Listener <- get_listeners_config()],
+ ok.
+
+get_listeners_config() ->
+ Listeners = case {has_configured_legacy_listener(),
+ has_configured_tcp_listener(),
+ has_configured_tls_listener()} of
+ {false, false, false} ->
+ %% nothing is configured
+ [get_tcp_listener()];
+ {false, false, true} ->
+ [get_tls_listener()];
+ {false, true, false} ->
+ [get_tcp_listener()];
+ {false, true, true} ->
+ [get_tcp_listener(),
+ get_tls_listener()];
+ {true, false, false} ->
+ [get_legacy_listener()];
+ {true, false, true} ->
+ [get_legacy_listener(),
+ get_tls_listener()];
+ {true, true, false} ->
+ %% This combination makes some sense:
+ %% legacy listener can be used to set up TLS :/
+ [get_legacy_listener(),
+ get_tcp_listener()];
+ {true, true, true} ->
+ %% what is happening?
+ rabbit_log:warning("Management plugin: TCP, TLS and a legacy (management.listener.*) listener are all configured. "
+ "Only two listeners at a time are supported. "
+ "Ignoring the legacy listener"),
+ [get_tcp_listener(),
+ get_tls_listener()]
+ end,
+ maybe_disable_sendfile(Listeners).
+
+maybe_disable_sendfile(Listeners) ->
+ DisableSendfile = #{sendfile => false},
+ F = fun(L0) ->
+ CowboyOptsL0 = proplists:get_value(cowboy_opts, L0, []),
+ CowboyOptsM0 = maps:from_list(CowboyOptsL0),
+ CowboyOptsM1 = maps:merge(DisableSendfile, CowboyOptsM0),
+ CowboyOptsL1 = maps:to_list(CowboyOptsM1),
+ L1 = lists:keydelete(cowboy_opts, 1, L0),
+ [{cowboy_opts, CowboyOptsL1}|L1]
+ end,
+ lists:map(F, Listeners).
+
+has_configured_legacy_listener() ->
+ has_configured_listener(listener).
+
+has_configured_tcp_listener() ->
+ has_configured_listener(tcp_config).
+
+has_configured_tls_listener() ->
+ has_configured_listener(ssl_config).
+
+has_configured_listener(Key) ->
+ case application:get_env(rabbitmq_management, Key, undefined) of
+ undefined -> false;
+ _ -> true
+ end.
+
+get_legacy_listener() ->
+ {ok, Listener0} = application:get_env(rabbitmq_management, listener),
+ {ok, Listener1} = ensure_port(tcp, Listener0),
+ Listener1.
+
+get_tls_listener() ->
+ {ok, Listener0} = application:get_env(rabbitmq_management, ssl_config),
+ {ok, Listener1} = ensure_port(tls, Listener0),
+ Port = proplists:get_value(port, Listener1),
+ case proplists:get_value(cowboy_opts, Listener0) of
+ undefined ->
+ [
+ {port, Port},
+ {ssl, true},
+ {ssl_opts, Listener0}
+ ];
+ CowboyOpts ->
+ Listener1 = lists:keydelete(cowboy_opts, 1, Listener0),
+ [
+ {port, Port},
+ {ssl, true},
+ {ssl_opts, Listener1},
+ {cowboy_opts, CowboyOpts}
+ ]
+ end.
+
+get_tcp_listener() ->
+ Listener0 = application:get_env(rabbitmq_management, tcp_config, []),
+ {ok, Listener1} = ensure_port(tcp, Listener0),
+ Listener1.
+
+start_listener(Listener, IgnoreApps, NeedLogStartup) ->
+ {Type, ContextName} = case is_tls(Listener) of
+ true -> {tls, ?TLS_CONTEXT};
+ false -> {tcp, ?TCP_CONTEXT}
+ end,
+ {ok, _} = register_context(ContextName, Listener, IgnoreApps),
+ case NeedLogStartup of
+ true -> log_startup(Type, Listener);
+ false -> ok
+ end,
+ ok.
+
+register_context(ContextName, Listener, IgnoreApps) ->
+ Dispatcher = rabbit_mgmt_dispatcher:build_dispatcher(IgnoreApps),
+ rabbit_web_dispatch:register_context_handler(
+ ContextName, Listener, "",
+ Dispatcher, "RabbitMQ Management").
+
+unregister_all_contexts() ->
+ rabbit_web_dispatch:unregister_context(?TCP_CONTEXT),
+ rabbit_web_dispatch:unregister_context(?TLS_CONTEXT).
+
+ensure_port(tls, Listener) ->
+ do_ensure_port(?DEFAULT_TLS_PORT, Listener);
+ensure_port(tcp, Listener) ->
+ do_ensure_port(?DEFAULT_PORT, Listener).
+
+do_ensure_port(Port, Listener) ->
+ %% include default port if it's not provided in the config
+ %% as Cowboy won't start if the port is missing
+ M0 = maps:from_list(Listener),
+ M1 = maps:merge(#{port => Port}, M0),
+ {ok, maps:to_list(M1)}.
+
+log_startup(tcp, Listener) ->
+ rabbit_log:info("Management plugin: HTTP (non-TLS) listener started on port ~w", [port(Listener)]);
+log_startup(tls, Listener) ->
+ rabbit_log:info("Management plugin: HTTPS listener started on port ~w", [port(Listener)]).
+
+
+port(Listener) ->
+ proplists:get_value(port, Listener, ?DEFAULT_PORT).
+
+is_tls(Listener) ->
+ case proplists:get_value(ssl, Listener) of
+ undefined -> false;
+ false -> false;
+ _ -> true
+ end.
+
+start() ->
+ %% Modern TCP listener uses management.tcp.*.
+ %% Legacy TCP (or TLS) listener uses management.listener.*.
+ %% Modern TLS listener uses management.ssl.*
+ start_configured_listeners([], true),
+ rabbit_mgmt_sup_sup:start_link().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_cors.erl b/deps/rabbitmq_management/src/rabbit_mgmt_cors.erl
new file mode 100644
index 0000000000..3871c1f8e2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_cors.erl
@@ -0,0 +1,84 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% Useful documentation about CORS:
+%% * https://tools.ietf.org/html/rfc6454
+%% * https://www.w3.org/TR/cors/
+%% * https://staticapps.org/articles/cross-domain-requests-with-cors/
+-module(rabbit_mgmt_cors).
+
+-export([set_headers/2]).
+
+set_headers(ReqData, Module) ->
+ %% Send vary: origin by default if nothing else was set.
+ ReqData1 = cowboy_req:set_resp_header(<<"vary">>, <<"origin">>, ReqData),
+ case match_origin(ReqData1) of
+ false ->
+ ReqData1;
+ Origin ->
+ ReqData2 = case cowboy_req:method(ReqData1) of
+ <<"OPTIONS">> -> handle_options(ReqData1, Module);
+ _ -> ReqData1
+ end,
+ ReqData3 = cowboy_req:set_resp_header(<<"access-control-allow-origin">>,
+ Origin,
+ ReqData2),
+ cowboy_req:set_resp_header(<<"access-control-allow-credentials">>,
+ "true",
+ ReqData3)
+ end.
+
+%% Set max-age from configuration (default: 30 minutes).
+%% Set allow-methods from what is defined in Module:allowed_methods/2.
+%% Set allow-headers to the same as the request (accept all headers).
+handle_options(ReqData0, Module) ->
+ MaxAge = application:get_env(rabbitmq_management, cors_max_age, 1800),
+ Methods = case erlang:function_exported(Module, allowed_methods, 2) of
+ false -> [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>];
+ true -> element(1, Module:allowed_methods(undefined, undefined))
+ end,
+ AllowMethods = string:join([binary_to_list(M) || M <- Methods], ", "),
+ ReqHeaders = cowboy_req:header(<<"access-control-request-headers">>, ReqData0),
+
+ ReqData1 = case MaxAge of
+ undefined -> ReqData0;
+ _ -> cowboy_req:set_resp_header(<<"access-control-max-age">>,
+ integer_to_list(MaxAge),
+ ReqData0)
+ end,
+ ReqData2 = case ReqHeaders of
+ undefined -> ReqData1;
+ _ -> cowboy_req:set_resp_header(<<"access-control-allow-headers">>,
+ ReqHeaders,
+ ReqData0)
+ end,
+ cowboy_req:set_resp_header(<<"access-control-allow-methods">>,
+ AllowMethods,
+ ReqData2).
+
+%% If the origin header is missing or "null", we disable CORS.
+%% Otherwise, we only enable it if the origin is found in the
+%% cors_allow_origins configuration variable, or if "*" is (it
+%% allows all origins).
+match_origin(ReqData) ->
+ case cowboy_req:header(<<"origin">>, ReqData) of
+ undefined -> false;
+ <<"null">> -> false;
+ Origin ->
+ AllowedOrigins = application:get_env(rabbitmq_management,
+ cors_allow_origins, []),
+ case lists:member(binary_to_list(Origin), AllowedOrigins) of
+ true ->
+ Origin;
+ false ->
+ %% Maybe the configuration explicitly allows "*".
+ case lists:member("*", AllowedOrigins) of
+ true -> Origin;
+ false -> false
+ end
+ end
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_csp.erl b/deps/rabbitmq_management/src/rabbit_mgmt_csp.erl
new file mode 100644
index 0000000000..80bba64fc2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_csp.erl
@@ -0,0 +1,28 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% Sets CSP header(s) on the response if configured,
+%% see https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP.
+
+-module(rabbit_mgmt_csp).
+
+-export([set_headers/1]).
+
+-define(CSP_HEADER, <<"content-security-policy">>).
+
+%%
+%% API
+%%
+
+set_headers(ReqData) ->
+ case application:get_env(rabbitmq_management, content_security_policy) of
+ undefined -> ReqData;
+ {ok, Value} ->
+ cowboy_req:set_resp_header(?CSP_HEADER,
+ rabbit_data_coercion:to_binary(Value),
+ ReqData)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_db.erl b/deps/rabbitmq_management/src/rabbit_mgmt_db.erl
new file mode 100644
index 0000000000..c45e7b86f6
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_db.erl
@@ -0,0 +1,799 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_db).
+
+%% pg2 is deprecated in OTP 23.
+-compile(nowarn_deprecated_function).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit_core_metrics.hrl").
+
+-behaviour(gen_server2).
+
+-export([start_link/0]).
+
+-export([augment_exchanges/3, augment_queues/3,
+ augment_nodes/2, augment_vhosts/2,
+ get_channel/2, get_connection/2,
+ get_all_channels/1, get_all_connections/1,
+ get_all_consumers/0, get_all_consumers/1,
+ get_overview/2, get_overview/1]).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3, handle_pre_hibernate/1,
+ format_message_queue/2]).
+
+-import(rabbit_misc, [pget/3]).
+
+-type maybe_slide() :: exometer_slide:slide() | not_found.
+-type slide_data() :: #{atom() => {maybe_slide(), maybe_slide()}}.
+-type maybe_range() :: rabbit_mgmt_stats:maybe_range().
+-type ranges() :: {maybe_range(), maybe_range(), maybe_range(), maybe_range()}.
+-type mfargs() :: {module(), atom(), [any()]}.
+-type lookup_key() :: atom() | {atom(), any()}.
+
+-define(NO_RANGES, {no_range, no_range, no_range, no_range}).
+
+-define(DEFAULT_TIMEOUT, 30000).
+
+%% The management database responds to queries from the various
+%% rabbit_mgmt_wm_* modules. It calls out to all rabbit nodes to fetch
+%% node-local data and aggregates it before returning it. It uses a worker-
+%% pool to provide a degree of parallelism.
+%%
+%% The management database reads metrics and stats written by the
+%% rabbit_mgmt_metrics_collector(s).
+%%
+%% The metrics collectors (there is one for each stats table - see ?TABLES
+%% in rabbit_mgmt_metrics.hrl) periodically read their corresponding core
+%% metrics ETS tables and aggregate the data into the management specific ETS
+%% tables.
+%%
+%% The metric collectors consume two types of core metrics: created (when an
+%% object is created, containing immutable facts about it) and stats (emitted on
+%% a timer, with mutable facts about the object). Deleted events are handled
+%% by the rabbit_mgmt_metrics_gc process.
+%% In this context "objects" means connections, channels, exchanges, queues,
+%% consumers, vhosts and nodes. Note that we do not care about users,
+%% permissions, bindings, parameters or policies.
+%%
+%% Connections and channels are identified by pids. Queues and
+%% exchanges are identified by names (which are #resource{}s). VHosts
+%% and nodes are identified by names which are binaries. And consumers
+%% are identified by {ChPid, QName, CTag}.
+%%
+%% The management collectors records the "created" metrics for
+%% connections, channels and consumers, and can thus be authoritative
+%% about those objects. For queues, exchanges and nodes we go to
+%% Mnesia to find out the immutable details of the objects.
+%%
+%% For everything other than consumers, the collectors can then augment
+%% these immutable details with stats, as the object changes. (We
+%% never emit anything very interesting about consumers).
+%%
+%% Stats on the inbound side are referred to as coarse and
+%% fine-grained. Fine grained statistics are the message rates
+%% maintained by channels and associated with tuples: {publishing
+%% channel, exchange}, {publishing channel, exchange, queue} and
+%% {queue, consuming channel}. Coarse grained stats are everything
+%% else and are associated with only one object, not a tuple.
+%%
+%% Within the management database though we rearrange things a bit: we
+%% refer to basic stats, simple stats and detail stats.
+%%
+%% Basic stats are those coarse grained stats for which we do not
+%% retain a history and do not perform any calculations -
+%% e.g. connection.state or channel.prefetch_count.
+%%
+%% Simple stats are those for which we do history / calculations which
+%% are associated with one object *after aggregation* - so these might
+%% originate with coarse grained stats - e.g. connection.send_oct or
+%% queue.messages_ready. But they might also originate from fine
+%% grained stats which have been aggregated - e.g. the message rates
+%% for a vhost or queue.
+%%
+%% Finally, detailed stats are those for which we do history /
+%% calculations which are associated with two objects. These
+%% have to have originated as fine grained stats, but can still have
+%% been aggregated.
+%%
+%% Created metrics and basic stats are stored in ETS tables by object.
+%% Simple and detailed stats (which only differ depending on how
+%% they're keyed) are stored in aggregated stats tables
+%% (see rabbit_mgmt_stats.erl and include/rabbit_mgmt_metrics.hrl)
+%%
+%% Keys from simple and detailed stats are aggregated in several
+%% records, stored in different ETS tables. We store a base counter
+%% for everything that happened before the samples we have kept,
+%% and a series of records which add the timestamp as part of the key.
+%%
+%% There is also a GC process to handle the deleted/closed
+%% rabbit events to remove the corresponding objects from the aggregated
+%% stats ETS tables.
+%%
+%% We also have an old_aggr_stats table to let us calculate instantaneous
+%% rates, in order to apportion simple / detailed stats into time
+%% slices as they come in. These instantaneous rates are not returned
+%% in response to any query, the rates shown in the API are calculated
+%% at query time. old_aggr_stats contains both coarse and fine
+%% entries. Coarse entries are pruned when the corresponding object is
+%% deleted, and fine entries are pruned when the emitting channel is
+%% closed, and whenever we receive new fine stats from a channel. So
+%% it's quite close to being a cache of "the previous stats we
+%% received".
+%%
+%% Overall the object is to do some aggregation when metrics are read
+%% and only aggregate metrics between nodes at query time.
+
+%%----------------------------------------------------------------------------
+%% API
+%%----------------------------------------------------------------------------
+
+start_link() ->
+ gen_server2:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+augment_exchanges(Xs, Ranges, basic) ->
+ submit(fun(Interval) -> list_exchange_stats(Ranges, Xs, Interval) end);
+augment_exchanges(Xs, Ranges, _) ->
+ submit(fun(Interval) -> detail_exchange_stats(Ranges, Xs, Interval) end).
+
+%% we can only cache if no ranges are requested.
+%% The mgmt ui doesn't use ranges for queue listings
+-spec augment_queues([proplists:proplist()], ranges(), basic | full) -> any().
+augment_queues(Qs, ?NO_RANGES = Ranges, basic) ->
+ submit_cached(queues,
+ fun(Interval, Queues) ->
+ list_queue_stats(Ranges, Queues, Interval)
+ end, Qs, max(60000, length(Qs) * 2));
+augment_queues(Qs, Ranges, basic) ->
+ submit(fun(Interval) -> list_queue_stats(Ranges, Qs, Interval) end);
+augment_queues(Qs, Ranges, _) ->
+ submit(fun(Interval) -> detail_queue_stats(Ranges, Qs, Interval) end).
+
+augment_vhosts(VHosts, Ranges) ->
+ submit(fun(Interval) -> vhost_stats(Ranges, VHosts, Interval) end).
+
+augment_nodes(Nodes, Ranges) ->
+ submit(fun(Interval) -> node_stats(Ranges, Nodes, Interval) end).
+
+get_channel(Name, Ranges) ->
+ submit(fun(Interval) ->
+ case created_stats_delegated(Name, channel_created_stats) of
+ not_found -> not_found;
+ Ch -> [Result] =
+ detail_channel_stats(Ranges, [Ch], Interval),
+ Result
+ end
+ end).
+
+get_connection(Name, Ranges) ->
+ submit(fun(Interval) ->
+ case created_stats_delegated(Name, connection_created_stats) of
+ not_found -> not_found;
+ C ->
+ [Result] = connection_stats(Ranges, [C], Interval),
+ Result
+ end
+ end).
+
+get_all_channels(?NO_RANGES = Ranges) ->
+ submit_cached(channels,
+ fun(Interval) ->
+ Chans = created_stats_delegated(channel_created_stats),
+ list_channel_stats(Ranges, Chans, Interval)
+ end);
+get_all_channels(Ranges) ->
+ submit(fun(Interval) ->
+ Chans = created_stats_delegated(channel_created_stats),
+ list_channel_stats(Ranges, Chans, Interval)
+ end).
+
+get_all_connections(?NO_RANGES = Ranges) ->
+ submit_cached(connections,
+ fun(Interval) ->
+ Chans = created_stats_delegated(connection_created_stats),
+ connection_stats(Ranges, Chans, Interval)
+ end);
+get_all_connections(Ranges) ->
+ submit(fun(Interval) ->
+ Chans = created_stats_delegated(connection_created_stats),
+ connection_stats(Ranges, Chans, Interval)
+ end).
+
+get_all_consumers() -> get_all_consumers(all).
+get_all_consumers(VHosts) ->
+ submit(fun(_Interval) -> consumers_stats(VHosts) end).
+
+get_overview(Ranges) -> get_overview(all, Ranges).
+get_overview(User, Ranges) ->
+ submit(fun(Interval) -> overview(User, Ranges, Interval) end).
+
+%%----------------------------------------------------------------------------
+%% Internal, gen_server2 callbacks
+%%----------------------------------------------------------------------------
+
+-record(state, {interval}).
+
+init([]) ->
+ {ok, Interval} = application:get_env(rabbit, collect_statistics_interval),
+ rabbit_log:info("Statistics database started."),
+ {ok, #state{interval = Interval}, hibernate,
+ {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}.
+
+handle_call(_Request, _From, State) ->
+ reply(not_understood, State).
+
+handle_cast(_Request, State) ->
+ noreply(State).
+
+handle_info(_Info, State) ->
+ noreply(State).
+
+terminate(_Arg, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+reply(Reply, NewState) -> {reply, Reply, NewState, hibernate}.
+noreply(NewState) -> {noreply, NewState, hibernate}.
+
+handle_pre_hibernate(State) ->
+ %% rabbit_event can end up holding on to some memory after a busy
+ %% workout, but it's not a gen_server so we can't make it
+ %% hibernate. The best we can do is forcibly GC it here (if
+ %% rabbit_mgmt_db is hibernating the odds are rabbit_event is
+ %% quiescing in some way too).
+ _ = rpc:multicall(
+ rabbit_nodes:all_running(), rabbit_mgmt_db_handler, gc, []),
+ {hibernate, State}.
+
+format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ).
+
+%%----------------------------------------------------------------------------
+%% Internal, utilities
+%%----------------------------------------------------------------------------
+
+pget(Key, List) -> pget(Key, List, unknown).
+
+-type id_name() :: 'name' | 'route' | 'pid'.
+-spec id_name(atom()) -> id_name().
+
+%% id_name() and id() are for use when handling events, id_lookup()
+%% for when augmenting. The difference is that when handling events a
+%% queue name will be a resource, but when augmenting we will be
+%% passed a queue proplist that will already have been formatted -
+%% i.e. it will have name and vhost keys.
+id_name(node_stats) -> name;
+id_name(node_node_stats) -> route;
+id_name(vhost_stats) -> name;
+id_name(queue_stats) -> name;
+id_name(exchange_stats) -> name;
+id_name(channel_stats) -> pid;
+id_name(connection_stats) -> pid.
+
+id(Type, List) -> pget(id_name(Type), List).
+
+id_lookup(queue_stats, List) ->
+ rabbit_misc:r(pget(vhost, List), queue, pget(name, List));
+id_lookup(exchange_stats, List) ->
+ rabbit_misc:r(pget(vhost, List), exchange, pget(name, List));
+id_lookup(Type, List) ->
+ id(Type, List).
+
+
+%%----------------------------------------------------------------------------
+%% Internal, querying side api
+%%----------------------------------------------------------------------------
+
+overview(User, Ranges, Interval) ->
+ VHosts = case User of
+ all -> rabbit_vhost:list_names();
+ _ -> rabbit_mgmt_util:list_visible_vhosts_names(User)
+ end,
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, overview_data,
+ [User, Ranges, VHosts]}),
+ MessageStats = lists:append(
+ [format_range(DataLookup, vhost_stats_fine_stats,
+ pick_range(fine_stats, Ranges), Interval),
+ format_range(DataLookup, vhost_msg_rates,
+ pick_range(queue_msg_rates, Ranges), Interval),
+ format_range(DataLookup, vhost_stats_deliver_stats,
+ pick_range(deliver_get, Ranges), Interval)]),
+
+ ChurnRates = format_range(DataLookup, connection_churn_rates,
+ pick_range(queue_msg_counts, Ranges), Interval),
+ QueueStats = format_range(DataLookup, vhost_msg_stats,
+ pick_range(queue_msg_counts, Ranges), Interval),
+ %% Filtering out the user's consumers would be rather expensive so let's
+ %% just not show it
+ Consumers = case User of
+ all -> [{consumers, maps:get(consumers_count, DataLookup)}];
+ _ -> []
+ end,
+ ObjectTotals = Consumers ++
+ [{queues, length([Q || V <- VHosts, Q <- rabbit_amqqueue:list(V)])},
+ {exchanges, length([X || V <- VHosts, X <- rabbit_exchange:list(V)])},
+ {connections, maps:get(connections_count, DataLookup)},
+ {channels, maps:get(channels_count, DataLookup)}],
+
+ [{message_stats, MessageStats},
+ {churn_rates, ChurnRates},
+ {queue_totals, QueueStats},
+ {object_totals, ObjectTotals},
+ {statistics_db_event_queue, event_queue()}]. % TODO: event queue?
+
+event_queue() ->
+ lists:foldl(fun ({T, _}, Sum) ->
+ case whereis(rabbit_mgmt_metrics_collector:name(T)) of
+ P when is_pid(P) ->
+ {message_queue_len, Len} =
+ erlang:process_info(P, message_queue_len),
+ Sum + Len;
+ _ -> Sum
+ end
+ end, 0, ?CORE_TABLES).
+
+consumers_stats(VHost) ->
+ Data = get_data_from_nodes({rabbit_mgmt_data, consumer_data, [VHost]}),
+ Consumers = rabbit_mgmt_data_compat:fill_consumer_active_fields(
+ [V || {_,V} <- maps:to_list(Data)]),
+ ChPids = [ pget(channel_pid, Con)
+ || Con <- Consumers, [] =:= pget(channel_details, Con)],
+ ChDets = get_channel_detail_lookup(ChPids),
+ [merge_channel_into_obj(Con, ChDets) || Con <- Consumers].
+
+-spec list_queue_stats(ranges(), [proplists:proplist()], integer()) ->
+ [proplists:proplist()].
+list_queue_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(queue_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_list_queue_data, [Ids, Ranges]}),
+ adjust_hibernated_memory_use(
+ [begin
+ Id = id_lookup(queue_stats, Obj),
+ Pid = pget(pid, Obj),
+ QueueData = maps:get(Id, DataLookup),
+ Props = maps:get(queue_stats, QueueData),
+ Stats = queue_stats(QueueData, Ranges, Interval),
+ {Pid, combine(Props, Obj) ++ Stats}
+ end || Obj <- Objs]).
+
+detail_queue_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(queue_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_detail_queue_data,
+ [Ids, Ranges]}),
+
+ QueueStats = adjust_hibernated_memory_use(
+ [begin
+ Id = id_lookup(queue_stats, Obj),
+ Pid = pget(pid, Obj),
+ QueueData = maps:get(Id, DataLookup),
+ Props = maps:get(queue_stats, QueueData),
+ Stats = queue_stats(QueueData, Ranges, Interval),
+ ConsumerStats = rabbit_mgmt_data_compat:fill_consumer_active_fields(
+ maps:get(consumer_stats, QueueData)),
+ Consumers = [{consumer_details, ConsumerStats}],
+ StatsD = [{deliveries,
+ detail_stats(QueueData, channel_queue_stats_deliver_stats,
+ deliver_get, second(Id), Ranges, Interval)},
+ {incoming,
+ detail_stats(QueueData, queue_exchange_stats_publish,
+ fine_stats, first(Id), Ranges, Interval)}],
+ Details = augment_details(Obj, []),
+ {Pid, combine(Props, Obj) ++ Stats ++ StatsD ++ Consumers ++ Details}
+ end || Obj <- Objs]),
+
+ % patch up missing channel details
+ ChPids = lists:usort(get_pids_for_missing_channel_details(QueueStats)),
+ ChDets = get_channel_detail_lookup(ChPids),
+ Merged = merge_channel_details(QueueStats, ChDets),
+ Merged.
+
+node_node_stats(Lookup, Node, Ranges, Interval) ->
+ LocalNodeNodeMetrics = maps:from_list(ets:tab2list(node_node_metrics)),
+ RemoteNodeNodeMetrics = maps:get(node_node_metrics, Lookup),
+ NodeNodeMetrics = maps:merge(LocalNodeNodeMetrics, RemoteNodeNodeMetrics),
+ node_node_stats(Lookup, Node, Ranges, Interval, NodeNodeMetrics).
+
+node_node_stats(Lookup, Node, Ranges, Interval, NodeNodeMetrics) ->
+ Table = node_node_coarse_stats,
+ Type = coarse_node_node_stats,
+ [begin
+ {Stats, DetailId} = get_detail_stats(Key, Lookup, Table, Type,
+ first(Node), Ranges, Interval),
+ NodeMetrics = maybe_fetch_value(Key, NodeNodeMetrics),
+ lists:flatten([{stats, Stats}, DetailId, NodeMetrics])
+ end || {{T, Key}, _} <- maps:to_list(Lookup), T =:= Table].
+
+detail_stats(Lookup, Table, Type, Id, Ranges, Interval) ->
+ [begin
+ {Stats, DetailId} = get_detail_stats(Key, Lookup, Table, Type,
+ Id, Ranges, Interval),
+ [{stats, Stats}|DetailId] %TODO: not actually delegated
+ end || {{T, Key}, _} <- maps:to_list(Lookup), T =:= Table].
+
+get_detail_stats(Key, Lookup, Table, Type, Id, Ranges, Interval) ->
+ Range = pick_range(Type, Ranges),
+ Stats = format_range(Lookup, {Table, Key}, Range, Interval),
+ DetailId = format_detail_id(revert(Id, Key)),
+ {Stats, DetailId}.
+
+queue_stats(QueueData, Ranges, Interval) ->
+ message_stats(format_range(QueueData, queue_stats_publish,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(QueueData, queue_stats_deliver_stats,
+ pick_range(deliver_get, Ranges), Interval)) ++
+ format_range(QueueData, queue_process_stats,
+ pick_range(process_stats, Ranges), Interval) ++
+ format_range(QueueData, queue_msg_stats,
+ pick_range(queue_msg_counts, Ranges), Interval).
+
+channel_stats(ChannelData, Ranges, Interval) ->
+ message_stats(format_range(ChannelData, channel_stats_fine_stats,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(ChannelData, channel_stats_deliver_stats,
+ pick_range(deliver_get, Ranges), Interval)) ++
+ format_range(ChannelData, channel_process_stats,
+ pick_range(process_stats, Ranges), Interval).
+
+-spec format_range(slide_data(), lookup_key(), maybe_range(), non_neg_integer()) ->
+ proplists:proplist().
+format_range(Data, Key, Range0, Interval) ->
+ Table = case Key of
+ {T, _} -> T;
+ T -> T
+ end,
+ InstantRateFun = fun() -> fetch_slides(1, Key, Data) end,
+ SamplesFun = fun() -> fetch_slides(2, Key, Data) end,
+ Now = exometer_slide:timestamp(),
+ rabbit_mgmt_stats:format_range(Range0, Now, Table, Interval, InstantRateFun,
+ SamplesFun).
+
+%% basic.get-empty metric
+fetch_slides(Ele, Key, Data)
+ when Key =:= channel_queue_stats_deliver_stats orelse
+ Key =:= channel_stats_deliver_stats orelse
+ Key =:= queue_stats_deliver_stats orelse
+ Key =:= vhost_stats_deliver_stats orelse
+ (is_tuple(Key) andalso
+ (element(1, Key) =:= channel_queue_stats_deliver_stats orelse
+ element(1, Key) =:= channel_stats_deliver_stats orelse
+ element(1, Key) =:= queue_stats_deliver_stats orelse
+ element(1, Key) =:= vhost_stats_deliver_stats)) ->
+ case element(Ele, maps:get(Key, Data)) of
+ not_found -> [];
+ Slides when is_list(Slides) ->
+ [rabbit_mgmt_data_compat:fill_get_empty_queue_metric(S)
+ || S <- Slides, not_found =/= S];
+ Slide ->
+ [rabbit_mgmt_data_compat:fill_get_empty_queue_metric(Slide)]
+ end;
+%% drop_unroutable metric
+fetch_slides(Ele, Key, Data)
+ when Key =:= channel_stats_fine_stats orelse
+ Key =:= channel_exchange_stats_fine_stats orelse
+ Key =:= vhost_stats_fine_stats orelse
+ (is_tuple(Key) andalso
+ (element(1, Key) =:= channel_stats_fine_stats orelse
+ element(1, Key) =:= channel_exchange_stats_fine_stats orelse
+ element(1, Key) =:= vhost_stats_fine_stats)) ->
+ case element(Ele, maps:get(Key, Data)) of
+ not_found -> [];
+ Slides when is_list(Slides) ->
+ [rabbit_mgmt_data_compat:fill_drop_unroutable_metric(S)
+ || S <- Slides, not_found =/= S];
+ Slide ->
+ [rabbit_mgmt_data_compat:fill_drop_unroutable_metric(Slide)]
+ end;
+fetch_slides(Ele, Key, Data) ->
+ case element(Ele, maps:get(Key, Data)) of
+ not_found -> [];
+ Slides when is_list(Slides) ->
+ [S || S <- Slides, not_found =/= S];
+ Slide ->
+ [Slide]
+ end.
+
+get_channel_detail_lookup(ChPids) ->
+ ChDets = delegate_invoke({rabbit_mgmt_data, augment_channel_pids, [ChPids]}),
+ maps:from_list([{pget(pid, C), C} || [_|_] = C <- lists:append(ChDets)]).
+
+merge_channel_details(QueueStats, Lookup) ->
+ [begin
+ Cons = pget(consumer_details, QueueStat),
+ Cons1 = [merge_channel_into_obj(Con, Lookup) || Con <- Cons],
+ rabbit_misc:pset(consumer_details, Cons1, QueueStat)
+ end || QueueStat <- QueueStats].
+
+merge_channel_into_obj(Obj, ChDet) ->
+ case pget(channel_details, Obj) of
+ [] -> case maps:find(pget(channel_pid, Obj), ChDet) of
+ {ok, CHd} ->
+ rabbit_misc:pset(channel_details, CHd, Obj);
+ error ->
+ Obj
+ end;
+ _ -> Obj
+ end.
+
+get_pids_for_missing_channel_details(QueueStats) ->
+ CDs = lists:append([pget(consumer_details, QueueStat) || QueueStat <- QueueStats]),
+ [ pget(channel_pid, CD) || CD <- CDs, [] =:= pget(channel_details, CD)].
+
+
+list_exchange_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(exchange_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_exchange_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(exchange_stats, Obj),
+ ExData = maps:get(Id, DataLookup),
+ Stats = message_stats(format_range(ExData, exchange_stats_publish_out,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(ExData, exchange_stats_publish_in,
+ pick_range(deliver_get, Ranges), Interval)),
+ Obj ++ Stats
+ end || Obj <- Objs].
+
+detail_exchange_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(exchange_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_exchange_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(exchange_stats, Obj),
+ ExData = maps:get(Id, DataLookup),
+ Stats = message_stats(format_range(ExData, exchange_stats_publish_out,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(ExData, exchange_stats_publish_in,
+ pick_range(deliver_get, Ranges), Interval)),
+ StatsD = [{incoming,
+ detail_stats(ExData, channel_exchange_stats_fine_stats,
+ fine_stats, second(Id), Ranges, Interval)},
+ {outgoing,
+ detail_stats(ExData, queue_exchange_stats_publish,
+ fine_stats, second(Id), Ranges, Interval)}],
+ %% remove live state? not sure it has!
+ Obj ++ StatsD ++ Stats
+ end || Obj <- Objs].
+
+connection_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(connection_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_connection_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(connection_stats, Obj),
+ ConnData = maps:get(Id, DataLookup),
+ Props = maps:get(connection_stats, ConnData),
+ Stats = format_range(ConnData, connection_stats_coarse_conn_stats,
+ pick_range(coarse_conn_stats, Ranges), Interval),
+ Details = augment_details(Obj, []), % TODO: not delegated
+ combine(Props, Obj) ++ Details ++ Stats
+ end || Obj <- Objs].
+
+list_channel_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(channel_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_list_channel_data, [Ids, Ranges]}),
+ ChannelStats =
+ [begin
+ Id = id_lookup(channel_stats, Obj),
+ ChannelData = maps:get(Id, DataLookup),
+ Props = maps:get(channel_stats, ChannelData),
+ Stats = channel_stats(ChannelData, Ranges, Interval),
+ combine(Props, Obj) ++ Stats
+ end || Obj <- Objs],
+ ChannelStats.
+
+detail_channel_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(channel_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_detail_channel_data,
+ [Ids, Ranges]}),
+ ChannelStats =
+ [begin
+ Id = id_lookup(channel_stats, Obj),
+ ChannelData = maps:get(Id, DataLookup),
+ Props = maps:get(channel_stats, ChannelData),
+ Stats = channel_stats(ChannelData, Ranges, Interval),
+ ConsumerStats = rabbit_mgmt_data_compat:fill_consumer_active_fields(
+ maps:get(consumer_stats, ChannelData)),
+ Consumers = [{consumer_details, ConsumerStats}],
+ StatsD = [{publishes,
+ detail_stats(ChannelData, channel_exchange_stats_fine_stats,
+ fine_stats, first(Id), Ranges, Interval)},
+ {deliveries,
+ detail_stats(ChannelData, channel_queue_stats_deliver_stats,
+ fine_stats, first(Id), Ranges, Interval)}],
+ combine(Props, Obj) ++ Consumers ++ Stats ++ StatsD
+ end || Obj <- Objs],
+ rabbit_mgmt_format:strip_pids(ChannelStats).
+
+vhost_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(vhost_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_vhost_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(vhost_stats, Obj),
+ VData = maps:get(Id, DataLookup),
+ Stats = format_range(VData, vhost_stats_coarse_conn_stats,
+ pick_range(coarse_conn_stats, Ranges), Interval) ++
+ format_range(VData, vhost_msg_stats,
+ pick_range(queue_msg_rates, Ranges), Interval),
+ StatsD = message_stats(format_range(VData, vhost_stats_fine_stats,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(VData, vhost_stats_deliver_stats,
+ pick_range(deliver_get, Ranges), Interval)),
+ Details = augment_details(Obj, []),
+ Obj ++ Details ++ Stats ++ StatsD
+ end || Obj <- Objs].
+
+node_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(node_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_node_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(node_stats, Obj),
+ NData = maps:get(Id, DataLookup),
+ Props = maps:get(node_stats, NData),
+ Stats = format_range(NData, node_coarse_stats,
+ pick_range(coarse_node_stats, Ranges), Interval) ++
+ format_range(NData, node_persister_stats,
+ pick_range(coarse_node_stats, Ranges), Interval) ++
+ format_range(NData, connection_churn_rates,
+ pick_range(churn_rates, Ranges), Interval),
+ NodeNodeStats = node_node_stats(NData, Id, Ranges, Interval),
+ StatsD = [{cluster_links, NodeNodeStats}],
+ MgmtStats = maps:get(mgmt_stats, NData),
+ Details = augment_details(Obj, []), % augmentation needs to be node local
+ combine(Props, Obj) ++ Details ++ Stats ++ StatsD ++ MgmtStats
+ end || Obj <- Objs].
+
+combine(New, Old) ->
+ case pget(state, Old) of
+ unknown -> New ++ Old;
+ live -> New ++ lists:keydelete(state, 1, Old);
+ _ -> lists:keydelete(state, 1, New) ++ Old
+ end.
+
+revert({'_', _}, {Id, _}) ->
+ Id;
+revert({_, '_'}, {_, Id}) ->
+ Id.
+
+%%----------------------------------------------------------------------------
+%% Internal, delegated operations
+%%----------------------------------------------------------------------------
+
+-spec get_data_from_nodes(mfargs()) -> #{atom() => any()}.
+get_data_from_nodes(MFA) ->
+ Data = delegate_invoke(MFA),
+ lists:foldl(fun(D, Agg) ->
+ maps_merge(fun merge_data/3, D, Agg)
+ end, #{}, Data).
+
+maps_merge(Fun, M1, M2) ->
+ JustM2 = maps:without(maps:keys(M1), M2),
+ maps:merge(JustM2,
+ maps:map(fun(K, V1) ->
+ case maps:find(K, M2) of
+ {ok, V2} -> Fun(K, V1, V2);
+ error -> V1
+ end
+ end,
+ M1)).
+
+-spec merge_data(atom(), any(), any()) -> any().
+merge_data(_, A, B) when is_integer(A), is_integer(B) -> A + B;
+merge_data(_, A, B) when is_list(A), is_list(B) ->
+ A ++ B;
+merge_data(_, {A1, B1}, {[_|_] = A2, [_|_] = B2}) ->
+ {[A1 | A2], [B1 | B2]};
+merge_data(_, {A1, B1}, {A2, B2}) -> % first slide
+ {[A1, A2], [B1, B2]};
+merge_data(_, D1, D2) -> % we assume if we get here both values a maps
+ try
+ maps_merge(fun merge_data/3, D1, D2)
+ catch
+ error:Err ->
+ rabbit_log:debug("merge_data err ~p got: ~p ~p ~n", [Err, D1, D2]),
+ case is_map(D1) of
+ true -> D1;
+ false -> D2
+ end
+ end.
+
+%% We do this when retrieving the queue record rather than when
+%% storing it since the memory use will drop *after* we find out about
+%% hibernation, so to do it when we receive a queue stats event would
+%% be fiddly and racy. This should be quite cheap though.
+adjust_hibernated_memory_use(Qs) ->
+ Pids = [Pid || {Pid, Q} <- Qs, pget(idle_since, Q, not_idle) =/= not_idle],
+ %% We use delegate here not for ordering reasons but because we
+ %% want to get the right amount of parallelism and minimise
+ %% cross-cluster communication.
+ {Mem, _BadNodes} = delegate:invoke(Pids, {erlang, process_info, [memory]}),
+ MemDict = maps:from_list([{P, M} || {P, M = {memory, _}} <- Mem]),
+ [case maps:find(Pid, MemDict) of
+ error -> Q;
+ {ok, Memory} -> [Memory | proplists:delete(memory, Q)]
+ end || {Pid, Q} <- Qs].
+
+-spec created_stats_delegated(any(), fun((any()) -> any()) | atom()) -> not_found | any().
+created_stats_delegated(Key, Type) ->
+ Data = delegate_invoke({rabbit_mgmt_data, augmented_created_stats, [Key, Type]}),
+ case [X || X <- Data, X =/= not_found] of
+ [] -> not_found;
+ [X] -> X
+ end.
+
+created_stats_delegated(Type) ->
+ lists:append(
+ delegate_invoke({rabbit_mgmt_data, augmented_created_stats, [Type]})).
+
+-spec delegate_invoke(mfargs()) -> [any()].
+delegate_invoke(FunOrMFA) ->
+ MemberPids = [P || P <- pg2:get_members(management_db)],
+ {Results, Errors} = delegate:invoke(MemberPids, ?DELEGATE_PREFIX, FunOrMFA),
+ case Errors of
+ [] -> ok;
+ _ -> rabbit_log:warning("Management delegate query returned errors:~n~p", [Errors])
+ end,
+ [R || {_, R} <- Results].
+
+submit(Fun) ->
+ {ok, Interval} = application:get_env(rabbit, collect_statistics_interval),
+ worker_pool:submit(management_worker_pool, fun() -> Fun(Interval) end, reuse).
+
+submit_cached(Key, Fun) ->
+ {ok, Interval} = application:get_env(rabbit, collect_statistics_interval),
+ {ok, Res} = rabbit_mgmt_db_cache:fetch(Key, fun() -> Fun(Interval) end),
+ Res.
+
+submit_cached(Key, Fun, Arg, Timeout) ->
+ {ok, Interval} = application:get_env(rabbit, collect_statistics_interval),
+ {ok, Res} = rabbit_mgmt_db_cache:fetch(Key,
+ fun(A) -> Fun(Interval, A) end,
+ [Arg], Timeout),
+ Res.
+
+%% Note: Assumes Key is a two-tuple.
+%% If not found at first, Key is reversed and tried again.
+%% Seems to be necessary based on node_node_metrics table keys
+%% and Key values in Lookup
+maybe_fetch_value(Key, Dict) ->
+ maybe_fetch_value(maps:is_key(Key, Dict), go, Key, Dict).
+
+maybe_fetch_value(true, _Cont, Key, Dict) ->
+ maps:get(Key, Dict);
+maybe_fetch_value(false, stop, _Key, _Dict) ->
+ [];
+maybe_fetch_value(false, go, Key, Dict) ->
+ Key2 = reverse_key(Key),
+ maybe_fetch_value(maps:is_key(Key2, Dict), stop, Key2, Dict).
+
+message_stats([]) ->
+ [];
+message_stats(Stats) ->
+ [{message_stats, Stats}].
+
+pick_range(Table, Ranges) ->
+ rabbit_mgmt_data:pick_range(Table, Ranges).
+
+first(Id) ->
+ {Id, '_'}.
+
+second(Id) ->
+ {'_', Id}.
+
+reverse_key({K1, K2}) ->
+ {K2, K1}.
+
+augment_details(Obj, Acc) ->
+ rabbit_mgmt_data:augment_details(Obj, Acc).
+
+format_detail_id(ChPid) when is_pid(ChPid) ->
+ augment_details([{channel, ChPid}], []);
+format_detail_id(#resource{name = Name, virtual_host = Vhost, kind = Kind}) ->
+ [{Kind, [{name, Name}, {vhost, Vhost}]}];
+format_detail_id(Node) when is_atom(Node) ->
+ [{name, Node}].
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_db_cache.erl b/deps/rabbitmq_management/src/rabbit_mgmt_db_cache.erl
new file mode 100644
index 0000000000..b4d92dd543
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_db_cache.erl
@@ -0,0 +1,143 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+-module(rabbit_mgmt_db_cache).
+
+-behaviour(gen_server).
+
+%% API functions
+-export([start_link/1]).
+-export([process_name/1,
+ fetch/2,
+ fetch/3,
+ fetch/4]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-record(state, {data :: any() | none,
+ args :: [any()],
+ timer_ref :: undefined | reference(),
+ multiplier :: integer()}).
+
+-type error_desc() :: key_not_found | timeout | {throw, atom()}.
+-type fetch_fun() :: fun((_) -> any()) | fun(() -> any()).
+-type fetch_ret() :: {ok, any()} | {error, error_desc()}.
+
+-define(DEFAULT_MULT, 5).
+-define(DEFAULT_TIMEOUT, 60000).
+-define(CHILD(Key), {rabbit_mgmt_db_cache:process_name(Key),
+ {rabbit_mgmt_db_cache, start_link, [Key]},
+ permanent, 5000, worker,
+ [rabbit_mgmt_db_cache]}).
+-define(RESET_STATE(State), State#state{data = none, args = []}).
+
+%% Implements an adaptive cache that times the value generating fun
+%% and uses the return value as the cached value for the time it took
+%% to produce multiplied by some factor (defaults to 5).
+%% There is one cache process per key. New processes are started as
+%% required. The cache is invalidated if the arguments to the fetch
+%% fun have changed.
+
+
+%%%===================================================================
+%%% API functions
+%%%===================================================================
+
+-spec fetch(atom(), fetch_fun()) -> fetch_ret().
+fetch(Key, FetchFun) ->
+ fetch(Key, FetchFun, []).
+
+-spec fetch(atom(), fetch_fun(), [any()]) -> fetch_ret().
+fetch(Key, FetchFun, Args) when is_list(Args) ->
+ fetch(Key, FetchFun, Args, ?DEFAULT_TIMEOUT).
+
+-spec fetch(atom(), fetch_fun(), [any()], integer()) -> fetch_ret().
+fetch(Key, FetchFun, FunArgs, Timeout) ->
+ ProcName = process_name(Key),
+ Pid = case whereis(ProcName) of
+ undefined ->
+ {ok, P} = supervisor:start_child(rabbit_mgmt_db_cache_sup,
+ ?CHILD(Key)),
+ P;
+ P -> P
+ end,
+ gen_server:call(Pid, {fetch, FetchFun, FunArgs}, Timeout).
+
+
+-spec process_name(atom()) -> atom().
+process_name(Key) ->
+ list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(Key)).
+
+-spec start_link(atom()) -> {ok, pid()} | ignore | {error, any()}.
+start_link(Key) ->
+ gen_server:start_link({local, process_name(Key)}, ?MODULE, [], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+init([]) ->
+ Mult = application:get_env(rabbitmq_management, management_db_cache_multiplier,
+ ?DEFAULT_MULT),
+ {ok, #state{data = none,
+ args = [],
+ multiplier = Mult}}.
+
+handle_call({fetch, _FetchFun, FunArgs} = Msg, From,
+ #state{data = CachedData, args = Args} = State) when
+ CachedData =/= none andalso Args =/= FunArgs ->
+ %% there is cached data that needs to be invalidated
+ handle_call(Msg, From, ?RESET_STATE(State));
+handle_call({fetch, FetchFun, FunArgs}, _From,
+ #state{data = none,
+ multiplier = Mult, timer_ref = Ref} = State) ->
+ %% force a gc here to clean up previously cleared data
+ garbage_collect(),
+ case Ref of
+ R when is_reference(R) ->
+ _ = erlang:cancel_timer(R);
+ _ -> ok
+ end,
+
+ try timer:tc(FetchFun, FunArgs) of
+ {Time, Data} ->
+ case trunc(Time / 1000 * Mult) of
+ 0 -> {reply, {ok, Data}, ?RESET_STATE(State)}; % no need to cache that
+ T ->
+ TimerRef = erlang:send_after(T, self(), purge_cache),
+ {reply, {ok, Data}, State#state{data = Data,
+ timer_ref = TimerRef,
+ args = FunArgs}}
+ end
+ catch
+ Throw -> {reply, {error, {throw, Throw}}, State}
+ end;
+handle_call({fetch, _FetchFun, _}, _From, #state{data = Data} = State) ->
+ Reply = {ok, Data},
+ {reply, Reply, State};
+handle_call(purge_cache, _From, State) ->
+ {reply, ok, ?RESET_STATE(State), hibernate}.
+
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(purge_cache, State) ->
+ {noreply, ?RESET_STATE(State), hibernate};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_db_cache_sup.erl b/deps/rabbitmq_management/src/rabbit_mgmt_db_cache_sup.erl
new file mode 100644
index 0000000000..1805f25872
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_db_cache_sup.erl
@@ -0,0 +1,29 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+-module(rabbit_mgmt_db_cache_sup).
+
+-behaviour(supervisor).
+
+%% API functions
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%%%===================================================================
+%%% API functions
+%%%===================================================================
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%===================================================================
+%%% Supervisor callbacks
+%%%===================================================================
+
+init([]) ->
+ {ok, {{one_for_one, 5, 10}, []}}.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl
new file mode 100644
index 0000000000..e0de5c8bce
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl
@@ -0,0 +1,185 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_dispatcher).
+
+-export([modules/1, build_dispatcher/1]).
+
+-behaviour(rabbit_mgmt_extension).
+-export([dispatcher/0, web_ui/0]).
+
+build_dispatcher(Ignore) ->
+ Routes = build_routes(Ignore),
+ cowboy_router:compile(Routes).
+
+build_routes(Ignore) ->
+ ManagementApp = module_app(?MODULE),
+ Prefix = rabbit_mgmt_util:get_path_prefix(),
+ RootIdxRtes = build_root_index_routes(Prefix, ManagementApp),
+ ApiRdrRte = build_redirect_route("/api", Prefix ++ "/api/index.html"),
+ CliRdrRte = build_redirect_route("/cli", Prefix ++ "/cli/index.html"),
+ StatsRdrRte1 = build_redirect_route("/stats", Prefix ++ "/api/index.html"),
+ StatsRdrRte2 = build_redirect_route("/doc/stats.html", Prefix ++ "/api/index.html"),
+ MgmtRdrRte = {"/mgmt", rabbit_mgmt_wm_redirect, "/"},
+ LocalPaths = [{module_app(M), "www"} || M <- modules(Ignore)],
+ LocalStaticRte = {"/[...]", rabbit_mgmt_wm_static, LocalPaths},
+ % NB: order is significant in the routing list
+ Routes0 = build_module_routes(Ignore) ++
+ [ApiRdrRte, CliRdrRte, MgmtRdrRte, StatsRdrRte1, StatsRdrRte2, LocalStaticRte],
+ Routes1 = maybe_add_path_prefix(Routes0, Prefix),
+ % NB: ensure the root routes are first
+ Routes2 = RootIdxRtes ++ Routes1,
+ [{'_', Routes2}].
+
+build_root_index_routes("", ManagementApp) ->
+ [{"/", rabbit_mgmt_wm_static, root_idx_file(ManagementApp)}];
+build_root_index_routes(Prefix, ManagementApp) ->
+ [{"/", rabbit_mgmt_wm_redirect, Prefix ++ "/"},
+ {Prefix, rabbit_mgmt_wm_static, root_idx_file(ManagementApp)}].
+
+build_redirect_route(Path, Location) ->
+ {Path, rabbit_mgmt_wm_redirect, Location}.
+
+root_idx_file(ManagementApp) ->
+ {priv_file, ManagementApp, "www/index.html"}.
+
+maybe_add_path_prefix(Routes, "") ->
+ Routes;
+maybe_add_path_prefix(Routes, Prefix) ->
+ [{Prefix ++ Path, Mod, Args} || {Path, Mod, Args} <- Routes].
+
+build_module_routes(Ignore) ->
+ Routes = [Module:dispatcher() || Module <- modules(Ignore)],
+ [{"/api" ++ Path, Mod, Args} || {Path, Mod, Args} <- lists:append(Routes)].
+
+modules(IgnoreApps) ->
+ [Module || {App, Module, Behaviours} <-
+ %% Sort rabbitmq_management modules first. This is
+ %% a microoptimization because most files belong to
+ %% this application. Making it first avoids several
+ %% stats(2) which have a great chance of failing in other
+ %% applications.
+ lists:sort(
+ fun
+ ({rabbitmq_management, _, _}, _) -> true;
+ (_, {rabbitmq_management, _, _}) -> false;
+ ({A, _, _}, {B, _, _}) -> A =< B
+ end,
+ rabbit_misc:all_module_attributes(behaviour)),
+ not lists:member(App, IgnoreApps),
+ lists:member(rabbit_mgmt_extension, Behaviours)].
+
+module_app(Module) ->
+ {ok, App} = application:get_application(Module),
+ App.
+
+%%----------------------------------------------------------------------------
+
+web_ui() -> [{javascript, <<"dispatcher.js">>}].
+
+dispatcher() ->
+ [{"/overview", rabbit_mgmt_wm_overview, []},
+ {"/cluster-name", rabbit_mgmt_wm_cluster_name, []},
+ {"/nodes", rabbit_mgmt_wm_nodes, []},
+ {"/nodes/:node", rabbit_mgmt_wm_node, []},
+ {"/nodes/:node/memory", rabbit_mgmt_wm_node_memory, [absolute]},
+ {"/nodes/:node/memory/relative", rabbit_mgmt_wm_node_memory, [relative]},
+ {"/nodes/:node/memory/ets", rabbit_mgmt_wm_node_memory_ets, [absolute]},
+ {"/nodes/:node/memory/ets/relative", rabbit_mgmt_wm_node_memory_ets, [relative]},
+ {"/nodes/:node/memory/ets/:filter", rabbit_mgmt_wm_node_memory_ets, [absolute]},
+ {"/nodes/:node/memory/ets/:filter/relative", rabbit_mgmt_wm_node_memory_ets, [relative]},
+ {"/extensions", rabbit_mgmt_wm_extensions, []},
+ {"/all-configuration", rabbit_mgmt_wm_definitions, []}, %% This was the old name, let's not break things gratuitously.
+ {"/definitions", rabbit_mgmt_wm_definitions, []},
+ {"/definitions/:vhost", rabbit_mgmt_wm_definitions, []},
+ {"/parameters", rabbit_mgmt_wm_parameters, []},
+ {"/parameters/:component", rabbit_mgmt_wm_parameters, []},
+ {"/parameters/:component/:vhost", rabbit_mgmt_wm_parameters, []},
+ {"/parameters/:component/:vhost/:name", rabbit_mgmt_wm_parameter, []},
+ {"/global-parameters", rabbit_mgmt_wm_global_parameters, []},
+ {"/global-parameters/:name", rabbit_mgmt_wm_global_parameter, []},
+ {"/policies", rabbit_mgmt_wm_policies, []},
+ {"/policies/:vhost", rabbit_mgmt_wm_policies, []},
+ {"/policies/:vhost/:name", rabbit_mgmt_wm_policy, []},
+ {"/operator-policies", rabbit_mgmt_wm_operator_policies, []},
+ {"/operator-policies/:vhost", rabbit_mgmt_wm_operator_policies, []},
+ {"/operator-policies", rabbit_mgmt_wm_operator_policies, []},
+ {"/operator-policies/:vhost/:name", rabbit_mgmt_wm_operator_policy, []},
+ {"/vhost-limits/:vhost/:name", rabbit_mgmt_wm_limit, []},
+ {"/vhost-limits", rabbit_mgmt_wm_limits, []},
+ {"/vhost-limits/:vhost", rabbit_mgmt_wm_limits, []},
+ {"/connections", rabbit_mgmt_wm_connections, []},
+ {"/connections/:connection", rabbit_mgmt_wm_connection, []},
+ {"/connections/:connection/channels", rabbit_mgmt_wm_connection_channels, []},
+ {"/channels", rabbit_mgmt_wm_channels, []},
+ {"/channels/:channel", rabbit_mgmt_wm_channel, []},
+ {"/consumers", rabbit_mgmt_wm_consumers, []},
+ {"/consumers/:vhost", rabbit_mgmt_wm_consumers, []},
+ {"/exchanges", rabbit_mgmt_wm_exchanges, []},
+ {"/exchanges/:vhost", rabbit_mgmt_wm_exchanges, []},
+ {"/exchanges/:vhost/:exchange", rabbit_mgmt_wm_exchange, []},
+ {"/exchanges/:vhost/:exchange/publish", rabbit_mgmt_wm_exchange_publish, []},
+ {"/exchanges/:vhost/:exchange/bindings/source", rabbit_mgmt_wm_bindings, [exchange_source]},
+ {"/exchanges/:vhost/:exchange/bindings/destination", rabbit_mgmt_wm_bindings, [exchange_destination]},
+ {"/queues", rabbit_mgmt_wm_queues, []},
+ {"/queues/:vhost", rabbit_mgmt_wm_queues, []},
+ {"/queues/:vhost/:queue", rabbit_mgmt_wm_queue, []},
+ {"/queues/:vhost/:destination/bindings", rabbit_mgmt_wm_bindings, [queue]},
+ {"/queues/:vhost/:queue/contents", rabbit_mgmt_wm_queue_purge, []},
+ {"/queues/:vhost/:queue/get", rabbit_mgmt_wm_queue_get, []},
+ {"/queues/:vhost/:queue/actions", rabbit_mgmt_wm_queue_actions, []},
+ {"/bindings", rabbit_mgmt_wm_bindings, [all]},
+ {"/bindings/:vhost", rabbit_mgmt_wm_bindings, [all]},
+ {"/bindings/:vhost/e/:source/:dtype/:destination", rabbit_mgmt_wm_bindings, [source_destination]},
+ {"/bindings/:vhost/e/:source/:dtype/:destination/:props", rabbit_mgmt_wm_binding, []},
+ {"/vhosts", rabbit_mgmt_wm_vhosts, []},
+ {"/vhosts/:vhost", rabbit_mgmt_wm_vhost, []},
+ {"/vhosts/:vhost/start/:node", rabbit_mgmt_wm_vhost_restart, []},
+ {"/vhosts/:vhost/permissions", rabbit_mgmt_wm_permissions_vhost, []},
+ {"/vhosts/:vhost/topic-permissions", rabbit_mgmt_wm_topic_permissions_vhost, []},
+ %% /connections/:connection is already taken, we cannot use our standard scheme here
+ {"/vhosts/:vhost/connections", rabbit_mgmt_wm_connections_vhost, []},
+ %% /channels/:channel is already taken, we cannot use our standard scheme here
+ {"/vhosts/:vhost/channels", rabbit_mgmt_wm_channels_vhost, []},
+ {"/users/bulk-delete", rabbit_mgmt_wm_users_bulk_delete, []},
+ {"/users/without-permissions", rabbit_mgmt_wm_users, [without_permissions]},
+ {"/users", rabbit_mgmt_wm_users, [all]},
+ {"/users/:user", rabbit_mgmt_wm_user, []},
+ {"/users/:user/permissions", rabbit_mgmt_wm_permissions_user, []},
+ {"/users/:user/topic-permissions", rabbit_mgmt_wm_topic_permissions_user, []},
+ {"/user-limits/:user/:name", rabbit_mgmt_wm_user_limit, []},
+ {"/user-limits", rabbit_mgmt_wm_user_limits, []},
+ {"/user-limits/:user", rabbit_mgmt_wm_user_limits, []},
+ {"/feature-flags", rabbit_mgmt_wm_feature_flags, []},
+ {"/feature-flags/:name/enable", rabbit_mgmt_wm_feature_flag_enable, []},
+ {"/whoami", rabbit_mgmt_wm_whoami, []},
+ {"/permissions", rabbit_mgmt_wm_permissions, []},
+ {"/permissions/:vhost/:user", rabbit_mgmt_wm_permission, []},
+ {"/topic-permissions", rabbit_mgmt_wm_topic_permissions, []},
+ {"/topic-permissions/:vhost/:user", rabbit_mgmt_wm_topic_permission, []},
+ {"/topic-permissions/:vhost/:user/:exchange", rabbit_mgmt_wm_topic_permission, []},
+ {"/aliveness-test/:vhost", rabbit_mgmt_wm_aliveness_test, []},
+ %% now deprecated
+ {"/healthchecks/node", rabbit_mgmt_wm_healthchecks, []},
+ {"/healthchecks/node/:node", rabbit_mgmt_wm_healthchecks, []},
+ %% modern generation of fine-grained health checks
+ {"/health/checks/alarms", rabbit_mgmt_wm_health_check_alarms, []},
+ {"/health/checks/local-alarms", rabbit_mgmt_wm_health_check_local_alarms, []},
+ {"/health/checks/certificate-expiration/:within/:unit", rabbit_mgmt_wm_health_check_certificate_expiration, []},
+ {"/health/checks/port-listener/:port", rabbit_mgmt_wm_health_check_port_listener, []},
+ {"/health/checks/protocol-listener/:protocol", rabbit_mgmt_wm_health_check_protocol_listener, []},
+ {"/health/checks/virtual-hosts", rabbit_mgmt_wm_health_check_virtual_hosts, []},
+ {"/health/checks/node-is-mirror-sync-critical", rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical, []},
+ {"/health/checks/node-is-quorum-critical", rabbit_mgmt_wm_health_check_node_is_quorum_critical, []},
+ {"/reset", rabbit_mgmt_wm_reset, []},
+ {"/reset/:node", rabbit_mgmt_wm_reset, []},
+ {"/rebalance/queues", rabbit_mgmt_wm_rebalance_queues, [{queues, all}]},
+ {"/auth", rabbit_mgmt_wm_auth, []},
+ {"/auth/attempts/:node", rabbit_mgmt_wm_auth_attempts, [all]},
+ {"/auth/attempts/:node/source", rabbit_mgmt_wm_auth_attempts, [by_source]},
+ {"/login", rabbit_mgmt_wm_login, []}
+ ].
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_extension.erl b/deps/rabbitmq_management/src/rabbit_mgmt_extension.erl
new file mode 100644
index 0000000000..1ee72aaf36
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_extension.erl
@@ -0,0 +1,16 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_extension).
+
+%% Return a Cowboy dispatcher table to integrate
+-callback dispatcher() -> [{string(), atom(), [atom()]}].
+
+%% Return a proplist of information for the web UI to integrate
+%% this extension. Currently the proplist should have one key,
+%% 'javascript', the name of a javascript file to load and run.
+-callback web_ui() -> proplists:proplist().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_headers.erl b/deps/rabbitmq_management/src/rabbit_mgmt_headers.erl
new file mode 100644
index 0000000000..89bb464c01
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_headers.erl
@@ -0,0 +1,34 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% This module contains helper functions that control
+%% response headers related to CORS, CSP, HSTS and so on.
+-module(rabbit_mgmt_headers).
+
+-export([set_common_permission_headers/2]).
+-export([set_cors_headers/2, set_hsts_headers/2, set_csp_headers/2]).
+
+%%
+%% API
+%%
+
+set_cors_headers(ReqData, Module) ->
+ rabbit_mgmt_cors:set_headers(ReqData, Module).
+
+set_hsts_headers(ReqData, _Module) ->
+ rabbit_mgmt_hsts:set_headers(ReqData).
+
+set_csp_headers(ReqData, _Module) ->
+ rabbit_mgmt_csp:set_headers(ReqData).
+
+set_common_permission_headers(ReqData0, EndpointModule) ->
+ lists:foldl(fun(Fun, ReqData) ->
+ Fun(ReqData, EndpointModule)
+ end, ReqData0,
+ [fun set_csp_headers/2,
+ fun set_hsts_headers/2,
+ fun set_cors_headers/2]).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_hsts.erl b/deps/rabbitmq_management/src/rabbit_mgmt_hsts.erl
new file mode 100644
index 0000000000..004f3d4e7e
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_hsts.erl
@@ -0,0 +1,28 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% Sets HSTS header(s) on the response if configured,
+%% see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security.
+
+-module(rabbit_mgmt_hsts).
+
+-export([set_headers/1]).
+
+-define(HSTS_HEADER, <<"strict-transport-security">>).
+
+%%
+%% API
+%%
+
+set_headers(ReqData) ->
+ case application:get_env(rabbitmq_management, strict_transport_security) of
+ undefined -> ReqData;
+ {ok, Value} ->
+ cowboy_req:set_resp_header(?HSTS_HEADER,
+ rabbit_data_coercion:to_binary(Value),
+ ReqData)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_load_definitions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_load_definitions.erl
new file mode 100644
index 0000000000..ba9962d6cf
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_load_definitions.erl
@@ -0,0 +1,27 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_load_definitions).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-export([boot/0, maybe_load_definitions/0, maybe_load_definitions_from/2]).
+
+%% This module exists for backwards compatibility only.
+%% Definition import functionality is now a core server feature.
+
+boot() ->
+ rabbit_log:debug("Will import definitions file from management.load_definitions"),
+ rabbit_definitions:maybe_load_definitions(rabbitmq_management, load_definitions).
+
+maybe_load_definitions() ->
+ rabbit_definitions:maybe_load_definitions().
+
+maybe_load_definitions_from(true, Dir) ->
+ rabbit_definitions:maybe_load_definitions_from(true, Dir);
+maybe_load_definitions_from(false, File) ->
+ rabbit_definitions:maybe_load_definitions_from(false, File).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_reset_handler.erl b/deps/rabbitmq_management/src/rabbit_mgmt_reset_handler.erl
new file mode 100644
index 0000000000..4eb0219e46
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_reset_handler.erl
@@ -0,0 +1,79 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% When management extensions are enabled and/or disabled at runtime, the
+%% management web dispatch mechanism needs to be reset. This event handler
+%% deals with responding to 'plugins_changed' events for management
+%% extensions, forcing a reset when necessary.
+
+-module(rabbit_mgmt_reset_handler).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-rabbit_boot_step({?MODULE,
+ [{description, "management extension handling"},
+ {mfa, {gen_event, add_handler,
+ [rabbit_event, ?MODULE, []]}},
+ {cleanup, {gen_event, delete_handler,
+ [rabbit_event, ?MODULE, []]}},
+ {requires, rabbit_event},
+ {enables, recovery}]}).
+
+-import(rabbit_misc, [pget/2, pget/3]).
+
+%%----------------------------------------------------------------------------
+
+init([]) ->
+ {ok, []}.
+
+handle_call(_Request, State) ->
+ {ok, not_understood, State}.
+
+handle_event(#event{type = plugins_changed, props = Details}, State) ->
+ Enabled = pget(enabled, Details),
+ Disabled = pget(disabled, Details),
+ case extensions_changed(Enabled ++ Disabled) of
+ true ->
+ _ = rabbit_mgmt_app:reset_dispatcher(Disabled),
+ ok;
+ false -> ok
+ end,
+ {ok, State};
+
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Arg, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%----------------------------------------------------------------------------
+
+%% We explicitly ignore the case where management has been
+%% started/stopped since the dispatcher is either freshly created or
+%% about to vanish.
+extensions_changed(Apps) ->
+ not lists:member(rabbitmq_management, Apps) andalso
+ lists:any(fun is_extension/1, [Mod || App <- Apps, Mod <- mods(App)]).
+
+is_extension(Mod) ->
+ lists:member(rabbit_mgmt_extension,
+ pget(behaviour, Mod:module_info(attributes), [])).
+
+mods(App) ->
+ {ok, Modules} = application:get_key(App, modules),
+ Modules.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_stats.erl b/deps/rabbitmq_management/src/rabbit_mgmt_stats.erl
new file mode 100644
index 0000000000..14b3432790
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_stats.erl
@@ -0,0 +1,739 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_stats).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+
+-export([format_range/6]).
+
+-define(MICRO_TO_MILLI, 1000).
+
+-type maybe_range() :: no_range | #range{}.
+-type maybe_slide() :: exometer_slide:slide() | not_found.
+-type maybe_slide_fun() :: fun(() -> [maybe_slide()]).
+
+-export_type([maybe_range/0, maybe_slide/0, maybe_slide_fun/0]).
+
+
+%%----------------------------------------------------------------------------
+%% Query-time
+%%----------------------------------------------------------------------------
+
+-spec format_range(maybe_range(), exometer_slide:timestamp(), atom(),
+ non_neg_integer(), maybe_slide_fun(), maybe_slide_fun()) ->
+ proplists:proplist().
+format_range(no_range, Now, Table, Interval, InstantRateFun, _SamplesFun) ->
+ format_no_range(Table, Now, Interval, InstantRateFun);
+format_range(#range{last = Last, first = First, incr = Incr}, _Now, Table,
+ Interval, _InstantRateFun, SamplesFun) ->
+ case SamplesFun() of
+ [] ->
+ [];
+ Slides ->
+ Empty = empty(Table, 0),
+ Slide = exometer_slide:sum(Last, First, Incr, Slides, Empty),
+ List = exometer_slide:to_list(Last, First, Slide),
+ Length = length(List),
+ RangePoint = Last - Interval,
+ LastTwo = get_last_two(List, Length),
+ {Total, Rate} = calculate_rate(LastTwo, Table, RangePoint),
+ {Samples, SampleTotals} = format_samples(List, empty(Table, []),
+ Empty),
+ format_rate(Table, Total, Rate, Samples, SampleTotals, Length)
+ end.
+
+-spec format_no_range(atom(), exometer_slide:timestamp(), non_neg_integer(),
+ maybe_slide_fun()) -> proplists:proplist().
+format_no_range(Table, Now, Interval, InstantRateFun) ->
+ RangePoint = ((Now div Interval) * Interval) - Interval,
+ case calculate_instant_rate(InstantRateFun, Table, RangePoint) of
+ {Total, Rate} -> format_rate(Table, Total, Rate);
+ not_found -> []
+ end.
+
+-spec calculate_instant_rate(maybe_slide_fun(), atom(), integer()) ->
+ 'not_found' | {integer(), number()} | {tuple(), tuple()}.
+calculate_instant_rate(Fun, Table, RangePoint) ->
+ case Fun() of
+ [] ->
+ not_found;
+ Slides ->
+ Slide = exometer_slide:sum(Slides),
+ case exometer_slide:last_two(Slide) of
+ [] -> {empty(Table, 0), empty(Table, 0.0)};
+ [Last | T] ->
+ Total = get_total(Slide, Table),
+ Rate = rate_from_last_increment(Table, Last, T, RangePoint),
+ {Total, Rate}
+ end
+ end.
+
+calculate_rate([], Table, _RangePoint) ->
+ {empty(Table, 0), empty(Table, 0.0)};
+calculate_rate([{_, Total} = Last | T], Table, RangePoint) ->
+ Rate = rate_from_last_increment(Table, Last, T, RangePoint),
+ {Total, Rate}.
+
+get_last_two(List, Length) when Length =< 2 ->
+ lists:reverse(List);
+get_last_two(List, Length) ->
+ lists:reverse(lists:nthtail(Length - 2, List)).
+
+get_total(Slide, Table) ->
+ case exometer_slide:last(Slide) of
+ undefined -> empty(Table, 0);
+ Other -> Other
+ end.
+
+format_samples(Samples, ESamples, ETotal) ->
+ lists:foldl(fun({TS, Sample}, {SamplesAcc, TotalsAcc}) ->
+ append_full_sample(TS, Sample, SamplesAcc, TotalsAcc)
+ end, {ESamples, ETotal}, Samples).
+
+%% Ts for "totals", Ss for "samples", Vs for "values"
+%%
+%% connection_stats_coarse_conn_stats, channel_stats_fine_stats,
+%% vhost_stats_fine_stats, queue_msg_stats, vhost_msg_stats
+append_full_sample(TS, {V1, V2, V3}, {S1, S2, S3}, {T1, T2, T3}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2), append_sample(V3, TS, S3)},
+ {V1 + T1, V2 + T2, V3 + T3}};
+%% channel_exchange_stats_fine_stats
+append_full_sample(TS, {V1, V2, V3, V4}, {S1, S2, S3, S4}, {T1, T2, T3, T4}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2), append_sample(V3, TS, S3),
+ append_sample(V4, TS, S4)},
+ {V1 + T1, V2 + T2, V3 + T3, V4 + T4}};
+%% connection_churn_rates
+append_full_sample(TS, {V1, V2, V3, V4, V5, V6, V7},
+ {S1, S2, S3, S4, S5, S6, S7},
+ {T1, T2, T3, T4, T5, T6, T7}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2),
+ append_sample(V3, TS, S3), append_sample(V4, TS, S4),
+ append_sample(V5, TS, S5), append_sample(V6, TS, S6),
+ append_sample(V7, TS, S7)},
+ {V1 + T1, V2 + T2, V3 + T3, V4 + T4, V5 + T5, V6 + T6, V7 + T7}};
+%% channel_queue_stats_deliver_stats, queue_stats_deliver_stats,
+%% vhost_stats_deliver_stats, channel_stats_deliver_stats
+%% node_coarse_stats
+append_full_sample(TS, {V1, V2, V3, V4, V5, V6, V7, V8},
+ {S1, S2, S3, S4, S5, S6, S7, S8},
+ {T1, T2, T3, T4, T5, T6, T7, T8}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2),
+ append_sample(V3, TS, S3), append_sample(V4, TS, S4),
+ append_sample(V5, TS, S5), append_sample(V6, TS, S6),
+ append_sample(V7, TS, S7), append_sample(V8, TS, S8)},
+ {V1 + T1, V2 + T2, V3 + T3, V4 + T4, V5 + T5, V6 + T6, V7 + T7, V8 + T8}};
+%% channel_process_stats, queue_stats_publish, queue_exchange_stats_publish,
+%% exchange_stats_publish_out, exchange_stats_publish_in, queue_process_stats
+append_full_sample(TS, {V1}, {S1}, {T1}) ->
+ {{append_sample(V1, TS, S1)}, {V1 + T1}};
+%% node_persister_stats
+append_full_sample(TS,
+ {V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14,
+ V15, V16, V17, V18, V19, V20},
+ {S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14,
+ S15, S16, S17, S18, S19, S20},
+ {T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20}
+ ) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2),
+ append_sample(V3, TS, S3), append_sample(V4, TS, S4),
+ append_sample(V5, TS, S5), append_sample(V6, TS, S6),
+ append_sample(V7, TS, S7), append_sample(V8, TS, S8),
+ append_sample(V9, TS, S9), append_sample(V10, TS, S10),
+ append_sample(V11, TS, S11), append_sample(V12, TS, S12),
+ append_sample(V13, TS, S13), append_sample(V14, TS, S14),
+ append_sample(V15, TS, S15), append_sample(V16, TS, S16),
+ append_sample(V17, TS, S17), append_sample(V18, TS, S18),
+ append_sample(V19, TS, S19), append_sample(V20, TS, S20)},
+ {V1 + T1, V2 + T2, V3 + T3, V4 + T4, V5 + T5, V6 + T6, V7 + T7, V8 + T8,
+ V9 + T9, V10 + T10, V11 + T11, V12 + T12, V13 + T13, V14 + T14, V15 + T15,
+ V16 + T16, V17 + T17, V18 + T18, V19 + T19, V20 + T20}};
+%% node_node_coarse_stats, vhost_stats_coarse_connection_stats, queue_msg_rates,
+%% vhost_msg_rates
+append_full_sample(TS, {V1, V2}, {S1, S2}, {T1, T2}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2)}, {V1 + T1, V2 + T2}}.
+
+
+format_rate(connection_stats_coarse_conn_stats, {TR, TS, TRe}, {RR, RS, RRe}) ->
+ [
+ {send_oct, TS},
+ {send_oct_details, [{rate, RS}]},
+ {recv_oct, TR},
+ {recv_oct_details, [{rate, RR}]},
+ {reductions, TRe},
+ {reductions_details, [{rate, RRe}]}
+ ];
+format_rate(vhost_stats_coarse_conn_stats, {TR, TS}, {RR, RS}) ->
+ [
+ {send_oct, TS},
+ {send_oct_details, [{rate, RS}]},
+ {recv_oct, TR},
+ {recv_oct_details, [{rate, RR}]}
+ ];
+format_rate(Type, {TR, TW}, {RR, RW}) when Type =:= vhost_msg_rates;
+ Type =:= queue_msg_rates ->
+ [
+ {disk_reads, TR},
+ {disk_reads_details, [{rate, RR}]},
+ {disk_writes, TW},
+ {disk_writes_details, [{rate, RW}]}
+ ];
+format_rate(Type, {TP, TC, TRe, TDrop}, {RP, RC, RRe, RDrop})
+ when Type =:= channel_stats_fine_stats;
+ Type =:= vhost_stats_fine_stats;
+ Type =:= channel_exchange_stats_fine_stats ->
+ [
+ {publish, TP},
+ {publish_details, [{rate, RP}]},
+ {confirm, TC},
+ {confirm_details, [{rate, RC}]},
+ {return_unroutable, TRe},
+ {return_unroutable_details, [{rate, RRe}]},
+ {drop_unroutable, TDrop},
+ {drop_unroutable_details, [{rate, RDrop}]}
+ ];
+format_rate(Type, {TG, TGN, TD, TDN, TR, TA, TDG, TGE},
+ {RG, RGN, RD, RDN, RR, RA, RDG, RGE})
+ when Type =:= channel_queue_stats_deliver_stats;
+ Type =:= channel_stats_deliver_stats;
+ Type =:= vhost_stats_deliver_stats;
+ Type =:= queue_stats_deliver_stats ->
+ [
+ {get, TG},
+ {get_details, [{rate, RG}]},
+ {get_no_ack, TGN},
+ {get_no_ack_details, [{rate, RGN}]},
+ {deliver, TD},
+ {deliver_details, [{rate, RD}]},
+ {deliver_no_ack, TDN},
+ {deliver_no_ack_details, [{rate, RDN}]},
+ {redeliver, TR},
+ {redeliver_details, [{rate, RR}]},
+ {ack, TA},
+ {ack_details, [{rate, RA}]},
+ {deliver_get, TDG},
+ {deliver_get_details, [{rate, RDG}]},
+ {get_empty, TGE},
+ {get_empty_details, [{rate, RGE}]}
+ ];
+format_rate(Type, {TR}, {RR}) when Type =:= channel_process_stats;
+ Type =:= queue_process_stats ->
+ [
+ {reductions, TR},
+ {reductions_details, [{rate, RR}]}
+ ];
+format_rate(exchange_stats_publish_out, {TP}, {RP}) ->
+ [
+ {publish_out, TP},
+ {publish_out_details, [{rate, RP}]}
+ ];
+format_rate(exchange_stats_publish_in, {TP}, {RP}) ->
+ [
+ {publish_in, TP},
+ {publish_in_details, [{rate, RP}]}
+ ];
+format_rate(Type, {TP}, {RP}) when Type =:= queue_stats_publish;
+ Type =:= queue_exchange_stats_publish ->
+ [
+ {publish, TP},
+ {publish_details, [{rate, RP}]}
+ ];
+format_rate(Type, {TR, TU, TM}, {RR, RU, RM}) when Type =:= queue_msg_stats;
+ Type =:= vhost_msg_stats ->
+ [
+ {messages_ready, TR},
+ {messages_ready_details, [{rate, RR}]},
+ {messages_unacknowledged, TU},
+ {messages_unacknowledged_details, [{rate, RU}]},
+ {messages, TM},
+ {messages_details, [{rate, RM}]}
+ ];
+format_rate(node_coarse_stats, {TF, TS, TM, TD, TP, TGC, TGCW, TCS},
+ {RF, RS, RM, RD, RP, RGC, RGCW, RCS}) ->
+ [
+ {mem_used, TM},
+ {mem_used_details, [{rate, RM}]},
+ {fd_used, TF},
+ {fd_used_details, [{rate, RF}]},
+ {sockets_used, TS},
+ {sockets_used_details, [{rate, RS}]},
+ {proc_used, TP},
+ {proc_used_details, [{rate, RP}]},
+ {disk_free, TD},
+ {disk_free_details, [{rate, RD}]},
+ {gc_num, TGC},
+ {gc_num_details, [{rate, RGC}]},
+ {gc_bytes_reclaimed, TGCW},
+ {gc_bytes_reclaimed_details, [{rate, RGCW}]},
+ {context_switches, TCS},
+ {context_switches_details, [{rate, RCS}]}
+ ];
+format_rate(node_persister_stats,
+ {TIR, TIB, TIA, TIWC, TIWB, TIWAT, TIS, TISAT, TISC,
+ TISEAT, TIRC, TMRTC, TMDTC, TMSRC, TMSWC, TQIJWC, TQIWC, TQIRC,
+ TIO, TIOAT},
+ {RIR, RIB, RIA, RIWC, RIWB, RIWAT, RIS, RISAT, RISC,
+ RISEAT, RIRC, RMRTC, RMDTC, RMSRC, RMSWC, RQIJWC, RQIWC, RQIRC,
+ RIO, RIOAT}) ->
+ %% Calculates average times for read/write/sync/seek from the
+ %% accumulated time and count
+ %% io_<op>_avg_time is the average operation time for the life of the node
+ %% io_<op>_avg_time_details/rate is the average operation time during the
+ %% last time unit calculated (thus similar to an instant rate)
+ [
+ {io_read_count, TIR},
+ {io_read_count_details, [{rate, RIR}]},
+ {io_read_bytes, TIB},
+ {io_read_bytes_details, [{rate, RIB}]},
+ {io_read_avg_time, avg_time(TIA, TIR)},
+ {io_read_avg_time_details, [{rate, avg_time(RIA, RIR)}]},
+ {io_write_count, TIWC},
+ {io_write_count_details, [{rate, RIWC}]},
+ {io_write_bytes, TIWB},
+ {io_write_bytes_details, [{rate, RIWB}]},
+ {io_write_avg_time, avg_time(TIWAT, TIWC)},
+ {io_write_avg_time_details, [{rate, avg_time(RIWAT, RIWC)}]},
+ {io_sync_count, TIS},
+ {io_sync_count_details, [{rate, RIS}]},
+ {io_sync_avg_time, avg_time(TISAT, TIS)},
+ {io_sync_avg_time_details, [{rate, avg_time(RISAT, RIS)}]},
+ {io_seek_count, TISC},
+ {io_seek_count_details, [{rate, RISC}]},
+ {io_seek_avg_time, avg_time(TISEAT, TISC)},
+ {io_seek_avg_time_details, [{rate, avg_time(RISEAT, RISC)}]},
+ {io_reopen_count, TIRC},
+ {io_reopen_count_details, [{rate, RIRC}]},
+ {mnesia_ram_tx_count, TMRTC},
+ {mnesia_ram_tx_count_details, [{rate, RMRTC}]},
+ {mnesia_disk_tx_count, TMDTC},
+ {mnesia_disk_tx_count_details, [{rate, RMDTC}]},
+ {msg_store_read_count, TMSRC},
+ {msg_store_read_count_details, [{rate, RMSRC}]},
+ {msg_store_write_count, TMSWC},
+ {msg_store_write_count_details, [{rate, RMSWC}]},
+ {queue_index_journal_write_count, TQIJWC},
+ {queue_index_journal_write_count_details, [{rate, RQIJWC}]},
+ {queue_index_write_count, TQIWC},
+ {queue_index_write_count_details, [{rate, RQIWC}]},
+ {queue_index_read_count, TQIRC},
+ {queue_index_read_count_details, [{rate, RQIRC}]},
+ {io_file_handle_open_attempt_count, TIO},
+ {io_file_handle_open_attempt_count_details, [{rate, RIO}]},
+ {io_file_handle_open_attempt_avg_time, avg_time(TIOAT, TIO)},
+ {io_file_handle_open_attempt_avg_time_details, [{rate, avg_time(RIOAT, RIO)}]}
+ ];
+format_rate(node_node_coarse_stats, {TS, TR}, {RS, RR}) ->
+ [
+ {send_bytes, TS},
+ {send_bytes_details, [{rate, RS}]},
+ {recv_bytes, TR},
+ {recv_bytes_details, [{rate, RR}]}
+ ];
+format_rate(connection_churn_rates, {TCCr, TCCo, TChCr, TChCo, TQD, TQCr, TQCo},
+ {RCCr, RCCo, RChCr, RChCo, RQD, RQCr, RQCo}) ->
+ [
+ {connection_created, TCCr},
+ {connection_created_details, [{rate, RCCr}]},
+ {connection_closed, TCCo},
+ {connection_closed_details, [{rate, RCCo}]},
+ {channel_created, TChCr},
+ {channel_created_details, [{rate, RChCr}]},
+ {channel_closed, TChCo},
+ {channel_closed_details, [{rate, RChCo}]},
+ {queue_declared, TQD},
+ {queue_declared_details, [{rate, RQD}]},
+ {queue_created, TQCr},
+ {queue_created_details, [{rate, RQCr}]},
+ {queue_deleted, TQCo},
+ {queue_deleted_details, [{rate, RQCo}]}
+ ].
+
+format_rate(connection_stats_coarse_conn_stats, {TR, TS, TRe}, {RR, RS, RRe},
+ {SR, SS, SRe}, {STR, STS, STRe}, Length) ->
+ [
+ {send_oct, TS},
+ {send_oct_details, [{rate, RS},
+ {samples, SS}] ++ average(SS, STS, Length)},
+ {recv_oct, TR},
+ {recv_oct_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)},
+ {reductions, TRe},
+ {reductions_details, [{rate, RRe},
+ {samples, SRe}] ++ average(SRe, STRe, Length)}
+ ];
+format_rate(vhost_stats_coarse_conn_stats, {TR, TS}, {RR, RS}, {SR, SS},
+ {STR, STS}, Length) ->
+ [
+ {send_oct, TS},
+ {send_oct_details, [{rate, RS},
+ {samples, SS}] ++ average(SS, STS, Length)},
+ {recv_oct, TR},
+ {recv_oct_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)}
+ ];
+format_rate(Type, {TR, TW}, {RR, RW}, {SR, SW}, {STR, STW}, Length)
+ when Type =:= vhost_msg_rates;
+ Type =:= queue_msg_rates ->
+ [
+ {disk_reads, TR},
+ {disk_reads_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)},
+ {disk_writes, TW},
+ {disk_writes_details, [{rate, RW},
+ {samples, SW}] ++ average(SW, STW, Length)}
+ ];
+format_rate(Type, {TP, TC, TRe, TDrop}, {RP, RC, RRe, RDrop},
+ {SP, SC, SRe, SDrop}, {STP, STC, STRe, STDrop}, Length)
+ when Type =:= channel_stats_fine_stats;
+ Type =:= vhost_stats_fine_stats;
+ Type =:= channel_exchange_stats_fine_stats ->
+ [
+ {publish, TP},
+ {publish_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)},
+ {confirm, TC},
+ {confirm_details, [{rate, RC},
+ {samples, SC}] ++ average(SC, STC, Length)},
+ {return_unroutable, TRe},
+ {return_unroutable_details, [{rate, RRe},
+ {samples, SRe}] ++ average(SRe, STRe, Length)},
+ {drop_unroutable, TDrop},
+ {drop_unroutable_details, [{rate, RDrop},
+ {samples, SDrop}] ++ average(SDrop, STDrop, Length)}
+ ];
+format_rate(Type, {TG, TGN, TD, TDN, TR, TA, TDG, TGE},
+ {RG, RGN, RD, RDN, RR, RA, RDG, RGE},
+ {SG, SGN, SD, SDN, SR, SA, SDG, SGE},
+ {STG, STGN, STD, STDN, STR, STA, STDG, STGE},
+ Length)
+ when Type =:= channel_queue_stats_deliver_stats;
+ Type =:= channel_stats_deliver_stats;
+ Type =:= vhost_stats_deliver_stats;
+ Type =:= queue_stats_deliver_stats ->
+ [
+ {get, TG},
+ {get_details, [{rate, RG},
+ {samples, SG}] ++ average(SG, STG, Length)},
+ {get_no_ack, TGN},
+ {get_no_ack_details, [{rate, RGN},
+ {samples, SGN}] ++ average(SGN, STGN, Length)},
+ {deliver, TD},
+ {deliver_details, [{rate, RD},
+ {samples, SD}] ++ average(SD, STD, Length)},
+ {deliver_no_ack, TDN},
+ {deliver_no_ack_details, [{rate, RDN},
+ {samples, SDN}] ++ average(SDN, STDN, Length)},
+ {redeliver, TR},
+ {redeliver_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)},
+ {ack, TA},
+ {ack_details, [{rate, RA},
+ {samples, SA}] ++ average(SA, STA, Length)},
+ {deliver_get, TDG},
+ {deliver_get_details, [{rate, RDG},
+ {samples, SDG}] ++ average(SDG, STDG, Length)},
+ {get_empty, TGE},
+ {get_empty_details, [{rate, RGE},
+ {samples, SGE}] ++ average(SGE, STGE, Length)}
+ ];
+format_rate(Type, {TR}, {RR}, {SR}, {STR}, Length)
+ when Type =:= channel_process_stats;
+ Type =:= queue_process_stats ->
+ [
+ {reductions, TR},
+ {reductions_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)}
+ ];
+format_rate(exchange_stats_publish_out, {TP}, {RP}, {SP}, {STP}, Length) ->
+ [
+ {publish_out, TP},
+ {publish_out_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)}
+ ];
+format_rate(exchange_stats_publish_in, {TP}, {RP}, {SP}, {STP}, Length) ->
+ [
+ {publish_in, TP},
+ {publish_in_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)}
+ ];
+format_rate(Type, {TP}, {RP}, {SP}, {STP}, Length)
+ when Type =:= queue_stats_publish;
+ Type =:= queue_exchange_stats_publish ->
+ [
+ {publish, TP},
+ {publish_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)}
+ ];
+format_rate(Type, {TR, TU, TM}, {RR, RU, RM}, {SR, SU, SM}, {STR, STU, STM},
+ Length) when Type =:= queue_msg_stats;
+ Type =:= vhost_msg_stats ->
+ [
+ {messages_ready, TR},
+ {messages_ready_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)},
+ {messages_unacknowledged, TU},
+ {messages_unacknowledged_details, [{rate, RU},
+ {samples, SU}] ++ average(SU, STU, Length)},
+ {messages, TM},
+ {messages_details, [{rate, RM},
+ {samples, SM}] ++ average(SM, STM, Length)}
+ ];
+format_rate(node_coarse_stats, {TF, TS, TM, TD, TP, TGC, TGCW, TCS},
+ {RF, RS, RM, RD, RP, RGC, RGCW, RCS},
+ {SF, SS, SM, SD, SP, SGC, SGCW, SCS},
+ {STF, STS, STM, STD, STP, STGC, STGCW, STCS}, Length) ->
+ [
+ {mem_used, TM},
+ {mem_used_details, [{rate, RM},
+ {samples, SM}] ++ average(SM, STM, Length)},
+ {fd_used, TF},
+ {fd_used_details, [{rate, RF},
+ {samples, SF}] ++ average(SF, STF, Length)},
+ {sockets_used, TS},
+ {sockets_used_details, [{rate, RS},
+ {samples, SS}] ++ average(SS, STS, Length)},
+ {proc_used, TP},
+ {proc_used_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)},
+ {disk_free, TD},
+ {disk_free_details, [{rate, RD},
+ {samples, SD}] ++ average(SD, STD, Length)},
+ {gc_num, TGC},
+ {gc_num_details, [{rate, RGC},
+ {samples, SGC}] ++ average(SGC, STGC, Length)},
+ {gc_bytes_reclaimed, TGCW},
+ {gc_bytes_reclaimed_details, [{rate, RGCW},
+ {samples, SGCW}] ++ average(SGCW, STGCW, Length)},
+ {context_switches, TCS},
+ {context_switches_details, [{rate, RCS},
+ {samples, SCS}] ++ average(SCS, STCS, Length)}
+ ];
+format_rate(node_persister_stats,
+ {TIR, TIB, TIA, TIWC, TIWB, TIWAT, TIS, TISAT, TISC,
+ TISEAT, TIRC, TMRTC, TMDTC, TMSRC, TMSWC, TQIJWC, TQIWC, TQIRC,
+ TIO, TIOAT},
+ {RIR, RIB, _RIA, RIWC, RIWB, _RIWAT, RIS, _RISAT, RISC,
+ _RISEAT, RIRC, RMRTC, RMDTC, RMSRC, RMSWC, RQIJWC, RQIWC, RQIRC,
+ RIO, _RIOAT},
+ {SIR, SIB, SIA, SIWC, SIWB, SIWAT, SIS, SISAT, SISC,
+ SISEAT, SIRC, SMRTC, SMDTC, SMSRC, SMSWC, SQIJWC, SQIWC, SQIRC,
+ SIO, SIOAT},
+ {STIR, STIB, _STIA, STIWC, STIWB, _STIWAT, STIS, _STISAT, STISC,
+ _STISEAT, STIRC, STMRTC, STMDTC, STMSRC, STMSWC, STQIJWC, STQIWC, STQIRC,
+ STIO, _STIOAT}, Length) ->
+ %% Calculates average times for read/write/sync/seek from the
+ %% accumulated time and count
+ %% io_<op>_avg_time is the average operation time for the life of the node
+ %% io_<op>_avg_time_details/rate is the average operation time during the
+ %% last time unit calculated (thus similar to an instant rate)
+
+ [
+ {io_read_count, TIR},
+ {io_read_count_details, [{rate, RIR},
+ {samples, SIR}] ++ average(SIR, STIR, Length)},
+ {io_read_bytes, TIB},
+ {io_read_bytes_details, [{rate, RIB},
+ {samples, SIB}] ++ average(SIB, STIB, Length)},
+ {io_read_avg_time, avg_time(TIA, TIR)},
+ {io_read_avg_time_details, [{samples, unit_samples(SIA, SIR)}] ++
+ avg_time_details(avg_time(TIA, TIR))},
+ {io_write_count, TIWC},
+ {io_write_count_details, [{rate, RIWC},
+ {samples, SIWC}] ++ average(SIWC, STIWC, Length)},
+ {io_write_bytes, TIWB},
+ {io_write_bytes_details, [{rate, RIWB},
+ {samples, SIWB}] ++ average(SIWB, STIWB, Length)},
+ {io_write_avg_time, avg_time(TIWAT, TIWC)},
+ {io_write_avg_time_details, [{samples, unit_samples(SIWAT, SIWC)}] ++
+ avg_time_details(avg_time(TIWAT, TIWC))},
+ {io_sync_count, TIS},
+ {io_sync_count_details, [{rate, RIS},
+ {samples, SIS}] ++ average(SIS, STIS, Length)},
+ {io_sync_avg_time, avg_time(TISAT, TIS)},
+ {io_sync_avg_time_details, [{samples, unit_samples(SISAT, SIS)}] ++
+ avg_time_details(avg_time(TISAT, TIS))},
+ {io_seek_count, TISC},
+ {io_seek_count_details, [{rate, RISC},
+ {samples, SISC}] ++ average(SISC, STISC, Length)},
+ {io_seek_avg_time, avg_time(TISEAT, TISC)},
+ {io_seek_avg_time_details, [{samples, unit_samples(SISEAT, SISC)}] ++
+ avg_time_details(avg_time(TISEAT, TISC))},
+ {io_reopen_count, TIRC},
+ {io_reopen_count_details, [{rate, RIRC},
+ {samples, SIRC}] ++ average(SIRC, STIRC, Length)},
+ {mnesia_ram_tx_count, TMRTC},
+ {mnesia_ram_tx_count_details, [{rate, RMRTC},
+ {samples, SMRTC}] ++ average(SMRTC, STMRTC, Length)},
+ {mnesia_disk_tx_count, TMDTC},
+ {mnesia_disk_tx_count_details, [{rate, RMDTC},
+ {samples, SMDTC}] ++ average(SMDTC, STMDTC, Length)},
+ {msg_store_read_count, TMSRC},
+ {msg_store_read_count_details, [{rate, RMSRC},
+ {samples, SMSRC}] ++ average(SMSRC, STMSRC, Length)},
+ {msg_store_write_count, TMSWC},
+ {msg_store_write_count_details, [{rate, RMSWC},
+ {samples, SMSWC}] ++ average(SMSWC, STMSWC, Length)},
+ {queue_index_journal_write_count, TQIJWC},
+ {queue_index_journal_write_count_details, [{rate, RQIJWC},
+ {samples, SQIJWC}] ++ average(SQIJWC, STQIJWC, Length)},
+ {queue_index_write_count, TQIWC},
+ {queue_index_write_count_details, [{rate, RQIWC},
+ {samples, SQIWC}] ++ average(SQIWC, STQIWC, Length)},
+ {queue_index_read_count, TQIRC},
+ {queue_index_read_count_details, [{rate, RQIRC},
+ {samples, SQIRC}] ++ average(SQIRC, STQIRC, Length)},
+ {io_file_handle_open_attempt_count, TIO},
+ {io_file_handle_open_attempt_count_details, [{rate, RIO},
+ {samples, SIO}] ++ average(SIO, STIO, Length)},
+ {io_file_handle_open_attempt_avg_time, avg_time(TIOAT, TIO)},
+ {io_file_handle_open_attempt_avg_time_details,
+ [{samples, unit_samples(SIOAT, SIO)}] ++ avg_time_details(avg_time(TIOAT, TIO))}
+ ];
+format_rate(node_node_coarse_stats, {TS, TR}, {RS, RR}, {SS, SR}, {STS, STR}, Length) ->
+ [
+ {send_bytes, TS},
+ {send_bytes_details, [{rate, RS},
+ {samples, SS}] ++ average(SS, STS, Length)},
+ {recv_bytes, TR},
+ {recv_bytes_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)}
+ ];
+format_rate(connection_churn_rates, {TCCr, TCCo, TChCr, TChCo, TQD, TQCr, TQCo},
+ {RCCr, RCCo, RChCr, RChCo, RQD, RQCr, RQCo},
+ {SCCr, SCCo, SChCr, SChCo, SQD, SQCr, SQCo},
+ {STCCr, STCCo, STChCr, STChCo, STQD, STQCr, STQCo}, Length) ->
+ [
+ {connection_created, TCCr},
+ {connection_created_details, [{rate, RCCr},
+ {samples, SCCr}] ++ average(SCCr, STCCr, Length)},
+ {connection_closed, TCCo},
+ {connection_closed_details, [{rate, RCCo},
+ {samples, SCCo}] ++ average(SCCo, STCCo, Length)},
+ {channel_created, TChCr},
+ {channel_created_details, [{rate, RChCr},
+ {samples, SChCr}] ++ average(SChCr, STChCr, Length)},
+ {channel_closed, TChCo},
+ {channel_closed_details, [{rate, RChCo},
+ {samples, SChCo}] ++ average(SChCo, STChCo, Length)},
+ {queue_declared, TQD},
+ {queue_declared_details, [{rate, RQD},
+ {samples, SQD}] ++ average(SQD, STQD, Length)},
+ {queue_created, TQCr},
+ {queue_created_details, [{rate, RQCr},
+ {samples, SQCr}] ++ average(SQCr, STQCr, Length)},
+ {queue_deleted, TQCo},
+ {queue_deleted_details, [{rate, RQCo},
+ {samples, SQCo}] ++ average(SQCo, STQCo, Length)}
+ ].
+
+avg_time_details(Avg) ->
+ %% Rates don't make sense here, populate it with the average.
+ [{rate, Avg}, {avg_rate, Avg}, {avg, Avg}].
+
+average(_Samples, _Total, Length) when Length =< 1->
+ [];
+average(Samples, Total, Length) ->
+ [{sample, S2}, {timestamp, T2}] = hd(Samples),
+ [{sample, S1}, {timestamp, T1}] = lists:last(Samples),
+ [{avg_rate, (S2 - S1) * 1000 / (T2 - T1)},
+ {avg, Total / Length}].
+
+rate_from_last_increment(Table, {TS, _} = Last, T, RangePoint) ->
+ case TS - RangePoint of % [0]
+ D when D >= 0 ->
+ case rate_from_last_increment(Last, T) of
+ unknown ->
+ empty(Table, 0.0);
+ Rate ->
+ Rate
+ end;
+ _ ->
+ empty(Table, 0.0)
+ end.
+
+%% [0] Only display the rate if it's live - i.e. ((the end of the
+%% range) - interval) corresponds to the last data point we have
+rate_from_last_increment(_Total, []) ->
+ unknown;
+rate_from_last_increment(Total, [H | _T]) ->
+ rate_from_difference(Total, H).
+
+rate_from_difference({TS0, {A0, A1}}, {TS1, {B0, B1}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval)};
+rate_from_difference({TS0, {A0, A1, A2}}, {TS1, {B0, B1, B2}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3}}, {TS1, {B0, B1, B2, B3}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval), rate(A3, B3, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4}}, {TS1, {B0, B1, B2, B3, B4}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval), rate(A3, B3, Interval),
+ rate(A4, B4, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4, A5}}, {TS1, {B0, B1, B2, B3, B4, B5}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval), rate(A3, B3, Interval),
+ rate(A4, B4, Interval), rate(A5, B5, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4, A5, A6}},
+ {TS1, {B0, B1, B2, B3, B4, B5, B6}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval),
+ rate(A3, B3, Interval), rate(A4, B4, Interval), rate(A5, B5, Interval),
+ rate(A6, B6, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4, A5, A6, A7}},
+ {TS1, {B0, B1, B2, B3, B4, B5, B6, B7}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval),
+ rate(A3, B3, Interval), rate(A4, B4, Interval), rate(A5, B5, Interval),
+ rate(A6, B6, Interval), rate(A7, B7, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13,
+ A14, A15, A16, A17, A18, A19}},
+ {TS1, {B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13,
+ B14, B15, B16, B17, B18, B19}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval),
+ rate(A3, B3, Interval), rate(A4, B4, Interval), rate(A5, B5, Interval),
+ rate(A6, B6, Interval), rate(A7, B7, Interval), rate(A8, B8, Interval),
+ rate(A9, B9, Interval), rate(A10, B10, Interval), rate(A11, B11, Interval),
+ rate(A12, B12, Interval), rate(A13, B13, Interval), rate(A14, B14, Interval),
+ rate(A15, B15, Interval), rate(A16, B16, Interval), rate(A17, B17, Interval),
+ rate(A18, B18, Interval), rate(A19, B19, Interval)};
+rate_from_difference({TS0, {A0}}, {TS1, {B0}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval)}.
+
+rate(V1, V2, Interval) when is_number(V1), is_number(V2) ->
+ (V1 - V2) * 1000 / Interval;
+rate(_, _, _) ->
+ 0.
+
+append_sample(S, TS, List) ->
+ [[{sample, S}, {timestamp, TS}] | List].
+
+%%----------------------------------------------------------------------------
+%% Match specs to select from the ETS tables
+%%----------------------------------------------------------------------------
+
+avg_time(_Total, Count) when Count == 0;
+ Count == 0.0 ->
+ 0.0;
+avg_time(Total, Count) ->
+ (Total / Count) / ?MICRO_TO_MILLI.
+
+unit_samples(Total, Count) ->
+ lists:zipwith(fun(T, C) ->
+ TS = proplists:get_value(timestamp, T),
+ Sample = avg_time(proplists:get_value(sample, T),
+ proplists:get_value(sample, C)),
+ [{sample, Sample}, {timestamp, TS}]
+ end, Total, Count).
+
+empty(Table, Def) ->
+ rabbit_mgmt_data:empty(Table, Def).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_sup.erl b/deps/rabbitmq_management/src/rabbit_mgmt_sup.erl
new file mode 100644
index 0000000000..5f34f0d160
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_sup.erl
@@ -0,0 +1,42 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_sup).
+
+-behaviour(supervisor).
+
+-export([init/1]).
+-export([start_link/0]).
+-export([setup_wm_logging/0]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit_core_metrics.hrl").
+
+init([]) ->
+ DB = {rabbit_mgmt_db, {rabbit_mgmt_db, start_link, []},
+ permanent, ?WORKER_WAIT, worker, [rabbit_mgmt_db]},
+ WP = {management_worker_pool_sup, {worker_pool_sup, start_link, [3, management_worker_pool]},
+ permanent, ?SUPERVISOR_WAIT, supervisor, [management_worker_pool_sup]},
+ DBC = {rabbit_mgmt_db_cache_sup, {rabbit_mgmt_db_cache_sup, start_link, []},
+ permanent, ?SUPERVISOR_WAIT, supervisor, [rabbit_mgmt_db_cache_sup]},
+ {ok, {{one_for_one, 100, 1}, [DB, WP, DBC]}}.
+
+start_link() ->
+ Res = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
+ setup_wm_logging(),
+ Res.
+
+%% While the project has switched to Cowboy for HTTP handling, we still use
+%% the logger from Webmachine; at least until RabbitMQ switches to Lager or
+%% similar.
+setup_wm_logging() ->
+ {ok, LogDir} = application:get_env(rabbitmq_management, http_log_dir),
+ case LogDir of
+ none -> ok;
+ _ -> webmachine_log:add_handler(webmachine_log_handler, [LogDir])
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_sup_sup.erl b/deps/rabbitmq_management/src/rabbit_mgmt_sup_sup.erl
new file mode 100644
index 0000000000..e8dd164869
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_sup_sup.erl
@@ -0,0 +1,28 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_sup_sup).
+
+-behaviour(supervisor2).
+
+-export([init/1]).
+-export([start_link/0, start_child/0]).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+start_child() ->
+ supervisor2:start_child(?MODULE, sup()).
+
+sup() ->
+ {rabbit_mgmt_sup, {rabbit_mgmt_sup, start_link, []},
+ temporary, ?SUPERVISOR_WAIT, supervisor, [rabbit_mgmt_sup]}.
+
+init([]) ->
+ {ok, {{one_for_one, 0, 1}, [sup()]}}.
+
+start_link() ->
+ supervisor2:start_link({local, ?MODULE}, ?MODULE, []).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_util.erl b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl
new file mode 100644
index 0000000000..93f167e480
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl
@@ -0,0 +1,1220 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_util).
+
+%% TODO sort all this out; maybe there's scope for rabbit_mgmt_request?
+
+-export([is_authorized/2, is_authorized_admin/2, is_authorized_admin/4,
+ is_authorized_admin/3, vhost/1, vhost_from_headers/1]).
+-export([is_authorized_vhost/2, is_authorized_user/3,
+ is_authorized_user/4,
+ is_authorized_monitor/2, is_authorized_policies/2,
+ is_authorized_vhost_visible/2,
+ is_authorized_global_parameters/2]).
+
+-export([bad_request/3, bad_request_exception/4, internal_server_error/4,
+ id/2, parse_bool/1, parse_int/1]).
+-export([with_decode/4, not_found/3]).
+-export([with_channel/4, with_channel/5]).
+-export([props_to_method/2, props_to_method/4]).
+-export([all_or_one_vhost/2, reply/3, responder_map/1,
+ filter_vhost/3]).
+-export([filter_conn_ch_list/3, filter_user/2, list_login_vhosts/2,
+ list_login_vhosts_names/2]).
+-export([filter_tracked_conn_list/3]).
+-export([with_decode/5, decode/1, decode/2, set_resp_header/3,
+ args/1, read_complete_body/1]).
+-export([reply_list/3, reply_list/5, reply_list/4,
+ sort_list/2, destination_type/1, reply_list_or_paginate/3
+ ]).
+-export([post_respond/1, columns/1, is_monitor/1]).
+-export([list_visible_vhosts/1, list_visible_vhosts_names/1,
+ b64decode_or_throw/1, no_range/0, range/1,
+ range_ceil/1, floor/2, ceil/1, ceil/2]).
+-export([pagination_params/1,
+ maybe_filter_by_keyword/4,
+ get_value_param/2,
+ augment_resources/6
+ ]).
+-export([direct_request/6]).
+-export([qs_val/2]).
+-export([get_path_prefix/0]).
+-export([catch_no_such_user_or_vhost/2]).
+
+-export([disable_stats/1, enable_queue_totals/1]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+-define(FRAMING, rabbit_framing_amqp_0_9_1).
+-define(DEFAULT_PAGE_SIZE, 100).
+-define(MAX_PAGE_SIZE, 500).
+
+-define(MAX_RANGE, 500).
+
+-record(pagination, {page = undefined, page_size = undefined,
+ name = undefined, use_regex = undefined}).
+
+-record(aug_ctx, {req_data :: cowboy_req:req(),
+ pagination :: #pagination{},
+ sort = [] :: [atom()],
+ columns = [] :: [atom()],
+ data :: term()}).
+
+%%--------------------------------------------------------------------
+
+is_authorized(ReqData, Context) ->
+ is_authorized(ReqData, Context, '', fun(_) -> true end).
+
+is_authorized_admin(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"Not administrator user">>,
+ fun(#user{tags = Tags}) -> is_admin(Tags) end).
+
+is_authorized_admin(ReqData, Context, Token) ->
+ is_authorized(ReqData, Context,
+ rabbit_data_coercion:to_binary(
+ application:get_env(rabbitmq_management, uaa_client_id, "")),
+ Token, <<"Not administrator user">>,
+ fun(#user{tags = Tags}) -> is_admin(Tags) end).
+
+is_authorized_admin(ReqData, Context, Username, Password) ->
+ case is_basic_auth_disabled() of
+ true ->
+ Msg = "HTTP access denied: basic auth disabled",
+ rabbit_log:warning(Msg),
+ not_authorised(Msg, ReqData, Context);
+ false ->
+ is_authorized(ReqData, Context, Username, Password,
+ <<"Not administrator user">>,
+ fun(#user{tags = Tags}) -> is_admin(Tags) end)
+ end.
+
+is_authorized_monitor(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"Not monitor user">>,
+ fun(#user{tags = Tags}) -> is_monitor(Tags) end).
+
+is_authorized_vhost(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access virtual host">>,
+ fun(#user{tags = Tags} = User) ->
+ is_admin(Tags) orelse user_matches_vhost(ReqData, User)
+ end).
+
+is_authorized_vhost_visible(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access virtual host">>,
+ fun(#user{tags = Tags} = User) ->
+ is_admin(Tags) orelse user_matches_vhost_visible(ReqData, User)
+ end).
+
+disable_stats(ReqData) ->
+ MgmtOnly = case qs_val(<<"disable_stats">>, ReqData) of
+ <<"true">> -> true;
+ _ -> false
+ end,
+ MgmtOnly orelse get_bool_env(rabbitmq_management, disable_management_stats, false)
+ orelse get_bool_env(rabbitmq_management_agent, disable_metrics_collector, false).
+
+enable_queue_totals(ReqData) ->
+ EnableTotals = case qs_val(<<"enable_queue_totals">>, ReqData) of
+ <<"true">> -> true;
+ _ -> false
+ end,
+ EnableTotals orelse get_bool_env(rabbitmq_management, enable_queue_totals, false).
+
+get_bool_env(Application, Par, Default) ->
+ case application:get_env(Application, Par, Default) of
+ true -> true;
+ false -> false;
+ Other ->
+ rabbit_log:warning("Invalid configuration for application ~p: ~p set to ~p",
+ [Application, Par, Other]),
+ Default
+ end.
+
+user_matches_vhost(ReqData, User) ->
+ case vhost(ReqData) of
+ not_found -> true;
+ none -> true;
+ V ->
+ AuthzData = get_authz_data(ReqData),
+ lists:member(V, list_login_vhosts_names(User, AuthzData))
+ end.
+
+user_matches_vhost_visible(ReqData, User) ->
+ case vhost(ReqData) of
+ not_found -> true;
+ none -> true;
+ V ->
+ AuthzData = get_authz_data(ReqData),
+ lists:member(V, list_visible_vhosts_names(User, AuthzData))
+ end.
+
+get_authz_data(ReqData) ->
+ {PeerAddress, _PeerPort} = cowboy_req:peer(ReqData),
+ {ip, PeerAddress}.
+
+%% Used for connections / channels. A normal user can only see / delete
+%% their own stuff. Monitors can see other users' and delete their
+%% own. Admins can do it all.
+is_authorized_user(ReqData, Context, Item) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access object">>,
+ fun(#user{username = Username, tags = Tags}) ->
+ case cowboy_req:method(ReqData) of
+ <<"DELETE">> -> is_admin(Tags);
+ _ -> is_monitor(Tags)
+ end orelse Username == pget(user, Item)
+ end).
+
+%% For policies / parameters. Like is_authorized_vhost but you have to
+%% be a policymaker.
+is_authorized_policies(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access object">>,
+ fun(User = #user{tags = Tags}) ->
+ is_admin(Tags) orelse
+ (is_policymaker(Tags) andalso
+ user_matches_vhost(ReqData, User))
+ end).
+
+%% For global parameters. Must be policymaker.
+is_authorized_global_parameters(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access object">>,
+ fun(#user{tags = Tags}) ->
+ is_policymaker(Tags)
+ end).
+
+is_basic_auth_disabled() ->
+ get_bool_env(rabbitmq_management, disable_basic_auth, false).
+
+is_authorized(ReqData, Context, ErrorMsg, Fun) ->
+ case cowboy_req:method(ReqData) of
+ <<"OPTIONS">> -> {true, ReqData, Context};
+ _ -> is_authorized1(ReqData, Context, ErrorMsg, Fun)
+ end.
+
+is_authorized1(ReqData, Context, ErrorMsg, Fun) ->
+ case cowboy_req:parse_header(<<"authorization">>, ReqData) of
+ {basic, Username, Password} ->
+ case is_basic_auth_disabled() of
+ true ->
+ Msg = "HTTP access denied: basic auth disabled",
+ rabbit_log:warning(Msg),
+ not_authorised(Msg, ReqData, Context);
+ false ->
+ is_authorized(ReqData, Context,
+ Username, Password,
+ ErrorMsg, Fun)
+ end;
+ {bearer, Token} ->
+ Username = rabbit_data_coercion:to_binary(
+ application:get_env(rabbitmq_management, uaa_client_id, "")),
+ is_authorized(ReqData, Context, Username, Token, ErrorMsg, Fun);
+ _ ->
+ case is_basic_auth_disabled() of
+ true ->
+ Msg = "HTTP access denied: basic auth disabled",
+ rabbit_log:warning(Msg),
+ not_authorised(Msg, ReqData, Context);
+ false ->
+ {{false, ?AUTH_REALM}, ReqData, Context}
+ end
+ end.
+
+is_authorized_user(ReqData, Context, Username, Password) ->
+ Msg = <<"User not authorized">>,
+ Fun = fun(_) -> true end,
+ is_authorized(ReqData, Context, Username, Password, Msg, Fun).
+
+is_authorized(ReqData, Context, Username, Password, ErrorMsg, Fun) ->
+ ErrFun = fun (Msg) ->
+ rabbit_log:warning("HTTP access denied: user '~s' - ~s",
+ [Username, Msg]),
+ not_authorised(Msg, ReqData, Context)
+ end,
+ AuthProps = [{password, Password}] ++ case vhost(ReqData) of
+ VHost when is_binary(VHost) -> [{vhost, VHost}];
+ _ -> []
+ end,
+ {IP, _} = cowboy_req:peer(ReqData),
+ RemoteAddress = list_to_binary(inet:ntoa(IP)),
+ case rabbit_access_control:check_user_login(Username, AuthProps) of
+ {ok, User = #user{tags = Tags}} ->
+ case rabbit_access_control:check_user_loopback(Username, IP) of
+ ok ->
+ case is_mgmt_user(Tags) of
+ true ->
+ case Fun(User) of
+ true ->
+ rabbit_core_metrics:auth_attempt_succeeded(RemoteAddress,
+ Username, http),
+ {true, ReqData,
+ Context#context{user = User,
+ password = Password}};
+ false ->
+ rabbit_core_metrics:auth_attempt_failed(RemoteAddress,
+ Username, http),
+ ErrFun(ErrorMsg)
+ end;
+ false ->
+ rabbit_core_metrics:auth_attempt_failed(RemoteAddress, Username, http),
+ ErrFun(<<"Not management user">>)
+ end;
+ not_allowed ->
+ rabbit_core_metrics:auth_attempt_failed(RemoteAddress, Username, http),
+ ErrFun(<<"User can only log in via localhost">>)
+ end;
+ {refused, _Username, Msg, Args} ->
+ rabbit_core_metrics:auth_attempt_failed(RemoteAddress, Username, http),
+ rabbit_log:warning("HTTP access denied: ~s",
+ [rabbit_misc:format(Msg, Args)]),
+ not_authorised(<<"Login failed">>, ReqData, Context)
+ end.
+
+vhost_from_headers(ReqData) ->
+ case cowboy_req:header(<<"x-vhost">>, ReqData) of
+ undefined -> none;
+ %% blank x-vhost means "All hosts" is selected in the UI
+ <<>> -> none;
+ VHost -> VHost
+ end.
+
+vhost(ReqData) ->
+ case id(vhost, ReqData) of
+ none -> vhost_from_headers(ReqData);
+ VHost -> case rabbit_vhost:exists(VHost) of
+ true -> VHost;
+ false -> not_found
+ end
+ end.
+
+destination_type(ReqData) ->
+ case id(dtype, ReqData) of
+ <<"e">> -> exchange;
+ <<"q">> -> queue
+ end.
+
+%% Provides a map of content type-to-responder that are supported by
+%% reply/3. The map can be used in the content_types_provided/2 callback
+%% used by cowboy_rest. Responder functions must be
+%% exported from the caller module and must use reply/3
+%% under the hood.
+responder_map(FunctionName) ->
+ [
+ {<<"application/json">>, FunctionName},
+ {<<"application/bert">>, FunctionName}
+ ].
+
+reply({stop, _, _} = Reply, _ReqData, _Context) ->
+ Reply;
+reply(Facts, ReqData, Context) ->
+ reply0(extract_columns(Facts, ReqData), ReqData, Context).
+
+reply0(Facts, ReqData, Context) ->
+ ReqData1 = set_resp_header(<<"cache-control">>, "no-cache", ReqData),
+ try
+ case maps:get(media_type, ReqData1, undefined) of
+ {<<"application">>, <<"bert">>, _} ->
+ {term_to_binary(Facts), ReqData1, Context};
+ _ ->
+ {rabbit_json:encode(rabbit_mgmt_format:format_nulls(Facts)),
+ ReqData1, Context}
+ end
+ catch exit:{json_encode, E} ->
+ Error = iolist_to_binary(
+ io_lib:format("JSON encode error: ~p", [E])),
+ Reason = iolist_to_binary(
+ io_lib:format("While encoding: ~n~p", [Facts])),
+ internal_server_error(Error, Reason, ReqData1, Context)
+ end.
+
+reply_list(Facts, ReqData, Context) ->
+ reply_list(Facts, ["vhost", "name"], ReqData, Context, undefined).
+
+reply_list(Facts, DefaultSorts, ReqData, Context) ->
+ reply_list(Facts, DefaultSorts, ReqData, Context, undefined).
+
+get_value_param(Name, ReqData) ->
+ case qs_val(Name, ReqData) of
+ undefined -> undefined;
+ Bin -> binary_to_list(Bin)
+ end.
+
+reply_list(Facts, DefaultSorts, ReqData, Context, Pagination) ->
+ SortList =
+ sort_list_and_paginate(
+ extract_columns_list(Facts, ReqData),
+ DefaultSorts,
+ get_value_param(<<"sort">>, ReqData),
+ get_sort_reverse(ReqData), Pagination),
+
+ reply(SortList, ReqData, Context).
+
+-spec get_sort_reverse(cowboy_req:req()) -> atom().
+get_sort_reverse(ReqData) ->
+ case get_value_param(<<"sort_reverse">>, ReqData) of
+ undefined -> false;
+ V -> list_to_atom(V)
+ end.
+
+
+-spec is_pagination_requested(#pagination{} | undefined) -> boolean().
+is_pagination_requested(undefined) ->
+ false;
+is_pagination_requested(#pagination{}) ->
+ true.
+
+
+with_valid_pagination(ReqData, Context, Fun) ->
+ try
+ Pagination = pagination_params(ReqData),
+ Fun(Pagination)
+ catch error:badarg ->
+ Reason = iolist_to_binary(
+ io_lib:format("Pagination parameters are invalid", [])),
+ invalid_pagination(bad_request, Reason, ReqData, Context);
+ {error, ErrorType, S} ->
+ Reason = iolist_to_binary(S),
+ invalid_pagination(ErrorType, Reason, ReqData, Context)
+ end.
+
+reply_list_or_paginate(Facts, ReqData, Context) ->
+ with_valid_pagination(
+ ReqData, Context,
+ fun(Pagination) ->
+ reply_list(Facts, ["vhost", "name"], ReqData, Context, Pagination)
+ end).
+
+merge_sorts(DefaultSorts, Extra) ->
+ case Extra of
+ undefined -> DefaultSorts;
+ Extra -> [Extra | DefaultSorts]
+ end.
+
+%% Resource augmentation. Works out the most optimal configuration of the operations:
+%% sort, page, augment and executes it returning the result.
+
+column_strategy(all, _) -> extended;
+column_strategy(Cols, BasicColumns) ->
+ case Cols -- BasicColumns of
+ [] -> basic;
+ _ -> extended
+ end.
+
+% columns are [[binary()]] - this takes the first item
+columns_as_strings(all) -> all;
+columns_as_strings(Columns0) ->
+ [rabbit_data_coercion:to_list(C) || [C | _] <- Columns0].
+
+augment_resources(Resources, DefaultSort, BasicColumns, ReqData, Context,
+ AugmentFun) ->
+ with_valid_pagination(ReqData, Context,
+ fun (Pagination) ->
+ augment_resources0(Resources, DefaultSort,
+ BasicColumns, Pagination,
+ ReqData, AugmentFun)
+ end).
+
+augment_resources0(Resources, DefaultSort, BasicColumns, Pagination, ReqData,
+ AugmentFun) ->
+ SortFun = fun (AugCtx) -> sort(DefaultSort, AugCtx) end,
+ AugFun = fun (AugCtx) -> augment(AugmentFun, AugCtx) end,
+ PageFun = fun page/1,
+ Pagination = pagination_params(ReqData),
+ Sort = def(get_value_param(<<"sort">>, ReqData), DefaultSort),
+ Columns = def(columns(ReqData), all),
+ ColumnsAsStrings = columns_as_strings(Columns),
+ Pipeline =
+ case {Pagination =/= undefined,
+ column_strategy(Sort, BasicColumns),
+ column_strategy(ColumnsAsStrings, BasicColumns)} of
+ {false, basic, basic} -> % no pagination, no extended fields
+ [SortFun];
+ {false, _, _} ->
+ % no pagination, extended columns means we need to augment all - SLOW
+ [AugFun, SortFun];
+ {true, basic, basic} ->
+ [SortFun, PageFun];
+ {true, extended, _} ->
+ % pagination with extended sort columns - SLOW
+ [AugFun, SortFun, PageFun];
+ {true, basic, extended} ->
+ % pagination with extended columns and sorting on basic
+ % here we can reduce the augmentation set before
+ % augmenting
+ [SortFun, PageFun, AugFun]
+ end,
+ #aug_ctx{data = {_, Reply}} = run_augmentation(
+ #aug_ctx{req_data = ReqData,
+ pagination = Pagination,
+ sort = Sort,
+ columns = Columns,
+ data = {loaded, Resources}},
+ Pipeline),
+ rabbit_mgmt_format:strip_pids(Reply).
+
+run_augmentation(C, []) -> C;
+run_augmentation(C, [Next | Rem]) ->
+ C2 = Next(C),
+ run_augmentation(C2, Rem).
+
+sort(DefaultSort, Ctx = #aug_ctx{data = Data0,
+ req_data = ReqData,
+ sort = Sort}) ->
+ Data1 = get_data(Data0),
+ Data = sort_list(Data1, DefaultSort, Sort,
+ get_sort_reverse(ReqData)),
+ Ctx#aug_ctx{data = update_data(Data0, {sorted, Data})}.
+
+page(Ctx = #aug_ctx{data = Data0,
+ pagination = Pagination}) ->
+ Data = filter_and_paginate(get_data(Data0), Pagination),
+ Ctx#aug_ctx{data = update_data(Data0, {paged, Data})}.
+
+update_data({paged, Old}, New) ->
+ {paged, rabbit_misc:pset(items, get_data(New), Old)};
+update_data(_, New) ->
+ New.
+
+augment(AugmentFun, Ctx = #aug_ctx{data = Data0, req_data = ReqData}) ->
+ Data1 = get_data(Data0),
+ Data = AugmentFun(Data1, ReqData),
+ Ctx#aug_ctx{data = update_data(Data0, {augmented, Data})}.
+
+get_data({paged, Data}) ->
+ rabbit_misc:pget(items, Data);
+get_data({_, Data}) ->
+ Data.
+
+get_path_prefix() ->
+ EnvPrefix = rabbit_misc:get_env(rabbitmq_management, path_prefix, ""),
+ fixup_prefix(EnvPrefix).
+
+fixup_prefix("") ->
+ "";
+fixup_prefix([Char|_Rest]=EnvPrefix) when is_list(EnvPrefix), Char =:= $/ ->
+ EnvPrefix;
+fixup_prefix(EnvPrefix) when is_list(EnvPrefix) ->
+ "/" ++ EnvPrefix;
+fixup_prefix(EnvPrefix) when is_binary(EnvPrefix) ->
+ fixup_prefix(rabbit_data_coercion:to_list(EnvPrefix)).
+
+%% XXX sort_list_and_paginate/2 is a more proper name for this function, keeping it
+%% with this name for backwards compatibility
+-spec sort_list([Fact], [string()]) -> [Fact] when
+ Fact :: [{atom(), term()}].
+sort_list(Facts, Sorts) -> sort_list_and_paginate(Facts, Sorts, undefined, false,
+ undefined).
+
+-spec sort_list([Fact], [SortColumn], [SortColumn] | undefined, boolean()) -> [Fact] when
+ Fact :: [{atom(), term()}],
+ SortColumn :: string().
+sort_list(Facts, _, [], _) ->
+ %% Do not sort when we are explicitly requested to sort with an
+ %% empty sort columns list. Note that this clause won't match when
+ %% 'sort' parameter is not provided in a HTTP request at all.
+ Facts;
+sort_list(Facts, DefaultSorts, Sort, Reverse) ->
+ SortList = merge_sorts(DefaultSorts, Sort),
+ %% lists:sort/2 is much more expensive than lists:sort/1
+ Sorted = [V || {_K, V} <- lists:sort(
+ [{sort_key(F, SortList), F} || F <- Facts])],
+ maybe_reverse(Sorted, Reverse).
+
+sort_list_and_paginate(Facts, DefaultSorts, Sort, Reverse, Pagination) ->
+ filter_and_paginate(sort_list(Facts, DefaultSorts, Sort, Reverse), Pagination).
+
+filter_and_paginate(Sorted, Pagination) ->
+ ContextList = maybe_filter_context(Sorted, Pagination),
+ range_filter(ContextList, Pagination, Sorted).
+
+%%
+%% Filtering functions
+%%
+maybe_filter_context(List, #pagination{name = Name, use_regex = UseRegex}) when
+ is_list(Name) ->
+ lists:filter(fun(ListF) ->
+ maybe_filter_by_keyword(name, Name, ListF, UseRegex)
+ end,
+ List);
+%% Here it is backward with the other API(s), that don't filter the data
+maybe_filter_context(List, _) ->
+ List.
+
+
+match_value({_, Value}, ValueTag, UseRegex) when UseRegex =:= "true" ->
+ case re:run(Value, ValueTag, [caseless]) of
+ {match, _} -> true;
+ nomatch -> false
+ end;
+match_value({_, Value}, ValueTag, _) ->
+ Pos = string:str(string:to_lower(binary_to_list(Value)),
+ string:to_lower(ValueTag)),
+ case Pos of
+ Pos when Pos > 0 -> true;
+ _ -> false
+ end.
+
+maybe_filter_by_keyword(KeyTag, ValueTag, List, UseRegex) when
+ is_list(ValueTag), length(ValueTag) > 0 ->
+ match_value(lists:keyfind(KeyTag, 1, List), ValueTag, UseRegex);
+maybe_filter_by_keyword(_, _, _, _) ->
+ true.
+
+check_request_param(V, ReqData) ->
+ case qs_val(V, ReqData) of
+ undefined -> undefined;
+ Str -> list_to_integer(binary_to_list(Str))
+ end.
+
+%% Validates and returns pagination parameters:
+%% Page is assumed to be > 0, PageSize > 0 PageSize <= ?MAX_PAGE_SIZE
+pagination_params(ReqData) ->
+ PageNum = check_request_param(<<"page">>, ReqData),
+ PageSize = check_request_param(<<"page_size">>, ReqData),
+ Name = get_value_param(<<"name">>, ReqData),
+ UseRegex = get_value_param(<<"use_regex">>, ReqData),
+ case {PageNum, PageSize} of
+ {undefined, _} ->
+ undefined;
+ {PageNum, undefined} when is_integer(PageNum) andalso PageNum > 0 ->
+ #pagination{page = PageNum, page_size = ?DEFAULT_PAGE_SIZE,
+ name = Name, use_regex = UseRegex};
+ {PageNum, PageSize} when is_integer(PageNum)
+ andalso is_integer(PageSize)
+ andalso (PageNum > 0)
+ andalso (PageSize > 0)
+ andalso (PageSize =< ?MAX_PAGE_SIZE) ->
+ #pagination{page = PageNum, page_size = PageSize,
+ name = Name, use_regex = UseRegex};
+ _ -> throw({error, invalid_pagination_parameters,
+ io_lib:format("Invalid pagination parameters: page number ~p, page size ~p",
+ [PageNum, PageSize])})
+ end.
+
+-spec maybe_reverse([any()], string() | true | false) -> [any()].
+maybe_reverse([], _) ->
+ [];
+maybe_reverse(RangeList, true) when is_list(RangeList) ->
+ lists:reverse(RangeList);
+maybe_reverse(RangeList, false) ->
+ RangeList.
+
+%% for backwards compatibility, does not filter the list
+range_filter(List, undefined, _)
+ -> List;
+
+range_filter(List, RP = #pagination{page = PageNum, page_size = PageSize},
+ TotalElements) ->
+ Offset = (PageNum - 1) * PageSize + 1,
+ try
+ range_response(sublist(List, Offset, PageSize), RP, List,
+ TotalElements)
+ catch
+ error:function_clause ->
+ Reason = io_lib:format(
+ "Page out of range, page: ~p page size: ~p, len: ~p",
+ [PageNum, PageSize, length(List)]),
+ throw({error, page_out_of_range, Reason})
+ end.
+
+%% Injects pagination information into
+range_response([], #pagination{page = PageNum, page_size = PageSize},
+ TotalFiltered, TotalElements) ->
+ TotalPages = trunc((length(TotalFiltered) + PageSize - 1) / PageSize),
+ [{total_count, length(TotalElements)},
+ {item_count, 0},
+ {filtered_count, length(TotalFiltered)},
+ {page, PageNum},
+ {page_size, PageSize},
+ {page_count, TotalPages},
+ {items, []}
+ ];
+range_response(List, #pagination{page = PageNum, page_size = PageSize},
+ TotalFiltered, TotalElements) ->
+ TotalPages = trunc((length(TotalFiltered) + PageSize - 1) / PageSize),
+ [{total_count, length(TotalElements)},
+ {item_count, length(List)},
+ {filtered_count, length(TotalFiltered)},
+ {page, PageNum},
+ {page_size, PageSize},
+ {page_count, TotalPages},
+ {items, List}
+ ].
+
+sort_key(_Item, []) ->
+ [];
+sort_key(Item, [Sort | Sorts]) ->
+ [get_dotted_value(Sort, Item) | sort_key(Item, Sorts)].
+
+get_dotted_value(Key, Item) ->
+ Keys = string:tokens(Key, "."),
+ get_dotted_value0(Keys, Item).
+
+get_dotted_value0([Key], Item) ->
+ %% Put "nothing" before everything else, in number terms it usually
+ %% means 0.
+ pget_bin(list_to_binary(Key), Item, 0);
+get_dotted_value0([Key | Keys], Item) ->
+ get_dotted_value0(Keys, pget_bin(list_to_binary(Key), Item, [])).
+
+pget_bin(Key, Map, Default) when is_map(Map) ->
+ maps:get(Key, Map, Default);
+pget_bin(Key, List, Default) when is_list(List) ->
+ case lists:partition(fun ({K, _V}) -> a2b(K) =:= Key end, List) of
+ {[{_K, V}], _} -> V;
+ {[], _} -> Default
+ end.
+maybe_pagination(Item, false, ReqData) ->
+ extract_column_items(Item, columns(ReqData));
+maybe_pagination([{items, Item} | T], true, ReqData) ->
+ [{items, extract_column_items(Item, columns(ReqData))} |
+ maybe_pagination(T, true, ReqData)];
+maybe_pagination([H | T], true, ReqData) ->
+ [H | maybe_pagination(T,true, ReqData)];
+maybe_pagination(Item, true, ReqData) ->
+ [maybe_pagination(X, true, ReqData) || X <- Item].
+
+extract_columns(Item, ReqData) ->
+ maybe_pagination(Item, is_pagination_requested(pagination_params(ReqData)),
+ ReqData).
+
+extract_columns_list(Items, ReqData) ->
+ Cols = columns(ReqData),
+ [extract_column_items(Item, Cols) || Item <- Items].
+
+columns(ReqData) ->
+ case qs_val(<<"columns">>, ReqData) of
+ undefined -> all;
+ Bin -> [[list_to_binary(T) || T <- string:tokens(C, ".")]
+ || C <- string:tokens(binary_to_list(Bin), ",")]
+ end.
+
+extract_column_items(Item, all) ->
+ Item;
+extract_column_items(Item = [T | _], Cols) when is_tuple(T) ->
+ [{K, extract_column_items(V, descend_columns(a2b(K), Cols))} ||
+ {K, V} <- Item, want_column(a2b(K), Cols)];
+extract_column_items(L, Cols) when is_list(L) ->
+ [extract_column_items(I, Cols) || I <- L];
+extract_column_items(O, _Cols) ->
+ O.
+
+% want_column(_Col, all) -> true;
+want_column(Col, Cols) -> lists:any(fun([C|_]) -> C == Col end, Cols).
+
+descend_columns(_K, []) -> [];
+descend_columns( K, [[K] | _Rest]) -> all;
+descend_columns( K, [[K | K2] | Rest]) -> [K2 | descend_columns(K, Rest)];
+descend_columns( K, [[_K2 | _ ] | Rest]) -> descend_columns(K, Rest).
+
+a2b(A) when is_atom(A) -> list_to_binary(atom_to_list(A));
+a2b(B) -> B.
+
+bad_request(Reason, ReqData, Context) ->
+ halt_response(400, bad_request, Reason, ReqData, Context).
+
+not_authorised(Reason, ReqData, Context) ->
+ halt_response(401, not_authorised, Reason, ReqData, Context).
+
+not_found(Reason, ReqData, Context) ->
+ halt_response(404, not_found, Reason, ReqData, Context).
+
+internal_server_error(Error, Reason, ReqData, Context) ->
+ rabbit_log:error("~s~n~s", [Error, Reason]),
+ halt_response(500, Error, Reason, ReqData, Context).
+
+invalid_pagination(Type,Reason, ReqData, Context) ->
+ halt_response(400, Type, Reason, ReqData, Context).
+
+halt_response(Code, Type, Reason, ReqData, Context) ->
+ ReasonFormatted = format_reason(Reason),
+ Json = #{<<"error">> => Type,
+ <<"reason">> => ReasonFormatted},
+ ReqData1 = cowboy_req:reply(Code,
+ #{<<"content-type">> => <<"application/json">>},
+ rabbit_json:encode(Json), ReqData),
+ {stop, ReqData1, Context}.
+
+format_reason(Tuple) when is_tuple(Tuple) ->
+ rabbit_mgmt_format:tuple(Tuple);
+format_reason(Binary) when is_binary(Binary) ->
+ Binary;
+format_reason(Other) ->
+ case is_string(Other) of
+ true -> rabbit_mgmt_format:print("~ts", [Other]);
+ false -> rabbit_mgmt_format:print("~p", [Other])
+ end.
+
+is_string(List) when is_list(List) ->
+ lists:all(
+ fun(El) -> is_integer(El) andalso El > 0 andalso El < 16#10ffff end,
+ List);
+is_string(_) -> false.
+
+id(Key, ReqData) when Key =:= exchange;
+ Key =:= source;
+ Key =:= destination ->
+ case id0(Key, ReqData) of
+ <<"amq.default">> -> <<"">>;
+ Name -> Name
+ end;
+id(Key, ReqData) ->
+ id0(Key, ReqData).
+
+id0(Key, ReqData) ->
+ case cowboy_req:binding(Key, ReqData) of
+ undefined -> none;
+ Id -> Id
+ end.
+
+read_complete_body(Req) ->
+ read_complete_body(Req, <<"">>).
+read_complete_body(Req0, Acc) ->
+ case cowboy_req:read_body(Req0) of
+ {ok, Data, Req} -> {ok, <<Acc/binary, Data/binary>>, Req};
+ {more, Data, Req} -> read_complete_body(Req, <<Acc/binary, Data/binary>>)
+ end.
+
+with_decode(Keys, ReqData, Context, Fun) ->
+ {ok, Body, ReqData1} = read_complete_body(ReqData),
+ with_decode(Keys, Body, ReqData1, Context, Fun).
+
+with_decode(Keys, Body, ReqData, Context, Fun) ->
+ case decode(Keys, Body) of
+ {error, Reason} -> bad_request(Reason, ReqData, Context);
+ {ok, Values, JSON} -> try
+ Fun(Values, JSON, ReqData)
+ catch {error, Error} ->
+ bad_request(Error, ReqData, Context)
+ end
+ end.
+
+decode(Keys, Body) ->
+ case decode(Body) of
+ {ok, J0} ->
+ J = maps:fold(fun(K, V, Acc) ->
+ Acc#{binary_to_atom(K, utf8) => V}
+ end, J0, J0),
+ Results = [get_or_missing(K, J) || K <- Keys],
+ case [E || E = {key_missing, _} <- Results] of
+ [] -> {ok, Results, J};
+ Errors -> {error, Errors}
+ end;
+ Else -> Else
+ end.
+
+-type parsed_json() :: map() | atom() | binary().
+-spec decode(binary()) -> {ok, parsed_json()} | {error, term()}.
+
+decode(<<"">>) ->
+ {ok, #{}};
+%% some HTTP API clients include "null" for payload in certain scenarios
+decode(<<"null">>) ->
+ {ok, #{}};
+decode(<<"undefined">>) ->
+ {ok, #{}};
+decode(Body) ->
+ try
+ case rabbit_json:decode(Body) of
+ Val when is_map(Val) ->
+ {ok, Val};
+ Val when is_atom(Val) ->
+ {ok, #{}};
+ %% handle double encoded JSON, see rabbitmq/rabbitmq-management#839
+ Bin when is_binary(Bin) ->
+ {error, "invalid payload: the request body JSON-decoded to a string. "
+ "Is the input doubly-JSON-encoded?"};
+ _ ->
+ {error, not_json}
+ end
+ catch error:_ -> {error, not_json}
+ end.
+
+get_or_missing(K, L) ->
+ case maps:get(K, L, undefined) of
+ undefined -> {key_missing, K};
+ V -> V
+ end.
+
+get_node(Props) ->
+ case maps:get(<<"node">>, Props, undefined) of
+ undefined -> node();
+ N -> rabbit_nodes:make(
+ binary_to_list(N))
+ end.
+
+direct_request(MethodName, Transformers, Extra, ErrorMsg, ReqData,
+ Context = #context{user = User}) ->
+ with_vhost_and_props(
+ fun(VHost, Props, ReqData1) ->
+ Method = props_to_method(MethodName, Props, Transformers, Extra),
+ Node = get_node(Props),
+ case rabbit_misc:rpc_call(Node, rabbit_channel, handle_method,
+ [Method, none, #{}, none,
+ VHost, User]) of
+ {badrpc, nodedown} ->
+ Msg = io_lib:format("Node ~p could not be contacted", [Node]),
+ rabbit_log:warning(ErrorMsg, [Msg]),
+ bad_request(list_to_binary(Msg), ReqData1, Context);
+ {badrpc, {'EXIT', #amqp_error{name = not_found, explanation = Explanation}}} ->
+ rabbit_log:warning(ErrorMsg, [Explanation]),
+ not_found(Explanation, ReqData1, Context);
+ {badrpc, {'EXIT', #amqp_error{name = access_refused, explanation = Explanation}}} ->
+ rabbit_log:warning(ErrorMsg, [Explanation]),
+ not_authorised(<<"Access refused.">>, ReqData1, Context);
+ {badrpc, {'EXIT', #amqp_error{name = not_allowed, explanation = Explanation}}} ->
+ rabbit_log:warning(ErrorMsg, [Explanation]),
+ not_authorised(<<"Access refused.">>, ReqData1, Context);
+ {badrpc, {'EXIT', #amqp_error{explanation = Explanation}}} ->
+ rabbit_log:warning(ErrorMsg, [Explanation]),
+ bad_request(list_to_binary(Explanation), ReqData1, Context);
+ {badrpc, Reason} ->
+ rabbit_log:warning(ErrorMsg, [Reason]),
+ bad_request(
+ list_to_binary(
+ io_lib:format("Request to node ~s failed with ~p",
+ [Node, Reason])),
+ ReqData1, Context);
+ _ -> {true, ReqData1, Context}
+ end
+ end, ReqData, Context).
+
+with_vhost_and_props(Fun, ReqData, Context) ->
+ case vhost(ReqData) of
+ not_found ->
+ not_found(rabbit_data_coercion:to_binary("vhost_not_found"),
+ ReqData, Context);
+ VHost ->
+ {ok, Body, ReqData1} = read_complete_body(ReqData),
+ case decode(Body) of
+ {ok, Props} ->
+ try
+ Fun(VHost, Props, ReqData1)
+ catch {error, Error} ->
+ bad_request(Error, ReqData1, Context)
+ end;
+ {error, Reason} ->
+ bad_request(rabbit_mgmt_format:escape_html_tags(Reason),
+ ReqData1, Context)
+ end
+ end.
+
+props_to_method(MethodName, Props, Transformers, Extra) when Props =:= null orelse
+ Props =:= undefined ->
+ props_to_method(MethodName, #{}, Transformers, Extra);
+props_to_method(MethodName, Props, Transformers, Extra) ->
+ Props1 = [{list_to_atom(binary_to_list(K)), V} || {K, V} <- maps:to_list(Props)],
+ props_to_method(
+ MethodName, rabbit_mgmt_format:format(Props1 ++ Extra, {Transformers, true})).
+
+props_to_method(MethodName, Props) ->
+ Props1 = rabbit_mgmt_format:format(
+ Props,
+ {fun rabbit_mgmt_format:format_args/1, true}),
+ FieldNames = ?FRAMING:method_fieldnames(MethodName),
+ {Res, _Idx} = lists:foldl(
+ fun (K, {R, Idx}) ->
+ NewR = case pget(K, Props1) of
+ undefined -> R;
+ V -> setelement(Idx, R, V)
+ end,
+ {NewR, Idx + 1}
+ end, {?FRAMING:method_record(MethodName), 2},
+ FieldNames),
+ Res.
+
+parse_bool(V) -> rabbit_misc:parse_bool(V).
+
+parse_int(V) -> rabbit_misc:parse_int(V).
+
+with_channel(VHost, ReqData, Context, Fun) ->
+ with_channel(VHost, ReqData, Context, node(), Fun).
+
+with_channel(VHost, ReqData,
+ Context = #context{user = #user {username = Username},
+ password = Password},
+ Node, Fun) ->
+ Params = #amqp_params_direct{username = Username,
+ password = Password,
+ node = Node,
+ virtual_host = VHost},
+ case amqp_connection:start(Params) of
+ {ok, Conn} ->
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ try
+ Fun(Ch)
+ catch
+ exit:{{shutdown,
+ {server_initiated_close, ?NOT_FOUND, Reason}}, _} ->
+ not_found(Reason, ReqData, Context);
+ exit:{{shutdown,
+ {server_initiated_close, ?ACCESS_REFUSED, Reason}}, _} ->
+ not_authorised(Reason, ReqData, Context);
+ exit:{{shutdown, {ServerClose, Code, Reason}}, _}
+ when ServerClose =:= server_initiated_close;
+ ServerClose =:= server_initiated_hard_close ->
+ bad_request_exception(Code, Reason, ReqData, Context);
+ exit:{{shutdown, {connection_closing,
+ {ServerClose, Code, Reason}}}, _}
+ when ServerClose =:= server_initiated_close;
+ ServerClose =:= server_initiated_hard_close ->
+ bad_request_exception(Code, Reason, ReqData, Context)
+ after
+ catch amqp_channel:close(Ch),
+ catch amqp_connection:close(Conn)
+ end;
+ {error, {auth_failure, Msg}} ->
+ not_authorised(Msg, ReqData, Context);
+ {error, not_allowed} ->
+ not_authorised(<<"Access refused.">>, ReqData, Context);
+ {error, access_refused} ->
+ not_authorised(<<"Access refused.">>, ReqData, Context);
+ {error, {nodedown, N}} ->
+ bad_request(
+ list_to_binary(
+ io_lib:format("Node ~s could not be contacted", [N])),
+ ReqData, Context)
+ end.
+
+bad_request_exception(Code, Reason, ReqData, Context) ->
+ bad_request(list_to_binary(io_lib:format("~p ~s", [Code, Reason])),
+ ReqData, Context).
+
+all_or_one_vhost(ReqData, Fun) ->
+ case vhost(ReqData) of
+ none -> lists:append([Fun(V) || V <- rabbit_vhost:list_names()]);
+ not_found -> vhost_not_found;
+ VHost -> Fun(VHost)
+ end.
+
+filter_vhost(List, ReqData, Context) ->
+ User = #user{tags = Tags} = Context#context.user,
+ Fn = case is_admin(Tags) of
+ true -> fun list_visible_vhosts_names/2;
+ false -> fun list_login_vhosts_names/2
+ end,
+ AuthzData = get_authz_data(ReqData),
+ VHosts = Fn(User, AuthzData),
+ [I || I <- List, lists:member(pget(vhost, I), VHosts)].
+
+filter_user(List, _ReqData, #context{user = User}) ->
+ filter_user(List, User).
+
+filter_user(List, #user{username = Username, tags = Tags}) ->
+ case is_monitor(Tags) of
+ true -> List;
+ false -> [I || I <- List, pget(user, I) == Username]
+ end.
+
+filter_conn_ch_list(List, ReqData, Context) ->
+ rabbit_mgmt_format:strip_pids(
+ filter_user(
+ case vhost(ReqData) of
+ none -> List;
+ VHost -> [I || I <- List, pget(vhost, I) =:= VHost]
+ end, ReqData, Context)).
+
+filter_tracked_conn_list(List, ReqData, #context{user = #user{username = FilterUsername, tags = Tags}}) ->
+ %% A bit of duplicated code, but we only go through the list once
+ case {vhost(ReqData), is_monitor(Tags)} of
+ {none, true} ->
+ [[{name, Name},
+ {vhost, VHost},
+ {user, Username},
+ {node, Node}] || #tracked_connection{name = Name, vhost = VHost, username = Username, node = Node} <- List];
+ {FilterVHost, true} ->
+ [[{name, Name},
+ {vhost, VHost},
+ {user, Username},
+ {node, Node}] || #tracked_connection{name = Name, vhost = VHost, username = Username, node = Node} <- List, VHost == FilterVHost];
+ {none, false} ->
+ [[{name, Name},
+ {vhost, VHost},
+ {user, Username},
+ {node, Node}] || #tracked_connection{name = Name, vhost = VHost, username = Username, node = Node} <- List, Username == FilterUsername];
+ {FilterVHost, false} ->
+ [[{name, Name},
+ {vhost, VHost},
+ {user, Username},
+ {node, Node}] || #tracked_connection{name = Name, vhost = VHost, username = Username, node = Node} <- List, VHost == FilterVHost, Username == FilterUsername]
+ end.
+
+set_resp_header(K, V, ReqData) ->
+ cowboy_req:set_resp_header(K, strip_crlf(V), ReqData).
+
+strip_crlf(Str) -> lists:append(string:tokens(Str, "\r\n")).
+
+args([]) -> args(#{});
+args(L) -> rabbit_mgmt_format:to_amqp_table(L).
+
+%% Make replying to a post look like anything else...
+post_respond({true, ReqData, Context}) ->
+ {true, ReqData, Context};
+post_respond({stop, ReqData, Context}) ->
+ {stop, ReqData, Context};
+post_respond({JSON, ReqData, Context}) ->
+ {true, cowboy_req:set_resp_body(JSON, ReqData), Context}.
+
+is_admin(T) -> intersects(T, [administrator]).
+is_policymaker(T) -> intersects(T, [administrator, policymaker]).
+is_monitor(T) -> intersects(T, [administrator, monitoring]).
+is_mgmt_user(T) -> intersects(T, [administrator, monitoring, policymaker,
+ management]).
+
+intersects(A, B) -> lists:any(fun(I) -> lists:member(I, B) end, A).
+
+%% The distinction between list_visible_vhosts and list_login_vhosts
+%% is there to ensure that monitors can always learn of the
+%% existence of all vhosts, and can always see their contribution to
+%% global stats.
+
+list_visible_vhosts_names(User) ->
+ list_visible_vhosts(User, undefined).
+
+list_visible_vhosts_names(User, AuthzData) ->
+ list_visible_vhosts(User, AuthzData).
+
+list_visible_vhosts(User) ->
+ list_visible_vhosts(User, undefined).
+
+list_visible_vhosts(User = #user{tags = Tags}, AuthzData) ->
+ case is_monitor(Tags) of
+ true -> rabbit_vhost:list_names();
+ false -> list_login_vhosts_names(User, AuthzData)
+ end.
+
+list_login_vhosts_names(User, AuthzData) ->
+ [V || V <- rabbit_vhost:list_names(),
+ case catch rabbit_access_control:check_vhost_access(User, V, AuthzData, #{}) of
+ ok -> true;
+ NotOK ->
+ log_access_control_result(NotOK),
+ false
+ end].
+
+list_login_vhosts(User, AuthzData) ->
+ [V || V <- rabbit_vhost:all(),
+ case catch rabbit_access_control:check_vhost_access(User, vhost:get_name(V), AuthzData, #{}) of
+ ok -> true;
+ NotOK ->
+ log_access_control_result(NotOK),
+ false
+ end].
+
+% rabbitmq/rabbitmq-auth-backend-http#100
+log_access_control_result(NotOK) ->
+ rabbit_log:debug("rabbit_access_control:check_vhost_access result: ~p", [NotOK]).
+
+%% base64:decode throws lots of weird errors. Catch and convert to one
+%% that will cause a bad_request.
+b64decode_or_throw(B64) ->
+ rabbit_misc:b64decode_or_throw(B64).
+
+no_range() -> {no_range, no_range, no_range, no_range}.
+
+%% Take floor on queries so we make sure we only return samples
+%% for which we've finished receiving events. Fixes the "drop at
+%% the end" problem.
+range(ReqData) -> {range("lengths", fun floor/2, ReqData),
+ range("msg_rates", fun floor/2, ReqData),
+ range("data_rates", fun floor/2, ReqData),
+ range("node_stats", fun floor/2, ReqData)}.
+
+%% ...but if we know only one event could have contributed towards
+%% what we are interested in, then let's take the ceiling instead and
+%% get slightly fresher data that will match up with any
+%% non-historical data we have (e.g. queue length vs queue messages in
+%% RAM, they should both come from the same snapshot or we might
+%% report more messages in RAM than total).
+%%
+%% However, we only do this for queue lengths since a) it's the only
+%% thing where this ends up being really glaring and b) for other
+%% numbers we care more about the rate than the absolute value, and if
+%% we use ceil() we stand a 50:50 chance of looking up the last sample
+%% in the range before we get it, and thus deriving an instantaneous
+%% rate of 0.0.
+%%
+%% Age is assumed to be > 0, Incr > 0 and (Age div Incr) <= ?MAX_RANGE.
+%% The latter condition allows us to limit the number of samples that
+%% will be sent to the client.
+range_ceil(ReqData) -> {range("lengths", fun ceil/2, ReqData),
+ range("msg_rates", fun floor/2, ReqData),
+ range("data_rates", fun floor/2, ReqData),
+ range("node_stats", fun floor/2, ReqData)}.
+
+range(Prefix, Round, ReqData) ->
+ Age0 = int(Prefix ++ "_age", ReqData),
+ Incr0 = int(Prefix ++ "_incr", ReqData),
+ if
+ is_atom(Age0) orelse is_atom(Incr0) -> no_range;
+ (Age0 > 0) andalso (Incr0 > 0) andalso ((Age0 div Incr0) =< ?MAX_RANGE) ->
+ Age = Age0 * 1000,
+ Incr = Incr0 * 1000,
+ Now = os:system_time(milli_seconds),
+ Last = Round(Now, Incr),
+ #range{first = (Last - Age),
+ last = Last,
+ incr = Incr};
+ true -> throw({error, invalid_range_parameters,
+ io_lib:format("Invalid range parameters: age ~p, incr ~p",
+ [Age0, Incr0])})
+ end.
+
+floor(TS, Interval) -> (TS div Interval) * Interval.
+
+ceil(TS, Interval) -> case floor(TS, Interval) of
+ TS -> TS;
+ Floor -> Floor + Interval
+ end.
+
+ceil(X) when X < 0 ->
+ trunc(X);
+ceil(X) ->
+ T = trunc(X),
+ case X - T == 0 of
+ true -> T;
+ false -> T + 1
+ end.
+
+int(Name, ReqData) ->
+ case qs_val(list_to_binary(Name), ReqData) of
+ undefined -> undefined;
+ Bin -> case catch list_to_integer(binary_to_list(Bin)) of
+ {'EXIT', _} -> undefined;
+ Integer -> Integer
+ end
+ end.
+
+def(undefined, Def) -> Def;
+def(V, _) -> V.
+
+-spec qs_val(binary(), cowboy:req()) -> any() | undefined.
+qs_val(Name, ReqData) ->
+ Qs = cowboy_req:parse_qs(ReqData),
+ proplists:get_value(Name, Qs, undefined).
+
+-spec catch_no_such_user_or_vhost(fun(() -> Result), Replacement) -> Result | Replacement.
+catch_no_such_user_or_vhost(Fun, Replacement) ->
+ try
+ Fun()
+ catch throw:{error, {E, _}} when E =:= no_such_user; E =:= no_such_vhost ->
+ Replacement()
+ end.
+
+%% this retains the old, buggy, pre 23.1 behavour of lists:sublist/3 where an
+%% error is thrown when the request is out of range
+sublist(List, S, L) when is_integer(L), L >= 0 ->
+ lists:sublist(lists:nthtail(S-1, List), L).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_aliveness_test.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_aliveness_test.erl
new file mode 100644
index 0000000000..992ec954e2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_aliveness_test.erl
@@ -0,0 +1,71 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_aliveness_test).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+-define(QUEUE, <<"aliveness-test">>).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:with_channel(
+ rabbit_mgmt_util:vhost(ReqData),
+ ReqData,
+ Context,
+ fun(Ch) ->
+ #'queue.declare_ok'{queue = ?QUEUE} = amqp_channel:call(Ch, #'queue.declare'{
+ queue = ?QUEUE
+ }),
+ ok = amqp_channel:call(Ch, #'basic.publish'{routing_key = ?QUEUE}, #amqp_msg{
+ payload = <<"test_message">>
+ }),
+ case amqp_channel:call(Ch, #'basic.get'{queue = ?QUEUE, no_ack = true}) of
+ {#'basic.get_ok'{}, _} ->
+ %% Don't delete the queue. If this is pinged every few
+ %% seconds we don't want to create a mnesia transaction
+ %% each time.
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ #'basic.get_empty'{} ->
+ Reason = <<"aliveness-test queue is empty">>,
+ failure(Reason, ReqData, Context);
+ Error ->
+ Reason = rabbit_data_coercion:to_binary(Error),
+ failure(Reason, ReqData, Context)
+ end
+ end
+ ).
+
+failure(Reason, ReqData0, Context0) ->
+ Body = #{status => failed, reason => Reason},
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply(Body, ReqData0, Context0),
+ {stop, cowboy_req:reply(?HEALTH_CHECK_FAILURE_STATUS, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl
new file mode 100644
index 0000000000..6899fc54ee
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl
@@ -0,0 +1,51 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_auth).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ EnableUAA = application:get_env(rabbitmq_management, enable_uaa, false),
+ Data = case EnableUAA of
+ true ->
+ UAAClientId = application:get_env(rabbitmq_management, uaa_client_id, ""),
+ UAALocation = application:get_env(rabbitmq_management, uaa_location, ""),
+ case is_invalid([UAAClientId, UAALocation]) of
+ true ->
+ rabbit_log:warning("Disabling OAuth 2 authorization, relevant configuration settings are missing", []),
+ [{enable_uaa, false}, {uaa_client_id, <<>>}, {uaa_location, <<>>}];
+ false ->
+ [{enable_uaa, true},
+ {uaa_client_id, rabbit_data_coercion:to_binary(UAAClientId)},
+ {uaa_location, rabbit_data_coercion:to_binary(UAALocation)}]
+ end;
+ false ->
+ [{enable_uaa, false}, {uaa_client_id, <<>>}, {uaa_location, <<>>}]
+ end,
+ rabbit_mgmt_util:reply(Data, ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+is_invalid(List) ->
+ lists:any(fun(V) -> V == "" end, List).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth_attempts.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth_attempts.erl
new file mode 100644
index 0000000000..cdd11826ea
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth_attempts.erl
@@ -0,0 +1,79 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_auth_attempts).
+
+-export([init/2, to_json/2, content_types_provided/2, allowed_methods/2, is_authorized/2,
+ delete_resource/2, resource_exists/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE),
+ {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {node_exists(ReqData, get_node(ReqData)), ReqData, Context}.
+
+to_json(ReqData, {Mode, Context}) ->
+ rabbit_mgmt_util:reply(augment(Mode, ReqData), ReqData, Context).
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, Req2, Context2} = rabbit_mgmt_util:is_authorized_monitor(ReqData, Context),
+ {Res, Req2, {Mode, Context2}}.
+
+delete_resource(ReqData, Context) ->
+ Node = get_node(ReqData),
+ case node_exists(ReqData, Node) of
+ false ->
+ {false, ReqData, Context};
+ true ->
+ case rpc:call(Node, rabbit_core_metrics, reset_auth_attempt_metrics, [], infinity) of
+ {badrpc, _} -> {false, ReqData, Context};
+ ok -> {true, ReqData, Context}
+ end
+ end.
+%%--------------------------------------------------------------------
+get_node(ReqData) ->
+ list_to_atom(binary_to_list(rabbit_mgmt_util:id(node, ReqData))).
+
+node_exists(ReqData, Node) ->
+ case [N || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData),
+ proplists:get_value(name, N) == Node] of
+ [] -> false;
+ [_] -> true
+ end.
+
+augment(Mode, ReqData) ->
+ Node = get_node(ReqData),
+ case node_exists(ReqData, Node) of
+ false ->
+ not_found;
+ true ->
+ Fun = case Mode of
+ all -> get_auth_attempts;
+ by_source -> get_auth_attempts_by_source
+ end,
+ case rpc:call(Node, rabbit_core_metrics, Fun, [], infinity) of
+ {badrpc, _} -> not_available;
+ Result -> Result
+ end
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_binding.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_binding.erl
new file mode 100644
index 0000000000..4396edc53b
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_binding.erl
@@ -0,0 +1,141 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_binding).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ Binding = binding(ReqData),
+ {case Binding of
+ not_found -> false;
+ {bad_request, _} -> false;
+ _ -> case rabbit_binding:exists(Binding) of
+ true -> true;
+ _ -> false
+ end
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ with_binding(ReqData, Context,
+ fun(Binding) ->
+ rabbit_mgmt_util:reply(
+ rabbit_mgmt_format:binding(Binding),
+ ReqData, Context)
+ end).
+
+delete_resource(ReqData, Context) ->
+ MethodName = case rabbit_mgmt_util:destination_type(ReqData) of
+ exchange -> 'exchange.unbind';
+ queue -> 'queue.unbind'
+ end,
+ with_binding(
+ ReqData, Context,
+ fun(#binding{ source = #resource{name = S},
+ destination = #resource{name = D},
+ key = Key,
+ args = Args }) ->
+ rabbit_mgmt_util:direct_request(
+ MethodName,
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, D},
+ {exchange, S},
+ {destination, D},
+ {source, S},
+ {routing_key, Key},
+ {arguments, Args}],
+ "Unbinding error: ~s", ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+binding(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ VHost -> Source = rabbit_mgmt_util:id(source, ReqData),
+ Dest = rabbit_mgmt_util:id(destination, ReqData),
+ DestType = rabbit_mgmt_util:destination_type(ReqData),
+ Props = rabbit_mgmt_util:id(props, ReqData),
+ SName = rabbit_misc:r(VHost, exchange, Source),
+ DName = rabbit_misc:r(VHost, DestType, Dest),
+ case unpack(SName, DName, Props) of
+ {bad_request, Str} ->
+ {bad_request, Str};
+ {Key, Args} ->
+ #binding{ source = SName,
+ destination = DName,
+ key = Key,
+ args = Args }
+ end
+ end.
+
+unpack(Src, Dst, Props) ->
+ case rabbit_mgmt_format:tokenise(binary_to_list(Props)) of
+ ["\~"] -> {<<>>, []};
+ %% when routing_key is explicitly set to `null` in JSON payload,
+ %% the value would be stored as null the atom. See rabbitmq/rabbitmq-management#723 for details.
+ ["null"] -> {null, []};
+ ["undefined"] -> {undefined, []};
+ [Key] -> {unquote(Key), []};
+ ["\~", ArgsEnc] -> lookup(<<>>, ArgsEnc, Src, Dst);
+ %% see above
+ ["null", ArgsEnc] -> lookup(null, ArgsEnc, Src, Dst);
+ ["undefined", ArgsEnc] -> lookup(undefined, ArgsEnc, Src, Dst);
+ [Key, ArgsEnc] -> lookup(unquote(Key), ArgsEnc, Src, Dst);
+ _ -> {bad_request, {too_many_tokens, Props}}
+ end.
+
+lookup(RoutingKey, ArgsEnc, Src, Dst) ->
+ lookup(RoutingKey, unquote(ArgsEnc),
+ rabbit_binding:list_for_source_and_destination(Src, Dst)).
+
+lookup(_RoutingKey, _Hash, []) ->
+ {bad_request, "binding not found"};
+lookup(RoutingKey, Hash, [#binding{args = Args} | Rest]) ->
+ case args_hash(Args) =:= Hash of
+ true -> {RoutingKey, Args};
+ false -> lookup(RoutingKey, Hash, Rest)
+ end.
+
+args_hash(Args) ->
+ rabbit_mgmt_format:args_hash(Args).
+
+unquote(Name) ->
+ list_to_binary(rabbit_http_util:unquote(Name)).
+
+with_binding(ReqData, Context, Fun) ->
+ case binding(ReqData) of
+ {bad_request, Reason} ->
+ rabbit_mgmt_util:bad_request(Reason, ReqData, Context);
+ Binding ->
+ Fun(Binding)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_bindings.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_bindings.erl
new file mode 100644
index 0000000000..5ed0a82f8d
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_bindings.erl
@@ -0,0 +1,149 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_bindings).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([allowed_methods/2]).
+-export([content_types_accepted/2, accept_content/2, resource_exists/2]).
+-export([basic/1, augmented/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+%% The current version of Cowboy forces us to report the resource doesn't
+%% exist here in order to get a 201 response. It seems Cowboy confuses the
+%% resource from the request and the resource that will be created by POST.
+%% https://github.com/ninenines/cowboy/issues/723#issuecomment-161319576
+resource_exists(ReqData, {Mode, Context}) ->
+ case cowboy_req:method(ReqData) of
+ <<"POST">> ->
+ {false, ReqData, {Mode, Context}};
+ _ ->
+ {case list_bindings(Mode, ReqData) of
+ vhost_not_found -> false;
+ _ -> true
+ end, ReqData, {Mode, Context}}
+ end.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+%% Methods to add to the CORS header.
+%% This clause is called by rabbit_mgmt_cors:handle_options/2
+allowed_methods(undefined, undefined) ->
+ {[<<"HEAD">>, <<"GET">>, <<"POST">>, <<"OPTIONS">>], undefined, undefined};
+allowed_methods(ReqData, {Mode, Context}) ->
+ {case Mode of
+ source_destination -> [<<"HEAD">>, <<"GET">>, <<"POST">>, <<"OPTIONS">>];
+ _ -> [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]
+ end, ReqData, {Mode, Context}}.
+
+to_json(ReqData, {Mode, Context}) ->
+ Bs = [rabbit_mgmt_format:binding(B) || B <- list_bindings(Mode, ReqData)],
+ rabbit_mgmt_util:reply_list(
+ rabbit_mgmt_util:filter_vhost(Bs, ReqData, Context),
+ ["vhost", "source", "type", "destination",
+ "routing_key", "properties_key"],
+ ReqData, {Mode, Context}).
+
+accept_content(ReqData0, {_Mode, Context}) ->
+ {ok, Body, ReqData} = rabbit_mgmt_util:read_complete_body(ReqData0),
+ Source = rabbit_mgmt_util:id(source, ReqData),
+ Dest = rabbit_mgmt_util:id(destination, ReqData),
+ DestType = rabbit_mgmt_util:id(dtype, ReqData),
+ VHost = rabbit_mgmt_util:vhost(ReqData),
+ {ok, Props} = rabbit_mgmt_util:decode(Body),
+ MethodName = case rabbit_mgmt_util:destination_type(ReqData) of
+ exchange -> 'exchange.bind';
+ queue -> 'queue.bind'
+ end,
+ {Key, Args} = key_args(DestType, Props),
+ case rabbit_mgmt_util:direct_request(
+ MethodName,
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, Dest},
+ {exchange, Source},
+ {destination, Dest},
+ {source, Source},
+ {routing_key, Key},
+ {arguments, Args}],
+ "Binding error: ~s", ReqData, Context) of
+ {stop, _, _} = Res ->
+ Res;
+ {true, ReqData, Context2} ->
+ From = binary_to_list(cowboy_req:path(ReqData)),
+ Prefix = rabbit_mgmt_util:get_path_prefix(),
+ BindingProps = rabbit_mgmt_format:pack_binding_props(Key, Args),
+ UrlWithBindings = rabbit_mgmt_format:url("/api/bindings/~s/e/~s/~s/~s/~s",
+ [VHost, Source, DestType,
+ Dest, BindingProps]),
+ To = Prefix ++ binary_to_list(UrlWithBindings),
+ Loc = rabbit_web_dispatch_util:relativise(From, To),
+ {{true, Loc}, ReqData, Context2}
+ end.
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, RD2, C2} = rabbit_mgmt_util:is_authorized_vhost(ReqData, Context),
+ {Res, RD2, {Mode, C2}}.
+
+%%--------------------------------------------------------------------
+
+basic(ReqData) ->
+ [rabbit_mgmt_format:binding(B) ||
+ B <- list_bindings(all, ReqData)].
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context).
+
+key_args(<<"q">>, Props) ->
+ #'queue.bind'{routing_key = K, arguments = A} =
+ rabbit_mgmt_util:props_to_method(
+ 'queue.bind', Props, [], []),
+ {K, A};
+
+key_args(<<"e">>, Props) ->
+ #'exchange.bind'{routing_key = K, arguments = A} =
+ rabbit_mgmt_util:props_to_method(
+ 'exchange.bind', Props,
+ [], []),
+ {K, A}.
+
+%%--------------------------------------------------------------------
+
+list_bindings(all, ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData,
+ fun (VHost) ->
+ rabbit_binding:list(VHost)
+ end);
+list_bindings(exchange_source, ReqData) ->
+ rabbit_binding:list_for_source(r(exchange, exchange, ReqData));
+list_bindings(exchange_destination, ReqData) ->
+ rabbit_binding:list_for_destination(r(exchange, exchange, ReqData));
+list_bindings(queue, ReqData) ->
+ rabbit_binding:list_for_destination(r(queue, destination, ReqData));
+list_bindings(source_destination, ReqData) ->
+ DestType = rabbit_mgmt_util:destination_type(ReqData),
+ rabbit_binding:list_for_source_and_destination(
+ r(exchange, source, ReqData),
+ r(DestType, destination, ReqData)).
+
+r(Type, Name, ReqData) ->
+ rabbit_misc:r(rabbit_mgmt_util:vhost(ReqData), Type,
+ rabbit_mgmt_util:id(Name, ReqData)).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_channel.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channel.erl
new file mode 100644
index 0000000000..0b4aae6c13
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channel.erl
@@ -0,0 +1,68 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_channel).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ case channel(ReqData) of
+ not_found -> {false, ReqData, Context};
+ _Conn -> {true, ReqData, Context}
+ end;
+ true ->
+ {false, ReqData, Context}
+ end.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ Payload = rabbit_mgmt_format:clean_consumer_details(
+ rabbit_mgmt_format:strip_pids(channel(ReqData))),
+ rabbit_mgmt_util:reply(
+ maps:from_list(Payload),
+ ReqData, Context);
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ try
+ rabbit_mgmt_util:is_authorized_user(ReqData, Context, channel(ReqData))
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end;
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+%%--------------------------------------------------------------------
+
+channel(ReqData) ->
+ rabbit_mgmt_db:get_channel(rabbit_mgmt_util:id(channel, ReqData),
+ rabbit_mgmt_util:range(ReqData)).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels.erl
new file mode 100644
index 0000000000..a064b2f9f9
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels.erl
@@ -0,0 +1,50 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_channels).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ augmented/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ try
+ rabbit_mgmt_util:reply_list_or_paginate(augmented(ReqData, Context),
+ ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end;
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_conn_ch_list(
+ rabbit_mgmt_db:get_all_channels(
+ rabbit_mgmt_util:range(ReqData)), ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels_vhost.erl
new file mode 100644
index 0000000000..454a29e0bb
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels_vhost.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_channels_vhost).
+
+%% Lists channels in a vhost
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ augmented/2, resource_exists/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(rabbit_mgmt_util:id(vhost, ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ try
+ rabbit_mgmt_util:reply_list(augmented(ReqData, Context), ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end;
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_conn_ch_list(
+ rabbit_mgmt_db:get_all_channels(
+ rabbit_mgmt_util:range(ReqData)), ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_cluster_name.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_cluster_name.erl
new file mode 100644
index 0000000000..132da93ad2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_cluster_name.erl
@@ -0,0 +1,59 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_cluster_name).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(
+ [{name, rabbit_nodes:cluster_name()}], ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ rabbit_mgmt_util:with_decode(
+ [name], ReqData0, Context, fun([Name], _, ReqData) ->
+ rabbit_nodes:set_cluster_name(
+ as_binary(Name), Username),
+ {true, ReqData, Context}
+ end).
+
+is_authorized(ReqData, Context) ->
+ case cowboy_req:method(ReqData) of
+ <<"PUT">> -> rabbit_mgmt_util:is_authorized_admin(ReqData, Context);
+ _ -> rabbit_mgmt_util:is_authorized(ReqData, Context)
+ end.
+
+as_binary(Val) when is_binary(Val) ->
+ Val;
+as_binary(Val) when is_list(Val) ->
+ list_to_binary(Val).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection.erl
new file mode 100644
index 0000000000..7de605e6a5
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection.erl
@@ -0,0 +1,98 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_connection).
+
+-export([init/2, resource_exists/2, to_json/2, content_types_provided/2,
+ is_authorized/2, allowed_methods/2, delete_resource/2, conn/1]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ case conn(ReqData) of
+ not_found -> {false, ReqData, Context};
+ _Conn -> {true, ReqData, Context}
+ end.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_util:reply(
+ maps:from_list(rabbit_mgmt_format:strip_pids(conn_stats(ReqData))), ReqData, Context);
+ true ->
+ rabbit_mgmt_util:reply([{name, rabbit_mgmt_util:id(connection, ReqData)}],
+ ReqData, Context)
+ end.
+
+delete_resource(ReqData, Context) ->
+ case conn(ReqData) of
+ not_found -> ok;
+ Conn ->
+ case proplists:get_value(pid, Conn) of
+ undefined -> ok;
+ Pid when is_pid(Pid) ->
+ force_close_connection(ReqData, Conn, Pid)
+ end
+ end,
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ try
+ rabbit_mgmt_util:is_authorized_user(ReqData, Context, conn(ReqData))
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+%%--------------------------------------------------------------------
+
+conn(ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ conn_stats(ReqData);
+ true ->
+ case rabbit_connection_tracking:lookup(rabbit_mgmt_util:id(connection, ReqData)) of
+ #tracked_connection{name = Name, pid = Pid, username = Username, type = Type} ->
+ [{name, Name}, {pid, Pid}, {user, Username}, {type, Type}];
+ not_found ->
+ not_found
+ end
+ end.
+
+conn_stats(ReqData) ->
+ rabbit_mgmt_db:get_connection(rabbit_mgmt_util:id(connection, ReqData),
+ rabbit_mgmt_util:range_ceil(ReqData)).
+
+force_close_connection(ReqData, Conn, Pid) ->
+ Reason = case cowboy_req:header(<<"x-reason">>, ReqData) of
+ undefined -> "Closed via management plugin";
+ V -> binary_to_list(V)
+ end,
+ case proplists:get_value(type, Conn) of
+ direct -> amqp_direct_connection:server_close(Pid, 320, Reason);
+ network -> rabbit_networking:close_connection(Pid, Reason);
+ _ ->
+ % best effort, this will work for connections to the stream plugin
+ gen_server:call(Pid, {shutdown, Reason}, infinity)
+ end,
+ ok.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_channels.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_channels.erl
new file mode 100644
index 0000000000..dfbcfb5ba1
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_channels.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_connection_channels).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ case rabbit_mgmt_wm_connection:conn(ReqData) of
+ error -> {false, ReqData, Context};
+ _Conn -> {true, ReqData, Context}
+ end.
+
+to_json(ReqData, Context) ->
+ Name = proplists:get_value(name, rabbit_mgmt_wm_connection:conn(ReqData)),
+ Chs = rabbit_mgmt_db:get_all_channels(rabbit_mgmt_util:range(ReqData)),
+ rabbit_mgmt_util:reply_list(
+ [Ch || Ch <- rabbit_mgmt_util:filter_conn_ch_list(Chs, ReqData, Context),
+ conn_name(Ch) =:= Name],
+ ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ try
+ rabbit_mgmt_util:is_authorized_user(
+ ReqData, Context, rabbit_mgmt_wm_connection:conn(ReqData))
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+%%--------------------------------------------------------------------
+
+conn_name(Ch) ->
+ proplists:get_value(name, proplists:get_value(connection_details, Ch)).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections.erl
new file mode 100644
index 0000000000..0e1345abca
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_connections).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ augmented/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Connections = do_connections_query(ReqData, Context),
+ rabbit_mgmt_util:reply_list_or_paginate(Connections, ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_conn_ch_list(
+ rabbit_mgmt_db:get_all_connections(
+ rabbit_mgmt_util:range_ceil(ReqData)), ReqData, Context).
+
+do_connections_query(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ augmented(ReqData, Context);
+ true ->
+ rabbit_mgmt_util:filter_tracked_conn_list(rabbit_connection_tracking:list(),
+ ReqData, Context)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections_vhost.erl
new file mode 100644
index 0000000000..1840a7ae45
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections_vhost.erl
@@ -0,0 +1,49 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_connections_vhost).
+
+%% Lists connections in a vhost
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ augmented/2, resource_exists/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(rabbit_mgmt_util:id(vhost, ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ rabbit_mgmt_util:reply_list(augmented(ReqData, Context), ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_conn_ch_list(
+ rabbit_mgmt_db:get_all_connections(
+ rabbit_mgmt_util:range_ceil(ReqData)), ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_consumers.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_consumers.erl
new file mode 100644
index 0000000000..56945d3a5d
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_consumers.erl
@@ -0,0 +1,59 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+-module(rabbit_mgmt_wm_consumers).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> false;
+ none -> true; % none means `all`
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context = #context{user = User}) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ Arg = case rabbit_mgmt_util:vhost(ReqData) of
+ none -> all;
+ VHost -> VHost
+ end,
+ Consumers = rabbit_mgmt_format:strip_pids(rabbit_mgmt_db:get_all_consumers(Arg)),
+ Formatted = [rabbit_mgmt_format:format_consumer_arguments(C) || C <- Consumers],
+ rabbit_mgmt_util:reply_list(
+ filter_user(Formatted, User), ReqData, Context);
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+filter_user(List, #user{username = Username, tags = Tags}) ->
+ case rabbit_mgmt_util:is_monitor(Tags) of
+ true -> List;
+ false -> [I || I <- List,
+ pget(user, pget(channel_details, I)) == Username]
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl
new file mode 100644
index 0000000000..c0687993a9
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl
@@ -0,0 +1,298 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_definitions).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([content_types_accepted/2, allowed_methods/2, accept_json/2]).
+-export([accept_multipart/2]).
+-export([variances/2]).
+
+-export([apply_defs/3, apply_defs/5]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_json},
+ {{<<"multipart">>, <<"form-data">>, '*'}, accept_multipart}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ none ->
+ all_definitions(ReqData, Context);
+ not_found ->
+ rabbit_mgmt_util:bad_request(rabbit_data_coercion:to_binary("vhost_not_found"),
+ ReqData, Context);
+ VHost ->
+ vhost_definitions(ReqData, VHost, Context)
+ end.
+
+all_definitions(ReqData, Context) ->
+ Xs = [X || X <- rabbit_mgmt_wm_exchanges:basic(ReqData),
+ export_exchange(X)],
+ Qs = [Q || Q <- rabbit_mgmt_wm_queues:basic(ReqData),
+ export_queue(Q)],
+ QNames = [{pget(name, Q), pget(vhost, Q)} || Q <- Qs],
+ Bs = [B || B <- rabbit_mgmt_wm_bindings:basic(ReqData),
+ export_binding(B, QNames)],
+ Vsn = rabbit:base_product_version(),
+ ProductName = rabbit:product_name(),
+ ProductVersion = rabbit:product_version(),
+ rabbit_mgmt_util:reply(
+ [{rabbit_version, rabbit_data_coercion:to_binary(Vsn)},
+ {rabbitmq_version, rabbit_data_coercion:to_binary(Vsn)},
+ {product_name, rabbit_data_coercion:to_binary(ProductName)},
+ {product_version, rabbit_data_coercion:to_binary(ProductVersion)}] ++
+ filter(
+ [{users, rabbit_mgmt_wm_users:users(all)},
+ {vhosts, rabbit_mgmt_wm_vhosts:basic()},
+ {permissions, rabbit_mgmt_wm_permissions:permissions()},
+ {topic_permissions, rabbit_mgmt_wm_topic_permissions:topic_permissions()},
+ {parameters, rabbit_mgmt_wm_parameters:basic(ReqData)},
+ {global_parameters, rabbit_mgmt_wm_global_parameters:basic()},
+ {policies, rabbit_mgmt_wm_policies:basic(ReqData)},
+ {queues, Qs},
+ {exchanges, Xs},
+ {bindings, Bs}]),
+ case rabbit_mgmt_util:qs_val(<<"download">>, ReqData) of
+ undefined -> ReqData;
+ Filename -> rabbit_mgmt_util:set_resp_header(
+ <<"Content-Disposition">>,
+ "attachment; filename=" ++
+ binary_to_list(Filename), ReqData)
+ end,
+ Context).
+
+accept_json(ReqData0, Context) ->
+ {ok, Body, ReqData} = rabbit_mgmt_util:read_complete_body(ReqData0),
+ accept(Body, ReqData, Context).
+
+vhost_definitions(ReqData, VHost, Context) ->
+ %% rabbit_mgmt_wm_<>:basic/1 filters by VHost if it is available
+ Xs = [strip_vhost(X) || X <- rabbit_mgmt_wm_exchanges:basic(ReqData),
+ export_exchange(X)],
+ VQs = [Q || Q <- rabbit_mgmt_wm_queues:basic(ReqData), export_queue(Q)],
+ Qs = [strip_vhost(Q) || Q <- VQs],
+ QNames = [{pget(name, Q), pget(vhost, Q)} || Q <- VQs],
+ Bs = [strip_vhost(B) || B <- rabbit_mgmt_wm_bindings:basic(ReqData),
+ export_binding(B, QNames)],
+ {ok, Vsn} = application:get_key(rabbit, vsn),
+ Parameters = [rabbit_mgmt_format:parameter(
+ rabbit_mgmt_wm_parameters:fix_shovel_publish_properties(P))
+ || P <- rabbit_runtime_parameters:list(VHost)],
+ rabbit_mgmt_util:reply(
+ [{rabbit_version, rabbit_data_coercion:to_binary(Vsn)}] ++
+ filter(
+ [{parameters, Parameters},
+ {policies, rabbit_mgmt_wm_policies:basic(ReqData)},
+ {queues, Qs},
+ {exchanges, Xs},
+ {bindings, Bs}]),
+ case rabbit_mgmt_util:qs_val(<<"download">>, ReqData) of
+ undefined -> ReqData;
+ Filename ->
+ HeaderVal = "attachment; filename=" ++ binary_to_list(Filename),
+ rabbit_mgmt_util:set_resp_header(<<"Content-Disposition">>, HeaderVal, ReqData)
+ end,
+ Context).
+
+accept_multipart(ReqData0, Context) ->
+ {Parts, ReqData} = get_all_parts(ReqData0),
+ Redirect = get_part(<<"redirect">>, Parts),
+ Payload = get_part(<<"file">>, Parts),
+ Resp = {Res, _, _} = accept(Payload, ReqData, Context),
+ case {Res, Redirect} of
+ {true, unknown} -> {true, ReqData, Context};
+ {true, _} -> {{true, Redirect}, ReqData, Context};
+ _ -> Resp
+ end.
+
+is_authorized(ReqData, Context) ->
+ case rabbit_mgmt_util:qs_val(<<"auth">>, ReqData) of
+ undefined ->
+ case rabbit_mgmt_util:qs_val(<<"token">>, ReqData) of
+ undefined ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context);
+ Token ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context, Token)
+ end;
+ Auth ->
+ is_authorized_qs(ReqData, Context, Auth)
+ end.
+
+%% Support for the web UI - it can't add a normal "authorization"
+%% header for a file download.
+is_authorized_qs(ReqData, Context, Auth) ->
+ case rabbit_web_dispatch_util:parse_auth_header("Basic " ++ Auth) of
+ [Username, Password] -> rabbit_mgmt_util:is_authorized_admin(
+ ReqData, Context, Username, Password);
+ _ -> {?AUTH_REALM, ReqData, Context}
+ end.
+
+%%--------------------------------------------------------------------
+
+decode(<<"">>) ->
+ {ok, #{}};
+decode(Body) ->
+ try
+ Decoded = rabbit_json:decode(Body),
+ Normalised = maps:fold(fun(K, V, Acc) ->
+ Acc#{binary_to_atom(K, utf8) => V}
+ end, Decoded, Decoded),
+ {ok, Normalised}
+ catch error:_ -> {error, not_json}
+ end.
+
+accept(Body, ReqData, Context = #context{user = #user{username = Username}}) ->
+ %% At this point the request was fully received.
+ %% There is no point in the idle_timeout anymore.
+ disable_idle_timeout(ReqData),
+ case decode(Body) of
+ {error, E} ->
+ rabbit_log:error("Encountered an error when parsing definitions: ~p", [E]),
+ rabbit_mgmt_util:bad_request(rabbit_data_coercion:to_binary("failed_to_parse_json"),
+ ReqData, Context);
+ {ok, Map} ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ none ->
+ case apply_defs(Map, Username) of
+ {error, E} ->
+ rabbit_log:error("Encountered an error when importing definitions: ~p", [E]),
+ rabbit_mgmt_util:bad_request(E, ReqData, Context);
+ ok -> {true, ReqData, Context}
+ end;
+ not_found ->
+ rabbit_mgmt_util:not_found(rabbit_data_coercion:to_binary("vhost_not_found"),
+ ReqData, Context);
+ VHost when is_binary(VHost) ->
+ case apply_defs(Map, Username, VHost) of
+ {error, E} ->
+ rabbit_log:error("Encountered an error when importing definitions: ~p", [E]),
+ rabbit_mgmt_util:bad_request(E, ReqData, Context);
+ ok -> {true, ReqData, Context}
+ end
+ end
+ end.
+
+disable_idle_timeout(#{pid := Pid, streamid := StreamID}) ->
+ Pid ! {{Pid, StreamID}, {set_options, #{idle_timeout => infinity}}}.
+
+-spec apply_defs(Map :: #{atom() => any()}, ActingUser :: rabbit_types:username()) -> 'ok' | {error, term()}.
+
+apply_defs(Body, ActingUser) ->
+ rabbit_definitions:apply_defs(Body, ActingUser).
+
+-spec apply_defs(Map :: #{atom() => any()}, ActingUser :: rabbit_types:username(),
+ VHost :: vhost:name()) -> 'ok' | {error, term()}.
+
+apply_defs(Body, ActingUser, VHost) ->
+ rabbit_definitions:apply_defs(Body, ActingUser, VHost).
+
+-spec apply_defs(Map :: #{atom() => any()},
+ ActingUser :: rabbit_types:username(),
+ SuccessFun :: fun(() -> 'ok'),
+ ErrorFun :: fun((any()) -> 'ok'),
+ VHost :: vhost:name()) -> 'ok' | {error, term()}.
+
+apply_defs(Body, ActingUser, SuccessFun, ErrorFun, VHost) ->
+ rabbit_definitions:apply_defs(Body, ActingUser, SuccessFun, ErrorFun, VHost).
+
+get_all_parts(Req) ->
+ get_all_parts(Req, []).
+
+get_all_parts(Req0, Acc) ->
+ case cowboy_req:read_part(Req0) of
+ {done, Req1} ->
+ {Acc, Req1};
+ {ok, Headers, Req1} ->
+ Name = case cow_multipart:form_data(Headers) of
+ {data, N} -> N;
+ {file, N, _, _} -> N
+ end,
+ {ok, Body, Req2} = stream_part_body(Req1, <<>>),
+ get_all_parts(Req2, [{Name, Body}|Acc])
+ end.
+
+stream_part_body(Req0, Acc) ->
+ case cowboy_req:read_part_body(Req0) of
+ {more, Data, Req1} ->
+ stream_part_body(Req1, <<Acc/binary, Data/binary>>);
+ {ok, Data, Req1} ->
+ {ok, <<Acc/binary, Data/binary>>, Req1}
+ end.
+
+get_part(Name, Parts) ->
+ case lists:keyfind(Name, 1, Parts) of
+ false -> unknown;
+ {_, Value} -> Value
+ end.
+
+export_queue(Queue) ->
+ pget(owner_pid, Queue) == none.
+
+export_binding(Binding, Qs) ->
+ Src = pget(source, Binding),
+ Dest = pget(destination, Binding),
+ DestType = pget(destination_type, Binding),
+ VHost = pget(vhost, Binding),
+ Src =/= <<"">>
+ andalso
+ ( (DestType =:= queue andalso lists:member({Dest, VHost}, Qs))
+ orelse (DestType =:= exchange andalso Dest =/= <<"">>) ).
+
+export_exchange(Exchange) ->
+ export_name(pget(name, Exchange)).
+
+export_name(<<>>) -> false;
+export_name(<<"amq.", _/binary>>) -> false;
+export_name(_Name) -> true.
+
+%%--------------------------------------------------------------------
+
+rw_state() ->
+ [{users, [name, password_hash, hashing_algorithm, tags, limits]},
+ {vhosts, [name]},
+ {permissions, [user, vhost, configure, write, read]},
+ {topic_permissions, [user, vhost, exchange, write, read]},
+ {parameters, [vhost, component, name, value]},
+ {global_parameters, [name, value]},
+ {policies, [vhost, name, pattern, definition, priority, 'apply-to']},
+ {queues, [name, vhost, durable, auto_delete, arguments]},
+ {exchanges, [name, vhost, type, durable, auto_delete, internal,
+ arguments]},
+ {bindings, [source, vhost, destination, destination_type, routing_key,
+ arguments]}].
+
+filter(Items) ->
+ [filter_items(N, V, proplists:get_value(N, rw_state())) || {N, V} <- Items].
+
+filter_items(Name, List, Allowed) ->
+ {Name, [filter_item(I, Allowed) || I <- List]}.
+
+filter_item(Item, Allowed) ->
+ [{K, Fact} || {K, Fact} <- Item, lists:member(K, Allowed)].
+
+strip_vhost(Item) ->
+ lists:keydelete(vhost, 1, Item).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange.erl
new file mode 100644
index 0000000000..2d5930d1d0
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange.erl
@@ -0,0 +1,90 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_exchange).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2, exchange/1, exchange/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case exchange(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ [X] = rabbit_mgmt_db:augment_exchanges(
+ [exchange(ReqData)], rabbit_mgmt_util:range(ReqData), full),
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:strip_pids(X), ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+accept_content(ReqData, Context) ->
+ Name = rabbit_mgmt_util:id(exchange, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'exchange.declare',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{exchange, Name}], "Declare exchange error: ~s", ReqData, Context).
+
+delete_resource(ReqData, Context) ->
+ %% We need to retrieve manually if-unused, as the HTTP API uses '-'
+ %% while the record uses '_'
+ Name = id(ReqData),
+ IfUnused = <<"true">> =:= rabbit_mgmt_util:qs_val(<<"if-unused">>, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'exchange.delete',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{exchange, Name},
+ {if_unused, IfUnused}], "Delete exchange error: ~s", ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+exchange(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ VHost -> exchange(VHost, id(ReqData))
+ end.
+
+exchange(VHost, XName) ->
+ Name = rabbit_misc:r(VHost, exchange, XName),
+ case rabbit_exchange:lookup(Name) of
+ {ok, X} -> rabbit_mgmt_format:exchange(
+ rabbit_exchange:info(X));
+ {error, not_found} -> not_found
+ end.
+
+id(ReqData) ->
+ rabbit_mgmt_util:id(exchange, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange_publish.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange_publish.erl
new file mode 100644
index 0000000000..7e90b54ba1
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange_publish.erl
@@ -0,0 +1,105 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_exchange_publish).
+
+-export([init/2, resource_exists/2, is_authorized/2,
+ allowed_methods/2, content_types_provided/2, accept_content/2,
+ content_types_accepted/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_exchange:exchange(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+accept_content(ReqData, Context) ->
+ rabbit_mgmt_util:post_respond(do_it(ReqData, Context)).
+
+do_it(ReqData0, Context) ->
+ VHost = rabbit_mgmt_util:vhost(ReqData0),
+ X = rabbit_mgmt_util:id(exchange, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [routing_key, properties, payload, payload_encoding], ReqData0, Context,
+ fun ([RoutingKey, Props0, Payload0, Enc], _, ReqData) when is_binary(Payload0) ->
+ rabbit_mgmt_util:with_channel(
+ VHost, ReqData, Context,
+ fun (Ch) ->
+ MRef = erlang:monitor(process, Ch),
+ amqp_channel:register_confirm_handler(Ch, self()),
+ amqp_channel:register_return_handler(Ch, self()),
+ amqp_channel:call(Ch, #'confirm.select'{}),
+ Props = rabbit_mgmt_format:to_basic_properties(Props0),
+ Payload = decode(Payload0, Enc),
+ amqp_channel:cast(Ch, #'basic.publish'{
+ exchange = X,
+ routing_key = RoutingKey,
+ mandatory = true},
+ #amqp_msg{props = Props,
+ payload = Payload}),
+ receive
+ {#'basic.return'{}, _} ->
+ receive
+ #'basic.ack'{} -> ok
+ end,
+ good(MRef, false, ReqData, Context);
+ #'basic.ack'{} ->
+ good(MRef, true, ReqData, Context);
+ #'basic.nack'{} ->
+ erlang:demonitor(MRef),
+ bad(rejected, ReqData, Context);
+ {'DOWN', _, _, _, Err} ->
+ bad(Err, ReqData, Context)
+ end
+ end);
+ ([_RoutingKey, _Props, _Payload, _Enc], _, _ReqData) ->
+ throw({error, payload_not_string})
+ end).
+
+good(MRef, Routed, ReqData, Context) ->
+ erlang:demonitor(MRef),
+ rabbit_mgmt_util:reply([{routed, Routed}], ReqData, Context).
+
+bad({shutdown, {connection_closing,
+ {server_initiated_close, Code, Reason}}}, ReqData, Context) ->
+ rabbit_mgmt_util:bad_request_exception(Code, Reason, ReqData, Context);
+
+bad({shutdown, {server_initiated_close, Code, Reason}}, ReqData, Context) ->
+ rabbit_mgmt_util:bad_request_exception(Code, Reason, ReqData, Context);
+bad(rejected, ReqData, Context) ->
+ Msg = "Unable to publish message. Check queue limits.",
+ rabbit_mgmt_util:bad_request_exception(rejected, Msg, ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+decode(Payload, <<"string">>) -> Payload;
+decode(Payload, <<"base64">>) -> rabbit_mgmt_util:b64decode_or_throw(Payload);
+decode(_Payload, Enc) -> throw({error, {unsupported_encoding, Enc}}).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchanges.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchanges.erl
new file mode 100644
index 0000000000..9f7abab20f
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchanges.erl
@@ -0,0 +1,75 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_exchanges).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1, augmented/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-define(DEFAULT_SORT, ["vhost", "name"]).
+
+-define(BASIC_COLUMNS, ["vhost", "name", "type", "durable", "auto_delete",
+ "internal", "arguments", "pid"]).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case exchanges0(ReqData) of
+ vhost_not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Basic = rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData,
+ Context),
+ Data = rabbit_mgmt_util:augment_resources(Basic, ?DEFAULT_SORT,
+ ?BASIC_COLUMNS, ReqData,
+ Context, fun augment/2),
+ rabbit_mgmt_util:reply(Data, ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+augment(Basic, ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_exchanges(Basic, rabbit_mgmt_util:range(ReqData),
+ basic);
+ true ->
+ Basic
+ end.
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_db:augment_exchanges(
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context),
+ rabbit_mgmt_util:range(ReqData), basic).
+
+basic(ReqData) ->
+ [rabbit_mgmt_format:exchange(X) || X <- exchanges0(ReqData)].
+
+exchanges0(ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_exchange:info_all/1).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_extensions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_extensions.erl
new file mode 100644
index 0000000000..26c3ccbee6
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_extensions.erl
@@ -0,0 +1,33 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_extensions).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Modules = rabbit_mgmt_dispatcher:modules([]),
+ rabbit_mgmt_util:reply(
+ [Module:web_ui() || Module <- Modules], ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl
new file mode 100644
index 0000000000..fafb993951
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl
@@ -0,0 +1,55 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2019-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_feature_flag_enable).
+
+-export([init/2,
+ content_types_accepted/2, is_authorized/2,
+ allowed_methods/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _Args) ->
+ {cowboy_rest,
+ rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE),
+ #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"PUT">>, <<"OPTIONS">>], ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+accept_content(ReqData, #context{} = Context) ->
+ NameS = rabbit_mgmt_util:id(name, ReqData),
+ try
+ Name = list_to_existing_atom(binary_to_list(NameS)),
+ case rabbit_feature_flags:enable(Name) of
+ ok ->
+ {true, ReqData, Context};
+ {error, Reason1} ->
+ FormattedReason1 = rabbit_ff_extra:format_error(Reason1),
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(FormattedReason1), ReqData, Context)
+ end
+ catch
+ _:badarg ->
+ Reason2 = unsupported,
+ FormattedReason2 = rabbit_ff_extra:format_error(Reason2),
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(FormattedReason2), ReqData, Context)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flags.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flags.erl
new file mode 100644
index 0000000000..3f25045a36
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flags.erl
@@ -0,0 +1,45 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2019-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_feature_flags).
+
+-export([init/2, to_json/2,
+ content_types_provided/2,
+ is_authorized/2, allowed_methods/2]).
+-export([variances/2]).
+-export([feature_flags/0]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _Args) ->
+ {cowboy_rest,
+ rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE),
+ #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"OPTIONS">>], ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(feature_flags(), ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ {Res, Req2, Context2} = rabbit_mgmt_util:is_authorized_admin(ReqData, Context),
+ {Res, Req2, Context2}.
+
+%%--------------------------------------------------------------------
+
+feature_flags() ->
+ rabbit_ff_extra:cli_info().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameter.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameter.erl
new file mode 100644
index 0000000000..a81c5be8fa
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameter.erl
@@ -0,0 +1,71 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_global_parameter).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case parameter(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:parameter(parameter(ReqData)),
+ ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ rabbit_mgmt_util:with_decode(
+ [value], ReqData0, Context,
+ fun([Value], _, ReqData) ->
+ Val = if is_map(Value) -> maps:to_list(Value);
+ true -> Value
+ end,
+ rabbit_runtime_parameters:set_global(name(ReqData), Val, Username),
+ {true, ReqData, Context}
+ end).
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_runtime_parameters:clear_global(name(ReqData), Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_global_parameters(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+parameter(ReqData) ->
+ rabbit_runtime_parameters:lookup_global(name(ReqData)).
+
+name(ReqData) -> rabbit_data_coercion:to_atom(rabbit_mgmt_util:id(name, ReqData)).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameters.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameters.erl
new file mode 100644
index 0000000000..fa23283ad5
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameters.erl
@@ -0,0 +1,36 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_global_parameters).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+-export([basic/0]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(basic(), ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_global_parameters(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+basic() ->
+ rabbit_runtime_parameters:list_global().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_alarms.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_alarms.erl
new file mode 100644
index 0000000000..06db3bd79e
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_alarms.erl
@@ -0,0 +1,56 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_alarms'
+-module(rabbit_mgmt_wm_health_check_alarms).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Timeout = case cowboy_req:header(<<"timeout">>, ReqData) of
+ undefined -> 70000;
+ Val -> list_to_integer(binary_to_list(Val))
+ end,
+ case rabbit_alarm:get_alarms(Timeout) of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Xs when length(Xs) > 0 ->
+ Msg = "There are alarms in effect in the cluster",
+ failure(Msg, Xs, ReqData, Context)
+ end.
+
+failure(Message, Alarms0, ReqData, Context) ->
+ Alarms = rabbit_alarm:format_as_maps(Alarms0),
+ Body = #{
+ status => failed,
+ reason => rabbit_data_coercion:to_binary(Message),
+ alarms => Alarms
+ },
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply(Body, ReqData, Context),
+ {stop, cowboy_req:reply(?HEALTH_CHECK_FAILURE_STATUS, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_certificate_expiration.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_certificate_expiration.erl
new file mode 100644
index 0000000000..f0fd466b2a
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_certificate_expiration.erl
@@ -0,0 +1,178 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_certificate_expiration'
+-module(rabbit_mgmt_wm_health_check_certificate_expiration).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-define(DAYS_SECONDS, 86400).
+-define(WEEKS_SECONDS, ?DAYS_SECONDS * 7).
+-define(MONTHS_SECONDS, ?DAYS_SECONDS * (365 / 12)).
+-define(YEARS_SECONDS, ?DAYS_SECONDS * 365).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Listeners = rabbit_networking:active_listeners(),
+ Local = [L || #listener{node = N} = L <- Listeners, N == node()],
+ case convert(within(ReqData), unit(ReqData)) of
+ {error, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context);
+ Seconds ->
+ ExpiringListeners = lists:foldl(fun(L, Acc) ->
+ case listener_expiring_within(L, Seconds) of
+ false -> Acc;
+ Map -> [Map | Acc]
+ end
+ end, [], Local),
+ case ExpiringListeners of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ _ ->
+ Msg = <<"Certificates expiring">>,
+ failure(Msg, ExpiringListeners, ReqData, Context)
+ end
+ end.
+
+failure(Message, Listeners, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {expired, Listeners}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+within(ReqData) ->
+ rabbit_mgmt_util:id(within, ReqData).
+
+unit(ReqData) ->
+ rabbit_mgmt_util:id(unit, ReqData).
+
+convert(Time, Unit) ->
+ try
+ do_convert(binary_to_integer(Time), string:lowercase(binary_to_list(Unit)))
+ catch
+ error:badarg ->
+ {error, "Invalid expiration value."};
+ invalid_unit ->
+ {error, "Time unit not recognised. Use: days, seconds, months, years."}
+ end.
+
+do_convert(Time, "days") ->
+ Time * ?DAYS_SECONDS;
+do_convert(Time, "weeks") ->
+ Time * ?WEEKS_SECONDS;
+do_convert(Time, "months") ->
+ Time * ?MONTHS_SECONDS;
+do_convert(Time, "years") ->
+ Time * ?YEARS_SECONDS;
+do_convert(_, _) ->
+ throw(invalid_unit).
+
+listener_expiring_within(#listener{node = Node, protocol = Protocol, ip_address = Interface,
+ port = Port, opts = Opts}, Seconds) ->
+ Certfile = proplists:get_value(certfile, Opts),
+ Cacertfile = proplists:get_value(cacertfile, Opts),
+ Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
+ ExpiryDate = Now + Seconds,
+ CertfileExpiresOn = expired(cert_validity(read_cert(Certfile)), ExpiryDate),
+ CacertfileExpiresOn = expired(cert_validity(read_cert(Cacertfile)), ExpiryDate),
+ case {CertfileExpiresOn, CacertfileExpiresOn} of
+ {[], []} ->
+ false;
+ _ ->
+ #{node => Node,
+ protocol => Protocol,
+ interface => list_to_binary(inet:ntoa(Interface)),
+ port => Port,
+ certfile => list_to_binary(Certfile),
+ cacertfile => list_to_binary(Cacertfile),
+ certfile_expires_on => expires_on_list(CertfileExpiresOn),
+ cacertfile_expires_on => expires_on_list(CacertfileExpiresOn)
+ }
+ end.
+
+expires_on_list({error, Reason}) ->
+ {error, list_to_binary(Reason)};
+expires_on_list(ExpiresOn) ->
+ [seconds_to_bin(S) || S <- ExpiresOn].
+
+read_cert(undefined) ->
+ undefined;
+read_cert({pem, Pem}) ->
+ Pem;
+read_cert(Path) ->
+ case file:read_file(Path) of
+ {ok, Bin} ->
+ Bin;
+ Err ->
+ Err
+ end.
+
+cert_validity(undefined) ->
+ undefined;
+cert_validity(Cert) ->
+ DsaEntries = public_key:pem_decode(Cert),
+ case DsaEntries of
+ [] ->
+ {error, "The certificate file provided does not contain any PEM entry."};
+ _ ->
+ Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
+ lists:map(
+ fun({'Certificate', _, _} = DsaEntry) ->
+ #'Certificate'{tbsCertificate = TBSCertificate} = public_key:pem_entry_decode(DsaEntry),
+ #'TBSCertificate'{validity = Validity} = TBSCertificate,
+ #'Validity'{notAfter = NotAfter, notBefore = NotBefore} = Validity,
+ Start = pubkey_cert:time_str_2_gregorian_sec(NotBefore),
+ case Start > Now of
+ true ->
+ {error, "Certificate is not yet valid"};
+ false ->
+ pubkey_cert:time_str_2_gregorian_sec(NotAfter)
+ end;
+ ({Type, _, _}) ->
+ {error, io_lib:format("The certificate file provided contains a ~p entry",
+ [Type])}
+ end, DsaEntries)
+ end.
+
+expired(undefined, _ExpiryDate) ->
+ [];
+expired({error, _} = Error, _ExpiryDate) ->
+ Error;
+expired(Expires, ExpiryDate) ->
+ lists:filter(fun({error, _}) ->
+ true;
+ (Seconds) ->
+ Seconds < ExpiryDate
+ end, Expires).
+
+seconds_to_bin(Seconds) ->
+ {{Y, M, D}, {H, Min, S}} = calendar:gregorian_seconds_to_datetime(Seconds),
+ list_to_binary(lists:flatten(io_lib:format("~w-~2.2.0w-~2.2.0w ~w:~2.2.0w:~2.2.0w",
+ [Y, M, D, H, Min, S]))).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_local_alarms.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_local_alarms.erl
new file mode 100644
index 0000000000..4553efa3e2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_local_alarms.erl
@@ -0,0 +1,56 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-dignoastics check_local_alarms'
+-module(rabbit_mgmt_wm_health_check_local_alarms).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Timeout = case cowboy_req:header(<<"timeout">>, ReqData) of
+ undefined -> 70000;
+ Val -> list_to_integer(binary_to_list(Val))
+ end,
+ case rabbit_alarm:get_local_alarms(Timeout) of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Xs when length(Xs) > 0 ->
+ Msg = "There are alarms in effect on the node",
+ failure(Msg, Xs, ReqData, Context)
+ end.
+
+failure(Message, Alarms0, ReqData, Context) ->
+ Alarms = rabbit_alarm:format_as_maps(Alarms0),
+ Body = #{
+ status => failed,
+ reason => rabbit_data_coercion:to_binary(Message),
+ alarms => Alarms
+ },
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply(Body, ReqData, Context),
+ {stop, cowboy_req:reply(?HEALTH_CHECK_FAILURE_STATUS, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical.erl
new file mode 100644
index 0000000000..cef5512551
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_if_node_is_quorum_critical'
+-module(rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_nodes:is_single_node_cluster() of
+ true ->
+ rabbit_mgmt_util:reply([{status, ok},
+ {reason, <<"single node cluster">>}], ReqData, Context);
+ false ->
+ case rabbit_amqqueue:list_local_mirrored_classic_without_synchronised_mirrors_for_cli() of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Qs when length(Qs) > 0 ->
+ Msg = <<"There are classic mirrored queues without online synchronised mirrors">>,
+ failure(Msg, Qs, ReqData, Context)
+ end
+ end.
+
+failure(Message, Qs, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {queues, Qs}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_quorum_critical.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_quorum_critical.erl
new file mode 100644
index 0000000000..857c72b30b
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_quorum_critical.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_if_node_is_quorum_critical'
+-module(rabbit_mgmt_wm_health_check_node_is_quorum_critical).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_nodes:is_single_node_cluster() of
+ true ->
+ rabbit_mgmt_util:reply([{status, ok},
+ {reason, <<"single node cluster">>}], ReqData, Context);
+ false ->
+ case rabbit_quorum_queue:list_with_minimum_quorum_for_cli() of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Qs when length(Qs) > 0 ->
+ Msg = <<"There are quorum queues that would lose their quorum if the target node is shut down">>,
+ failure(Msg, Qs, ReqData, Context)
+ end
+ end.
+
+failure(Message, Qs, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {queues, Qs}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_port_listener.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_port_listener.erl
new file mode 100644
index 0000000000..1fcc00f613
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_port_listener.erl
@@ -0,0 +1,66 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_port_listener'
+-module(rabbit_mgmt_wm_health_check_port_listener).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case port(ReqData) of
+ none -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Port = binary_to_integer(port(ReqData)),
+ Listeners = rabbit_networking:active_listeners(),
+ Local = [L || #listener{node = N} = L <- Listeners, N == node()],
+ PortListeners = [L || #listener{port = P} = L <- Local, P == Port],
+ case PortListeners of
+ [] ->
+ Msg = <<"No active listener">>,
+ failure(Msg, Port, [P || #listener{port = P} <- Local], ReqData, Context);
+ _ ->
+ rabbit_mgmt_util:reply([{status, ok},
+ {port, Port}], ReqData, Context)
+ end
+ catch
+ error:badarg ->
+ rabbit_mgmt_util:bad_request(<<"Invalid port">>, ReqData, Context)
+ end.
+
+failure(Message, Missing, Ports, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {missing, Missing},
+ {ports, Ports}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+port(ReqData) ->
+ rabbit_mgmt_util:id(port, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_protocol_listener.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_protocol_listener.erl
new file mode 100644
index 0000000000..a0b7e4e6dd
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_protocol_listener.erl
@@ -0,0 +1,127 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_protocol_listener'
+-module(rabbit_mgmt_wm_health_check_protocol_listener).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case protocol(ReqData) of
+ none -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Protocol = normalize_protocol(protocol(ReqData)),
+ Listeners = rabbit_networking:active_listeners(),
+ Local = [L || #listener{node = N} = L <- Listeners, N == node()],
+ ProtoListeners = [L || #listener{protocol = P} = L <- Local, atom_to_list(P) == Protocol],
+ case ProtoListeners of
+ [] ->
+ Msg = <<"No active listener">>,
+ failure(Msg, Protocol, [P || #listener{protocol = P} <- Local], ReqData, Context);
+ _ ->
+ rabbit_mgmt_util:reply([{status, ok},
+ {protocol, list_to_binary(Protocol)}], ReqData, Context)
+ end.
+
+failure(Message, Missing, Protocols, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {missing, list_to_binary(Missing)},
+ {protocols, Protocols}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+protocol(ReqData) ->
+ rabbit_mgmt_util:id(protocol, ReqData).
+
+normalize_protocol(Protocol) ->
+ case string:lowercase(binary_to_list(Protocol)) of
+ "amqp091" -> "amqp";
+ "amqp0.9.1" -> "amqp";
+ "amqp0-9-1" -> "amqp";
+ "amqp0_9_1" -> "amqp";
+ "amqp10" -> "amqp";
+ "amqp1.0" -> "amqp";
+ "amqp1-0" -> "amqp";
+ "amqp1_0" -> "amqp";
+ "amqps" -> "amqp/ssl";
+ "mqtt3.1" -> "mqtt";
+ "mqtt3.1.1" -> "mqtt";
+ "mqtt31" -> "mqtt";
+ "mqtt311" -> "mqtt";
+ "mqtt3_1" -> "mqtt";
+ "mqtt3_1_1" -> "mqtt";
+ "mqtts" -> "mqtt/ssl";
+ "mqtt+tls" -> "mqtt/ssl";
+ "mqtt+ssl" -> "mqtt/ssl";
+ "stomp1.0" -> "stomp";
+ "stomp1.1" -> "stomp";
+ "stomp1.2" -> "stomp";
+ "stomp10" -> "stomp";
+ "stomp11" -> "stomp";
+ "stomp12" -> "stomp";
+ "stomp1_0" -> "stomp";
+ "stomp1_1" -> "stomp";
+ "stomp1_2" -> "stomp";
+ "stomps" -> "stomp/ssl";
+ "stomp+tls" -> "stomp/ssl";
+ "stomp+ssl" -> "stomp/ssl";
+ "https" -> "https";
+ "http1" -> "http";
+ "http1.1" -> "http";
+ "http_api" -> "http";
+ "management" -> "http";
+ "management_ui" -> "http";
+ "ui" -> "http";
+ "cli" -> "clustering";
+ "distribution" -> "clustering";
+ "webmqtt" -> "http/web-mqtt";
+ "web-mqtt" -> "http/web-mqtt";
+ "web_mqtt" -> "http/web-mqtt";
+ "webmqtt/tls" -> "https/web-mqtt";
+ "web-mqtt/tls" -> "https/web-mqtt";
+ "webmqtt/ssl" -> "https/web-mqtt";
+ "web-mqtt/ssl" -> "https/web-mqtt";
+ "webmqtt+tls" -> "https/web-mqtt";
+ "web-mqtt+tls" -> "https/web-mqtt";
+ "webmqtt+ssl" -> "https/web-mqtt";
+ "web-mqtt+ssl" -> "https/web-mqtt";
+ "webstomp" -> "http/web-stomp";
+ "web-stomp" -> "http/web-stomp";
+ "web_stomp" -> "http/web-stomp";
+ "webstomp/tls" -> "https/web-stomp";
+ "web-stomp/tls" -> "https/web-stomp";
+ "webstomp/ssl" -> "https/web-stomp";
+ "web-stomp/ssl" -> "https/web-stomp";
+ "webstomp+tls" -> "https/web-stomp";
+ "web-stomp+tls" -> "https/web-stomp";
+ "webstomp+ssl" -> "https/web-stomp";
+ "web-stomp+ssl" -> "https/web-stomp";
+ Any -> Any
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_virtual_hosts.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_virtual_hosts.erl
new file mode 100644
index 0000000000..0bc9680adc
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_virtual_hosts.erl
@@ -0,0 +1,48 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_virtual_hosts'
+-module(rabbit_mgmt_wm_health_check_virtual_hosts).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_vhost_sup_sup:check() of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Vs when length(Vs) > 0 ->
+ Msg = <<"Some virtual hosts are down">>,
+ failure(Msg, Vs, ReqData, Context)
+ end.
+
+failure(Message, Vs, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {'virtual-hosts', Vs}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_healthchecks.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_healthchecks.erl
new file mode 100644
index 0000000000..4fc61bd0a7
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_healthchecks.erl
@@ -0,0 +1,76 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% This original One True Health Check™ has been deprecated as too coarse-grained,
+%% intrusive and prone to false positives under load.
+-module(rabbit_mgmt_wm_healthchecks).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case node0(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Node = node0(ReqData),
+ Timeout = case cowboy_req:header(<<"timeout">>, ReqData) of
+ undefined -> 70000;
+ Val -> list_to_integer(binary_to_list(Val))
+ end,
+ case rabbit_health_check:node(Node, Timeout) of
+ ok ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ {badrpc, timeout} ->
+ ErrMsg = rabbit_mgmt_format:print("node ~p health check timed out", [Node]),
+ failure(ErrMsg, ReqData, Context);
+ {badrpc, Err} ->
+ failure(rabbit_mgmt_format:print("~p", Err), ReqData, Context);
+ {error_string, Err} ->
+ S = rabbit_mgmt_format:escape_html_tags(
+ rabbit_data_coercion:to_list(rabbit_mgmt_format:print(Err))),
+ failure(S, ReqData, Context)
+ end.
+
+failure(Message, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(?HEALTH_CHECK_FAILURE_STATUS, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+node0(ReqData) ->
+ Node = case rabbit_mgmt_util:id(node, ReqData) of
+ none ->
+ node();
+ Node0 ->
+ list_to_atom(binary_to_list(Node0))
+ end,
+ case [N || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData),
+ proplists:get_value(name, N) == Node] of
+ [] -> not_found;
+ [_] -> Node
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_limit.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_limit.erl
new file mode 100644
index 0000000000..5f0e27c4ba
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_limit.erl
@@ -0,0 +1,64 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_limit).
+
+-export([init/2,
+ content_types_accepted/2, is_authorized/2,
+ allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case rabbit_mgmt_util:vhost(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(vhost_not_found, ReqData0, Context);
+ VHost ->
+ rabbit_mgmt_util:with_decode(
+ [value], ReqData0, Context,
+ fun([Value], _Body, ReqData) ->
+ Name = rabbit_mgmt_util:id(name, ReqData),
+ case rabbit_vhost_limit:update_limit(VHost, Name, Value,
+ Username) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(Reason), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_vhost_limit:clear_limit(rabbit_mgmt_util:vhost(ReqData),
+ name(ReqData), Username),
+ {true, ReqData, Context}.
+
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_limits.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_limits.erl
new file mode 100644
index 0000000000..96fbf64100
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_limits.erl
@@ -0,0 +1,59 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_limits).
+
+-export([init/2, to_json/2, content_types_provided/2,
+ resource_exists/2, is_authorized/2, allowed_methods/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"GET">>, <<"OPTIONS">>], ReqData, Context}.
+
+%% Admin user can see all vhosts
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost_visible(ReqData, Context).
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(limits(ReqData, Context), [], ReqData, Context).
+
+limits(ReqData, Context) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ none ->
+ User = Context#context.user,
+ VisibleVhosts = rabbit_mgmt_util:list_visible_vhosts_names(User),
+ [ [{vhost, VHost}, {value, Value}]
+ || {VHost, Value} <- rabbit_vhost_limit:list(),
+ lists:member(VHost, VisibleVhosts) ];
+ VHost when is_binary(VHost) ->
+ case rabbit_vhost_limit:list(VHost) of
+ [] -> [];
+ Value -> [[{vhost, VHost}, {value, Value}]]
+ end
+ end.
+%%--------------------------------------------------------------------
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_login.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_login.erl
new file mode 100644
index 0000000000..fec7c99643
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_login.erl
@@ -0,0 +1,53 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_login).
+
+-export([init/2, is_authorized/2,
+ allowed_methods/2, accept_content/2, content_types_provided/2,
+ content_types_accepted/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"x-www-form-urlencoded">>, '*'}, accept_content}], ReqData, Context}.
+
+is_authorized(#{method := <<"OPTIONS">>} = ReqData, Context) ->
+ {true, ReqData, Context};
+is_authorized(ReqData0, Context) ->
+ {ok, Body, ReqData} = cowboy_req:read_urlencoded_body(ReqData0),
+ Username = proplists:get_value(<<"username">>, Body),
+ Password = proplists:get_value(<<"password">>, Body),
+ case rabbit_mgmt_util:is_authorized_user(ReqData, Context, Username, Password) of
+ {true, ReqData1, Context1} ->
+ Value = base64:encode(<<Username/binary,":",Password/binary>>),
+ {true, cowboy_req:set_resp_cookie(<<"auth">>, Value, ReqData1), Context1};
+ Other ->
+ Other
+ end.
+
+accept_content(ReqData, Context) ->
+ rabbit_mgmt_util:post_respond(do_login(ReqData, Context)).
+
+do_login(ReqData, Context) ->
+ rabbit_mgmt_util:reply(ok, ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_node.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node.erl
new file mode 100644
index 0000000000..52c1d4d9bb
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node.erl
@@ -0,0 +1,78 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_node).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case node0(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(node0(ReqData), ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_monitor(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+node0(ReqData) ->
+ Node = list_to_atom(binary_to_list(rabbit_mgmt_util:id(node, ReqData))),
+ [Data] = node_data(Node, ReqData),
+ augment(ReqData, Node, Data).
+
+augment(ReqData, Node, Data) ->
+ lists:foldl(fun (Key, DataN) -> augment(Key, ReqData, Node, DataN) end,
+ Data, [memory, binary]).
+
+augment(Key, ReqData, Node, Data) ->
+ case rabbit_mgmt_util:qs_val(list_to_binary(atom_to_list(Key)), ReqData) of
+ <<"true">> -> Res = case rpc:call(Node, rabbit_vm, Key, [], infinity) of
+ {badrpc, _} -> not_available;
+ Result -> Result
+ end,
+ [{Key, Res} | Data];
+ _ -> Data
+ end.
+
+node_data(Node, ReqData) ->
+ S = rabbit_mnesia:status(),
+ Nodes = proplists:get_value(nodes, S),
+ Running = proplists:get_value(running_nodes, S),
+ Type = find_type(Node, Nodes),
+ Basic = [[{name, Node}, {running, lists:member(Node, Running)}, {type, Type}]],
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_nodes(Basic, rabbit_mgmt_util:range_ceil(ReqData));
+ true ->
+ Basic
+ end.
+
+find_type(Node, [{Type, Nodes} | Rest]) ->
+ case lists:member(Node, Nodes) of
+ true -> Type;
+ false -> find_type(Node, Rest)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory.erl
new file mode 100644
index 0000000000..19e4237b44
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory.erl
@@ -0,0 +1,75 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_node_memory).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {node_exists(ReqData, get_node(ReqData)), ReqData, Context}.
+
+to_json(ReqData, {Mode, Context}) ->
+ rabbit_mgmt_util:reply(augment(Mode, ReqData), ReqData, {Mode, Context}).
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, RD, C} = rabbit_mgmt_util:is_authorized_monitor(ReqData, Context),
+ {Res, RD, {Mode, C}}.
+
+%%--------------------------------------------------------------------
+get_node(ReqData) ->
+ list_to_atom(binary_to_list(rabbit_mgmt_util:id(node, ReqData))).
+
+node_exists(ReqData, Node) ->
+ case [N || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData),
+ proplists:get_value(name, N) == Node] of
+ [] -> false;
+ [_] -> true
+ end.
+
+augment(Mode, ReqData) ->
+ Node = get_node(ReqData),
+ case node_exists(ReqData, Node) of
+ false ->
+ not_found;
+ true ->
+ case rpc:call(Node, rabbit_vm, memory, [], infinity) of
+ {badrpc, _} -> [{memory, not_available}];
+ Result -> [{memory, format(Mode, Result)}]
+ end
+ end.
+
+format(absolute, Result) ->
+ Result;
+format(relative, Result) ->
+ {value, {total, Totals}, Rest} = lists:keytake(total, 1, Result),
+ Total = proplists:get_value(rss, Totals),
+ [{total, 100} | [{K, percentage(V, Total)} || {K, V} <- Rest,
+ K =/= strategy]].
+
+percentage(Part, Total) ->
+ case round((Part/Total) * 100) of
+ 0 when Part =/= 0 ->
+ 1;
+ Int ->
+ Int
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory_ets.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory_ets.erl
new file mode 100644
index 0000000000..2bc40d22ce
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory_ets.erl
@@ -0,0 +1,85 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_node_memory_ets).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {node_exists(ReqData, get_node(ReqData)), ReqData, Context}.
+
+to_json(ReqData, {Mode, Context}) ->
+ rabbit_mgmt_util:reply(augment(Mode, ReqData), ReqData, {Mode, Context}).
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, RD, C} = rabbit_mgmt_util:is_authorized_monitor(ReqData, Context),
+ {Res, RD, {Mode, C}}.
+
+%%--------------------------------------------------------------------
+get_node(ReqData) ->
+ list_to_atom(binary_to_list(rabbit_mgmt_util:id(node, ReqData))).
+
+get_filter(ReqData) ->
+ case rabbit_mgmt_util:id(filter, ReqData) of
+ none -> all;
+ <<"management">> -> rabbit_mgmt_storage;
+ Other when is_binary(Other) -> list_to_atom(binary_to_list(Other));
+ _ -> all
+ end.
+
+node_exists(ReqData, Node) ->
+ case [N || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData),
+ proplists:get_value(name, N) == Node] of
+ [] -> false;
+ [_] -> true
+ end.
+
+augment(Mode, ReqData) ->
+ Node = get_node(ReqData),
+ Filter = get_filter(ReqData),
+ case node_exists(ReqData, Node) of
+ false ->
+ not_found;
+ true ->
+ case rpc:call(Node, rabbit_vm, ets_tables_memory,
+ [Filter], infinity) of
+ {badrpc, _} -> [{ets_tables_memory, not_available}];
+ [] -> [{ets_tables_memory, no_tables}];
+ Result -> [{ets_tables_memory, format(Mode, Result)}]
+ end
+ end.
+
+format(absolute, Result) ->
+ Total = lists:sum([V || {_K,V} <- Result]),
+ [{total, Total} | Result];
+format(relative, Result) ->
+ Total = lists:sum([V || {_K,V} <- Result]),
+ [{total, 100} | [{K, percentage(V, Total)} || {K, V} <- Result]].
+
+percentage(Part, Total) ->
+ case round((Part/Total) * 100) of
+ 0 when Part =/= 0 ->
+ 1;
+ Int ->
+ Int
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_nodes.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_nodes.erl
new file mode 100644
index 0000000000..c3de2e0bd2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_nodes.erl
@@ -0,0 +1,56 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_nodes).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([all_nodes/1, all_nodes_raw/0]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ rabbit_mgmt_util:reply_list(all_nodes(ReqData), ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_monitor(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+all_nodes(ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_nodes(
+ all_nodes_raw(), rabbit_mgmt_util:range_ceil(ReqData));
+ true ->
+ all_nodes_raw()
+ end.
+
+all_nodes_raw() ->
+ S = rabbit_mnesia:status(),
+ Nodes = proplists:get_value(nodes, S),
+ Types = proplists:get_keys(Nodes),
+ Running = proplists:get_value(running_nodes, S),
+ [[{name, Node}, {type, Type}, {running, lists:member(Node, Running)}] ||
+ Type <- Types, Node <- proplists:get_value(Type, Nodes)].
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policies.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policies.erl
new file mode 100644
index 0000000000..7490c427dd
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policies.erl
@@ -0,0 +1,49 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_operator_policies).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case basic(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context),
+ ["priority"], ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+basic(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ none -> rabbit_policy:list_op();
+ VHost -> rabbit_policy:list_op(VHost)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policy.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policy.erl
new file mode 100644
index 0000000000..85bec3631d
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policy.erl
@@ -0,0 +1,83 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_operator_policy).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case policy(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(policy(ReqData), ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case rabbit_mgmt_util:vhost(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(vhost_not_found, ReqData0, Context);
+ VHost ->
+ rabbit_mgmt_util:with_decode(
+ [pattern, definition], ReqData0, Context,
+ fun([Pattern, Definition], Body, ReqData) ->
+ case rabbit_policy:set_op(
+ VHost, name(ReqData), Pattern,
+ maps:to_list(Definition),
+ maps:get(priority, Body, undefined),
+ maps:get('apply-to', Body, undefined),
+ Username) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(Reason), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_policy:delete_op(
+ rabbit_mgmt_util:vhost(ReqData), name(ReqData), Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+policy(ReqData) ->
+ rabbit_policy:lookup_op(
+ rabbit_mgmt_util:vhost(ReqData), name(ReqData)).
+
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_overview.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_overview.erl
new file mode 100644
index 0000000000..3a7e821b7d
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_overview.erl
@@ -0,0 +1,182 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_overview).
+
+-export([init/2]).
+-export([to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2, pget/3]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context = #context{user = User = #user{tags = Tags}}) ->
+ RatesMode = rabbit_mgmt_agent_config:get_env(rates_mode),
+ SRP = get_sample_retention_policies(),
+ %% NB: this duplicates what's in /nodes but we want a global idea
+ %% of this. And /nodes is not accessible to non-monitor users.
+ ExchangeTypes = lists:sort(
+ fun(ET1, ET2) ->
+ proplists:get_value(name, ET1, none)
+ =<
+ proplists:get_value(name, ET2, none)
+ end,
+ rabbit_mgmt_external_stats:list_registry_plugins(exchange)),
+ Overview0 = [{management_version, version(rabbitmq_management)},
+ {rates_mode, RatesMode},
+ {sample_retention_policies, SRP},
+ {exchange_types, ExchangeTypes},
+ {product_version, list_to_binary(rabbit:product_version())},
+ {product_name, list_to_binary(rabbit:product_name())},
+ {rabbitmq_version, list_to_binary(rabbit:base_product_version())},
+ {cluster_name, rabbit_nodes:cluster_name()},
+ {erlang_version, erlang_version()},
+ {erlang_full_version, erlang_full_version()},
+ {disable_stats, rabbit_mgmt_util:disable_stats(ReqData)},
+ {enable_queue_totals, rabbit_mgmt_util:enable_queue_totals(ReqData)}],
+ try
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ Range = rabbit_mgmt_util:range(ReqData),
+ Overview =
+ case rabbit_mgmt_util:is_monitor(Tags) of
+ true ->
+ Overview0 ++
+ [{K, maybe_map(V)} ||
+ {K,V} <- rabbit_mgmt_db:get_overview(Range)] ++
+ [{node, node()},
+ {listeners, listeners()},
+ {contexts, web_contexts(ReqData)}];
+ _ ->
+ Overview0 ++
+ [{K, maybe_map(V)} ||
+ {K, V} <- rabbit_mgmt_db:get_overview(User, Range)]
+ end,
+ rabbit_mgmt_util:reply(Overview, ReqData, Context);
+ true ->
+ VHosts = case rabbit_mgmt_util:is_monitor(Tags) of
+ true -> rabbit_vhost:list_names();
+ _ -> rabbit_mgmt_util:list_visible_vhosts_names(User)
+ end,
+
+ ObjectTotals = case rabbit_mgmt_util:is_monitor(Tags) of
+ true ->
+ [{queues, rabbit_amqqueue:count()},
+ {exchanges, rabbit_exchange:count()},
+ {connections, rabbit_connection_tracking:count()}];
+ _ ->
+ [{queues, length([Q || V <- VHosts, Q <- rabbit_amqqueue:list(V)])},
+ {exchanges, length([X || V <- VHosts, X <- rabbit_exchange:list(V)])}]
+ end,
+ Overview = Overview0 ++
+ [{node, node()},
+ {object_totals, ObjectTotals}],
+ rabbit_mgmt_util:reply(Overview, ReqData, Context)
+ end
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+version(App) ->
+ {ok, V} = application:get_key(App, vsn),
+ list_to_binary(V).
+
+listeners() ->
+ rabbit_mgmt_util:sort_list(
+ [rabbit_mgmt_format:listener(L)
+ || L <- rabbit_networking:active_listeners()],
+ ["protocol", "port", "node"] ).
+
+maybe_map(L) when is_list(L) -> maps:from_list(L);
+maybe_map(V) -> V.
+
+%%--------------------------------------------------------------------
+
+web_contexts(ReqData) ->
+ rabbit_mgmt_util:sort_list(
+ lists:append(
+ [fmt_contexts(N) || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData)]),
+ ["description", "port", "node"]).
+
+fmt_contexts(Node) ->
+ [fmt_context(Node, C) || C <- pget(contexts, Node, [])].
+
+fmt_context(Node, C) ->
+ rabbit_mgmt_format:web_context([{node, pget(name, Node)} | C]).
+
+erlang_version() -> list_to_binary(rabbit_misc:otp_release()).
+
+erlang_full_version() ->
+ list_to_binary(rabbit_misc:otp_system_version()).
+
+get_sample_retention_policies() ->
+ P = rabbit_mgmt_agent_config:get_env(sample_retention_policies),
+ get_sample_retention_policies(P).
+
+get_sample_retention_policies(undefined) ->
+ [{global, []}, {basic, []}, {detailed, []}];
+get_sample_retention_policies(Policies) ->
+ [transform_retention_policy(Pol, Policies) || Pol <- [global, basic, detailed]].
+
+transform_retention_policy(Pol, Policies) ->
+ case proplists:lookup(Pol, Policies) of
+ none ->
+ {Pol, []};
+ {Pol, Intervals} ->
+ {Pol, transform_retention_intervals(Intervals, [])}
+ end.
+
+transform_retention_intervals([], Acc) ->
+ lists:sort(Acc);
+transform_retention_intervals([{MaxAgeInSeconds, _}|Rest], Acc) ->
+ %
+ % Seconds | Interval
+ % 60 | last minute
+ % 600 | last 10 minutes
+ % 3600 | last hour
+ % 28800 | last 8 hours
+ % 86400 | last day
+ %
+ % rabbitmq/rabbitmq-management#635
+ %
+ % We check for the max age in seconds to be within 10% of the value above.
+ % The reason being that the default values are "bit higher" to accommodate
+ % edge cases (see deps/rabbitmq_management_agent/Makefile)
+ AccVal = if
+ MaxAgeInSeconds >= 0 andalso MaxAgeInSeconds =< 66 ->
+ 60;
+ MaxAgeInSeconds >= 540 andalso MaxAgeInSeconds =< 660 ->
+ 600;
+ MaxAgeInSeconds >= 3240 andalso MaxAgeInSeconds =< 3960 ->
+ 3600;
+ MaxAgeInSeconds >= 25920 andalso MaxAgeInSeconds =< 31681 ->
+ 28800;
+ MaxAgeInSeconds >= 77760 andalso MaxAgeInSeconds =< 95041 ->
+ 86400;
+ true ->
+ 0
+ end,
+ transform_retention_intervals(Rest, [AccVal|Acc]).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameter.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameter.erl
new file mode 100644
index 0000000000..17a97ca4bf
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameter.erl
@@ -0,0 +1,88 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_parameter).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case parameter(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:parameter(
+ rabbit_mgmt_wm_parameters:fix_shovel_publish_properties(parameter(ReqData))),
+ ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = User}) ->
+ case rabbit_mgmt_util:vhost(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(vhost_not_found, ReqData0, Context);
+ VHost ->
+ rabbit_mgmt_util:with_decode(
+ [value], ReqData0, Context,
+ fun([Value], _, ReqData) ->
+ case rabbit_runtime_parameters:set(
+ VHost, component(ReqData), name(ReqData),
+ if
+ is_map(Value) -> maps:to_list(Value);
+ true -> Value
+ end,
+ User) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ S = rabbit_mgmt_format:escape_html_tags(
+ rabbit_data_coercion:to_list(Reason)),
+ rabbit_mgmt_util:bad_request(
+ rabbit_data_coercion:to_binary(S), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_runtime_parameters:clear(
+ rabbit_mgmt_util:vhost(ReqData), component(ReqData), name(ReqData), Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_policies(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+parameter(ReqData) ->
+ rabbit_runtime_parameters:lookup(
+ rabbit_mgmt_util:vhost(ReqData), component(ReqData), name(ReqData)).
+
+component(ReqData) -> rabbit_mgmt_util:id(component, ReqData).
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameters.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameters.erl
new file mode 100644
index 0000000000..6acd7a608f
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameters.erl
@@ -0,0 +1,78 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_parameters).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1]).
+-export([fix_shovel_publish_properties/1]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case basic(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context),
+ ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_policies(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+%% Hackish fix to make sure we return a JSON object instead of an empty list
+%% when the publish-properties value is empty. Should be removed in 3.7.0
+%% when we switch to a new JSON library.
+fix_shovel_publish_properties(P) ->
+ case lists:keyfind(component, 1, P) of
+ {_, <<"shovel">>} ->
+ case lists:keytake(value, 1, P) of
+ {value, {_, Values}, P2} ->
+ case lists:keytake(<<"publish-properties">>, 1, Values) of
+ {_, {_, []}, Values2} ->
+ P2 ++ [{value, Values2 ++ [{<<"publish-properties">>, empty_struct}]}];
+ _ ->
+ P
+ end;
+ _ -> P
+ end;
+ _ -> P
+ end.
+
+basic(ReqData) ->
+ Raw = case rabbit_mgmt_util:id(component, ReqData) of
+ none -> rabbit_runtime_parameters:list();
+ Name -> case rabbit_mgmt_util:vhost(ReqData) of
+ none -> rabbit_runtime_parameters:list_component(
+ Name);
+ not_found -> not_found;
+ VHost -> rabbit_runtime_parameters:list(
+ VHost, Name)
+ end
+ end,
+ case Raw of
+ not_found -> not_found;
+ _ -> [rabbit_mgmt_format:parameter(fix_shovel_publish_properties(P)) || P <- Raw]
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_permission.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permission.erl
new file mode 100644
index 0000000000..74df5ab960
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permission.erl
@@ -0,0 +1,102 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_permission).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case perms(ReqData) of
+ none -> false;
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(perms(ReqData), ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case perms(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found,
+ ReqData0, Context);
+ _ ->
+ User = rabbit_mgmt_util:id(user, ReqData0),
+ VHost = rabbit_mgmt_util:id(vhost, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [configure, write, read], ReqData0, Context,
+ fun([Conf, Write, Read], _, ReqData) ->
+ rabbit_auth_backend_internal:set_permissions(
+ User, VHost, Conf, Write, Read, Username),
+ {true, ReqData, Context}
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ VHost = rabbit_mgmt_util:id(vhost, ReqData),
+ rabbit_auth_backend_internal:clear_permissions(User, VHost, Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+perms(ReqData) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ case rabbit_auth_backend_internal:lookup_user(User) of
+ {ok, _} ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found ->
+ not_found;
+ VHost ->
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms =
+ rabbit_auth_backend_internal:list_user_vhost_permissions(
+ User, VHost),
+ case Perms of
+ [Rest] -> [{user, User},
+ {vhost, VHost} | Rest];
+ [] -> none
+ end
+ end,
+ fun() -> not_found end)
+ end;
+ {error, _} ->
+ not_found
+ end.
+
+
+
+
+
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions.erl
new file mode 100644
index 0000000000..ad1799b84b
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions.erl
@@ -0,0 +1,38 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_permissions).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([permissions/0]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(permissions(), ["vhost", "user"],
+ ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+permissions() ->
+ rabbit_auth_backend_internal:list_permissions().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_user.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_user.erl
new file mode 100644
index 0000000000..ad1a2e5f96
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_user.erl
@@ -0,0 +1,47 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_permissions_user).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_user:user(ReqData) of
+ {ok, _} -> true;
+ {error, _} -> false
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms = rabbit_auth_backend_internal:list_user_permissions(User),
+ rabbit_mgmt_util:reply_list([[{user, User} | Rest] || Rest <- Perms],
+ ["vhost", "user"], ReqData, Context)
+ end,
+ fun() ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found, ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_vhost.erl
new file mode 100644
index 0000000000..435c15faa4
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_vhost.erl
@@ -0,0 +1,44 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_permissions_vhost).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(rabbit_mgmt_wm_vhost:id(ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ VHost = rabbit_mgmt_util:id(vhost, ReqData),
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms = rabbit_auth_backend_internal:list_vhost_permissions(VHost),
+ rabbit_mgmt_util:reply_list([[{vhost, VHost} | Rest] || Rest <- Perms],
+ ["vhost", "user"], ReqData, Context)
+ end,
+ fun() ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found, ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_policies.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_policies.erl
new file mode 100644
index 0000000000..c2b1bf1a1a
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_policies.erl
@@ -0,0 +1,49 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_policies).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case basic(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context),
+ ["priority"], ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+basic(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ none -> rabbit_policy:list();
+ VHost -> rabbit_policy:list(VHost)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_policy.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_policy.erl
new file mode 100644
index 0000000000..5ab8033925
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_policy.erl
@@ -0,0 +1,82 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_policy).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case policy(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(policy(ReqData), ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case rabbit_mgmt_util:vhost(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(vhost_not_found, ReqData0, Context);
+ VHost ->
+ rabbit_mgmt_util:with_decode(
+ [pattern, definition], ReqData0, Context,
+ fun([Pattern, Definition], Body, ReqData) ->
+ case rabbit_policy:set(
+ VHost, name(ReqData), Pattern,
+ maps:to_list(Definition),
+ maps:get(priority, Body, undefined),
+ maps:get('apply-to', Body, undefined),
+ Username) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ rabbit_mgmt_util:bad_request(
+ rabbit_mgmt_format:escape_html_tags(Reason), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_policy:delete(
+ rabbit_mgmt_util:vhost(ReqData), name(ReqData), Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_policies(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+policy(ReqData) ->
+ rabbit_policy:lookup(
+ rabbit_mgmt_util:vhost(ReqData), name(ReqData)).
+
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue.erl
new file mode 100644
index 0000000000..6560be1524
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue.erl
@@ -0,0 +1,113 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queue).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2, queue/1, queue/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case queue(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ [Q] = rabbit_mgmt_db:augment_queues(
+ [queue(ReqData)], rabbit_mgmt_util:range_ceil(ReqData),
+ full),
+ Payload = rabbit_mgmt_format:clean_consumer_details(
+ rabbit_mgmt_format:strip_pids(Q)),
+ rabbit_mgmt_util:reply(ensure_defaults(Payload), ReqData, Context);
+ true ->
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:strip_pids(queue(ReqData)),
+ ReqData, Context)
+ end
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+accept_content(ReqData, Context) ->
+ Name = rabbit_mgmt_util:id(queue, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'queue.declare',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, Name}], "Declare queue error: ~s", ReqData, Context).
+
+delete_resource(ReqData, Context) ->
+ %% We need to retrieve manually if-unused and if-empty, as the HTTP API uses '-'
+ %% while the record uses '_'
+ IfUnused = <<"true">> =:= rabbit_mgmt_util:qs_val(<<"if-unused">>, ReqData),
+ IfEmpty = <<"true">> =:= rabbit_mgmt_util:qs_val(<<"if-empty">>, ReqData),
+ Name = rabbit_mgmt_util:id(queue, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'queue.delete',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, Name},
+ {if_unused, IfUnused},
+ {if_empty, IfEmpty}], "Delete queue error: ~s", ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+%% this is here to ensure certain data points are always there. When a queue
+%% is moved there can be transient periods where certain advanced metrics aren't
+%% yet available on the new node.
+ensure_defaults(Payload0) ->
+ case lists:keyfind(garbage_collection, 1, Payload0) of
+ {_K, _V} -> Payload0;
+ false ->
+ [{garbage_collection,
+ [{max_heap_size,-1},
+ {min_bin_vheap_size,-1},
+ {min_heap_size,-1},
+ {fullsweep_after,-1},
+ {minor_gcs,-1}]} | Payload0]
+ end.
+
+queue(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ VHost -> queue(VHost, rabbit_mgmt_util:id(queue, ReqData))
+ end.
+
+
+queue(VHost, QName) ->
+ Name = rabbit_misc:r(VHost, queue, QName),
+ case rabbit_amqqueue:lookup(Name) of
+ {ok, Q} -> rabbit_mgmt_format:queue(Q);
+ {error, not_found} -> not_found
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_actions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_actions.erl
new file mode 100644
index 0000000000..c3b13f40c2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_actions.erl
@@ -0,0 +1,68 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queue_actions).
+
+-export([init/2, resource_exists/2, is_authorized/2,
+ allowed_methods/2, content_types_accepted/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("rabbit/include/amqqueue.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_queue:queue(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+accept_content(ReqData, Context) ->
+ rabbit_mgmt_util:post_respond(do_it(ReqData, Context)).
+
+do_it(ReqData0, Context) ->
+ VHost = rabbit_mgmt_util:vhost(ReqData0),
+ QName = rabbit_mgmt_util:id(queue, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [action], ReqData0, Context,
+ fun([Action], _Body, ReqData) ->
+ rabbit_amqqueue:with(
+ rabbit_misc:r(VHost, queue, QName),
+ fun(Q) -> action(Action, Q, ReqData, Context) end)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+action(<<"sync">>, Q, ReqData, Context) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ spawn(fun() -> rabbit_amqqueue:sync_mirrors(QPid) end),
+ {true, ReqData, Context};
+
+action(<<"cancel_sync">>, Q, ReqData, Context) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ _ = rabbit_amqqueue:cancel_sync_mirrors(QPid),
+ {true, ReqData, Context};
+
+action(Else, _Q, ReqData, Context) ->
+ rabbit_mgmt_util:bad_request({unknown, Else}, ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_get.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_get.erl
new file mode 100644
index 0000000000..88ad9a0034
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_get.erl
@@ -0,0 +1,166 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queue_get).
+
+-export([init/2, resource_exists/2, is_authorized/2,
+ allowed_methods/2, accept_content/2, content_types_provided/2,
+ content_types_accepted/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_queue:queue(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+accept_content(ReqData, Context) ->
+ rabbit_mgmt_util:post_respond(do_it(ReqData, Context)).
+
+do_it(ReqData0, Context) ->
+ VHost = rabbit_mgmt_util:vhost(ReqData0),
+ Q = rabbit_mgmt_util:id(queue, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [ackmode, count, encoding], ReqData0, Context,
+ fun([AckModeBin, CountBin, EncBin], Body, ReqData) ->
+ rabbit_mgmt_util:with_channel(
+ VHost, ReqData, Context,
+ fun (Ch) ->
+ AckMode = list_to_atom(binary_to_list(AckModeBin)),
+ Count = rabbit_mgmt_util:parse_int(CountBin),
+ Enc = case EncBin of
+ <<"auto">> -> auto;
+ <<"base64">> -> base64;
+ _ -> throw({error, <<"Unsupported encoding. Please use auto or base64.">>})
+ end,
+ Trunc = case maps:get(truncate, Body, undefined) of
+ undefined -> none;
+ TruncBin -> rabbit_mgmt_util:parse_int(
+ TruncBin)
+ end,
+
+ Reply = basic_gets(Count, Ch, Q, AckMode, Enc, Trunc),
+ maybe_rejects(Reply, Ch, AckMode),
+ rabbit_mgmt_util:reply(remove_delivery_tag(Reply),
+ ReqData, Context)
+ end)
+ end).
+
+
+
+
+basic_gets(0, _, _, _, _, _) ->
+ [];
+
+basic_gets(Count, Ch, Q, AckMode, Enc, Trunc) ->
+ case basic_get(Ch, Q, AckMode, Enc, Trunc) of
+ none -> [];
+ M -> [M | basic_gets(Count - 1, Ch, Q, AckMode, Enc, Trunc)]
+ end.
+
+
+
+ackmode_to_requeue(reject_requeue_false) -> false;
+ackmode_to_requeue(reject_requeue_true) -> true.
+
+parse_ackmode(ack_requeue_false) -> true;
+parse_ackmode(ack_requeue_true) -> false;
+parse_ackmode(reject_requeue_false) -> false;
+parse_ackmode(reject_requeue_true) -> false.
+
+
+% the messages must rejects later,
+% because we get always the same message if the
+% messages are requeued inside basic_get/5
+maybe_rejects(R, Ch, AckMode) ->
+ lists:foreach(fun(X) ->
+ maybe_reject(Ch, AckMode,
+ proplists:get_value(delivery_tag, X))
+ end, R).
+
+% removes the delivery_tag from the reply.
+% it is not necessary
+remove_delivery_tag([]) -> [];
+remove_delivery_tag([H|T]) ->
+ [proplists:delete(delivery_tag, H) | [X || X <- remove_delivery_tag(T)]].
+
+
+maybe_reject(Ch, AckMode, DeliveryTag) when AckMode == reject_requeue_true;
+ AckMode == reject_requeue_false ->
+ amqp_channel:call(Ch,
+ #'basic.reject'{delivery_tag = DeliveryTag,
+ requeue = ackmode_to_requeue(AckMode)});
+maybe_reject(_Ch, _AckMode, _DeliveryTag) -> ok.
+
+
+basic_get(Ch, Q, AckMode, Enc, Trunc) ->
+ case amqp_channel:call(Ch,
+ #'basic.get'{queue = Q,
+ no_ack = parse_ackmode(AckMode)}) of
+ {#'basic.get_ok'{redelivered = Redelivered,
+ exchange = Exchange,
+ routing_key = RoutingKey,
+ message_count = MessageCount,
+ delivery_tag = DeliveryTag},
+ #amqp_msg{props = Props, payload = Payload}} ->
+ [{payload_bytes, size(Payload)},
+ {redelivered, Redelivered},
+ {exchange, Exchange},
+ {routing_key, RoutingKey},
+ {message_count, MessageCount},
+ {delivery_tag, DeliveryTag},
+ {properties, rabbit_mgmt_format:basic_properties(Props)}] ++
+ payload_part(maybe_truncate(Payload, Trunc), Enc);
+ #'basic.get_empty'{} ->
+ none
+ end.
+
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+maybe_truncate(Payload, none) -> Payload;
+maybe_truncate(Payload, Len) when size(Payload) < Len -> Payload;
+maybe_truncate(Payload, Len) ->
+ <<Start:Len/binary, _Rest/binary>> = Payload,
+ Start.
+
+payload_part(Payload, Enc) ->
+ {PL, E} = case Enc of
+ auto -> case is_utf8(Payload) of
+ true -> {Payload, string};
+ false -> {base64:encode(Payload), base64}
+ end;
+ _ -> {base64:encode(Payload), base64}
+ end,
+ [{payload, PL}, {payload_encoding, E}].
+
+is_utf8(<<>>) -> true;
+is_utf8(<<_/utf8, Rest/bits>>) -> is_utf8(Rest);
+is_utf8(_) -> false.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_purge.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_purge.erl
new file mode 100644
index 0000000000..b250984d11
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_purge.erl
@@ -0,0 +1,42 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queue_purge).
+
+-export([init/2, resource_exists/2, is_authorized/2, allowed_methods/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_queue:queue(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+delete_resource(ReqData, Context) ->
+ Name = rabbit_mgmt_util:id(queue, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'queue.purge',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, Name}], "Error purging queue: ~s", ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queues.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queues.erl
new file mode 100644
index 0000000000..7df50f61d9
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queues.erl
@@ -0,0 +1,113 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queues).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1]).
+-export([variances/2,
+ augmented/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbit/include/amqqueue.hrl").
+
+-define(BASIC_COLUMNS, ["vhost", "name", "durable", "auto_delete", "exclusive",
+ "owner_pid", "arguments", "pid", "state"]).
+
+-define(DEFAULT_SORT, ["vhost", "name"]).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case queues0(ReqData) of
+ vhost_not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Basic = basic_vhost_filtered(ReqData, Context),
+ Data = rabbit_mgmt_util:augment_resources(Basic, ?DEFAULT_SORT,
+ ?BASIC_COLUMNS, ReqData,
+ Context, fun augment/2),
+ rabbit_mgmt_util:reply(Data, ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData,
+ Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+%% Exported functions
+
+basic(ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ [rabbit_mgmt_format:queue(Q) || Q <- queues0(ReqData)] ++
+ [rabbit_mgmt_format:queue(amqqueue:set_state(Q, down)) ||
+ Q <- down_queues(ReqData)];
+ true ->
+ case rabbit_mgmt_util:enable_queue_totals(ReqData) of
+ false ->
+ [rabbit_mgmt_format:queue(Q) ++ policy(Q) || Q <- queues0(ReqData)] ++
+ [rabbit_mgmt_format:queue(amqqueue:set_state(Q, down)) ||
+ Q <- down_queues(ReqData)];
+ true ->
+ [rabbit_mgmt_format:queue_info(Q) || Q <- queues_with_totals(ReqData)] ++
+ [rabbit_mgmt_format:queue(amqqueue:set_state(Q, down)) ||
+ Q <- down_queues(ReqData)]
+ end
+ end.
+
+augmented(ReqData, Context) ->
+ augment(rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context), ReqData).
+
+%%--------------------------------------------------------------------
+%% Private helpers
+
+augment(Basic, ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_queues(Basic, rabbit_mgmt_util:range_ceil(ReqData),
+ basic);
+ true ->
+ Basic
+ end.
+
+basic_vhost_filtered(ReqData, Context) ->
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context).
+
+queues0(ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_amqqueue:list/1).
+
+queues_with_totals(ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData, fun collect_info_all/1).
+
+collect_info_all(VHostPath) ->
+ rabbit_amqqueue:collect_info_all(VHostPath, [name, durable, auto_delete, exclusive, owner_pid, arguments, type, state, policy, totals, type_specific]).
+
+down_queues(ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_amqqueue:list_down/1).
+
+policy(Q) ->
+ case rabbit_policy:name(Q) of
+ none -> [];
+ Policy -> [{policy, Policy}]
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_rebalance_queues.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_rebalance_queues.erl
new file mode 100644
index 0000000000..982655493c
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_rebalance_queues.erl
@@ -0,0 +1,58 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_rebalance_queues).
+
+-export([init/2, service_available/2, resource_exists/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ Headers = rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE),
+ {cowboy_rest, Headers, {Mode, #context{}}}.
+
+service_available(Req, {{queues, all}, _Context}=State) ->
+ {true, Req, State};
+service_available(Req, State) ->
+ {false, Req, State}.
+
+variances(Req, State) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, State}.
+
+content_types_provided(Req, State) ->
+ {[{{<<"text">>, <<"plain">>, '*'}, undefined}], Req, State}.
+
+content_types_accepted(Req, State) ->
+ {[{'*', accept_content}], Req, State}.
+
+allowed_methods(Req, State) ->
+ {[<<"POST">>, <<"OPTIONS">>], Req, State}.
+
+resource_exists(Req, State) ->
+ {true, Req, State}.
+
+accept_content(Req, {_Mode, #context{user = #user{username = Username}}}=State) ->
+ try
+ rabbit_log:info("User '~s' has initiated a queue rebalance", [Username]),
+ spawn(fun() ->
+ rabbit_amqqueue:rebalance(all, <<".*">>, <<".*">>)
+ end),
+ {true, Req, State}
+ catch
+ {error, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), Req, State)
+ end.
+
+is_authorized(Req0, {Mode, Context0}) ->
+ {Res, Req1, Context1} = rabbit_mgmt_util:is_authorized_admin(Req0, Context0),
+ {Res, Req1, {Mode, Context1}}.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_redirect.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_redirect.erl
new file mode 100644
index 0000000000..c5b3808d5a
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_redirect.erl
@@ -0,0 +1,13 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_redirect).
+-export([init/2]).
+
+init(Req0, RedirectTo) ->
+ Req = cowboy_req:reply(301, #{<<"location">> => RedirectTo}, Req0),
+ {ok, Req, RedirectTo}.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_reset.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_reset.erl
new file mode 100644
index 0000000000..ea3d698ece
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_reset.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_reset).
+
+-export([init/2, is_authorized/2, resource_exists/2,
+ allowed_methods/2, delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ case get_node(ReqData) of
+ none -> {true, ReqData, Context};
+ {ok, Node} -> {lists:member(Node, rabbit_nodes:all_running()),
+ ReqData, Context}
+ end.
+
+delete_resource(ReqData, Context) ->
+ case get_node(ReqData) of
+ none ->
+ rabbit_mgmt_storage:reset_all();
+ {ok, Node} ->
+ rpc:call(Node, rabbit_mgmt_storage, reset, [])
+ end,
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+
+get_node(ReqData) ->
+ case rabbit_mgmt_util:id(node, ReqData) of
+ none ->
+ none;
+ Node0 ->
+ Node = list_to_atom(binary_to_list(Node0)),
+ {ok, Node}
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl
new file mode 100644
index 0000000000..4a7f608a8a
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl
@@ -0,0 +1,65 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% Alias for cowboy_static that accepts a list of directories
+%% where static files can be found.
+
+-module(rabbit_mgmt_wm_static).
+
+-include_lib("kernel/include/file.hrl").
+
+-export([init/2]).
+-export([malformed_request/2]).
+-export([forbidden/2]).
+-export([content_types_provided/2]).
+-export([resource_exists/2]).
+-export([last_modified/2]).
+-export([generate_etag/2]).
+-export([get_file/2]).
+
+
+init(Req0, {priv_file, _App, _Path}=Opts) ->
+ Req1 = rabbit_mgmt_headers:set_common_permission_headers(Req0, ?MODULE),
+ cowboy_static:init(Req1, Opts);
+init(Req0, [{App, Path}]) ->
+ Req1 = rabbit_mgmt_headers:set_common_permission_headers(Req0, ?MODULE),
+ do_init(Req1, App, Path);
+init(Req0, [{App, Path}|Tail]) ->
+ Req1 = rabbit_mgmt_headers:set_common_permission_headers(Req0, ?MODULE),
+ PathInfo = cowboy_req:path_info(Req1),
+ Filepath = filename:join([code:priv_dir(App), Path|PathInfo]),
+ %% We use erl_prim_loader because the file may be inside an .ez archive.
+ FileInfo = erl_prim_loader:read_file_info(binary_to_list(Filepath)),
+ case FileInfo of
+ {ok, #file_info{type = regular}} -> do_init(Req1, App, Path);
+ {ok, #file_info{type = symlink}} -> do_init(Req1, App, Path);
+ _ -> init(Req0, Tail)
+ end.
+
+do_init(Req, App, Path) ->
+ cowboy_static:init(Req, {priv_dir, App, Path}).
+
+malformed_request(Req, State) ->
+ cowboy_static:malformed_request(Req, State).
+
+forbidden(Req, State) ->
+ cowboy_static:forbidden(Req, State).
+
+content_types_provided(Req, State) ->
+ cowboy_static:content_types_provided(Req, State).
+
+resource_exists(Req, State) ->
+ cowboy_static:resource_exists(Req, State).
+
+last_modified(Req, State) ->
+ cowboy_static:last_modified(Req, State).
+
+generate_etag(Req, State) ->
+ cowboy_static:generate_etag(Req, State).
+
+get_file(Req, State) ->
+ cowboy_static:get_file(Req, State).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permission.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permission.erl
new file mode 100644
index 0000000000..99f25bb69e
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permission.erl
@@ -0,0 +1,103 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_topic_permission).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case topic_perms(ReqData) of
+ none -> false;
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(topic_perms(ReqData), ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case topic_perms(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found,
+ ReqData0, Context);
+ _ ->
+ User = rabbit_mgmt_util:id(user, ReqData0),
+ VHost = rabbit_mgmt_util:id(vhost, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [exchange, write, read], ReqData0, Context,
+ fun([Exchange, Write, Read], _, ReqData) ->
+ rabbit_auth_backend_internal:set_topic_permissions(
+ User, VHost, Exchange, Write, Read, Username),
+ {true, ReqData, Context}
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ VHost = rabbit_mgmt_util:id(vhost, ReqData),
+ case rabbit_mgmt_util:id(exchange, ReqData) of
+ none ->
+ rabbit_auth_backend_internal:clear_topic_permissions(User, VHost, Username);
+ Exchange ->
+ rabbit_auth_backend_internal:clear_topic_permissions(User, VHost, Exchange,
+ Username)
+ end,
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+topic_perms(ReqData) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ case rabbit_auth_backend_internal:lookup_user(User) of
+ {ok, _} ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found ->
+ not_found;
+ VHost ->
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms =
+ rabbit_auth_backend_internal:list_user_vhost_topic_permissions(
+ User, VHost),
+ case Perms of
+ [] -> none;
+ TopicPermissions -> [[{user, User}, {vhost, VHost} | TopicPermission]
+ || TopicPermission <- TopicPermissions]
+ end
+ end,
+ fun() -> not_found end)
+ end;
+ {error, _} ->
+ not_found
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions.erl
new file mode 100644
index 0000000000..c1406043cd
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions.erl
@@ -0,0 +1,38 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_topic_permissions).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([topic_permissions/0]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(topic_permissions(), ["vhost", "user"],
+ ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+topic_permissions() ->
+ rabbit_auth_backend_internal:list_topic_permissions().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_user.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_user.erl
new file mode 100644
index 0000000000..7d9c2d58fc
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_user.erl
@@ -0,0 +1,47 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_topic_permissions_user).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_user:user(ReqData) of
+ {ok, _} -> true;
+ {error, _} -> false
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms = rabbit_auth_backend_internal:list_user_topic_permissions(User),
+ rabbit_mgmt_util:reply_list([[{user, User} | Rest] || Rest <- Perms],
+ ["vhost", "user"], ReqData, Context)
+ end,
+ fun() ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found, ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_vhost.erl
new file mode 100644
index 0000000000..8540ee9a77
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_vhost.erl
@@ -0,0 +1,44 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_topic_permissions_vhost).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(rabbit_mgmt_wm_vhost:id(ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ VHost = rabbit_mgmt_util:id(vhost, ReqData),
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms = rabbit_auth_backend_internal:list_vhost_topic_permissions(VHost),
+ rabbit_mgmt_util:reply_list([[{vhost, VHost} | Rest] || Rest <- Perms],
+ ["vhost", "user"], ReqData, Context)
+ end,
+ fun() ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found, ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_user.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user.erl
new file mode 100644
index 0000000000..4c014565f1
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user.erl
@@ -0,0 +1,73 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_user).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2, user/1, put_user/2, put_user/3]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case user(ReqData) of
+ {ok, _} -> true;
+ {error, _} -> false
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ {ok, User} = user(ReqData),
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:internal_user(User),
+ ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = ActingUser}}) ->
+ Username = rabbit_mgmt_util:id(user, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [], ReqData0, Context,
+ fun(_, User, ReqData) ->
+ put_user(User#{name => Username}, ActingUser),
+ {true, ReqData, Context}
+ end).
+
+delete_resource(ReqData, Context = #context{user = #user{username = ActingUser}}) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ rabbit_auth_backend_internal:delete_user(User, ActingUser),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+user(ReqData) ->
+ rabbit_auth_backend_internal:lookup_user(rabbit_mgmt_util:id(user, ReqData)).
+
+put_user(User, ActingUser) ->
+ put_user(User, undefined, ActingUser).
+
+put_user(User, Version, ActingUser) ->
+ rabbit_auth_backend_internal:put_user(User, Version, ActingUser).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limit.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limit.erl
new file mode 100644
index 0000000000..8e59c55371
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limit.erl
@@ -0,0 +1,62 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_user_limit).
+
+-export([init/2,
+ content_types_accepted/2, is_authorized/2,
+ allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+accept_content(ReqData0, Context = #context{user = #user{username = ActingUser}}) ->
+ case rabbit_mgmt_util:id(user, ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(user_not_found, ReqData0, Context);
+ Username ->
+ rabbit_mgmt_util:with_decode(
+ [value], ReqData0, Context,
+ fun([Value], _Body, ReqData) ->
+ Limit = #{name(ReqData) => Value},
+ case rabbit_auth_backend_internal:set_user_limits(Username, Limit, ActingUser) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(Reason), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = ActingUser}}) ->
+ ok = rabbit_auth_backend_internal:clear_user_limits(
+ rabbit_mgmt_util:id(user, ReqData), name(ReqData), ActingUser),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limits.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limits.erl
new file mode 100644
index 0000000000..7e4d541f32
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limits.erl
@@ -0,0 +1,63 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_user_limits).
+
+-export([init/2, to_json/2, content_types_provided/2,
+ resource_exists/2, is_authorized/2, allowed_methods/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"GET">>, <<"OPTIONS">>], ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost_visible(ReqData, Context).
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case user(ReqData) of
+ none -> true;
+ Username -> rabbit_auth_backend_internal:exists(Username)
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(limits(ReqData, Context), [], ReqData, Context).
+
+limits(ReqData, _Context) ->
+ case user(ReqData) of
+ none ->
+ [ [{user, internal_user:get_username(U)}, {value, maps:to_list(internal_user:get_limits(U))}]
+ || U <- rabbit_auth_backend_internal:all_users(),
+ internal_user:get_limits(U) =/= #{}];
+ Username when is_binary(Username) ->
+ case rabbit_auth_backend_internal:get_user_limits(Username) of
+ Value when is_map(Value) ->
+ case maps:size(Value) of
+ 0 -> [];
+ _ -> [[{user, Username}, {value, maps:to_list(Value)}]]
+ end;
+ undefined ->
+ []
+ end
+ end.
+%%--------------------------------------------------------------------
+
+user(ReqData) ->
+ rabbit_mgmt_util:id(user, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_users.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_users.erl
new file mode 100644
index 0000000000..67b8345ab9
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_users.erl
@@ -0,0 +1,59 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_users).
+
+-export([init/2, to_json/2,
+ content_types_provided/2,
+ is_authorized/2, allowed_methods/2]).
+-export([variances/2]).
+-export([users/1]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"OPTIONS">>], ReqData, Context}.
+
+to_json(ReqData, {Mode, Context}) ->
+ rabbit_mgmt_util:reply_list(users(Mode), ReqData, Context).
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, Req2, Context2} = rabbit_mgmt_util:is_authorized_admin(ReqData, Context),
+ {Res, Req2, {Mode, Context2}}.
+
+%%--------------------------------------------------------------------
+
+users(all) ->
+ [begin
+ {ok, User} = rabbit_auth_backend_internal:lookup_user(pget(user, U)),
+ rabbit_mgmt_format:internal_user(User)
+ end || U <- rabbit_auth_backend_internal:list_users()];
+users(without_permissions) ->
+ lists:foldl(fun(U, Acc) ->
+ Username = pget(user, U),
+ case rabbit_auth_backend_internal:list_user_permissions(Username) of
+ [] ->
+ {ok, User} = rabbit_auth_backend_internal:lookup_user(Username),
+ [rabbit_mgmt_format:internal_user(User) | Acc];
+ _ ->
+ Acc
+ end
+ end, [], rabbit_auth_backend_internal:list_users()).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_users_bulk_delete.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_users_bulk_delete.erl
new file mode 100644
index 0000000000..6f6a7863a2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_users_bulk_delete.erl
@@ -0,0 +1,47 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_users_bulk_delete).
+
+-export([init/2,
+ content_types_accepted/2,
+ content_types_provided/2,
+ is_authorized/2,
+ allowed_methods/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = ActingUser}}) ->
+ rabbit_mgmt_util:with_decode(
+ [users], ReqData0, Context,
+ fun([Users], _, ReqData) ->
+ [rabbit_auth_backend_internal:delete_user(User, ActingUser)
+ || User <- Users],
+ {true, ReqData, Context}
+ end).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost.erl
new file mode 100644
index 0000000000..e62eecac97
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost.erl
@@ -0,0 +1,97 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_vhost).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2, id/1, put_vhost/5]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(id(ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Id = id(ReqData),
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_util:reply(
+ hd(rabbit_mgmt_db:augment_vhosts(
+ [rabbit_vhost:info(Id)], rabbit_mgmt_util:range(ReqData))),
+ ReqData, Context);
+ true ->
+ rabbit_mgmt_util:reply(rabbit_vhost:info(Id), ReqData, Context)
+ end
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ Name = id(ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [], ReqData0, Context,
+ fun(_, BodyMap, ReqData) ->
+ Trace = rabbit_mgmt_util:parse_bool(maps:get(tracing, BodyMap, undefined)),
+ Description = maps:get(description, BodyMap, <<"">>),
+ Tags = maps:get(tags, BodyMap, <<"">>),
+ case put_vhost(Name, Description, Tags, Trace, Username) of
+ ok ->
+ {true, ReqData, Context};
+ {error, timeout} = E ->
+ rabbit_mgmt_util:internal_server_error(
+ "Timed out while waiting for the vhost to initialise", E,
+ ReqData0, Context)
+ end
+ end).
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ VHost = id(ReqData),
+ try
+ rabbit_vhost:delete(VHost, Username)
+ catch _:{error, {no_such_vhost, _}} ->
+ ok
+ end,
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+id(ReqData) ->
+ case rabbit_mgmt_util:id(vhost, ReqData) of
+ [Value] -> Value;
+ Value -> Value
+ end.
+
+put_vhost(Name, Description, Tags, Trace, Username) ->
+ rabbit_vhost:put_vhost(Name, Description, Tags, Trace, Username).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl
new file mode 100644
index 0000000000..e22ff058c7
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl
@@ -0,0 +1,56 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_vhost_restart).
+
+-export([init/2, resource_exists/2, is_authorized/2,
+ allowed_methods/2, content_types_accepted/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ VHost = id(ReqData),
+ {rabbit_vhost:exists(VHost), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+accept_content(ReqData, Context) ->
+ VHost = id(ReqData),
+ NodeB = rabbit_mgmt_util:id(node, ReqData),
+ Node = binary_to_atom(NodeB, utf8),
+ case rabbit_vhost_sup_sup:start_vhost(VHost, Node) of
+ {ok, _} ->
+ {true, ReqData, Context};
+ {error, {already_started, _}} ->
+ {true, ReqData, Context};
+ {error, Err} ->
+ Message = io_lib:format("Request to node ~s failed with ~p",
+ [Node, Err]),
+ rabbit_mgmt_util:bad_request(list_to_binary(Message), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+id(ReqData) ->
+ rabbit_mgmt_util:id(vhost, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhosts.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhosts.erl
new file mode 100644
index 0000000000..7380faa4f0
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhosts.erl
@@ -0,0 +1,69 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_vhosts).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+-export([basic/0, augmented/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-define(BASIC_COLUMNS, ["name", "tracing", "pid"]).
+
+-define(DEFAULT_SORT, ["name"]).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context = #context{user = User}) ->
+ try
+ Basic = [rabbit_vhost:info(V)
+ || V <- rabbit_mgmt_util:list_visible_vhosts(User)],
+ Data = rabbit_mgmt_util:augment_resources(Basic, ?DEFAULT_SORT,
+ ?BASIC_COLUMNS, ReqData,
+ Context, fun augment/2),
+ rabbit_mgmt_util:reply(Data, ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+augment(Basic, ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_vhosts(Basic, rabbit_mgmt_util:range(ReqData));
+ true ->
+ Basic
+ end.
+
+augmented(ReqData, #context{user = User}) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_vhosts(
+ [rabbit_vhost:info(V) || V <- rabbit_mgmt_util:list_visible_vhosts(User)],
+ rabbit_mgmt_util:range(ReqData));
+ true ->
+ [rabbit_vhost:info(V) || V <- rabbit_mgmt_util:list_visible_vhosts(User)]
+ end.
+
+basic() ->
+ rabbit_vhost:info_all([name]).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_whoami.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_whoami.erl
new file mode 100644
index 0000000000..22a1063443
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_whoami.erl
@@ -0,0 +1,36 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_whoami).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context = #context{user = User}) ->
+ FormattedUser = rabbit_mgmt_format:user(User),
+ Expiration = case application:get_env(rabbitmq_management, login_session_timeout) of
+ undefined -> [];
+ {ok, Val} -> [{login_session_timeout, Val}]
+ end,
+ rabbit_mgmt_util:reply(FormattedUser ++ Expiration, ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/test/cache_SUITE.erl b/deps/rabbitmq_management/test/cache_SUITE.erl
new file mode 100644
index 0000000000..00f9cd56c8
--- /dev/null
+++ b/deps/rabbitmq_management/test/cache_SUITE.erl
@@ -0,0 +1,109 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(cache_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("proper/include/proper.hrl").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, non_parallel_tests}
+ ].
+
+groups() ->
+ [
+ {non_parallel_tests, [], [
+ name,
+ fetch,
+ fetch_cached,
+ fetch_stale,
+ fetch_stale_after_expiry,
+ fetch_throws,
+ fetch_cached_with_same_args,
+ fetch_cached_with_different_args_invalidates_cache
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(Config) ->
+ Config.
+
+init_per_group(_, Config) -> Config.
+
+end_per_group(_, Config) -> Config.
+
+init_per_testcase(_Testcase, Config) ->
+ {ok, P} = rabbit_mgmt_db_cache:start_link(banana),
+ rabbit_ct_helpers:set_config(Config, {sut, P}).
+
+end_per_testcase(_Testcase, Config) ->
+ P = ?config(sut, Config),
+ _ = gen_server:stop(P),
+ Config.
+
+-define(DEFAULT_CACHE_TIME, 5000).
+
+%% tests
+
+name(Config) ->
+ ct:pal(?LOW_IMPORTANCE, "Priv: ~p", [?config(priv_dir, Config)]),
+ rabbit_mgmt_db_cache_banana = rabbit_mgmt_db_cache:process_name(banana).
+
+fetch_new_key(_Config) ->
+ {error, key_not_found} = rabbit_mgmt_db_cache:fetch(this_is_not_the_key_you_are_looking_for,
+ fun() -> 123 end).
+
+fetch(_Config) ->
+ {ok, 123} = rabbit_mgmt_db_cache:fetch(banana, fun() -> 123 end).
+
+fetch_cached(_Config) ->
+ {ok, 123} = rabbit_mgmt_db_cache:fetch(banana, fun() ->
+ timer:sleep(100),
+ 123 end),
+ {ok, 123} = rabbit_mgmt_db_cache:fetch(banana, fun() -> 321 end).
+
+fetch_stale(Config) ->
+ P = ?config(sut, Config),
+ {ok, 123} = rabbit_mgmt_db_cache:fetch(banana, fun() -> 123 end),
+ ok = gen_server:call(P, purge_cache),
+ {ok, 321} = rabbit_mgmt_db_cache:fetch(banana, fun() -> 321 end).
+
+fetch_stale_after_expiry(_Config) ->
+ {ok, 123} = rabbit_mgmt_db_cache:fetch(banana, fun() -> 123 end), % expire quickly
+ timer:sleep(500),
+ {ok, 321} = rabbit_mgmt_db_cache:fetch(banana, fun() -> 321 end).
+
+fetch_throws(_Config) ->
+ {error, {throw, banana_face}} =
+ rabbit_mgmt_db_cache:fetch(banana, fun() -> throw(banana_face) end),
+ {ok, 123} = rabbit_mgmt_db_cache:fetch(banana, fun() -> 123 end).
+
+fetch_cached_with_same_args(_Config) ->
+ {ok, 123} = rabbit_mgmt_db_cache:fetch(banana, fun(_) ->
+ timer:sleep(100),
+ 123
+ end, [42]),
+ {ok, 123} = rabbit_mgmt_db_cache:fetch(banana, fun(_) -> 321 end, [42]).
+
+fetch_cached_with_different_args_invalidates_cache(_Config) ->
+ {ok, 123} = rabbit_mgmt_db_cache:fetch(banana, fun(_) ->
+ timer:sleep(100),
+ 123
+ end, [42]),
+ {ok, 321} = rabbit_mgmt_db_cache:fetch(banana, fun(_) ->
+ timer:sleep(100),
+ 321 end, [442]),
+ {ok, 321} = rabbit_mgmt_db_cache:fetch(banana, fun(_) -> 456 end, [442]).
diff --git a/deps/rabbitmq_management/test/clustering_SUITE.erl b/deps/rabbitmq_management/test/clustering_SUITE.erl
new file mode 100644
index 0000000000..fc096962af
--- /dev/null
+++ b/deps/rabbitmq_management/test/clustering_SUITE.erl
@@ -0,0 +1,874 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(clustering_SUITE).
+
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("rabbit_common/include/rabbit_core_metrics.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+-include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl").
+
+-import(rabbit_ct_broker_helpers, [get_node_config/3, restart_node/2]).
+-import(rabbit_mgmt_test_util, [http_get/2, http_put/4, http_delete/3]).
+-import(rabbit_misc, [pget/2]).
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, non_parallel_tests}
+ ].
+
+groups() ->
+ [{non_parallel_tests, [], [
+ list_cluster_nodes_test,
+ multi_node_case1_test,
+ ha_queue_hosted_on_other_node,
+ ha_queue_with_multiple_consumers,
+ queue_on_other_node,
+ queue_with_multiple_consumers,
+ queue_consumer_cancelled,
+ queue_consumer_channel_closed,
+ queue,
+ queues_single,
+ queues_multiple,
+ queues_removed,
+ channels_multiple_on_different_nodes,
+ channel_closed,
+ channel,
+ channel_other_node,
+ channel_with_consumer_on_other_node,
+ channel_with_consumer_on_one_node,
+ consumers,
+ connections,
+ exchanges,
+ exchange,
+ vhosts,
+ nodes,
+ overview,
+ disable_plugin
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+merge_app_env(Config) ->
+ Config1 = rabbit_ct_helpers:merge_app_env(Config,
+ {rabbit, [
+ {collect_statistics, fine},
+ {collect_statistics_interval, 500}
+ ]}),
+ rabbit_ct_helpers:merge_app_env(Config1,
+ {rabbitmq_management_agent, [
+ {rates_mode, detailed},
+ {sample_retention_policies,
+ %% List of {MaxAgeInSeconds, SampleEveryNSeconds}
+ [{global, [{605, 5}, {3660, 60}, {29400, 600}, {86400, 1800}]},
+ {basic, [{605, 5}, {3600, 60}]},
+ {detailed, [{10, 5}]}] }]}).
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ inets:start(),
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodename_suffix, ?MODULE},
+ {rmq_nodes_count, 2}
+ ]),
+ Config2 = merge_app_env(Config1),
+ rabbit_ct_helpers:run_setup_steps(Config2,
+ rabbit_ct_broker_helpers:setup_steps()).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config,
+ rabbit_ct_broker_helpers:teardown_steps()).
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+init_per_testcase(multi_node_case1_test = Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase);
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, clear_all_table_data, []),
+ rabbit_ct_broker_helpers:rpc(Config, 1, ?MODULE, clear_all_table_data, []),
+ rabbit_ct_broker_helpers:close_all_connections(Config, 0, <<"clustering_SUITE:init_per_testcase">>),
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config),
+ Config1 = rabbit_ct_helpers:set_config(Config, {conn, Conn}),
+ rabbit_ct_helpers:testcase_started(Config1, Testcase).
+
+end_per_testcase(multi_node_case1_test = Testcase, Config) ->
+ rabbit_ct_broker_helpers:close_all_connections(Config, 0, <<"clustering_SUITE:end_per_testcase">>),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase);
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_client_helpers:close_connection(?config(conn, Config)),
+ rabbit_ct_broker_helpers:close_all_connections(Config, 0, <<"clustering_SUITE:end_per_testcase">>),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase).
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+list_cluster_nodes_test(Config) ->
+ %% see rmq_nodes_count in init_per_suite
+ ?assertEqual(2, length(http_get(Config, "/nodes"))),
+ passed.
+
+multi_node_case1_test(Config) ->
+ Nodename1 = rabbit_data_coercion:to_binary(get_node_config(Config, 0, nodename)),
+ Nodename2 = rabbit_data_coercion:to_binary(get_node_config(Config, 1, nodename)),
+ Policy = [{pattern, <<".*">>},
+ {definition, [{'ha-mode', <<"all">>}]}],
+ http_put(Config, "/policies/%2F/HA", Policy, [?CREATED, ?NO_CONTENT]),
+ http_delete(Config, "/queues/%2F/multi-node-test-queue", [?NO_CONTENT, ?NOT_FOUND]),
+
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ _ = queue_declare(Chan, <<"multi-node-test-queue">>),
+ Q = wait_for_mirrored_queue(Config, "/queues/%2F/multi-node-test-queue"),
+
+ ?assert(lists:member(maps:get(node, Q), [Nodename1, Nodename2])),
+ [Mirror] = maps:get(slave_nodes, Q),
+ [Mirror] = maps:get(synchronised_slave_nodes, Q),
+ ?assert(lists:member(Mirror, [Nodename1, Nodename2])),
+
+ %% restart node2 so that queue master migrates
+ restart_node(Config, 1),
+
+ Q2 = wait_for_mirrored_queue(Config, "/queues/%2F/multi-node-test-queue"),
+ http_delete(Config, "/queues/%2F/multi-node-test-queue", ?NO_CONTENT),
+ http_delete(Config, "/policies/%2F/HA", ?NO_CONTENT),
+
+ ?assert(lists:member(maps:get(node, Q2), [Nodename1, Nodename2])),
+
+ rabbit_ct_client_helpers:close_connection(Conn),
+
+ passed.
+
+ha_queue_hosted_on_other_node(Config) ->
+ Policy = [{pattern, <<".*">>},
+ {definition, [{'ha-mode', <<"all">>}]}],
+ http_put(Config, "/policies/%2F/HA", Policy, [?CREATED, ?NO_CONTENT]),
+
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ _ = queue_declare_durable(Chan, <<"ha-queue">>),
+ _ = wait_for_mirrored_queue(Config, "/queues/%2F/ha-queue"),
+
+ {ok, Chan2} = amqp_connection:open_channel(?config(conn, Config)),
+ consume(Chan, <<"ha-queue">>),
+
+ timer:sleep(5100),
+ force_stats(),
+ Res = http_get(Config, "/queues/%2F/ha-queue"),
+
+ % assert some basic data is there
+ [Cons] = maps:get(consumer_details, Res),
+ #{} = maps:get(channel_details, Cons), % channel details proplist must not be empty
+ 0 = maps:get(prefetch_count, Cons), % check one of the augmented properties
+ <<"ha-queue">> = maps:get(name, Res),
+
+ amqp_channel:close(Chan),
+ amqp_channel:close(Chan2),
+ rabbit_ct_client_helpers:close_connection(Conn),
+
+ http_delete(Config, "/queues/%2F/ha-queue", ?NO_CONTENT),
+ http_delete(Config, "/policies/%2F/HA", ?NO_CONTENT),
+
+ ok.
+
+ha_queue_with_multiple_consumers(Config) ->
+ Policy = [{pattern, <<".*">>},
+ {definition, [{'ha-mode', <<"all">>}]}],
+ http_put(Config, "/policies/%2F/HA", Policy, [?CREATED, ?NO_CONTENT]),
+
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ _ = queue_declare_durable(Chan, <<"ha-queue3">>),
+ _ = wait_for_mirrored_queue(Config, "/queues/%2F/ha-queue3"),
+
+ consume(Chan, <<"ha-queue3">>),
+ force_stats(),
+
+ {ok, Chan2} = amqp_connection:open_channel(?config(conn, Config)),
+ consume(Chan2, <<"ha-queue3">>),
+
+ timer:sleep(5100),
+ force_stats(),
+
+ Res = http_get(Config, "/queues/%2F/ha-queue3"),
+
+ % assert some basic data is there
+ [C1, C2] = maps:get(consumer_details, Res),
+ % channel details proplist must not be empty
+ #{} = maps:get(channel_details, C1),
+ #{} = maps:get(channel_details, C2),
+ % check one of the augmented properties
+ 0 = maps:get(prefetch_count, C1),
+ 0 = maps:get(prefetch_count, C2),
+ <<"ha-queue3">> = maps:get(name, Res),
+
+ amqp_channel:close(Chan),
+ amqp_channel:close(Chan2),
+
+ http_delete(Config, "/queues/%2F/ha-queue3", ?NO_CONTENT),
+ http_delete(Config, "/policies/%2F/HA", ?NO_CONTENT),
+
+ ok.
+
+queue_on_other_node(Config) ->
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ _ = queue_declare(Chan, <<"some-queue">>),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ {ok, Chan2} = amqp_connection:open_channel(?config(conn, Config)),
+ consume(Chan2, <<"some-queue">>),
+
+ timer:sleep(5100),
+ force_stats(),
+ Res = http_get(Config, "/queues/%2F/some-queue"),
+
+ % assert some basic data is present
+ [Cons] = maps:get(consumer_details, Res),
+ #{} = maps:get(channel_details, Cons), % channel details proplist must not be empty
+ 0 = maps:get(prefetch_count, Cons), % check one of the augmented properties
+ <<"some-queue">> = maps:get(name, Res),
+
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+
+ amqp_channel:close(Chan),
+ amqp_channel:close(Chan2),
+ rabbit_ct_client_helpers:close_connection(Conn),
+
+ ok.
+
+queue_with_multiple_consumers(Config) ->
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ Q = <<"multi-consumer-queue1">>,
+ _ = queue_declare(Chan, Q),
+ _ = wait_for_queue(Config, "/queues/%2F/multi-consumer-queue1"),
+
+
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan2} = amqp_connection:open_channel(Conn),
+ consume(Chan, Q),
+ consume(Chan2, Q),
+ publish(Chan2, Q),
+ publish(Chan, Q),
+ % ensure a message has been consumed and acked
+ receive
+ {#'basic.deliver'{delivery_tag = T}, _} ->
+ amqp_channel:cast(Chan, #'basic.ack'{delivery_tag = T})
+ end,
+
+ timer:sleep(5100),
+ force_stats(),
+
+ Res = http_get(Config, "/queues/%2F/multi-consumer-queue1"),
+ http_delete(Config, "/queues/%2F/multi-consumer-queue1", ?NO_CONTENT),
+
+ % assert some basic data is there
+ [C1, C2] = maps:get(consumer_details, Res),
+ % channel details proplist must not be empty
+ #{} = maps:get(channel_details, C1),
+ #{} = maps:get(channel_details, C2),
+ % check one of the augmented properties
+ 0 = maps:get(prefetch_count, C1),
+ 0 = maps:get(prefetch_count, C2),
+ Q = maps:get(name, Res),
+
+ amqp_channel:close(Chan),
+ amqp_channel:close(Chan2),
+ rabbit_ct_client_helpers:close_connection(Conn),
+
+ ok.
+
+queue_consumer_cancelled(Config) ->
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ _ = queue_declare(Chan, <<"some-queue">>),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ Tag = consume(Chan, <<"some-queue">>),
+
+ #'basic.cancel_ok'{} =
+ amqp_channel:call(Chan, #'basic.cancel'{consumer_tag = Tag}),
+ force_stats(),
+ Res = http_get(Config, "/queues/%2F/some-queue"),
+
+ amqp_channel:close(Chan),
+
+ % assert there are no consumer details
+ [] = maps:get(consumer_details, Res),
+ <<"some-queue">> = maps:get(name, Res),
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+ ok.
+
+queue_consumer_channel_closed(Config) ->
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ _ = queue_declare(Chan, <<"some-queue">>),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ consume(Chan, <<"some-queue">>),
+ force_stats(), % ensure channel stats have been written
+
+ amqp_channel:close(Chan),
+ force_stats(),
+
+ Res = http_get(Config, "/queues/%2F/some-queue"),
+ % assert there are no consumer details
+ [] = maps:get(consumer_details, Res),
+ <<"some-queue">> = maps:get(name, Res),
+
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+ ok.
+
+queue(Config) ->
+ http_put(Config, "/queues/%2F/some-queue", none, [?CREATED, ?NO_CONTENT]),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ {ok, Chan2} = amqp_connection:open_channel(?config(conn, Config)),
+
+ publish(Chan, <<"some-queue">>),
+ basic_get(Chan, <<"some-queue">>),
+ publish(Chan2, <<"some-queue">>),
+ basic_get(Chan2, <<"some-queue">>),
+ force_stats(),
+ timer:sleep(5100),
+ Res = http_get(Config, "/queues/%2F/some-queue"),
+ % assert single queue is returned
+ [#{} | _] = maps:get(deliveries, Res),
+
+ amqp_channel:close(Chan),
+ amqp_channel:close(Chan2),
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+
+ ok.
+
+queues_single(Config) ->
+ http_put(Config, "/queues/%2F/some-queue", none, [?CREATED, ?NO_CONTENT]),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ force_stats(),
+ Res = http_get(Config, "/queues/%2F"),
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+
+ % assert at least one queue is returned
+ ?assert(length(Res) >= 1),
+
+ ok.
+
+queues_multiple(Config) ->
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ _ = queue_declare(Chan, <<"some-queue">>),
+ _ = queue_declare(Chan, <<"some-other-queue">>),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+ _ = wait_for_queue(Config, "/queues/%2F/some-other-queue"),
+
+ force_stats(),
+ timer:sleep(5100),
+
+ Res = http_get(Config, "/queues/%2F"),
+ [Q1, Q2 | _] = Res,
+
+ % assert some basic data is present
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+ http_delete(Config, "/queues/%2F/some-other-queue", ?NO_CONTENT),
+
+ false = (maps:get(name, Q1) =:= maps:get(name, Q2)),
+ amqp_channel:close(Chan),
+
+ ok.
+
+queues_removed(Config) ->
+ http_put(Config, "/queues/%2F/some-queue", none, [?CREATED, ?NO_CONTENT]),
+ force_stats(),
+ N = length(http_get(Config, "/queues/%2F")),
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+ force_stats(),
+ ?assertEqual(N - 1, length(http_get(Config, "/queues/%2F"))),
+ ok.
+
+channels_multiple_on_different_nodes(Config) ->
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ _ = queue_declare(Chan, <<"some-queue">>),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ Conn2 = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan2} = amqp_connection:open_channel(Conn2),
+ consume(Chan, <<"some-queue">>),
+
+ timer:sleep(5100),
+ force_stats(),
+
+ Res = http_get(Config, "/channels"),
+ % assert two channels are present
+ [_,_] = Res,
+
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+
+ amqp_channel:close(Chan),
+ amqp_channel:close(Chan2),
+ rabbit_ct_client_helpers:close_connection(Conn),
+ rabbit_ct_client_helpers:close_connection(Conn2),
+
+ ok.
+
+channel_closed(Config) ->
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ _ = queue_declare(Chan, <<"some-queue">>),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ {ok, Chan2} = amqp_connection:open_channel(?config(conn, Config)),
+ force_stats(),
+
+ consume(Chan2, <<"some-queue">>),
+ amqp_channel:close(Chan),
+
+ timer:sleep(5100),
+ force_stats(),
+
+ Res = http_get(Config, "/channels"),
+ % assert one channel is present
+ [_] = Res,
+
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+
+ amqp_channel:close(Chan2),
+
+ ok.
+
+channel(Config) ->
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ [{_, ChData}] = rabbit_ct_broker_helpers:rpc(Config, 0, ets, tab2list, [channel_created]),
+
+ ChName = uri_string:recompose(#{path => binary_to_list(pget(name, ChData))}),
+ timer:sleep(5100),
+ force_stats(),
+ Res = http_get(Config, "/channels/" ++ ChName ),
+ % assert channel is non empty
+ #{} = Res,
+
+ amqp_channel:close(Chan),
+ ok.
+
+channel_other_node(Config) ->
+ Q = <<"some-queue">>,
+ http_put(Config, "/queues/%2F/some-queue", none, [?CREATED, ?NO_CONTENT]),
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ [{_, ChData}] = rabbit_ct_broker_helpers:rpc(Config, 1, ets, tab2list,
+ [channel_created]),
+ ChName = uri_string:recompose(#{path => binary_to_list(pget(name, ChData))}),
+ consume(Chan, Q),
+ publish(Chan, Q),
+
+ timer:sleep(5100),
+ force_stats(),
+
+ Res = http_get(Config, "/channels/" ++ ChName ),
+ % assert channel is non empty
+ #{} = Res,
+ [#{}] = maps:get(deliveries, Res),
+ #{} = maps:get(connection_details, Res),
+
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+ amqp_connection:close(Conn),
+
+ ok.
+
+channel_with_consumer_on_other_node(Config) ->
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ Q = <<"some-queue">>,
+ _ = queue_declare(Chan, Q),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ ChName = get_channel_name(Config, 0),
+ consume(Chan, Q),
+ publish(Chan, Q),
+
+ timer:sleep(5100),
+ force_stats(),
+
+ Res = http_get(Config, "/channels/" ++ ChName),
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+ % assert channel is non empty
+ #{} = Res,
+ [#{}] = maps:get(consumer_details, Res),
+
+ amqp_channel:close(Chan),
+
+ ok.
+
+channel_with_consumer_on_one_node(Config) ->
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+ Q = <<"some-queue">>,
+ _ = queue_declare(Chan, Q),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ ChName = get_channel_name(Config, 0),
+ consume(Chan, Q),
+
+ timer:sleep(5100),
+ force_stats(),
+
+ Res = http_get(Config, "/channels/" ++ ChName),
+ amqp_channel:close(Chan),
+
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+ % assert channel is non empty
+ #{} = Res,
+ [#{}] = maps:get(consumer_details, Res),
+ ok.
+
+consumers(Config) ->
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 0),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ _ = queue_declare(Chan, <<"some-queue">>),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ Conn2 = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan2} = amqp_connection:open_channel(Conn2),
+ consume(Chan, <<"some-queue">>),
+ consume(Chan2, <<"some-queue">>),
+
+ timer:sleep(5100),
+ force_stats(),
+ Res = http_get(Config, "/consumers"),
+
+ % assert there are two non-empty consumer records
+ [#{} = C1, #{} = C2] = Res,
+ #{} = maps:get(channel_details, C1),
+ #{} = maps:get(channel_details, C2),
+
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+
+ amqp_channel:close(Chan),
+ rabbit_ct_client_helpers:close_connection(Conn),
+ rabbit_ct_client_helpers:close_connection(Conn2),
+
+ ok.
+
+
+connections(Config) ->
+ %% one connection is maintained by CT helpers
+ {ok, Chan} = amqp_connection:open_channel(?config(conn, Config)),
+
+ Conn2 = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 0),
+ {ok, _Chan2} = amqp_connection:open_channel(Conn2),
+
+ %% channel count needs a bit longer for 2nd chan
+ timer:sleep(5100),
+ force_stats(),
+
+ Res = http_get(Config, "/connections"),
+
+ % assert there are two non-empty connection records
+ [#{} = C1, #{} = C2] = Res,
+ 1 = maps:get(channels, C1),
+ 1 = maps:get(channels, C2),
+
+ amqp_channel:close(Chan),
+ rabbit_ct_client_helpers:close_connection(Conn2),
+
+ ok.
+
+
+exchanges(Config) ->
+ {ok, _Chan0} = amqp_connection:open_channel(?config(conn, Config)),
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ QName = <<"exchanges-test">>,
+ XName = <<"some-exchange">>,
+ Q = queue_declare(Chan, QName),
+ exchange_declare(Chan, XName),
+ queue_bind(Chan, XName, Q, <<"some-key">>),
+ consume(Chan, QName),
+ publish_to(Chan, XName, <<"some-key">>),
+
+ force_stats(),
+ Res = http_get(Config, "/exchanges"),
+ [X] = [X || X <- Res, maps:get(name, X) =:= XName],
+
+ ?assertEqual(<<"direct">>, maps:get(type, X)),
+
+ amqp_channel:close(Chan),
+ rabbit_ct_client_helpers:close_connection(Conn),
+
+ ok.
+
+
+exchange(Config) ->
+ {ok, _Chan0} = amqp_connection:open_channel(?config(conn, Config)),
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ QName = <<"exchanges-test">>,
+ XName = <<"some-other-exchange">>,
+ Q = queue_declare(Chan, QName),
+ exchange_declare(Chan, XName),
+ queue_bind(Chan, XName, Q, <<"some-key">>),
+ consume(Chan, QName),
+ publish_to(Chan, XName, <<"some-key">>),
+
+ force_stats(),
+ force_stats(),
+ Res = http_get(Config, "/exchanges/%2F/some-other-exchange"),
+
+ ?assertEqual(<<"direct">>, maps:get(type, Res)),
+
+ amqp_channel:close(Chan),
+ rabbit_ct_client_helpers:close_connection(Conn),
+
+ ok.
+
+vhosts(Config) ->
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 0),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ _ = queue_declare(Chan, <<"some-queue">>),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ Conn2 = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan2} = amqp_connection:open_channel(Conn2),
+ publish(Chan2, <<"some-queue">>),
+ timer:sleep(5100), % TODO force stat emission
+ force_stats(),
+ Res = http_get(Config, "/vhosts"),
+
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+ % default vhost
+ [#{} = Vhost] = Res,
+ % assert vhost has some message stats
+ #{} = maps:get(message_stats, Vhost),
+
+ amqp_channel:close(Chan),
+ amqp_channel:close(Chan2),
+ rabbit_ct_client_helpers:close_connection(Conn),
+
+ ok.
+
+nodes(Config) ->
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 0),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ _ = queue_declare(Chan, <<"some-queue">>),
+ _ = wait_for_queue(Config, "/queues/%2F/some-queue"),
+
+ {ok, Chan2} = amqp_connection:open_channel(Conn),
+ publish(Chan2, <<"some-queue">>),
+ timer:sleep(5100), % TODO force stat emission
+ force_stats(),
+ Res = http_get(Config, "/nodes"),
+ http_delete(Config, "/queues/%2F/some-queue", ?NO_CONTENT),
+
+ [#{} = N1 , #{} = N2] = Res,
+ ?assert(is_binary(maps:get(name, N1))),
+ ?assert(is_binary(maps:get(name, N2))),
+ [#{} | _] = maps:get(cluster_links, N1),
+ [#{} | _] = maps:get(cluster_links, N2),
+
+ amqp_channel:close(Chan),
+ amqp_channel:close(Chan2),
+ rabbit_ct_client_helpers:close_connection(Conn),
+
+ ok.
+
+overview(Config) ->
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 0),
+ {ok, Chan} = amqp_connection:open_channel(Conn),
+ _ = queue_declare(Chan, <<"queue-n1">>),
+ _ = queue_declare(Chan, <<"queue-n2">>),
+ _ = wait_for_queue(Config, "/queues/%2F/queue-n1"),
+ _ = wait_for_queue(Config, "/queues/%2F/queue-n2"),
+
+ Conn2 = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 1),
+ {ok, Chan2} = amqp_connection:open_channel(Conn2),
+ publish(Chan, <<"queue-n1">>),
+ publish(Chan2, <<"queue-n2">>),
+ timer:sleep(5100), % TODO force stat emission
+ force_stats(), % channel count needs a bit longer for 2nd chan
+ Res = http_get(Config, "/overview"),
+
+ http_delete(Config, "/queues/%2F/queue-n1", ?NO_CONTENT),
+ http_delete(Config, "/queues/%2F/queue-n2", ?NO_CONTENT),
+ % assert there are two non-empty connection records
+ ObjTots = maps:get(object_totals, Res),
+ ?assert(maps:get(connections, ObjTots) >= 2),
+ ?assert(maps:get(channels, ObjTots) >= 2),
+ #{} = QT = maps:get(queue_totals, Res),
+ ?assert(maps:get(messages_ready, QT) >= 2),
+ MS = maps:get(message_stats, Res),
+ ?assert(maps:get(publish, MS) >= 2),
+ ChurnRates = maps:get(churn_rates, Res),
+ ?assertEqual(maps:get(queue_declared, ChurnRates), 2),
+ ?assertEqual(maps:get(queue_created, ChurnRates), 2),
+ ?assertEqual(maps:get(queue_deleted, ChurnRates), 0),
+ ?assertEqual(maps:get(channel_created, ChurnRates), 2),
+ ?assertEqual(maps:get(channel_closed, ChurnRates), 0),
+ ?assertEqual(maps:get(connection_closed, ChurnRates), 0),
+
+ amqp_channel:close(Chan),
+ amqp_channel:close(Chan2),
+ rabbit_ct_client_helpers:close_connection(Conn),
+ rabbit_ct_client_helpers:close_connection(Conn2),
+
+ ok.
+
+disable_plugin(Config) ->
+ Node = get_node_config(Config, 0, nodename),
+ Status0 = rabbit_ct_broker_helpers:rpc(Config, Node, rabbit, status, []),
+ Listeners0 = proplists:get_value(listeners, Status0),
+ ?assert(lists:member(http, listener_protos(Listeners0))),
+ rabbit_ct_broker_helpers:disable_plugin(Config, Node, 'rabbitmq_web_dispatch'),
+ Status = rabbit_ct_broker_helpers:rpc(Config, Node, rabbit, status, []),
+ Listeners = proplists:get_value(listeners, Status),
+ ?assert(not lists:member(http, listener_protos(Listeners))),
+ rabbit_ct_broker_helpers:enable_plugin(Config, Node, 'rabbitmq_management').
+
+%%----------------------------------------------------------------------------
+%%
+
+clear_all_table_data() ->
+ [ets:delete_all_objects(T) || {T, _} <- ?CORE_TABLES],
+ [ets:delete_all_objects(T) || {T, _} <- ?TABLES],
+ [gen_server:call(P, purge_cache)
+ || {_, P, _, _} <- supervisor:which_children(rabbit_mgmt_db_cache_sup)],
+ send_to_all_collectors(purge_old_stats).
+
+get_channel_name(Config, Node) ->
+ [{_, ChData}|_] = rabbit_ct_broker_helpers:rpc(Config, Node, ets, tab2list,
+ [channel_created]),
+ uri_string:recompose(#{path => binary_to_list(pget(name, ChData))}).
+
+consume(Channel, Queue) ->
+ #'basic.consume_ok'{consumer_tag = Tag} =
+ amqp_channel:call(Channel, #'basic.consume'{queue = Queue}),
+ Tag.
+
+publish(Channel, Key) ->
+ Payload = <<"foobar">>,
+ Publish = #'basic.publish'{routing_key = Key},
+ amqp_channel:cast(Channel, Publish, #amqp_msg{payload = Payload}).
+
+basic_get(Channel, Queue) ->
+ Publish = #'basic.get'{queue = Queue},
+ amqp_channel:call(Channel, Publish).
+
+publish_to(Channel, Exchange, Key) ->
+ Payload = <<"foobar">>,
+ Publish = #'basic.publish'{routing_key = Key,
+ exchange = Exchange},
+ amqp_channel:cast(Channel, Publish, #amqp_msg{payload = Payload}).
+
+exchange_declare(Chan, Name) ->
+ Declare = #'exchange.declare'{exchange = Name},
+ #'exchange.declare_ok'{} = amqp_channel:call(Chan, Declare).
+
+queue_declare(Chan) ->
+ Declare = #'queue.declare'{},
+ #'queue.declare_ok'{queue = Q} = amqp_channel:call(Chan, Declare),
+ Q.
+
+queue_declare(Chan, Name) ->
+ Declare = #'queue.declare'{queue = Name},
+ #'queue.declare_ok'{queue = Q} = amqp_channel:call(Chan, Declare),
+ Q.
+
+queue_declare_durable(Chan, Name) ->
+ Declare = #'queue.declare'{queue = Name, durable = true, exclusive = false},
+ #'queue.declare_ok'{queue = Q} = amqp_channel:call(Chan, Declare),
+ Q.
+
+queue_bind(Chan, Ex, Q, Key) ->
+ Binding = #'queue.bind'{queue = Q,
+ exchange = Ex,
+ routing_key = Key},
+ #'queue.bind_ok'{} = amqp_channel:call(Chan, Binding).
+
+wait_for_mirrored_queue(Config, Path) ->
+ wait_for_queue(Config, Path, [slave_nodes, synchronised_slave_nodes]).
+
+wait_for_queue(Config, Path) ->
+ wait_for_queue(Config, Path, []).
+
+wait_for_queue(Config, Path, Keys) ->
+ wait_for_queue(Config, Path, Keys, 1000).
+
+wait_for_queue(_Config, Path, Keys, 0) ->
+ exit({timeout, {Path, Keys}});
+
+wait_for_queue(Config, Path, Keys, Count) ->
+ Res = http_get(Config, Path),
+ case present(Keys, Res) of
+ false -> timer:sleep(10),
+ wait_for_queue(Config, Path, Keys, Count - 1);
+ true -> Res
+ end.
+
+present([], _Res) ->
+ true;
+present(Keys, Res) ->
+ lists:all(fun (Key) ->
+ X = maps:get(Key, Res, undefined),
+ X =/= [] andalso X =/= undefined
+ end, Keys).
+
+extract_node(N) ->
+ list_to_atom(hd(string:tokens(binary_to_list(N), "@"))).
+
+%% debugging utilities
+
+trace_fun(Config, MFs) ->
+ Nodename1 = get_node_config(Config, 0, nodename),
+ Nodename2 = get_node_config(Config, 1, nodename),
+ dbg:tracer(process, {fun(A,_) ->
+ ct:pal(?LOW_IMPORTANCE,
+ "TRACE: ~p", [A])
+ end, ok}),
+ dbg:n(Nodename1),
+ dbg:n(Nodename2),
+ dbg:p(all,c),
+ [ dbg:tpl(M, F, cx) || {M, F} <- MFs],
+ [ dbg:tpl(M, F, A, cx) || {M, F, A} <- MFs].
+
+dump_table(Config, Table) ->
+ Data = rabbit_ct_broker_helpers:rpc(Config, 0, ets, tab2list, [Table]),
+ ct:pal(?LOW_IMPORTANCE, "Node 0: Dump of table ~p:~n~p~n", [Table, Data]),
+ Data0 = rabbit_ct_broker_helpers:rpc(Config, 1, ets, tab2list, [Table]),
+ ct:pal(?LOW_IMPORTANCE, "Node 1: Dump of table ~p:~n~p~n", [Table, Data0]).
+
+force_stats() ->
+ force_all(),
+ timer:sleep(2000).
+
+force_all() ->
+ [begin
+ {rabbit_mgmt_external_stats, N} ! emit_update,
+ timer:sleep(125)
+ end || N <- [node() | nodes()]],
+ send_to_all_collectors(collect_metrics).
+
+send_to_all_collectors(Msg) ->
+ [begin
+ [{rabbit_mgmt_metrics_collector:name(Table), N} ! Msg
+ || {Table, _} <- ?CORE_TABLES]
+ end || N <- [node() | nodes()]].
+
+listener_protos(Listeners) ->
+ [listener_proto(L) || L <- Listeners].
+
+listener_proto(#listener{protocol = Proto}) ->
+ Proto;
+listener_proto(Proto) when is_atom(Proto) ->
+ Proto;
+%% rabbit:status/0 used this formatting before rabbitmq/rabbitmq-cli#340
+listener_proto({Proto, _Port, _Interface}) ->
+ Proto.
diff --git a/deps/rabbitmq_management/test/clustering_prop_SUITE.erl b/deps/rabbitmq_management/test/clustering_prop_SUITE.erl
new file mode 100644
index 0000000000..98790745db
--- /dev/null
+++ b/deps/rabbitmq_management/test/clustering_prop_SUITE.erl
@@ -0,0 +1,280 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(clustering_prop_SUITE).
+
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("common_test/include/ct.hrl").
+-include_lib("proper/include/proper.hrl").
+-include_lib("rabbit_common/include/rabbit_core_metrics.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+-include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl").
+
+
+-import(rabbit_ct_broker_helpers, [get_node_config/3]).
+-import(rabbit_mgmt_test_util, [http_get/2, http_get_from_node/3]).
+-import(rabbit_misc, [pget/2]).
+
+-compile([export_all, nowarn_format]).
+
+-export_type([rmqnode/0, queues/0]).
+
+all() ->
+ [
+ {group, non_parallel_tests}
+ ].
+
+groups() ->
+ [{non_parallel_tests, [], [
+ prop_connection_channel_counts_test
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+merge_app_env(Config) ->
+ Config1 = rabbit_ct_helpers:merge_app_env(Config,
+ {rabbit, [
+ {collect_statistics, fine},
+ {collect_statistics_interval, 500}
+ ]}),
+ rabbit_ct_helpers:merge_app_env(Config1,
+ {rabbitmq_management, [
+ {rates_mode, detailed},
+ {sample_retention_policies,
+ %% List of {MaxAgeInSeconds, SampleEveryNSeconds}
+ [{global, [{605, 5}, {3660, 60}, {29400, 600}, {86400, 1800}]},
+ {basic, [{605, 5}, {3600, 60}]},
+ {detailed, [{605, 5}]}] }]}).
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ inets:start(),
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodename_suffix, ?MODULE},
+ {rmq_nodes_count, 3}
+ ]),
+ Config2 = merge_app_env(Config1),
+ rabbit_ct_helpers:run_setup_steps(Config2,
+ rabbit_ct_broker_helpers:setup_steps()).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config,
+ rabbit_ct_broker_helpers:teardown_steps()).
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+init_per_testcase(multi_node_case1_test = Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase);
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, clear_all_table_data, []),
+ rabbit_ct_broker_helpers:rpc(Config, 1, ?MODULE, clear_all_table_data, []),
+ rabbit_ct_broker_helpers:rpc(Config, 2, ?MODULE, clear_all_table_data, []),
+ rabbit_ct_helpers:testcase_started(Config, Testcase).
+
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_finished(Config, Testcase).
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+prop_connection_channel_counts_test(Config) ->
+ Fun = fun () -> prop_connection_channel_counts(Config) end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 10).
+
+-type rmqnode() :: 0|1|2.
+-type queues() :: qn1 | qn2 | qn3.
+
+prop_connection_channel_counts(Config) ->
+ ?FORALL(Ops, list(frequency([{6, {add_conn, rmqnode(),
+ list(chan)}},
+ {3, rem_conn},
+ {6, rem_chan},
+ {1, force_stats}])),
+ begin
+ % ensure we begin with no connections
+ true = validate_counts(Config, []),
+ Cons = lists:foldl(fun (Op, Agg) ->
+ execute_op(Config, Op, Agg)
+ end, [], Ops),
+ force_stats(),
+ Res = validate_counts(Config, Cons),
+ cleanup(Cons),
+ force_stats(),
+ Res
+ end).
+
+validate_counts(Config, Conns) ->
+ Expected = length(Conns),
+ ChanCount = lists:sum([length(Chans) || {conn, _, Chans} <- Conns]),
+ C1 = length(http_get_from_node(Config, 0, "/connections")),
+ C2 = length(http_get_from_node(Config, 1, "/connections")),
+ C3 = length(http_get_from_node(Config, 2, "/connections")),
+ Ch1 = length(http_get_from_node(Config, 0, "/channels")),
+ Ch2 = length(http_get_from_node(Config, 1, "/channels")),
+ Ch3 = length(http_get_from_node(Config, 2, "/channels")),
+ [Expected, Expected, Expected, ChanCount, ChanCount, ChanCount]
+ =:= [C1, C2, C3, Ch1, Ch2, Ch3].
+
+
+cleanup(Conns) ->
+ [rabbit_ct_client_helpers:close_connection(Conn)
+ || {conn, Conn, _} <- Conns].
+
+execute_op(Config, {add_conn, Node, Chans}, State) ->
+ Conn = rabbit_ct_client_helpers:open_unmanaged_connection(Config, Node),
+ Chans1 = [begin
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ Ch
+ end || _ <- Chans],
+ State ++ [{conn, Conn, Chans1}];
+execute_op(_Config, rem_chan, [{conn, Conn, [Ch | Chans]} | Rem]) ->
+ ok = amqp_channel:close(Ch),
+ Rem ++ [{conn, Conn, Chans}];
+execute_op(_Config, rem_chan, State) -> State;
+execute_op(_Config, rem_conn, []) ->
+ [];
+execute_op(_Config, rem_conn, [{conn, Conn, _Chans} | Rem]) ->
+ rabbit_ct_client_helpers:close_connection(Conn),
+ Rem;
+execute_op(_Config, force_stats, State) ->
+ force_stats(),
+ State.
+
+%%----------------------------------------------------------------------------
+%%
+
+force_stats() ->
+ force_all(),
+ timer:sleep(5000).
+
+force_all() ->
+ [begin
+ {rabbit_mgmt_external_stats, N} ! emit_update,
+ timer:sleep(100),
+ [{rabbit_mgmt_metrics_collector:name(Table), N} ! collect_metrics
+ || {Table, _} <- ?CORE_TABLES]
+ end
+ || N <- [node() | nodes()]].
+
+clear_all_table_data() ->
+ [ets:delete_all_objects(T) || {T, _} <- ?CORE_TABLES],
+ [ets:delete_all_objects(T) || {T, _} <- ?TABLES],
+ [gen_server:call(P, purge_cache)
+ || {_, P, _, _} <- supervisor:which_children(rabbit_mgmt_db_cache_sup)].
+
+get_channel_name(Config, Node) ->
+ [{_, ChData}|_] = rabbit_ct_broker_helpers:rpc(Config, Node, ets, tab2list,
+ [channel_created]),
+ uri_string:recompose(#{path => binary_to_list(pget(name, ChData))}).
+
+consume(Channel, Queue) ->
+ #'basic.consume_ok'{consumer_tag = Tag} =
+ amqp_channel:call(Channel, #'basic.consume'{queue = Queue}),
+ Tag.
+
+publish(Channel, Key) ->
+ Payload = <<"foobar">>,
+ Publish = #'basic.publish'{routing_key = Key},
+ amqp_channel:cast(Channel, Publish, #amqp_msg{payload = Payload}).
+
+basic_get(Channel, Queue) ->
+ Publish = #'basic.get'{queue = Queue},
+ amqp_channel:call(Channel, Publish).
+
+publish_to(Channel, Exchange, Key) ->
+ Payload = <<"foobar">>,
+ Publish = #'basic.publish'{routing_key = Key,
+ exchange = Exchange},
+ amqp_channel:cast(Channel, Publish, #amqp_msg{payload = Payload}).
+
+exchange_declare(Chan, Name) ->
+ Declare = #'exchange.declare'{exchange = Name},
+ #'exchange.declare_ok'{} = amqp_channel:call(Chan, Declare).
+
+queue_declare(Chan) ->
+ Declare = #'queue.declare'{},
+ #'queue.declare_ok'{queue = Q} = amqp_channel:call(Chan, Declare),
+ Q.
+
+queue_declare(Chan, Name) ->
+ Declare = #'queue.declare'{queue = Name},
+ #'queue.declare_ok'{queue = Q} = amqp_channel:call(Chan, Declare),
+ Q.
+
+queue_bind(Chan, Ex, Q, Key) ->
+ Binding = #'queue.bind'{queue = Q,
+ exchange = Ex,
+ routing_key = Key},
+ #'queue.bind_ok'{} = amqp_channel:call(Chan, Binding).
+
+wait_for(Config, Path) ->
+ wait_for(Config, Path, [slave_nodes, synchronised_slave_nodes]).
+
+wait_for(Config, Path, Keys) ->
+ wait_for(Config, Path, Keys, 1000).
+
+wait_for(_Config, Path, Keys, 0) ->
+ exit({timeout, {Path, Keys}});
+
+wait_for(Config, Path, Keys, Count) ->
+ Res = http_get(Config, Path),
+ case present(Keys, Res) of
+ false -> timer:sleep(10),
+ wait_for(Config, Path, Keys, Count - 1);
+ true -> Res
+ end.
+
+present(Keys, Res) ->
+ lists:all(fun (Key) ->
+ X = pget(Key, Res),
+ X =/= [] andalso X =/= undefined
+ end, Keys).
+
+assert_single_node(Exp, Act) ->
+ ?assertEqual(1, length(Act)),
+ assert_node(Exp, hd(Act)).
+
+assert_nodes(Exp, Act0) ->
+ Act = [extract_node(A) || A <- Act0],
+ ?assertEqual(length(Exp), length(Act)),
+ [?assert(lists:member(E, Act)) || E <- Exp].
+
+assert_node(Exp, Act) ->
+ ?assertEqual(Exp, list_to_atom(binary_to_list(Act))).
+
+extract_node(N) ->
+ list_to_atom(hd(string:tokens(binary_to_list(N), "@"))).
+
+%% debugging utilities
+
+trace_fun(Config, MFs) ->
+ Nodename1 = get_node_config(Config, 0, nodename),
+ Nodename2 = get_node_config(Config, 1, nodename),
+ dbg:tracer(process, {fun(A,_) ->
+ ct:pal(?LOW_IMPORTANCE,
+ "TRACE: ~p", [A])
+ end, ok}),
+ dbg:n(Nodename1),
+ dbg:n(Nodename2),
+ dbg:p(all,c),
+ [ dbg:tpl(M, F, cx) || {M, F} <- MFs],
+ [ dbg:tpl(M, F, A, cx) || {M, F, A} <- MFs].
+
+dump_table(Config, Table) ->
+ Data = rabbit_ct_broker_helpers:rpc(Config, 0, ets, tab2list, [Table]),
+ ct:pal(?LOW_IMPORTANCE, "Node 0: Dump of table ~p:~n~p~n", [Table, Data]),
+ Data0 = rabbit_ct_broker_helpers:rpc(Config, 1, ets, tab2list, [Table]),
+ ct:pal(?LOW_IMPORTANCE, "Node 1: Dump of table ~p:~n~p~n", [Table, Data0]).
+
diff --git a/deps/rabbitmq_management/test/config_schema_SUITE.erl b/deps/rabbitmq_management/test/config_schema_SUITE.erl
new file mode 100644
index 0000000000..e40337c60a
--- /dev/null
+++ b/deps/rabbitmq_management/test/config_schema_SUITE.erl
@@ -0,0 +1,55 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(config_schema_SUITE).
+
+-compile(export_all).
+
+all() ->
+ [
+ run_snippets
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ Config1 = rabbit_ct_helpers:run_setup_steps(Config),
+ rabbit_ct_config_schema:init_schemas(rabbitmq_management, Config1).
+
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config).
+
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodename_suffix, Testcase}
+ ]),
+ rabbit_ct_helpers:run_steps(Config1,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps()).
+
+end_per_testcase(Testcase, Config) ->
+ Config1 = rabbit_ct_helpers:run_steps(Config,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps()),
+ rabbit_ct_helpers:testcase_finished(Config1, Testcase).
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+run_snippets(Config) ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0,
+ ?MODULE, run_snippets1, [Config]).
+
+run_snippets1(Config) ->
+ rabbit_ct_config_schema:run_snippets(Config).
+
diff --git a/deps/rabbitmq_management/test/config_schema_SUITE_data/certs/cacert.pem b/deps/rabbitmq_management/test/config_schema_SUITE_data/certs/cacert.pem
new file mode 100644
index 0000000000..eaf6b67806
--- /dev/null
+++ b/deps/rabbitmq_management/test/config_schema_SUITE_data/certs/cacert.pem
@@ -0,0 +1 @@
+I'm not a certificate
diff --git a/deps/rabbitmq_management/test/config_schema_SUITE_data/certs/cert.pem b/deps/rabbitmq_management/test/config_schema_SUITE_data/certs/cert.pem
new file mode 100644
index 0000000000..eaf6b67806
--- /dev/null
+++ b/deps/rabbitmq_management/test/config_schema_SUITE_data/certs/cert.pem
@@ -0,0 +1 @@
+I'm not a certificate
diff --git a/deps/rabbitmq_management/test/config_schema_SUITE_data/certs/key.pem b/deps/rabbitmq_management/test/config_schema_SUITE_data/certs/key.pem
new file mode 100644
index 0000000000..eaf6b67806
--- /dev/null
+++ b/deps/rabbitmq_management/test/config_schema_SUITE_data/certs/key.pem
@@ -0,0 +1 @@
+I'm not a certificate
diff --git a/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbit-mgmt/access.log b/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbit-mgmt/access.log
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbit-mgmt/access.log
diff --git a/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets b/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets
new file mode 100644
index 0000000000..aa7b8b12c7
--- /dev/null
+++ b/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets
@@ -0,0 +1,525 @@
+[{http_log,
+ "listeners.tcp.default = 5672
+ collect_statistics_interval = 10000
+ management.http_log_dir = test/config_schema_SUITE_data/rabbit-mgmt
+ management.rates_mode = basic",
+ [{rabbit,[{tcp_listeners,[5672]},{collect_statistics_interval,10000}]},
+ {rabbitmq_management,
+ [{http_log_dir,"test/config_schema_SUITE_data/rabbit-mgmt"},
+ {rates_mode,basic}]}],
+ [rabbitmq_management]},
+
+ %%
+ %% TCP listener
+ %%
+
+ {tcp_listener_port_only,
+ "management.tcp.port = 15674",
+ [{rabbitmq_management,[
+ {tcp_config,[
+ {port,15674}
+ ]}
+ ]}],
+ [rabbitmq_management]},
+
+ {tcp_listener_interface_port,
+ "management.tcp.ip = 192.168.1.2
+ management.tcp.port = 15674",
+ [{rabbitmq_management,[
+ {tcp_config,[
+ {ip, "192.168.1.2"},
+ {port,15674}
+ ]}
+ ]}],
+ [rabbitmq_management]},
+
+ {tcp_listener_server_opts_compress,
+ "management.tcp.compress = true",
+ [
+ {rabbitmq_management, [
+ {tcp_config, [{cowboy_opts, [{compress, true}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tcp_listener_server_opts_compress_and_idle_timeout,
+ "management.tcp.compress = true
+ management.tcp.idle_timeout = 123",
+ [
+ {rabbitmq_management, [
+ {tcp_config, [{cowboy_opts, [{compress, true},
+ {idle_timeout, 123}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tcp_listener_server_opts_compress_and_multiple_timeouts,
+ "management.tcp.compress = true
+ management.tcp.idle_timeout = 123
+ management.tcp.inactivity_timeout = 456
+ management.tcp.request_timeout = 789",
+ [
+ {rabbitmq_management, [
+ {tcp_config, [{cowboy_opts, [{compress, true},
+ {idle_timeout, 123},
+ {inactivity_timeout, 456},
+ {request_timeout, 789}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tcp_listener_server_opts_multiple_timeouts_only,
+ "management.tcp.idle_timeout = 123
+ management.tcp.inactivity_timeout = 456
+ management.tcp.request_timeout = 789",
+ [
+ {rabbitmq_management, [
+ {tcp_config, [{cowboy_opts, [{idle_timeout, 123},
+ {inactivity_timeout, 456},
+ {request_timeout, 789}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tcp_listener_server_opts_shutdown_timeout,
+ "management.tcp.shutdown_timeout = 7000",
+ [
+ {rabbitmq_management, [
+ {tcp_config, [{cowboy_opts, [{shutdown_timeout, 7000}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tcp_listener_server_opts_max_keepalive,
+ "management.tcp.max_keepalive = 120",
+ [
+ {rabbitmq_management, [
+ {tcp_config, [{cowboy_opts, [{max_keepalive, 120}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+
+ %%
+ %% TLS listener
+ %%
+
+ {tls_listener_port_only,
+ "management.ssl.port = 15671",
+ [{rabbitmq_management,[
+ {ssl_config,[
+ {port,15671}
+ ]}
+ ]}],
+ [rabbitmq_management]},
+
+ {tls_listener_interface_port,
+ "management.ssl.ip = 192.168.1.2
+ management.ssl.port = 15671",
+ [{rabbitmq_management,[
+ {ssl_config,[
+ {ip, "192.168.1.2"},
+ {port,15671}
+ ]}
+ ]}],
+ [rabbitmq_management]},
+
+ {tls_listener,
+ "management.ssl.ip = 192.168.1.2
+ management.ssl.port = 15671
+ management.ssl.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ management.ssl.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ management.ssl.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ management.ssl.verify = verify_none
+ management.ssl.fail_if_no_peer_cert = false",
+ [{rabbitmq_management,[
+ {ssl_config,[
+ {ip, "192.168.1.2"},
+ {port,15671},
+ {cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile,"test/config_schema_SUITE_data/certs/key.pem"},
+ {verify, verify_none},
+ {fail_if_no_peer_cert, false}
+ ]}
+ ]}],
+ [rabbitmq_management]},
+
+ {tls_listener_cipher_suites,
+ "management.ssl.ip = 192.168.1.2
+ management.ssl.port = 15671
+ management.ssl.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ management.ssl.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ management.ssl.keyfile = test/config_schema_SUITE_data/certs/key.pem
+
+ management.ssl.honor_cipher_order = true
+ management.ssl.honor_ecc_order = true
+ management.ssl.client_renegotiation = false
+ management.ssl.secure_renegotiate = true
+
+ management.ssl.verify = verify_peer
+ management.ssl.fail_if_no_peer_cert = false
+
+ management.ssl.versions.1 = tlsv1.2
+ management.ssl.versions.2 = tlsv1.1
+
+ management.ssl.ciphers.1 = ECDHE-ECDSA-AES256-GCM-SHA384
+ management.ssl.ciphers.2 = ECDHE-RSA-AES256-GCM-SHA384
+ management.ssl.ciphers.3 = ECDHE-ECDSA-AES256-SHA384
+ management.ssl.ciphers.4 = ECDHE-RSA-AES256-SHA384
+ management.ssl.ciphers.5 = ECDH-ECDSA-AES256-GCM-SHA384
+ management.ssl.ciphers.6 = ECDH-RSA-AES256-GCM-SHA384
+ management.ssl.ciphers.7 = ECDH-ECDSA-AES256-SHA384
+ management.ssl.ciphers.8 = ECDH-RSA-AES256-SHA384
+ management.ssl.ciphers.9 = DHE-RSA-AES256-GCM-SHA384",
+ [{rabbitmq_management,[
+ {ssl_config,[
+ {ip, "192.168.1.2"},
+ {port,15671},
+ {cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile,"test/config_schema_SUITE_data/certs/key.pem"},
+
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, false},
+
+ {honor_cipher_order, true},
+ {honor_ecc_order, true},
+ {client_renegotiation, false},
+ {secure_renegotiate, true},
+
+ {versions,['tlsv1.2','tlsv1.1']},
+ {ciphers, [
+ "ECDHE-ECDSA-AES256-GCM-SHA384",
+ "ECDHE-RSA-AES256-GCM-SHA384",
+ "ECDHE-ECDSA-AES256-SHA384",
+ "ECDHE-RSA-AES256-SHA384",
+ "ECDH-ECDSA-AES256-GCM-SHA384",
+ "ECDH-RSA-AES256-GCM-SHA384",
+ "ECDH-ECDSA-AES256-SHA384",
+ "ECDH-RSA-AES256-SHA384",
+ "DHE-RSA-AES256-GCM-SHA384"
+ ]}
+ ]}
+ ]}],
+ [rabbitmq_management]},
+
+ {tls_listener_server_opts_compress,
+ "management.ssl.compress = true",
+ [
+ {rabbitmq_management, [
+ {ssl_config, [{cowboy_opts, [{compress, true}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tls_listener_server_opts_compress_and_idle_timeout,
+ "management.ssl.compress = true
+ management.ssl.idle_timeout = 123",
+ [
+ {rabbitmq_management, [
+ {ssl_config, [{cowboy_opts, [{compress, true},
+ {idle_timeout, 123}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tls_listener_server_opts_compress_and_multiple_timeouts,
+ "management.ssl.compress = true
+ management.ssl.idle_timeout = 123
+ management.ssl.inactivity_timeout = 456
+ management.ssl.request_timeout = 789",
+ [
+ {rabbitmq_management, [
+ {ssl_config, [{cowboy_opts, [{compress, true},
+ {idle_timeout, 123},
+ {inactivity_timeout, 456},
+ {request_timeout, 789}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tls_listener_server_opts_multiple_timeouts_only,
+ "management.ssl.idle_timeout = 123
+ management.ssl.inactivity_timeout = 456
+ management.ssl.request_timeout = 789",
+ [
+ {rabbitmq_management, [
+ {ssl_config, [{cowboy_opts, [{idle_timeout, 123},
+ {inactivity_timeout, 456},
+ {request_timeout, 789}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tls_listener_server_opts_shutdown_timeout,
+ "management.ssl.shutdown_timeout = 7000",
+ [
+ {rabbitmq_management, [
+ {ssl_config, [{cowboy_opts, [{shutdown_timeout, 7000}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {tls_listener_server_opts_max_keepalive,
+ "management.ssl.max_keepalive = 120",
+ [
+ {rabbitmq_management, [
+ {ssl_config, [{cowboy_opts, [{max_keepalive, 120}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ %%
+ %% Retention Policies
+ %%
+
+ {retention_policies,
+ "management.sample_retention_policies.global.minute = 5
+ management.sample_retention_policies.global.hour = 60
+ management.sample_retention_policies.global.day = 1200
+
+ management.sample_retention_policies.basic.minute = 5
+ management.sample_retention_policies.basic.hour = 60
+
+ management.sample_retention_policies.detailed.10 = 5",
+ [{rabbitmq_management,
+ [{sample_retention_policies,
+ [{global,[{60,5},{3600,60},{86400,1200}]},
+ {basic,[{60,5},{3600,60}]},
+ {detailed,[{10,5}]}]}]}],
+ [rabbitmq_management]},
+
+ {path_prefix,
+ "management.path_prefix = /a/prefix",
+ [
+ {rabbitmq_management, [
+ {path_prefix, "/a/prefix"}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {login_session_timeout,
+ "management.login_session_timeout = 30",
+ [
+ {rabbitmq_management, [
+ {login_session_timeout, 30}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ %%
+ %% Inter-node query result caching
+ %%
+
+ {db_cache_multiplier,
+ "management.db_cache_multiplier = 7",
+ [
+ {rabbitmq_management, [
+ {management_db_cache_multiplier, 7}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ %%
+ %% CORS
+ %%
+
+ {cors_settings,
+ "management.cors.allow_origins.1 = https://origin1.org
+ management.cors.allow_origins.2 = https://origin2.org
+ management.cors.max_age = 3600",
+ [
+ {rabbitmq_management, [
+ {cors_allow_origins, ["https://origin1.org", "https://origin2.org"]},
+ {cors_max_age, 3600}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {cors_wildcard,
+ "management.cors.allow_origins.1 = *
+ management.cors.max_age = 3600",
+ [
+ {rabbitmq_management, [
+ {cors_allow_origins, ["*"]},
+ {cors_max_age, 3600}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+
+ %%
+ %% CSP
+ %%
+
+ {csp_policy_case1,
+ "management.csp.policy = default-src 'self'",
+ [
+ {rabbitmq_management, [
+ {content_security_policy, "default-src 'self'"}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {csp_policy_case2,
+ "management.csp.policy = default-src https://onlinebanking.examplebank.com",
+ [
+ {rabbitmq_management, [
+ {content_security_policy, "default-src https://onlinebanking.examplebank.com"}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {csp_policy_case3,
+ "management.csp.policy = default-src 'self' *.mailsite.com; img-src *",
+ [
+ {rabbitmq_management, [
+ {content_security_policy, "default-src 'self' *.mailsite.com; img-src *"}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ %%
+ %% HSTS
+ %%
+
+ {hsts_policy_case1,
+ "management.hsts.policy = max-age=31536000; includeSubDomains",
+ [
+ {rabbitmq_management, [
+ {strict_transport_security, "max-age=31536000; includeSubDomains"}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {csp_and_hsts_combined,
+ "management.csp.policy = default-src 'self' *.mailsite.com; img-src *
+ management.hsts.policy = max-age=31536000; includeSubDomains",
+ [
+ {rabbitmq_management, [
+ {content_security_policy, "default-src 'self' *.mailsite.com; img-src *"},
+ {strict_transport_security, "max-age=31536000; includeSubDomains"}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+
+ %%
+ %% Legacy listener configuration
+ %%
+
+ {legacy_tcp_listener,
+ "management.listener.port = 12345",
+ [{rabbitmq_management,[{listener,[{port,12345}]}]}],
+ [rabbitmq_management]},
+
+ {legacy_ssl_listener,
+ "management.listener.port = 15671
+ management.listener.ssl = true
+ management.listener.ssl_opts.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ management.listener.ssl_opts.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ management.listener.ssl_opts.keyfile = test/config_schema_SUITE_data/certs/key.pem",
+ [{rabbitmq_management,
+ [{listener,
+ [{port,15671},
+ {ssl,true},
+ {ssl_opts,
+ [{cacertfile,
+ "test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile,
+ "test/config_schema_SUITE_data/certs/key.pem"}]}]}]}],
+ [rabbitmq_management]},
+
+ {legacy_tcp_listener_ip,
+ "management.listener.port = 15672
+ management.listener.ip = 127.0.0.1",
+ [{rabbitmq_management,[{listener,[{port,15672},{ip,"127.0.0.1"}]}]}],
+ [rabbitmq_management]},
+ {legacy_ssl_listener_port,
+ "management.listener.port = 15672
+ management.listener.ssl = true
+
+ management.listener.ssl_opts.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ management.listener.ssl_opts.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ management.listener.ssl_opts.keyfile = test/config_schema_SUITE_data/certs/key.pem",
+ [{rabbitmq_management,
+ [{listener,
+ [{port,15672},
+ {ssl,true},
+ {ssl_opts,
+ [{cacertfile,
+ "test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile,
+ "test/config_schema_SUITE_data/certs/key.pem"}]}]}]}],
+ [rabbitmq_management]},
+
+ {legacy_server_opts_compress,
+ "management.listener.server.compress = true",
+ [
+ {rabbitmq_management, [
+ {listener, [{cowboy_opts, [{compress, true}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {legacy_server_opts_compress_and_idle_timeout,
+ "management.listener.server.compress = true
+ management.listener.server.idle_timeout = 123",
+ [
+ {rabbitmq_management, [
+ {listener, [{cowboy_opts, [{compress, true},
+ {idle_timeout, 123}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {legacy_server_opts_compress_and_multiple_timeouts,
+ "management.listener.server.compress = true
+ management.listener.server.idle_timeout = 123
+ management.listener.server.inactivity_timeout = 456
+ management.listener.server.request_timeout = 789",
+ [
+ {rabbitmq_management, [
+ {listener, [{cowboy_opts, [{compress, true},
+ {idle_timeout, 123},
+ {inactivity_timeout, 456},
+ {request_timeout, 789}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {legacy_server_opts_multiple_timeouts_only,
+ "management.listener.server.idle_timeout = 123
+ management.listener.server.inactivity_timeout = 456
+ management.listener.server.request_timeout = 789",
+ [
+ {rabbitmq_management, [
+ {listener, [{cowboy_opts, [{idle_timeout, 123},
+ {inactivity_timeout, 456},
+ {request_timeout, 789}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {legacy_server_opts_shutdown_timeout,
+ "management.listener.server.shutdown_timeout = 7000",
+ [
+ {rabbitmq_management, [
+ {listener, [{cowboy_opts, [{shutdown_timeout, 7000}]}]}
+ ]}
+ ], [rabbitmq_management]
+ },
+
+ {legacy_server_opts_max_keepalive,
+ "management.listener.server.max_keepalive = 120",
+ [
+ {rabbitmq_management, [
+ {listener, [{cowboy_opts, [{max_keepalive, 120}]}]}
+ ]}
+ ], [rabbitmq_management]
+ }
+
+].
diff --git a/deps/rabbitmq_management/test/listener_config_SUITE.erl b/deps/rabbitmq_management/test/listener_config_SUITE.erl
new file mode 100644
index 0000000000..46d65d2be3
--- /dev/null
+++ b/deps/rabbitmq_management/test/listener_config_SUITE.erl
@@ -0,0 +1,135 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(listener_config_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, non_parallel_tests}
+ ].
+
+groups() ->
+ [{non_parallel_tests, [], [
+ no_config_defaults,
+ tcp_config_only,
+ ssl_config_only,
+
+ multiple_listeners
+ ]}].
+
+init_per_suite(Config) ->
+ application:load(rabbitmq_management),
+ Config.
+
+end_per_suite(Config) ->
+ Config.
+
+init_per_testcase(_, Config) ->
+ application:unset_env(rabbitmq_management, listener),
+ application:unset_env(rabbitmq_management, tcp_config),
+ application:unset_env(rabbitmq_management, ssl_config),
+ Config.
+
+end_per_testcase(_, Config) ->
+ application:unset_env(rabbitmq_management, listener),
+ application:unset_env(rabbitmq_management, tcp_config),
+ application:unset_env(rabbitmq_management, ssl_config),
+ Config.
+
+%%
+%% Test Cases
+%%
+
+no_config_defaults(_Config) ->
+ ?assertEqual([
+ [
+ {cowboy_opts,[
+ {sendfile, false}
+ ]},
+ {port, 15672}]
+ ], rabbit_mgmt_app:get_listeners_config()).
+
+
+tcp_config_only(_Config) ->
+ application:set_env(rabbitmq_management, tcp_config, [
+ {port, 999},
+ {cowboy_opts, [
+ {idle_timeout, 10000}
+ ]}
+ ]),
+
+ Expected = [
+ {cowboy_opts,[
+ {idle_timeout, 10000},
+ {sendfile, false}
+ ]},
+ {port, 999}
+ ],
+ ?assertEqual(lists:usort(Expected), get_single_listener_config()).
+
+ssl_config_only(_Config) ->
+ application:set_env(rabbitmq_management, ssl_config, [
+ {port, 999},
+ {idle_timeout, 10000}
+ ]),
+
+ Expected = [
+ {cowboy_opts,[
+ {sendfile,false}
+ ]},
+ {port, 999},
+ {ssl, true},
+ {ssl_opts, [
+ {port, 999},
+ {idle_timeout, 10000}
+ ]}
+ ],
+ ?assertEqual(lists:usort(Expected), get_single_listener_config()).
+
+multiple_listeners(_Config) ->
+ application:set_env(rabbitmq_management, tcp_config, [
+ {port, 998},
+ {cowboy_opts, [
+ {idle_timeout, 10000}
+ ]}
+ ]),
+ application:set_env(rabbitmq_management, ssl_config, [
+ {port, 999},
+ {idle_timeout, 10000}
+ ]),
+ Expected = [
+ [
+ {cowboy_opts, [
+ {idle_timeout, 10000},
+ {sendfile, false}
+ ]},
+ {port,998}
+ ],
+
+ [
+ {cowboy_opts,[
+ {sendfile, false}
+ ]},
+ {port, 999},
+ {ssl, true},
+ {ssl_opts, [
+ {port, 999},
+ {idle_timeout, 10000}
+ ]}
+ ]
+ ],
+ ?assertEqual(lists:usort(Expected), rabbit_mgmt_app:get_listeners_config()).
+
+
+get_single_listener_config() ->
+ [Config] = rabbit_mgmt_app:get_listeners_config(),
+ lists:usort(Config).
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl
new file mode 100644
index 0000000000..85ae582503
--- /dev/null
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl
@@ -0,0 +1,3545 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_http_SUITE).
+
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl").
+
+-import(rabbit_ct_client_helpers, [close_connection/1, close_channel/1,
+ open_unmanaged_connection/1]).
+-import(rabbit_mgmt_test_util, [assert_list/2, assert_item/2, test_item/2,
+ assert_keys/2, assert_no_keys/2,
+ http_get/2, http_get/3, http_get/5,
+ http_get_no_auth/3,
+ http_get_no_map/2,
+ http_put/4, http_put/6,
+ http_post/4, http_post/6,
+ http_upload_raw/8,
+ http_delete/3, http_delete/5,
+ http_put_raw/4, http_post_accept_json/4,
+ req/4, auth_header/2,
+ assert_permanent_redirect/3,
+ uri_base_from/2, format_for_upload/1,
+ amqp_port/1, req/6]).
+
+-import(rabbit_misc, [pget/2]).
+
+-define(COLLECT_INTERVAL, 1000).
+-define(PATH_PREFIX, "/custom-prefix").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, all_tests_with_prefix},
+ {group, all_tests_without_prefix},
+ {group, user_limits_ff}
+ ].
+
+groups() ->
+ [
+ {all_tests_with_prefix, [], all_tests()},
+ {all_tests_without_prefix, [], all_tests()},
+ {user_limits_ff, [], [
+ user_limits_list_test,
+ user_limit_set_test
+ ]}
+ ].
+
+all_tests() -> [
+ cli_redirect_test,
+ api_redirect_test,
+ stats_redirect_test,
+ overview_test,
+ auth_test,
+ cluster_name_test,
+ nodes_test,
+ memory_test,
+ ets_tables_memory_test,
+ vhosts_test,
+ vhosts_description_test,
+ vhosts_trace_test,
+ users_test,
+ users_legacy_administrator_test,
+ adding_a_user_with_password_test,
+ adding_a_user_with_password_hash_test,
+ adding_a_user_with_permissions_in_single_operation_test,
+ adding_a_user_without_tags_fails_test,
+ adding_a_user_without_password_or_hash_test,
+ adding_a_user_with_both_password_and_hash_fails_test,
+ updating_a_user_without_password_or_hash_clears_password_test,
+ user_credential_validation_accept_everything_succeeds_test,
+ user_credential_validation_min_length_succeeds_test,
+ user_credential_validation_min_length_fails_test,
+ updating_tags_of_a_passwordless_user_test,
+ permissions_validation_test,
+ permissions_list_test,
+ permissions_test,
+ connections_test,
+ multiple_invalid_connections_test,
+ exchanges_test,
+ queues_test,
+ quorum_queues_test,
+ queues_well_formed_json_test,
+ bindings_test,
+ bindings_post_test,
+ bindings_null_routing_key_test,
+ bindings_e2e_test,
+ permissions_administrator_test,
+ permissions_vhost_test,
+ permissions_amqp_test,
+ permissions_connection_channel_consumer_test,
+ consumers_cq_test,
+ consumers_qq_test,
+ definitions_test,
+ definitions_vhost_test,
+ definitions_password_test,
+ definitions_remove_things_test,
+ definitions_server_named_queue_test,
+ definitions_with_charset_test,
+ long_definitions_test,
+ long_definitions_multipart_test,
+ aliveness_test,
+ arguments_test,
+ arguments_table_test,
+ queue_purge_test,
+ queue_actions_test,
+ exclusive_consumer_test,
+ exclusive_queue_test,
+ connections_channels_pagination_test,
+ exchanges_pagination_test,
+ exchanges_pagination_permissions_test,
+ queue_pagination_test,
+ queue_pagination_columns_test,
+ queues_pagination_permissions_test,
+ samples_range_test,
+ sorting_test,
+ format_output_test,
+ columns_test,
+ get_test,
+ get_encoding_test,
+ get_fail_test,
+ publish_test,
+ publish_large_message_test,
+ publish_accept_json_test,
+ publish_fail_test,
+ publish_base64_test,
+ publish_unrouted_test,
+ if_empty_unused_test,
+ parameters_test,
+ global_parameters_test,
+ policy_test,
+ policy_permissions_test,
+ issue67_test,
+ extensions_test,
+ cors_test,
+ vhost_limits_list_test,
+ vhost_limit_set_test,
+ rates_test,
+ single_active_consumer_cq_test,
+ single_active_consumer_qq_test,
+ oauth_test,
+ disable_basic_auth_test,
+ login_test,
+ csp_headers_test,
+ auth_attempts_test
+].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+merge_app_env(Config) ->
+ Config1 = rabbit_ct_helpers:merge_app_env(Config,
+ {rabbit, [
+ {collect_statistics_interval, ?COLLECT_INTERVAL}
+ ]}),
+ rabbit_ct_helpers:merge_app_env(Config1,
+ {rabbitmq_management, [
+ {sample_retention_policies,
+ [{global, [{605, 1}]},
+ {basic, [{605, 1}]},
+ {detailed, [{10, 1}]}]
+ }]}).
+
+start_broker(Config) ->
+ Setup0 = rabbit_ct_broker_helpers:setup_steps(),
+ Setup1 = rabbit_ct_client_helpers:setup_steps(),
+ Steps = Setup0 ++ Setup1,
+ rabbit_ct_helpers:run_setup_steps(Config, Steps).
+
+finish_init(Group, Config) ->
+ rabbit_ct_helpers:log_environment(),
+ inets:start(),
+ NodeConf = [{rmq_nodename_suffix, Group}],
+ Config1 = rabbit_ct_helpers:set_config(Config, NodeConf),
+ merge_app_env(Config1).
+
+enable_feature_flag_or_skip(FFName, Group, Config0) ->
+ Config1 = finish_init(Group, Config0),
+ Config2 = start_broker(Config1),
+ Nodes = rabbit_ct_broker_helpers:get_node_configs(
+ Config2, nodename),
+ Ret = rabbit_ct_broker_helpers:rpc(
+ Config2, 0,
+ rabbit_feature_flags,
+ is_supported_remotely,
+ [Nodes, [FFName], 60000]),
+ case Ret of
+ true ->
+ ok = rabbit_ct_broker_helpers:rpc(
+ Config2, 0, rabbit_feature_flags, enable, [FFName]),
+ Config2;
+ false ->
+ end_per_group(Group, Config2),
+ {skip, rabbit_misc:format("Feature flag '~s' is not supported", [FFName])}
+ end.
+
+init_per_group(all_tests_with_prefix=Group, Config0) ->
+ PathConfig = {rabbitmq_management, [{path_prefix, ?PATH_PREFIX}]},
+ Config1 = rabbit_ct_helpers:merge_app_env(Config0, PathConfig),
+ Config2 = finish_init(Group, Config1),
+ Config3 = start_broker(Config2),
+ Nodes = rabbit_ct_broker_helpers:get_node_configs(
+ Config3, nodename),
+ Ret = rabbit_ct_broker_helpers:rpc(
+ Config3, 0,
+ rabbit_feature_flags,
+ is_supported_remotely,
+ [Nodes, [quorum_queue], 60000]),
+ case Ret of
+ true ->
+ ok = rabbit_ct_broker_helpers:rpc(
+ Config3, 0, rabbit_feature_flags, enable, [quorum_queue]),
+ Config3;
+ false ->
+ end_per_group(Group, Config3),
+ {skip, "Quorum queues are unsupported"}
+ end;
+init_per_group(user_limits_ff = Group, Config0) ->
+ enable_feature_flag_or_skip(user_limits, Group, Config0);
+init_per_group(Group, Config0) ->
+ enable_feature_flag_or_skip(quorum_queue, Group, Config0).
+
+end_per_group(_, Config) ->
+ inets:stop(),
+ Teardown0 = rabbit_ct_client_helpers:teardown_steps(),
+ Teardown1 = rabbit_ct_broker_helpers:teardown_steps(),
+ Steps = Teardown0 ++ Teardown1,
+ rabbit_ct_helpers:run_teardown_steps(Config, Steps).
+
+init_per_testcase(Testcase = permissions_vhost_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost">>),
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost1">>),
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost2">>),
+ rabbit_ct_helpers:testcase_started(Config, Testcase);
+
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_broker_helpers:close_all_connections(Config, 0, <<"rabbit_mgmt_SUITE:init_per_testcase">>),
+ rabbit_ct_helpers:testcase_started(Config, Testcase).
+
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_broker_helpers:close_all_connections(Config, 0, <<"rabbit_mgmt_SUITE:end_per_testcase">>),
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, disable_basic_auth, false]),
+ Config1 = end_per_testcase0(Testcase, Config),
+ rabbit_ct_helpers:testcase_finished(Config1, Testcase).
+
+end_per_testcase0(T, Config)
+ when T =:= long_definitions_test; T =:= long_definitions_multipart_test ->
+ Vhosts = long_definitions_vhosts(T),
+ [rabbit_ct_broker_helpers:delete_vhost(Config, Name)
+ || #{name := Name} <- Vhosts],
+ Config;
+end_per_testcase0(queues_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"downvhost">>),
+ Config;
+end_per_testcase0(vhost_limits_list_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"limit_test_vhost_1">>),
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"limit_test_vhost_2">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"limit_test_vhost_1_user">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"limit_test_vhost_2_user">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"no_vhost_user">>),
+ Config;
+end_per_testcase0(vhost_limit_set_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"limit_test_vhost_1">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"limit_test_vhost_1_user">>),
+ Config;
+end_per_testcase0(user_limits_list_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"limit_test_vhost_1">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"limit_test_user_1_user">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"limit_test_user_2_user">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"no_vhost_user">>),
+ Config;
+end_per_testcase0(user_limit_set_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"limit_test_vhost_1">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"limit_test_user_1_user">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"limit_test_vhost_1_user">>),
+ Config;
+end_per_testcase0(permissions_vhost_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost1">>),
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost2">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"myuser1">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"myuser2">>),
+ Config;
+end_per_testcase0(_, Config) -> Config.
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+overview_test(Config) ->
+ %% Rather crude, but this req doesn't say much and at least this means it
+ %% didn't blow up.
+ true = 0 < length(maps:get(listeners, http_get(Config, "/overview"))),
+ http_put(Config, "/users/myuser", [{password, <<"myuser">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_get(Config, "/overview", "myuser", "myuser", ?OK),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ passed.
+
+cluster_name_test(Config) ->
+ http_put(Config, "/users/myuser", [{password, <<"myuser">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/cluster-name", [{name, "foo"}], "myuser", "myuser", ?NOT_AUTHORISED),
+ http_put(Config, "/cluster-name", [{name, "foo"}], {group, '2xx'}),
+ #{name := <<"foo">>} = http_get(Config, "/cluster-name", "myuser", "myuser", ?OK),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ passed.
+
+nodes_test(Config) ->
+ http_put(Config, "/users/user", [{password, <<"user">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/users/monitor", [{password, <<"monitor">>},
+ {tags, <<"monitoring">>}], {group, '2xx'}),
+ DiscNode = #{type => <<"disc">>, running => true},
+ assert_list([DiscNode], http_get(Config, "/nodes")),
+ assert_list([DiscNode], http_get(Config, "/nodes", "monitor", "monitor", ?OK)),
+ http_get(Config, "/nodes", "user", "user", ?NOT_AUTHORISED),
+ [Node] = http_get(Config, "/nodes"),
+ Path = "/nodes/" ++ binary_to_list(maps:get(name, Node)),
+ assert_item(DiscNode, http_get(Config, Path, ?OK)),
+ assert_item(DiscNode, http_get(Config, Path, "monitor", "monitor", ?OK)),
+ http_get(Config, Path, "user", "user", ?NOT_AUTHORISED),
+ http_delete(Config, "/users/user", {group, '2xx'}),
+ http_delete(Config, "/users/monitor", {group, '2xx'}),
+ passed.
+
+memory_test(Config) ->
+ [Node] = http_get(Config, "/nodes"),
+ Path = "/nodes/" ++ binary_to_list(maps:get(name, Node)) ++ "/memory",
+ Result = http_get(Config, Path, ?OK),
+ assert_keys([memory], Result),
+ Keys = [total, connection_readers, connection_writers, connection_channels,
+ connection_other, queue_procs, queue_slave_procs, plugins,
+ other_proc, mnesia, mgmt_db, msg_index, other_ets, binary, code,
+ atom, other_system, allocated_unused, reserved_unallocated],
+ assert_keys(Keys, maps:get(memory, Result)),
+ http_get(Config, "/nodes/nonode/memory", ?NOT_FOUND),
+ %% Relative memory as a percentage of the total
+ Result1 = http_get(Config, Path ++ "/relative", ?OK),
+ assert_keys([memory], Result1),
+ Breakdown = maps:get(memory, Result1),
+ assert_keys(Keys, Breakdown),
+
+ assert_item(#{total => 100}, Breakdown),
+ %% allocated_unused and reserved_unallocated
+ %% make this test pretty unpredictable
+ assert_percentage(Breakdown, 20),
+ http_get(Config, "/nodes/nonode/memory/relative", ?NOT_FOUND),
+ passed.
+
+ets_tables_memory_test(Config) ->
+ [Node] = http_get(Config, "/nodes"),
+ Path = "/nodes/" ++ binary_to_list(maps:get(name, Node)) ++ "/memory/ets",
+ Result = http_get(Config, Path, ?OK),
+ assert_keys([ets_tables_memory], Result),
+ NonMgmtKeys = [rabbit_vhost,rabbit_user_permission],
+ Keys = [queue_stats, vhost_stats_coarse_conn_stats,
+ connection_created_stats, channel_process_stats, consumer_stats,
+ queue_msg_rates],
+ assert_keys(Keys ++ NonMgmtKeys, maps:get(ets_tables_memory, Result)),
+ http_get(Config, "/nodes/nonode/memory/ets", ?NOT_FOUND),
+ %% Relative memory as a percentage of the total
+ ResultRelative = http_get(Config, Path ++ "/relative", ?OK),
+ assert_keys([ets_tables_memory], ResultRelative),
+ Breakdown = maps:get(ets_tables_memory, ResultRelative),
+ assert_keys(Keys, Breakdown),
+ assert_item(#{total => 100}, Breakdown),
+ assert_percentage(Breakdown),
+ http_get(Config, "/nodes/nonode/memory/ets/relative", ?NOT_FOUND),
+
+ ResultMgmt = http_get(Config, Path ++ "/management", ?OK),
+ assert_keys([ets_tables_memory], ResultMgmt),
+ assert_keys(Keys, maps:get(ets_tables_memory, ResultMgmt)),
+ assert_no_keys(NonMgmtKeys, maps:get(ets_tables_memory, ResultMgmt)),
+
+ ResultMgmtRelative = http_get(Config, Path ++ "/management/relative", ?OK),
+ assert_keys([ets_tables_memory], ResultMgmtRelative),
+ assert_keys(Keys, maps:get(ets_tables_memory, ResultMgmtRelative)),
+ assert_no_keys(NonMgmtKeys, maps:get(ets_tables_memory, ResultMgmtRelative)),
+ assert_item(#{total => 100}, maps:get(ets_tables_memory, ResultMgmtRelative)),
+ assert_percentage(maps:get(ets_tables_memory, ResultMgmtRelative)),
+
+ ResultUnknownFilter = http_get(Config, Path ++ "/blahblah", ?OK),
+ #{ets_tables_memory := <<"no_tables">>} = ResultUnknownFilter,
+ passed.
+
+assert_percentage(Breakdown0) ->
+ assert_percentage(Breakdown0, 0).
+
+assert_percentage(Breakdown0, ExtraMargin) ->
+ Breakdown = maps:to_list(Breakdown0),
+ Total = lists:sum([P || {K, P} <- Breakdown, K =/= total]),
+ AcceptableMargin = (length(Breakdown) - 1) + ExtraMargin,
+ %% Rounding up and down can lose some digits. Never more than the number
+ %% of items in the breakdown.
+ case ((Total =< 100 + AcceptableMargin) andalso (Total >= 100 - AcceptableMargin)) of
+ false ->
+ throw({bad_percentage, Total, Breakdown});
+ true ->
+ ok
+ end.
+
+auth_test(Config) ->
+ http_put(Config, "/users/user", [{password, <<"user">>},
+ {tags, <<"">>}], {group, '2xx'}),
+ test_auth(Config, ?NOT_AUTHORISED, []),
+ test_auth(Config, ?NOT_AUTHORISED, [auth_header("user", "user")]),
+ test_auth(Config, ?NOT_AUTHORISED, [auth_header("guest", "gust")]),
+ test_auth(Config, ?OK, [auth_header("guest", "guest")]),
+ http_delete(Config, "/users/user", {group, '2xx'}),
+ passed.
+
+%% This test is rather over-verbose as we're trying to test understanding of
+%% Webmachine
+vhosts_test(Config) ->
+ assert_list([#{name => <<"/">>}], http_get(Config, "/vhosts")),
+ %% Create a new one
+ http_put(Config, "/vhosts/myvhost", none, {group, '2xx'}),
+ %% PUT should be idempotent
+ http_put(Config, "/vhosts/myvhost", none, {group, '2xx'}),
+ %% Check it's there
+ assert_list([#{name => <<"/">>}, #{name => <<"myvhost">>}],
+ http_get(Config, "/vhosts")),
+ %% Check individually
+ assert_item(#{name => <<"/">>}, http_get(Config, "/vhosts/%2F", ?OK)),
+ assert_item(#{name => <<"myvhost">>},http_get(Config, "/vhosts/myvhost")),
+
+ %% Crash it
+ rabbit_ct_broker_helpers:force_vhost_failure(Config, <<"myvhost">>),
+ [NodeData] = http_get(Config, "/nodes"),
+ Node = binary_to_atom(maps:get(name, NodeData), utf8),
+ assert_item(#{name => <<"myvhost">>, cluster_state => #{Node => <<"stopped">>}},
+ http_get(Config, "/vhosts/myvhost")),
+
+ %% Restart it
+ http_post(Config, "/vhosts/myvhost/start/" ++ atom_to_list(Node), [], {group, '2xx'}),
+ assert_item(#{name => <<"myvhost">>, cluster_state => #{Node => <<"running">>}},
+ http_get(Config, "/vhosts/myvhost")),
+
+ %% Delete it
+ http_delete(Config, "/vhosts/myvhost", {group, '2xx'}),
+ %% It's not there
+ http_get(Config, "/vhosts/myvhost", ?NOT_FOUND),
+ http_delete(Config, "/vhosts/myvhost", ?NOT_FOUND),
+
+ passed.
+
+vhosts_description_test(Config) ->
+ Ret = rabbit_ct_broker_helpers:enable_feature_flag(
+ Config, virtual_host_metadata),
+
+ http_put(Config, "/vhosts/myvhost", [{description, <<"vhost description">>},
+ {tags, <<"tag1,tag2">>}], {group, '2xx'}),
+ Expected = case Ret of
+ {skip, _} ->
+ #{name => <<"myvhost">>};
+ _ ->
+ #{name => <<"myvhost">>,
+ metadata => #{
+ description => <<"vhost description">>,
+ tags => [<<"tag1">>, <<"tag2">>]
+ }}
+ end,
+ assert_item(Expected, http_get(Config, "/vhosts/myvhost")),
+
+ %% Delete it
+ http_delete(Config, "/vhosts/myvhost", {group, '2xx'}),
+
+ passed.
+
+vhosts_trace_test(Config) ->
+ http_put(Config, "/vhosts/myvhost", none, {group, '2xx'}),
+ Disabled = #{name => <<"myvhost">>, tracing => false},
+ Enabled = #{name => <<"myvhost">>, tracing => true},
+ assert_item(Disabled, http_get(Config, "/vhosts/myvhost")),
+ http_put(Config, "/vhosts/myvhost", [{tracing, true}], {group, '2xx'}),
+ assert_item(Enabled, http_get(Config, "/vhosts/myvhost")),
+ http_put(Config, "/vhosts/myvhost", [{tracing, true}], {group, '2xx'}),
+ assert_item(Enabled, http_get(Config, "/vhosts/myvhost")),
+ http_put(Config, "/vhosts/myvhost", [{tracing, false}], {group, '2xx'}),
+ assert_item(Disabled, http_get(Config, "/vhosts/myvhost")),
+ http_delete(Config, "/vhosts/myvhost", {group, '2xx'}),
+
+ passed.
+
+users_test(Config) ->
+ assert_item(#{name => <<"guest">>, tags => <<"administrator">>},
+ http_get(Config, "/whoami")),
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, login_session_timeout, 100]),
+ assert_item(#{name => <<"guest">>,
+ tags => <<"administrator">>,
+ login_session_timeout => 100},
+ http_get(Config, "/whoami")),
+ http_get(Config, "/users/myuser", ?NOT_FOUND),
+ http_put_raw(Config, "/users/myuser", "Something not JSON", ?BAD_REQUEST),
+ http_put(Config, "/users/myuser", [{flim, <<"flam">>}], ?BAD_REQUEST),
+ http_put(Config, "/users/myuser", [{tags, <<"management">>},
+ {password, <<"myuser">>}],
+ {group, '2xx'}),
+ http_put(Config, "/users/myuser", [{password_hash, <<"not_hash">>}], ?BAD_REQUEST),
+ http_put(Config, "/users/myuser", [{password_hash,
+ <<"IECV6PZI/Invh0DL187KFpkO5Jc=">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ assert_item(#{name => <<"myuser">>, tags => <<"management">>,
+ password_hash => <<"IECV6PZI/Invh0DL187KFpkO5Jc=">>,
+ hashing_algorithm => <<"rabbit_password_hashing_sha256">>},
+ http_get(Config, "/users/myuser")),
+
+ http_put(Config, "/users/myuser", [{password_hash,
+ <<"IECV6PZI/Invh0DL187KFpkO5Jc=">>},
+ {hashing_algorithm, <<"rabbit_password_hashing_md5">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ assert_item(#{name => <<"myuser">>, tags => <<"management">>,
+ password_hash => <<"IECV6PZI/Invh0DL187KFpkO5Jc=">>,
+ hashing_algorithm => <<"rabbit_password_hashing_md5">>},
+ http_get(Config, "/users/myuser")),
+ http_put(Config, "/users/myuser", [{password, <<"password">>},
+ {tags, <<"administrator, foo">>}], {group, '2xx'}),
+ assert_item(#{name => <<"myuser">>, tags => <<"administrator,foo">>},
+ http_get(Config, "/users/myuser")),
+ assert_list(lists:sort([#{name => <<"myuser">>, tags => <<"administrator,foo">>},
+ #{name => <<"guest">>, tags => <<"administrator">>}]),
+ lists:sort(http_get(Config, "/users"))),
+ test_auth(Config, ?OK, [auth_header("myuser", "password")]),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ test_auth(Config, ?NOT_AUTHORISED, [auth_header("myuser", "password")]),
+ http_get(Config, "/users/myuser", ?NOT_FOUND),
+ passed.
+
+without_permissions_users_test(Config) ->
+ assert_item(#{name => <<"guest">>, tags => <<"administrator">>},
+ http_get(Config, "/whoami")),
+ http_put(Config, "/users/myuser", [{password_hash,
+ <<"IECV6PZI/Invh0DL187KFpkO5Jc=">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ Perms = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/permissions/%2F/myuser", Perms, {group, '2xx'}),
+ http_put(Config, "/users/myuserwithoutpermissions", [{password_hash,
+ <<"IECV6PZI/Invh0DL187KFpkO5Jc=">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ assert_list([#{name => <<"myuserwithoutpermissions">>, tags => <<"management">>,
+ hashing_algorithm => <<"rabbit_password_hashing_sha256">>,
+ password_hash => <<"IECV6PZI/Invh0DL187KFpkO5Jc=">>}],
+ http_get(Config, "/users/without-permissions")),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ http_delete(Config, "/users/myuserwithoutpermissions", {group, '2xx'}),
+ passed.
+
+users_bulk_delete_test(Config) ->
+ assert_item(#{name => <<"guest">>, tags => <<"administrator">>},
+ http_get(Config, "/whoami")),
+ http_put(Config, "/users/myuser1", [{tags, <<"management">>}, {password, <<"myuser">>}],
+ {group, '2xx'}),
+ http_put(Config, "/users/myuser2", [{tags, <<"management">>}, {password, <<"myuser">>}],
+ {group, '2xx'}),
+ http_put(Config, "/users/myuser3", [{tags, <<"management">>}, {password, <<"myuser">>}],
+ {group, '2xx'}),
+ http_get(Config, "/users/myuser1", {group, '2xx'}),
+ http_get(Config, "/users/myuser2", {group, '2xx'}),
+ http_get(Config, "/users/myuser3", {group, '2xx'}),
+
+ http_post_json(Config, "/users/bulk-delete",
+ "{\"users\": [\"myuser1\", \"myuser2\"]}", {group, '2xx'}),
+ http_get(Config, "/users/myuser1", ?NOT_FOUND),
+ http_get(Config, "/users/myuser2", ?NOT_FOUND),
+ http_get(Config, "/users/myuser3", {group, '2xx'}),
+ http_post_json(Config, "/users/bulk-delete", "{\"users\": [\"myuser3\"]}",
+ {group, '2xx'}),
+ http_get(Config, "/users/myuser3", ?NOT_FOUND),
+ passed.
+
+users_legacy_administrator_test(Config) ->
+ http_put(Config, "/users/myuser1", [{administrator, <<"true">>},
+ {password, <<"myuser1">>}],
+ {group, '2xx'}),
+ http_put(Config, "/users/myuser2", [{administrator, <<"false">>},
+ {password, <<"myuser2">>}],
+ {group, '2xx'}),
+ assert_item(#{name => <<"myuser1">>, tags => <<"administrator">>},
+ http_get(Config, "/users/myuser1")),
+ assert_item(#{name => <<"myuser2">>, tags => <<"">>},
+ http_get(Config, "/users/myuser2")),
+ http_delete(Config, "/users/myuser1", {group, '2xx'}),
+ http_delete(Config, "/users/myuser2", {group, '2xx'}),
+ passed.
+
+adding_a_user_with_password_test(Config) ->
+ http_put(Config, "/users/user10", [{tags, <<"management">>},
+ {password, <<"password">>}],
+ [?CREATED, ?NO_CONTENT]),
+ http_delete(Config, "/users/user10", ?NO_CONTENT).
+adding_a_user_with_password_hash_test(Config) ->
+ http_put(Config, "/users/user11", [{tags, <<"management">>},
+ %% SHA-256 of "secret"
+ {password_hash, <<"2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b">>}],
+ [?CREATED, ?NO_CONTENT]),
+ http_delete(Config, "/users/user11", ?NO_CONTENT).
+
+adding_a_user_with_permissions_in_single_operation_test(Config) ->
+ QArgs = #{},
+ PermArgs = #{configure => <<".*">>,
+ write => <<".*">>,
+ read => <<".*">>},
+ http_delete(Config, "/vhosts/vhost42", [?NO_CONTENT, ?NOT_FOUND]),
+ http_delete(Config, "/vhosts/vhost43", [?NO_CONTENT, ?NOT_FOUND]),
+ http_delete(Config, "/users/user-preconfigured-perms", [?NO_CONTENT, ?NOT_FOUND]),
+
+ http_put(Config, "/vhosts/vhost42", none, [?CREATED, ?NO_CONTENT]),
+ http_put(Config, "/vhosts/vhost43", none, [?CREATED, ?NO_CONTENT]),
+
+ http_put(Config, "/users/user-preconfigured-perms", [{password, <<"user-preconfigured-perms">>},
+ {tags, <<"management">>},
+ {permissions, [
+ {<<"vhost42">>, PermArgs},
+ {<<"vhost43">>, PermArgs}
+ ]}],
+ [?CREATED, ?NO_CONTENT]),
+ assert_list([#{tracing => false, name => <<"vhost42">>},
+ #{tracing => false, name => <<"vhost43">>}],
+ http_get(Config, "/vhosts", "user-preconfigured-perms", "user-preconfigured-perms", ?OK)),
+ http_put(Config, "/queues/vhost42/myqueue", QArgs,
+ "user-preconfigured-perms", "user-preconfigured-perms", [?CREATED, ?NO_CONTENT]),
+ http_put(Config, "/queues/vhost43/myqueue", QArgs,
+ "user-preconfigured-perms", "user-preconfigured-perms", [?CREATED, ?NO_CONTENT]),
+ Test1 =
+ fun(Path) ->
+ http_get(Config, Path, "user-preconfigured-perms", "user-preconfigured-perms", ?OK)
+ end,
+ Test2 =
+ fun(Path1, Path2) ->
+ http_get(Config, Path1 ++ "/vhost42/" ++ Path2, "user-preconfigured-perms", "user-preconfigured-perms",
+ ?OK),
+ http_get(Config, Path1 ++ "/vhost43/" ++ Path2, "user-preconfigured-perms", "user-preconfigured-perms",
+ ?OK)
+ end,
+ Test1("/exchanges"),
+ Test2("/exchanges", ""),
+ Test2("/exchanges", "amq.direct"),
+ Test1("/queues"),
+ Test2("/queues", ""),
+ Test2("/queues", "myqueue"),
+ Test1("/bindings"),
+ Test2("/bindings", ""),
+ Test2("/queues", "myqueue/bindings"),
+ Test2("/exchanges", "amq.default/bindings/source"),
+ Test2("/exchanges", "amq.default/bindings/destination"),
+ Test2("/bindings", "e/amq.default/q/myqueue"),
+ Test2("/bindings", "e/amq.default/q/myqueue/myqueue"),
+ http_delete(Config, "/vhosts/vhost42", ?NO_CONTENT),
+ http_delete(Config, "/vhosts/vhost43", ?NO_CONTENT),
+ http_delete(Config, "/users/user-preconfigured-perms", ?NO_CONTENT),
+ passed.
+
+adding_a_user_without_tags_fails_test(Config) ->
+ http_put(Config, "/users/no-tags", [{password, <<"password">>}], ?BAD_REQUEST).
+
+%% creating a passwordless user makes sense when x509x certificates or another
+%% "external" authentication mechanism or backend is used.
+%% See rabbitmq/rabbitmq-management#383.
+adding_a_user_without_password_or_hash_test(Config) ->
+ http_put(Config, "/users/no-pwd", [{tags, <<"management">>}], [?CREATED, ?NO_CONTENT]),
+ http_put(Config, "/users/no-pwd", [{tags, <<"management">>}], [?CREATED, ?NO_CONTENT]),
+ http_delete(Config, "/users/no-pwd", ?NO_CONTENT).
+
+adding_a_user_with_both_password_and_hash_fails_test(Config) ->
+ http_put(Config, "/users/myuser", [{password, <<"password">>},
+ {password_hash, <<"password_hash">>}],
+ ?BAD_REQUEST),
+ http_put(Config, "/users/myuser", [{tags, <<"management">>},
+ {password, <<"password">>},
+ {password_hash, <<"password_hash">>}], ?BAD_REQUEST).
+
+updating_a_user_without_password_or_hash_clears_password_test(Config) ->
+ http_put(Config, "/users/myuser", [{tags, <<"management">>},
+ {password, <<"myuser">>}], [?CREATED, ?NO_CONTENT]),
+ %% in this case providing no password or password_hash will
+ %% clear users' credentials
+ http_put(Config, "/users/myuser", [{tags, <<"management">>}], [?CREATED, ?NO_CONTENT]),
+ assert_item(#{name => <<"myuser">>,
+ tags => <<"management">>,
+ password_hash => <<>>,
+ hashing_algorithm => <<"rabbit_password_hashing_sha256">>},
+ http_get(Config, "/users/myuser")),
+ http_delete(Config, "/users/myuser", ?NO_CONTENT).
+
+-define(NON_GUEST_USERNAME, <<"abc">>).
+
+user_credential_validation_accept_everything_succeeds_test(Config) ->
+ rabbit_ct_broker_helpers:delete_user(Config, ?NON_GUEST_USERNAME),
+ rabbit_ct_broker_helpers:switch_credential_validator(Config, accept_everything),
+ http_put(Config, "/users/abc", [{password, <<"password">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ rabbit_ct_broker_helpers:delete_user(Config, ?NON_GUEST_USERNAME).
+
+user_credential_validation_min_length_succeeds_test(Config) ->
+ rabbit_ct_broker_helpers:delete_user(Config, ?NON_GUEST_USERNAME),
+ rabbit_ct_broker_helpers:switch_credential_validator(Config, min_length, 5),
+ http_put(Config, "/users/abc", [{password, <<"password">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ rabbit_ct_broker_helpers:delete_user(Config, ?NON_GUEST_USERNAME),
+ rabbit_ct_broker_helpers:switch_credential_validator(Config, accept_everything).
+
+user_credential_validation_min_length_fails_test(Config) ->
+ rabbit_ct_broker_helpers:delete_user(Config, ?NON_GUEST_USERNAME),
+ rabbit_ct_broker_helpers:switch_credential_validator(Config, min_length, 5),
+ http_put(Config, "/users/abc", [{password, <<"_">>},
+ {tags, <<"management">>}], ?BAD_REQUEST),
+ rabbit_ct_broker_helpers:switch_credential_validator(Config, accept_everything).
+
+updating_tags_of_a_passwordless_user_test(Config) ->
+ rabbit_ct_broker_helpers:delete_user(Config, ?NON_GUEST_USERNAME),
+ http_put(Config, "/users/abc", [{tags, <<"management">>},
+ {password, <<"myuser">>}], [?CREATED, ?NO_CONTENT]),
+
+ %% clear user's password
+ http_put(Config, "/users/abc", [{tags, <<"management">>}], [?CREATED, ?NO_CONTENT]),
+ assert_item(#{name => ?NON_GUEST_USERNAME,
+ tags => <<"management">>,
+ password_hash => <<>>,
+ hashing_algorithm => <<"rabbit_password_hashing_sha256">>},
+ http_get(Config, "/users/abc")),
+
+ http_put(Config, "/users/abc", [{tags, <<"impersonator">>}], [?CREATED, ?NO_CONTENT]),
+ assert_item(#{name => ?NON_GUEST_USERNAME,
+ tags => <<"impersonator">>,
+ password_hash => <<>>,
+ hashing_algorithm => <<"rabbit_password_hashing_sha256">>},
+ http_get(Config, "/users/abc")),
+
+ http_put(Config, "/users/abc", [{tags, <<"">>}], [?CREATED, ?NO_CONTENT]),
+ assert_item(#{name => ?NON_GUEST_USERNAME,
+ tags => <<"">>,
+ password_hash => <<>>,
+ hashing_algorithm => <<"rabbit_password_hashing_sha256">>},
+ http_get(Config, "/users/abc")),
+
+ http_delete(Config, "/users/abc", ?NO_CONTENT).
+
+permissions_validation_test(Config) ->
+ Good = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/permissions/wrong/guest", Good, ?BAD_REQUEST),
+ http_put(Config, "/permissions/%2F/wrong", Good, ?BAD_REQUEST),
+ http_put(Config, "/permissions/%2F/guest",
+ [{configure, <<"[">>}, {write, <<".*">>}, {read, <<".*">>}],
+ ?BAD_REQUEST),
+ http_put(Config, "/permissions/%2F/guest", Good, {group, '2xx'}),
+ passed.
+
+permissions_list_test(Config) ->
+ AllPerms = http_get(Config, "/permissions"),
+ GuestInDefaultVHost = #{user => <<"guest">>,
+ vhost => <<"/">>,
+ configure => <<".*">>,
+ write => <<".*">>,
+ read => <<".*">>},
+
+ ?assert(lists:member(GuestInDefaultVHost, AllPerms)),
+
+ http_put(Config, "/users/myuser1", [{password, <<"">>}, {tags, <<"administrator">>}],
+ {group, '2xx'}),
+ http_put(Config, "/users/myuser2", [{password, <<"">>}, {tags, <<"administrator">>}],
+ {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost1", none, {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost2", none, {group, '2xx'}),
+
+ Perms = [{configure, <<"foo">>}, {write, <<"foo">>}, {read, <<"foo">>}],
+ http_put(Config, "/permissions/myvhost1/myuser1", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/myvhost2/myuser1", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/myvhost1/myuser2", Perms, {group, '2xx'}),
+
+ %% The user that creates the vhosts gets permission automatically
+ %% See https://github.com/rabbitmq/rabbitmq-management/issues/444
+ ?assertEqual(6, length(http_get(Config, "/permissions"))),
+ ?assertEqual(2, length(http_get(Config, "/users/myuser1/permissions"))),
+ ?assertEqual(1, length(http_get(Config, "/users/myuser2/permissions"))),
+
+ http_get(Config, "/users/notmyuser/permissions", ?NOT_FOUND),
+ http_get(Config, "/vhosts/notmyvhost/permissions", ?NOT_FOUND),
+
+ http_delete(Config, "/users/myuser1", {group, '2xx'}),
+ http_delete(Config, "/users/myuser2", {group, '2xx'}),
+ http_delete(Config, "/vhosts/myvhost1", {group, '2xx'}),
+ http_delete(Config, "/vhosts/myvhost2", {group, '2xx'}),
+ passed.
+
+permissions_test(Config) ->
+ http_put(Config, "/users/myuser", [{password, <<"myuser">>}, {tags, <<"administrator">>}],
+ {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost", none, {group, '2xx'}),
+
+ http_put(Config, "/permissions/myvhost/myuser",
+ [{configure, <<"foo">>}, {write, <<"foo">>}, {read, <<"foo">>}],
+ {group, '2xx'}),
+
+ Permission = #{user => <<"myuser">>,
+ vhost => <<"myvhost">>,
+ configure => <<"foo">>,
+ write => <<"foo">>,
+ read => <<"foo">>},
+ %% The user that creates the vhosts gets permission automatically
+ %% See https://github.com/rabbitmq/rabbitmq-management/issues/444
+ PermissionOwner = #{user => <<"guest">>,
+ vhost => <<"/">>,
+ configure => <<".*">>,
+ write => <<".*">>,
+ read => <<".*">>},
+ Default = #{user => <<"guest">>,
+ vhost => <<"/">>,
+ configure => <<".*">>,
+ write => <<".*">>,
+ read => <<".*">>},
+ Permission = http_get(Config, "/permissions/myvhost/myuser"),
+ assert_list(lists:sort([Permission, PermissionOwner, Default]),
+ lists:sort(http_get(Config, "/permissions"))),
+ assert_list([Permission], http_get(Config, "/users/myuser/permissions")),
+ http_delete(Config, "/permissions/myvhost/myuser", {group, '2xx'}),
+ http_get(Config, "/permissions/myvhost/myuser", ?NOT_FOUND),
+
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ http_delete(Config, "/vhosts/myvhost", {group, '2xx'}),
+ passed.
+
+topic_permissions_list_test(Config) ->
+ http_put(Config, "/users/myuser1", [{password, <<"">>}, {tags, <<"administrator">>}],
+ {group, '2xx'}),
+ http_put(Config, "/users/myuser2", [{password, <<"">>}, {tags, <<"administrator">>}],
+ {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost1", none, {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost2", none, {group, '2xx'}),
+
+ TopicPerms = [{exchange, <<"amq.topic">>}, {write, <<"^a">>}, {read, <<"^b">>}],
+ http_put(Config, "/topic-permissions/myvhost1/myuser1", TopicPerms, {group, '2xx'}),
+ http_put(Config, "/topic-permissions/myvhost2/myuser1", TopicPerms, {group, '2xx'}),
+ http_put(Config, "/topic-permissions/myvhost1/myuser2", TopicPerms, {group, '2xx'}),
+
+ TopicPerms2 = [{exchange, <<"amq.direct">>}, {write, <<"^a">>}, {read, <<"^b">>}],
+ http_put(Config, "/topic-permissions/myvhost1/myuser1", TopicPerms2, {group, '2xx'}),
+
+ 4 = length(http_get(Config, "/topic-permissions")),
+ 3 = length(http_get(Config, "/users/myuser1/topic-permissions")),
+ 1 = length(http_get(Config, "/users/myuser2/topic-permissions")),
+ 3 = length(http_get(Config, "/vhosts/myvhost1/topic-permissions")),
+ 1 = length(http_get(Config, "/vhosts/myvhost2/topic-permissions")),
+
+ http_get(Config, "/users/notmyuser/topic-permissions", ?NOT_FOUND),
+ http_get(Config, "/vhosts/notmyvhost/topic-permissions", ?NOT_FOUND),
+
+ %% Delete permissions for a single vhost-user-exchange combination
+ http_delete(Config, "/topic-permissions/myvhost1/myuser1/amq.direct", {group, '2xx'}),
+ 3 = length(http_get(Config, "/topic-permissions")),
+
+ http_delete(Config, "/users/myuser1", {group, '2xx'}),
+ http_delete(Config, "/users/myuser2", {group, '2xx'}),
+ http_delete(Config, "/vhosts/myvhost1", {group, '2xx'}),
+ http_delete(Config, "/vhosts/myvhost2", {group, '2xx'}),
+ passed.
+
+topic_permissions_test(Config) ->
+ http_put(Config, "/users/myuser1", [{password, <<"">>}, {tags, <<"administrator">>}],
+ {group, '2xx'}),
+ http_put(Config, "/users/myuser2", [{password, <<"">>}, {tags, <<"administrator">>}],
+ {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost1", none, {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost2", none, {group, '2xx'}),
+
+ TopicPerms = [{exchange, <<"amq.topic">>}, {write, <<"^a">>}, {read, <<"^b">>}],
+ http_put(Config, "/topic-permissions/myvhost1/myuser1", TopicPerms, {group, '2xx'}),
+ http_put(Config, "/topic-permissions/myvhost2/myuser1", TopicPerms, {group, '2xx'}),
+ http_put(Config, "/topic-permissions/myvhost1/myuser2", TopicPerms, {group, '2xx'}),
+
+ 3 = length(http_get(Config, "/topic-permissions")),
+ 1 = length(http_get(Config, "/topic-permissions/myvhost1/myuser1")),
+ 1 = length(http_get(Config, "/topic-permissions/myvhost2/myuser1")),
+ 1 = length(http_get(Config, "/topic-permissions/myvhost1/myuser2")),
+
+ http_get(Config, "/topic-permissions/myvhost1/notmyuser", ?NOT_FOUND),
+ http_get(Config, "/topic-permissions/notmyvhost/myuser2", ?NOT_FOUND),
+
+ http_delete(Config, "/users/myuser1", {group, '2xx'}),
+ http_delete(Config, "/users/myuser2", {group, '2xx'}),
+ http_delete(Config, "/vhosts/myvhost1", {group, '2xx'}),
+ http_delete(Config, "/vhosts/myvhost2", {group, '2xx'}),
+ passed.
+
+connections_test(Config) ->
+ {Conn, _Ch} = open_connection_and_channel(Config),
+ LocalPort = local_port(Conn),
+ Path = binary_to_list(
+ rabbit_mgmt_format:print(
+ "/connections/127.0.0.1%3A~w%20-%3E%20127.0.0.1%3A~w",
+ [LocalPort, amqp_port(Config)])),
+ timer:sleep(1500),
+ Connection = http_get(Config, Path, ?OK),
+ ?assert(maps:is_key(recv_oct, Connection)),
+ ?assert(maps:is_key(garbage_collection, Connection)),
+ ?assert(maps:is_key(send_oct_details, Connection)),
+ ?assert(maps:is_key(reductions, Connection)),
+ http_delete(Config, Path, {group, '2xx'}),
+ %% TODO rabbit_reader:shutdown/2 returns before the connection is
+ %% closed. It may not be worth fixing.
+ Fun = fun() ->
+ try
+ http_get(Config, Path, ?NOT_FOUND),
+ true
+ catch
+ _:_ ->
+ false
+ end
+ end,
+ wait_until(Fun, 60),
+ close_connection(Conn),
+ passed.
+
+multiple_invalid_connections_test(Config) ->
+ Count = 100,
+ spawn_invalid(Config, Count),
+ Page0 = http_get(Config, "/connections?page=1&page_size=100", ?OK),
+ wait_for_answers(Count),
+ Page1 = http_get(Config, "/connections?page=1&page_size=100", ?OK),
+ ?assertEqual(0, maps:get(total_count, Page0)),
+ ?assertEqual(0, maps:get(total_count, Page1)),
+ passed.
+
+test_auth(Config, Code, Headers) ->
+ {ok, {{_, Code, _}, _, _}} = req(Config, get, "/overview", Headers),
+ passed.
+
+exchanges_test(Config) ->
+ %% Can list exchanges
+ http_get(Config, "/exchanges", {group, '2xx'}),
+ %% Can pass booleans or strings
+ Good = [{type, <<"direct">>}, {durable, <<"true">>}],
+ http_put(Config, "/vhosts/myvhost", none, {group, '2xx'}),
+ http_get(Config, "/exchanges/myvhost/foo", ?NOT_FOUND),
+ http_put(Config, "/exchanges/myvhost/foo", Good, {group, '2xx'}),
+ http_put(Config, "/exchanges/myvhost/foo", Good, {group, '2xx'}),
+ http_get(Config, "/exchanges/%2F/foo", ?NOT_FOUND),
+ assert_item(#{name => <<"foo">>,
+ vhost => <<"myvhost">>,
+ type => <<"direct">>,
+ durable => true,
+ auto_delete => false,
+ internal => false,
+ arguments => #{}},
+ http_get(Config, "/exchanges/myvhost/foo")),
+ http_put(Config, "/exchanges/badvhost/bar", Good, ?NOT_FOUND),
+ http_put(Config, "/exchanges/myvhost/bar", [{type, <<"bad_exchange_type">>}],
+ ?BAD_REQUEST),
+ http_put(Config, "/exchanges/myvhost/bar", [{type, <<"direct">>},
+ {durable, <<"troo">>}],
+ ?BAD_REQUEST),
+ http_put(Config, "/exchanges/myvhost/foo", [{type, <<"direct">>}],
+ ?BAD_REQUEST),
+
+ http_delete(Config, "/exchanges/myvhost/foo", {group, '2xx'}),
+ http_delete(Config, "/exchanges/myvhost/foo", ?NOT_FOUND),
+
+ http_delete(Config, "/vhosts/myvhost", {group, '2xx'}),
+ http_get(Config, "/exchanges/badvhost", ?NOT_FOUND),
+ passed.
+
+queues_test(Config) ->
+ Good = [{durable, true}],
+ http_get(Config, "/queues/%2F/foo", ?NOT_FOUND),
+ http_put(Config, "/queues/%2F/foo", Good, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/foo", Good, {group, '2xx'}),
+ http_get(Config, "/queues/%2F/foo", ?OK),
+
+ rabbit_ct_broker_helpers:add_vhost(Config, <<"downvhost">>),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, <<"downvhost">>),
+ http_put(Config, "/queues/downvhost/foo", Good, {group, '2xx'}),
+ http_put(Config, "/queues/downvhost/bar", Good, {group, '2xx'}),
+
+ rabbit_ct_broker_helpers:force_vhost_failure(Config, <<"downvhost">>),
+ %% The vhost is down
+ Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+ DownVHost = #{name => <<"downvhost">>, tracing => false, cluster_state => #{Node => <<"stopped">>}},
+ assert_item(DownVHost, http_get(Config, "/vhosts/downvhost")),
+
+ DownQueues = http_get(Config, "/queues/downvhost"),
+ DownQueue = http_get(Config, "/queues/downvhost/foo"),
+
+ assert_list([#{name => <<"bar">>,
+ vhost => <<"downvhost">>,
+ state => <<"stopped">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{}},
+ #{name => <<"foo">>,
+ vhost => <<"downvhost">>,
+ state => <<"stopped">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{}}], DownQueues),
+ assert_item(#{name => <<"foo">>,
+ vhost => <<"downvhost">>,
+ state => <<"stopped">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{}}, DownQueue),
+
+ http_put(Config, "/queues/badvhost/bar", Good, ?NOT_FOUND),
+ http_put(Config, "/queues/%2F/bar",
+ [{durable, <<"troo">>}],
+ ?BAD_REQUEST),
+ http_put(Config, "/queues/%2F/foo",
+ [{durable, false}],
+ ?BAD_REQUEST),
+
+ http_put(Config, "/queues/%2F/baz", Good, {group, '2xx'}),
+ Queues = http_get(Config, "/queues/%2F"),
+ Queue = http_get(Config, "/queues/%2F/foo"),
+ assert_list([#{name => <<"baz">>,
+ vhost => <<"/">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{}},
+ #{name => <<"foo">>,
+ vhost => <<"/">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{}}], Queues),
+ assert_item(#{name => <<"foo">>,
+ vhost => <<"/">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{}}, Queue),
+
+ http_delete(Config, "/queues/%2F/foo", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/baz", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/foo", ?NOT_FOUND),
+ http_get(Config, "/queues/badvhost", ?NOT_FOUND),
+
+ http_delete(Config, "/queues/downvhost/foo", {group, '2xx'}),
+ http_delete(Config, "/queues/downvhost/bar", {group, '2xx'}),
+ passed.
+
+quorum_queues_test(Config) ->
+ %% Test in a loop that no metrics are left behing after deleting a queue
+ quorum_queues_test_loop(Config, 5).
+
+quorum_queues_test_loop(_Config, 0) ->
+ passed;
+quorum_queues_test_loop(Config, N) ->
+ Good = [{durable, true}, {arguments, [{'x-queue-type', 'quorum'}]}],
+ http_get(Config, "/queues/%2f/qq", ?NOT_FOUND),
+ http_put(Config, "/queues/%2f/qq", Good, {group, '2xx'}),
+
+ {Conn, Ch} = open_connection_and_channel(Config),
+ Publish = fun() ->
+ amqp_channel:call(
+ Ch, #'basic.publish'{exchange = <<"">>,
+ routing_key = <<"qq">>},
+ #amqp_msg{payload = <<"message">>})
+ end,
+ Publish(),
+ Publish(),
+ wait_until(fun() ->
+ Num = maps:get(messages, http_get(Config, "/queues/%2f/qq?lengths_age=60&lengths_incr=5&msg_rates_age=60&msg_rates_incr=5&data_rates_age=60&data_rates_incr=5"), undefined),
+ ct:pal("wait_until got ~w", [N]),
+ 2 == Num
+ end, 100),
+
+ http_delete(Config, "/queues/%2f/qq", {group, '2xx'}),
+ http_put(Config, "/queues/%2f/qq", Good, {group, '2xx'}),
+
+ wait_until(fun() ->
+ 0 == maps:get(messages, http_get(Config, "/queues/%2f/qq?lengths_age=60&lengths_incr=5&msg_rates_age=60&msg_rates_incr=5&data_rates_age=60&data_rates_incr=5"), undefined)
+ end, 100),
+
+ http_delete(Config, "/queues/%2f/qq", {group, '2xx'}),
+ close_connection(Conn),
+ quorum_queues_test_loop(Config, N-1).
+
+queues_well_formed_json_test(Config) ->
+ %% TODO This test should be extended to the whole API
+ Good = [{durable, true}],
+ http_put(Config, "/queues/%2F/foo", Good, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/baz", Good, {group, '2xx'}),
+
+ Queues = http_get_no_map(Config, "/queues/%2F"),
+ %% Ensure keys are unique
+ [begin
+ Sorted = lists:sort(Q),
+ Sorted = lists:usort(Q)
+ end || Q <- Queues],
+
+ http_delete(Config, "/queues/%2F/foo", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/baz", {group, '2xx'}),
+ passed.
+
+bindings_test(Config) ->
+ XArgs = [{type, <<"direct">>}],
+ QArgs = #{},
+ http_put(Config, "/exchanges/%2F/myexchange", XArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/myqueue", QArgs, {group, '2xx'}),
+ BArgs = [{routing_key, <<"routing">>}, {arguments, []}],
+ http_post(Config, "/bindings/%2F/e/myexchange/q/myqueue", BArgs, {group, '2xx'}),
+ http_get(Config, "/bindings/%2F/e/myexchange/q/myqueue/routing", ?OK),
+ http_get(Config, "/bindings/%2F/e/myexchange/q/myqueue/rooting", ?NOT_FOUND),
+ Binding =
+ #{source => <<"myexchange">>,
+ vhost => <<"/">>,
+ destination => <<"myqueue">>,
+ destination_type => <<"queue">>,
+ routing_key => <<"routing">>,
+ arguments => #{},
+ properties_key => <<"routing">>},
+ DBinding =
+ #{source => <<"">>,
+ vhost => <<"/">>,
+ destination => <<"myqueue">>,
+ destination_type => <<"queue">>,
+ routing_key => <<"myqueue">>,
+ arguments => #{},
+ properties_key => <<"myqueue">>},
+ Binding = http_get(Config, "/bindings/%2F/e/myexchange/q/myqueue/routing"),
+ assert_list([Binding],
+ http_get(Config, "/bindings/%2F/e/myexchange/q/myqueue")),
+ assert_list([DBinding, Binding],
+ http_get(Config, "/queues/%2F/myqueue/bindings")),
+ assert_list([Binding],
+ http_get(Config, "/exchanges/%2F/myexchange/bindings/source")),
+ http_delete(Config, "/bindings/%2F/e/myexchange/q/myqueue/routing", {group, '2xx'}),
+ http_delete(Config, "/bindings/%2F/e/myexchange/q/myqueue/routing", ?NOT_FOUND),
+ http_delete(Config, "/exchanges/%2F/myexchange", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/myqueue", {group, '2xx'}),
+ http_get(Config, "/bindings/badvhost", ?NOT_FOUND),
+ http_get(Config, "/bindings/badvhost/myqueue/myexchange/routing", ?NOT_FOUND),
+ http_get(Config, "/bindings/%2F/e/myexchange/q/myqueue/routing", ?NOT_FOUND),
+ passed.
+
+bindings_post_test(Config) ->
+ XArgs = [{type, <<"direct">>}],
+ QArgs = #{},
+ BArgs = [{routing_key, <<"routing">>}, {arguments, [{foo, <<"bar">>}]}],
+ http_put(Config, "/exchanges/%2F/myexchange", XArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/myqueue", QArgs, {group, '2xx'}),
+ http_post(Config, "/bindings/%2F/e/myexchange/q/badqueue", BArgs, ?NOT_FOUND),
+ http_post(Config, "/bindings/%2F/e/badexchange/q/myqueue", BArgs, ?NOT_FOUND),
+
+ Headers1 = http_post(Config, "/bindings/%2F/e/myexchange/q/myqueue", #{}, {group, '2xx'}),
+ Want0 = "myqueue/\~",
+ ?assertEqual(Want0, pget("location", Headers1)),
+
+ Headers2 = http_post(Config, "/bindings/%2F/e/myexchange/q/myqueue", BArgs, {group, '2xx'}),
+ %% Args hash is calculated from a table, generated from args.
+ Hash = table_hash([{<<"foo">>,longstr,<<"bar">>}]),
+ PropertiesKey = "routing\~" ++ Hash,
+
+ Want1 = "myqueue/" ++ PropertiesKey,
+ ?assertEqual(Want1, pget("location", Headers2)),
+
+ PropertiesKeyBin = list_to_binary(PropertiesKey),
+ Want2 = #{source => <<"myexchange">>,
+ vhost => <<"/">>,
+ destination => <<"myqueue">>,
+ destination_type => <<"queue">>,
+ routing_key => <<"routing">>,
+ arguments => #{foo => <<"bar">>},
+ properties_key => PropertiesKeyBin},
+ URI = "/bindings/%2F/e/myexchange/q/myqueue/" ++ PropertiesKey,
+ ?assertEqual(Want2, http_get(Config, URI, ?OK)),
+
+ http_get(Config, URI ++ "x", ?NOT_FOUND),
+ http_delete(Config, URI, {group, '2xx'}),
+ http_delete(Config, "/exchanges/%2F/myexchange", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/myqueue", {group, '2xx'}),
+ passed.
+
+bindings_null_routing_key_test(Config) ->
+ http_delete(Config, "/exchanges/%2F/myexchange", {one_of, [201, 404]}),
+ XArgs = [{type, <<"direct">>}],
+ QArgs = #{},
+ BArgs = [{routing_key, null}, {arguments, #{}}],
+ http_put(Config, "/exchanges/%2F/myexchange", XArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/myqueue", QArgs, {group, '2xx'}),
+ http_post(Config, "/bindings/%2F/e/myexchange/q/badqueue", BArgs, ?NOT_FOUND),
+ http_post(Config, "/bindings/%2F/e/badexchange/q/myqueue", BArgs, ?NOT_FOUND),
+
+ Headers1 = http_post(Config, "/bindings/%2F/e/myexchange/q/myqueue", #{}, {group, '2xx'}),
+ Want0 = "myqueue/\~",
+ ?assertEqual(Want0, pget("location", Headers1)),
+
+ Headers2 = http_post(Config, "/bindings/%2F/e/myexchange/q/myqueue", BArgs, {group, '2xx'}),
+ %% Args hash is calculated from a table, generated from args.
+ Hash = table_hash([]),
+ PropertiesKey = "null\~" ++ Hash,
+
+ ?assertEqual("myqueue/null", pget("location", Headers2)),
+ Want1 = #{arguments => #{},
+ destination => <<"myqueue">>,
+ destination_type => <<"queue">>,
+ properties_key => <<"null">>,
+ routing_key => null,
+ source => <<"myexchange">>,
+ vhost => <<"/">>},
+ URI = "/bindings/%2F/e/myexchange/q/myqueue/" ++ PropertiesKey,
+ ?assertEqual(Want1, http_get(Config, URI, ?OK)),
+
+ http_get(Config, URI ++ "x", ?NOT_FOUND),
+ http_delete(Config, URI, {group, '2xx'}),
+ http_delete(Config, "/exchanges/%2F/myexchange", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/myqueue", {group, '2xx'}),
+ passed.
+
+bindings_e2e_test(Config) ->
+ BArgs = [{routing_key, <<"routing">>}, {arguments, []}],
+ http_post(Config, "/bindings/%2F/e/amq.direct/e/badexchange", BArgs, ?NOT_FOUND),
+ http_post(Config, "/bindings/%2F/e/badexchange/e/amq.fanout", BArgs, ?NOT_FOUND),
+ Headers = http_post(Config, "/bindings/%2F/e/amq.direct/e/amq.fanout", BArgs, {group, '2xx'}),
+ "amq.fanout/routing" = pget("location", Headers),
+ #{source := <<"amq.direct">>,
+ vhost := <<"/">>,
+ destination := <<"amq.fanout">>,
+ destination_type := <<"exchange">>,
+ routing_key := <<"routing">>,
+ arguments := #{},
+ properties_key := <<"routing">>} =
+ http_get(Config, "/bindings/%2F/e/amq.direct/e/amq.fanout/routing", ?OK),
+ http_delete(Config, "/bindings/%2F/e/amq.direct/e/amq.fanout/routing", {group, '2xx'}),
+ http_post(Config, "/bindings/%2F/e/amq.direct/e/amq.headers", BArgs, {group, '2xx'}),
+ Binding =
+ #{source => <<"amq.direct">>,
+ vhost => <<"/">>,
+ destination => <<"amq.headers">>,
+ destination_type => <<"exchange">>,
+ routing_key => <<"routing">>,
+ arguments => #{},
+ properties_key => <<"routing">>},
+ Binding = http_get(Config, "/bindings/%2F/e/amq.direct/e/amq.headers/routing"),
+ assert_list([Binding],
+ http_get(Config, "/bindings/%2F/e/amq.direct/e/amq.headers")),
+ assert_list([Binding],
+ http_get(Config, "/exchanges/%2F/amq.direct/bindings/source")),
+ assert_list([Binding],
+ http_get(Config, "/exchanges/%2F/amq.headers/bindings/destination")),
+ http_delete(Config, "/bindings/%2F/e/amq.direct/e/amq.headers/routing", {group, '2xx'}),
+ http_get(Config, "/bindings/%2F/e/amq.direct/e/amq.headers/rooting", ?NOT_FOUND),
+ passed.
+
+permissions_administrator_test(Config) ->
+ http_put(Config, "/users/isadmin", [{password, <<"isadmin">>},
+ {tags, <<"administrator">>}], {group, '2xx'}),
+ http_put(Config, "/users/notadmin", [{password, <<"notadmin">>},
+ {tags, <<"administrator">>}], {group, '2xx'}),
+ http_put(Config, "/users/notadmin", [{password, <<"notadmin">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ Test =
+ fun(Path) ->
+ http_get(Config, Path, "notadmin", "notadmin", ?NOT_AUTHORISED),
+ http_get(Config, Path, "isadmin", "isadmin", ?OK),
+ http_get(Config, Path, "guest", "guest", ?OK)
+ end,
+ Test("/vhosts/%2F"),
+ Test("/vhosts/%2F/permissions"),
+ Test("/users"),
+ Test("/users/guest"),
+ Test("/users/guest/permissions"),
+ Test("/permissions"),
+ Test("/permissions/%2F/guest"),
+ http_delete(Config, "/users/notadmin", {group, '2xx'}),
+ http_delete(Config, "/users/isadmin", {group, '2xx'}),
+ passed.
+
+permissions_vhost_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/users/myadmin", [{password, <<"myadmin">>},
+ {tags, <<"administrator">>}], {group, '2xx'}),
+ http_put(Config, "/users/myuser", [{password, <<"myuser">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost1", none, {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost2", none, {group, '2xx'}),
+ http_put(Config, "/permissions/myvhost1/myuser", PermArgs, {group, '2xx'}),
+ http_put(Config, "/permissions/myvhost1/guest", PermArgs, {group, '2xx'}),
+ http_put(Config, "/permissions/myvhost2/guest", PermArgs, {group, '2xx'}),
+ assert_list([#{name => <<"/">>},
+ #{name => <<"myvhost1">>},
+ #{name => <<"myvhost2">>}], http_get(Config, "/vhosts", ?OK)),
+ assert_list([#{name => <<"myvhost1">>}],
+ http_get(Config, "/vhosts", "myuser", "myuser", ?OK)),
+ http_put(Config, "/queues/myvhost1/myqueue", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/myvhost2/myqueue", QArgs, {group, '2xx'}),
+ Test1 =
+ fun(Path) ->
+ Results = http_get(Config, Path, "myuser", "myuser", ?OK),
+ [case maps:get(vhost, Result) of
+ <<"myvhost2">> ->
+ throw({got_result_from_vhost2_in, Path, Result});
+ _ ->
+ ok
+ end || Result <- Results]
+ end,
+ Test2 =
+ fun(Path1, Path2) ->
+ http_get(Config, Path1 ++ "/myvhost1/" ++ Path2, "myuser", "myuser",
+ ?OK),
+ http_get(Config, Path1 ++ "/myvhost2/" ++ Path2, "myuser", "myuser",
+ ?NOT_AUTHORISED)
+ end,
+ Test3 =
+ fun(Path1) ->
+ http_get(Config, Path1 ++ "/myvhost1/", "myadmin", "myadmin",
+ ?OK)
+ end,
+ Test1("/exchanges"),
+ Test2("/exchanges", ""),
+ Test2("/exchanges", "amq.direct"),
+ Test3("/exchanges"),
+ Test1("/queues"),
+ Test2("/queues", ""),
+ Test3("/queues"),
+ Test2("/queues", "myqueue"),
+ Test1("/bindings"),
+ Test2("/bindings", ""),
+ Test3("/bindings"),
+ Test2("/queues", "myqueue/bindings"),
+ Test2("/exchanges", "amq.default/bindings/source"),
+ Test2("/exchanges", "amq.default/bindings/destination"),
+ Test2("/bindings", "e/amq.default/q/myqueue"),
+ Test2("/bindings", "e/amq.default/q/myqueue/myqueue"),
+ http_delete(Config, "/vhosts/myvhost1", {group, '2xx'}),
+ http_delete(Config, "/vhosts/myvhost2", {group, '2xx'}),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ http_delete(Config, "/users/myadmin", {group, '2xx'}),
+ passed.
+
+permissions_amqp_test(Config) ->
+ %% Just test that it works at all, not that it works in all possible cases.
+ QArgs = #{},
+ PermArgs = [{configure, <<"foo.*">>}, {write, <<"foo.*">>},
+ {read, <<"foo.*">>}],
+ http_put(Config, "/users/myuser", [{password, <<"myuser">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/permissions/%2F/myuser", PermArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/bar-queue", QArgs, "myuser", "myuser",
+ ?NOT_AUTHORISED),
+ http_put(Config, "/queues/%2F/bar-queue", QArgs, "nonexistent", "nonexistent",
+ ?NOT_AUTHORISED),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ passed.
+
+%% Opens a new connection and a channel on it.
+%% The channel is not managed by rabbit_ct_client_helpers and
+%% should be explicitly closed by the caller.
+open_connection_and_channel(Config) ->
+ Conn = rabbit_ct_client_helpers:open_connection(Config, 0),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ {Conn, Ch}.
+
+get_conn(Config, Username, Password) ->
+ Port = amqp_port(Config),
+ {ok, Conn} = amqp_connection:start(#amqp_params_network{
+ port = Port,
+ username = list_to_binary(Username),
+ password = list_to_binary(Password)}),
+ LocalPort = local_port(Conn),
+ ConnPath = rabbit_misc:format(
+ "/connections/127.0.0.1%3A~w%20-%3E%20127.0.0.1%3A~w",
+ [LocalPort, Port]),
+ ChPath = rabbit_misc:format(
+ "/channels/127.0.0.1%3A~w%20-%3E%20127.0.0.1%3A~w%20(1)",
+ [LocalPort, Port]),
+ ConnChPath = rabbit_misc:format(
+ "/connections/127.0.0.1%3A~w%20-%3E%20127.0.0.1%3A~w/channels",
+ [LocalPort, Port]),
+ {Conn, ConnPath, ChPath, ConnChPath}.
+
+permissions_connection_channel_consumer_test(Config) ->
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/users/user", [{password, <<"user">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/permissions/%2F/user", PermArgs, {group, '2xx'}),
+ http_put(Config, "/users/monitor", [{password, <<"monitor">>},
+ {tags, <<"monitoring">>}], {group, '2xx'}),
+ http_put(Config, "/permissions/%2F/monitor", PermArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test", #{}, {group, '2xx'}),
+
+ {Conn1, UserConn, UserCh, UserConnCh} = get_conn(Config, "user", "user"),
+ {Conn2, MonConn, MonCh, MonConnCh} = get_conn(Config, "monitor", "monitor"),
+ {Conn3, AdmConn, AdmCh, AdmConnCh} = get_conn(Config, "guest", "guest"),
+ {ok, Ch1} = amqp_connection:open_channel(Conn1),
+ {ok, Ch2} = amqp_connection:open_channel(Conn2),
+ {ok, Ch3} = amqp_connection:open_channel(Conn3),
+ [amqp_channel:subscribe(
+ Ch, #'basic.consume'{queue = <<"test">>}, self()) ||
+ Ch <- [Ch1, Ch2, Ch3]],
+ timer:sleep(1500),
+ AssertLength = fun (Path, User, Len) ->
+ Res = http_get(Config, Path, User, User, ?OK),
+ ?assertEqual(Len, length(Res))
+ end,
+ [begin
+ AssertLength(P, "user", 1),
+ AssertLength(P, "monitor", 3),
+ AssertLength(P, "guest", 3)
+ end || P <- ["/connections", "/channels", "/consumers", "/consumers/%2F"]],
+
+ AssertRead = fun(Path, UserStatus) ->
+ http_get(Config, Path, "user", "user", UserStatus),
+ http_get(Config, Path, "monitor", "monitor", ?OK),
+ http_get(Config, Path, ?OK)
+ end,
+ AssertRead(UserConn, ?OK),
+ AssertRead(MonConn, ?NOT_AUTHORISED),
+ AssertRead(AdmConn, ?NOT_AUTHORISED),
+ AssertRead(UserCh, ?OK),
+ AssertRead(MonCh, ?NOT_AUTHORISED),
+ AssertRead(AdmCh, ?NOT_AUTHORISED),
+ AssertRead(UserConnCh, ?OK),
+ AssertRead(MonConnCh, ?NOT_AUTHORISED),
+ AssertRead(AdmConnCh, ?NOT_AUTHORISED),
+
+ AssertClose = fun(Path, User, Status) ->
+ http_delete(Config, Path, User, User, Status)
+ end,
+ AssertClose(UserConn, "monitor", ?NOT_AUTHORISED),
+ AssertClose(MonConn, "user", ?NOT_AUTHORISED),
+ AssertClose(AdmConn, "guest", {group, '2xx'}),
+ AssertClose(MonConn, "guest", {group, '2xx'}),
+ AssertClose(UserConn, "user", {group, '2xx'}),
+
+ http_delete(Config, "/users/user", {group, '2xx'}),
+ http_delete(Config, "/users/monitor", {group, '2xx'}),
+ http_get(Config, "/connections/foo", ?NOT_FOUND),
+ http_get(Config, "/channels/foo", ?NOT_FOUND),
+ http_delete(Config, "/queues/%2F/test", {group, '2xx'}),
+ passed.
+
+consumers_cq_test(Config) ->
+ consumers_test(Config, [{'x-queue-type', <<"classic">>}]).
+
+consumers_qq_test(Config) ->
+ consumers_test(Config, [{'x-queue-type', <<"quorum">>}]).
+
+consumers_test(Config, Args) ->
+ QArgs = [{auto_delete, false}, {durable, true},
+ {arguments, Args}],
+ http_put(Config, "/queues/%2F/test", QArgs, {group, '2xx'}),
+ {Conn, _ConnPath, _ChPath, _ConnChPath} = get_conn(Config, "guest", "guest"),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ amqp_channel:subscribe(
+ Ch, #'basic.consume'{queue = <<"test">>,
+ no_ack = false,
+ consumer_tag = <<"my-ctag">> }, self()),
+ timer:sleep(1500),
+ assert_list([#{exclusive => false,
+ ack_required => true,
+ active => true,
+ activity_status => <<"up">>,
+ consumer_tag => <<"my-ctag">>}], http_get(Config, "/consumers")),
+ amqp_connection:close(Conn),
+ http_delete(Config, "/queues/%2F/test", {group, '2xx'}),
+ passed.
+
+single_active_consumer_cq_test(Config) ->
+ single_active_consumer(Config,
+ "/queues/%2F/single-active-consumer-cq",
+ <<"single-active-consumer-cq">>,
+ [{'x-queue-type', <<"classic">>}]).
+
+single_active_consumer_qq_test(Config) ->
+ single_active_consumer(Config,
+ "/queues/%2F/single-active-consumer-qq",
+ <<"single-active-consumer-qq">>,
+ [{'x-queue-type', <<"quorum">>}]).
+
+single_active_consumer(Config, Url, QName, Args) ->
+ QArgs = [{auto_delete, false}, {durable, true},
+ {arguments, [{'x-single-active-consumer', true}] ++ Args}],
+ http_put(Config, Url, QArgs, {group, '2xx'}),
+ {Conn, _ConnPath, _ChPath, _ConnChPath} = get_conn(Config, "guest", "guest"),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ amqp_channel:subscribe(
+ Ch, #'basic.consume'{queue = QName,
+ no_ack = true,
+ consumer_tag = <<"1">> }, self()),
+ {ok, Ch2} = amqp_connection:open_channel(Conn),
+ amqp_channel:subscribe(
+ Ch2, #'basic.consume'{queue = QName,
+ no_ack = true,
+ consumer_tag = <<"2">> }, self()),
+ timer:sleep(1500),
+ assert_list([#{exclusive => false,
+ ack_required => false,
+ active => true,
+ activity_status => <<"single_active">>,
+ consumer_tag => <<"1">>},
+ #{exclusive => false,
+ ack_required => false,
+ active => false,
+ activity_status => <<"waiting">>,
+ consumer_tag => <<"2">>}], http_get(Config, "/consumers")),
+ amqp_channel:close(Ch),
+ timer:sleep(1500),
+ assert_list([#{exclusive => false,
+ ack_required => false,
+ active => true,
+ activity_status => <<"single_active">>,
+ consumer_tag => <<"2">>}], http_get(Config, "/consumers")),
+ amqp_connection:close(Conn),
+ http_delete(Config, Url, {group, '2xx'}),
+ passed.
+
+defs(Config, Key, URI, CreateMethod, Args) ->
+ defs(Config, Key, URI, CreateMethod, Args,
+ fun(URI2) -> http_delete(Config, URI2, {group, '2xx'}) end).
+
+defs_v(Config, Key, URI, CreateMethod, Args) ->
+ Rep1 = fun (S, S2) -> re:replace(S, "<vhost>", S2, [{return, list}]) end,
+ ReplaceVHostInArgs = fun(M, V2) -> maps:map(fun(vhost, _) -> V2;
+ (_, V1) -> V1 end, M) end,
+
+ %% Test against default vhost
+ defs(Config, Key, Rep1(URI, "%2F"), CreateMethod, ReplaceVHostInArgs(Args, <<"/">>)),
+
+ %% Test against new vhost
+ http_put(Config, "/vhosts/test", none, {group, '2xx'}),
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/permissions/test/guest", PermArgs, {group, '2xx'}),
+ DeleteFun0 = fun(URI2) ->
+ http_delete(Config, URI2, {group, '2xx'})
+ end,
+ DeleteFun1 = fun(_) ->
+ http_delete(Config, "/vhosts/test", {group, '2xx'})
+ end,
+ defs(Config, Key, Rep1(URI, "test"),
+ CreateMethod, ReplaceVHostInArgs(Args, <<"test">>),
+ DeleteFun0, DeleteFun1).
+
+create(Config, CreateMethod, URI, Args) ->
+ case CreateMethod of
+ put -> http_put(Config, URI, Args, {group, '2xx'}),
+ URI;
+ put_update -> http_put(Config, URI, Args, {group, '2xx'}),
+ URI;
+ post -> Headers = http_post(Config, URI, Args, {group, '2xx'}),
+ rabbit_web_dispatch_util:unrelativise(
+ URI, pget("location", Headers))
+ end.
+
+defs(Config, Key, URI, CreateMethod, Args, DeleteFun) ->
+ defs(Config, Key, URI, CreateMethod, Args, DeleteFun, DeleteFun).
+
+defs(Config, Key, URI, CreateMethod, Args, DeleteFun0, DeleteFun1) ->
+ %% Create the item
+ URI2 = create(Config, CreateMethod, URI, Args),
+ %% Make sure it ends up in definitions
+ Definitions = http_get(Config, "/definitions", ?OK),
+ true = lists:any(fun(I) -> test_item(Args, I) end, maps:get(Key, Definitions)),
+
+ %% Delete it
+ DeleteFun0(URI2),
+
+ %% Post the definitions back, it should get recreated in correct form
+ http_post(Config, "/definitions", Definitions, {group, '2xx'}),
+ assert_item(Args, http_get(Config, URI2, ?OK)),
+
+ %% And delete it again
+ DeleteFun1(URI2),
+
+ passed.
+
+register_parameters_and_policy_validator(Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_runtime_parameters_util, register, []),
+ rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_runtime_parameters_util, register_policy_validator, []).
+
+unregister_parameters_and_policy_validator(Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_runtime_parameters_util, unregister_policy_validator, []),
+ rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_runtime_parameters_util, unregister, []).
+
+definitions_test(Config) ->
+ register_parameters_and_policy_validator(Config),
+
+ defs_v(Config, queues, "/queues/<vhost>/my-queue", put,
+ #{name => <<"my-queue">>,
+ durable => true}),
+ defs_v(Config, exchanges, "/exchanges/<vhost>/my-exchange", put,
+ #{name => <<"my-exchange">>,
+ type => <<"direct">>}),
+ defs_v(Config, bindings, "/bindings/<vhost>/e/amq.direct/e/amq.fanout", post,
+ #{routing_key => <<"routing">>, arguments => #{}}),
+ defs_v(Config, policies, "/policies/<vhost>/my-policy", put,
+ #{vhost => vhost,
+ name => <<"my-policy">>,
+ pattern => <<".*">>,
+ definition => #{testpos => [1, 2, 3]},
+ priority => 1}),
+ defs_v(Config, parameters, "/parameters/test/<vhost>/good", put,
+ #{vhost => vhost,
+ component => <<"test">>,
+ name => <<"good">>,
+ value => <<"ignore">>}),
+ defs(Config, global_parameters, "/global-parameters/good", put,
+ #{name => <<"good">>,
+ value => #{a => <<"b">>}}),
+ defs(Config, users, "/users/myuser", put,
+ #{name => <<"myuser">>,
+ password_hash => <<"WAbU0ZIcvjTpxM3Q3SbJhEAM2tQ=">>,
+ hashing_algorithm => <<"rabbit_password_hashing_sha256">>,
+ tags => <<"management">>}),
+ defs(Config, vhosts, "/vhosts/myvhost", put,
+ #{name => <<"myvhost">>}),
+ defs(Config, permissions, "/permissions/%2F/guest", put,
+ #{user => <<"guest">>,
+ vhost => <<"/">>,
+ configure => <<"c">>,
+ write => <<"w">>,
+ read => <<"r">>}),
+ defs(Config, topic_permissions, "/topic-permissions/%2F/guest", put,
+ #{user => <<"guest">>,
+ vhost => <<"/">>,
+ exchange => <<"amq.topic">>,
+ write => <<"^a">>,
+ read => <<"^b">>}),
+
+ %% We just messed with guest's permissions
+ http_put(Config, "/permissions/%2F/guest",
+ #{configure => <<".*">>,
+ write => <<".*">>,
+ read => <<".*">>}, {group, '2xx'}),
+ BrokenConfig =
+ #{users => [],
+ vhosts => [],
+ permissions => [],
+ queues => [],
+ exchanges => [#{name => <<"not.direct">>,
+ vhost => <<"/">>,
+ type => <<"definitely not direct">>,
+ durable => true,
+ auto_delete => false,
+ arguments => []}
+ ],
+ bindings => []},
+ http_post(Config, "/definitions", BrokenConfig, ?BAD_REQUEST),
+
+ unregister_parameters_and_policy_validator(Config),
+ passed.
+
+long_definitions_test(Config) ->
+ %% Vhosts take time to start. Generate a bunch of them
+ Vhosts = long_definitions_vhosts(long_definitions_test),
+ LongDefs =
+ #{users => [],
+ vhosts => Vhosts,
+ permissions => [],
+ queues => [],
+ exchanges => [],
+ bindings => []},
+ http_post(Config, "/definitions", LongDefs, {group, '2xx'}),
+ passed.
+
+long_definitions_multipart_test(Config) ->
+ %% Vhosts take time to start. Generate a bunch of them
+ Vhosts = long_definitions_vhosts(long_definitions_multipart_test),
+ LongDefs =
+ #{users => [],
+ vhosts => Vhosts,
+ permissions => [],
+ queues => [],
+ exchanges => [],
+ bindings => []},
+ Data = binary_to_list(format_for_upload(LongDefs)),
+ CodeExp = {group, '2xx'},
+ Boundary = "------------long_definitions_multipart_test",
+ Body = format_multipart_filedata(Boundary, [{file, "file", Data}]),
+ ContentType = lists:concat(["multipart/form-data; boundary=", Boundary]),
+ MoreHeaders = [{"content-type", ContentType}, {"content-length", integer_to_list(length(Body))}],
+ http_upload_raw(Config, post, "/definitions", Body, "guest", "guest", CodeExp, MoreHeaders),
+ passed.
+
+long_definitions_vhosts(long_definitions_test) ->
+ [#{name => <<"long_definitions_test-", (integer_to_binary(N))/binary>>} ||
+ N <- lists:seq(1, 120)];
+long_definitions_vhosts(long_definitions_multipart_test) ->
+ Bin = list_to_binary(lists:flatten(lists:duplicate(524288, "X"))),
+ [#{name => <<"long_definitions_test-", Bin/binary, (integer_to_binary(N))/binary>>} ||
+ N <- lists:seq(1, 16)].
+
+defs_vhost(Config, Key, URI, CreateMethod, Args) ->
+ Rep1 = fun (S, S2) -> re:replace(S, "<vhost>", S2, [{return, list}]) end,
+ ReplaceVHostInArgs = fun(M, V2) -> maps:map(fun(vhost, _) -> V2;
+ (_, V1) -> V1 end, M) end,
+
+ %% Create test vhost
+ http_put(Config, "/vhosts/test", none, {group, '2xx'}),
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/permissions/test/guest", PermArgs, {group, '2xx'}),
+
+ %% Test against default vhost
+ defs_vhost(Config, Key, URI, Rep1, "%2F", "test", CreateMethod,
+ ReplaceVHostInArgs(Args, <<"/">>), ReplaceVHostInArgs(Args, <<"test">>),
+ fun(URI2) -> http_delete(Config, URI2, {group, '2xx'}) end),
+
+ %% Test against test vhost
+ defs_vhost(Config, Key, URI, Rep1, "test", "%2F", CreateMethod,
+ ReplaceVHostInArgs(Args, <<"test">>), ReplaceVHostInArgs(Args, <<"/">>),
+ fun(URI2) -> http_delete(Config, URI2, {group, '2xx'}) end),
+
+ %% Remove test vhost
+ http_delete(Config, "/vhosts/test", {group, '2xx'}).
+
+
+defs_vhost(Config, Key, URI0, Rep1, VHost1, VHost2, CreateMethod, Args1, Args2,
+ DeleteFun) ->
+ %% Create the item
+ URI2 = create(Config, CreateMethod, Rep1(URI0, VHost1), Args1),
+ %% Make sure it ends up in definitions
+ Definitions = http_get(Config, "/definitions/" ++ VHost1, ?OK),
+ true = lists:any(fun(I) -> test_item(Args1, I) end, maps:get(Key, Definitions)),
+
+ %% Make sure it is not in the other vhost
+ Definitions0 = http_get(Config, "/definitions/" ++ VHost2, ?OK),
+ false = lists:any(fun(I) -> test_item(Args2, I) end, maps:get(Key, Definitions0)),
+
+ %% Post the definitions back
+ http_post(Config, "/definitions/" ++ VHost2, Definitions, {group, '2xx'}),
+
+ %% Make sure it is now in the other vhost
+ Definitions1 = http_get(Config, "/definitions/" ++ VHost2, ?OK),
+ true = lists:any(fun(I) -> test_item(Args2, I) end, maps:get(Key, Definitions1)),
+
+ %% Delete it
+ DeleteFun(URI2),
+ URI3 = create(Config, CreateMethod, Rep1(URI0, VHost2), Args2),
+ DeleteFun(URI3),
+ passed.
+
+definitions_vhost_test(Config) ->
+ %% Ensures that definitions can be exported/imported from a single virtual
+ %% host to another
+
+ register_parameters_and_policy_validator(Config),
+
+ defs_vhost(Config, queues, "/queues/<vhost>/my-queue", put,
+ #{name => <<"my-queue">>,
+ durable => true}),
+ defs_vhost(Config, exchanges, "/exchanges/<vhost>/my-exchange", put,
+ #{name => <<"my-exchange">>,
+ type => <<"direct">>}),
+ defs_vhost(Config, bindings, "/bindings/<vhost>/e/amq.direct/e/amq.fanout", post,
+ #{routing_key => <<"routing">>, arguments => #{}}),
+ defs_vhost(Config, policies, "/policies/<vhost>/my-policy", put,
+ #{vhost => vhost,
+ name => <<"my-policy">>,
+ pattern => <<".*">>,
+ definition => #{testpos => [1, 2, 3]},
+ priority => 1}),
+
+ defs_vhost(Config, parameters, "/parameters/vhost-limits/<vhost>/limits", put,
+ #{vhost => vhost,
+ name => <<"limits">>,
+ component => <<"vhost-limits">>,
+ value => #{ 'max-connections' => 100 }}),
+ Upload =
+ #{queues => [],
+ exchanges => [],
+ policies => [],
+ parameters => [],
+ bindings => []},
+ http_post(Config, "/definitions/othervhost", Upload, ?NOT_FOUND),
+
+ unregister_parameters_and_policy_validator(Config),
+ passed.
+
+definitions_password_test(Config) ->
+ % Import definitions from 3.5.x
+ Config35 = #{rabbit_version => <<"3.5.4">>,
+ users => [#{name => <<"myuser">>,
+ password_hash => <<"WAbU0ZIcvjTpxM3Q3SbJhEAM2tQ=">>,
+ tags => <<"management">>}
+ ]},
+ Expected35 = #{name => <<"myuser">>,
+ password_hash => <<"WAbU0ZIcvjTpxM3Q3SbJhEAM2tQ=">>,
+ hashing_algorithm => <<"rabbit_password_hashing_md5">>,
+ tags => <<"management">>},
+ http_post(Config, "/definitions", Config35, {group, '2xx'}),
+ Definitions35 = http_get(Config, "/definitions", ?OK),
+ ct:pal("Definitions35: ~p", [Definitions35]),
+ Users35 = maps:get(users, Definitions35),
+ true = lists:any(fun(I) -> test_item(Expected35, I) end, Users35),
+
+ %% Import definitions from from 3.6.0
+ Config36 = #{rabbit_version => <<"3.6.0">>,
+ users => [#{name => <<"myuser">>,
+ password_hash => <<"WAbU0ZIcvjTpxM3Q3SbJhEAM2tQ=">>,
+ tags => <<"management">>}
+ ]},
+ Expected36 = #{name => <<"myuser">>,
+ password_hash => <<"WAbU0ZIcvjTpxM3Q3SbJhEAM2tQ=">>,
+ hashing_algorithm => <<"rabbit_password_hashing_sha256">>,
+ tags => <<"management">>},
+ http_post(Config, "/definitions", Config36, {group, '2xx'}),
+
+ Definitions36 = http_get(Config, "/definitions", ?OK),
+ Users36 = maps:get(users, Definitions36),
+ true = lists:any(fun(I) -> test_item(Expected36, I) end, Users36),
+
+ %% No hashing_algorithm provided
+ ConfigDefault = #{rabbit_version => <<"3.6.1">>,
+ users => [#{name => <<"myuser">>,
+ password_hash => <<"WAbU0ZIcvjTpxM3Q3SbJhEAM2tQ=">>,
+ tags => <<"management">>}
+ ]},
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbit,
+ password_hashing_module,
+ rabbit_password_hashing_sha512]),
+
+ ExpectedDefault = #{name => <<"myuser">>,
+ password_hash => <<"WAbU0ZIcvjTpxM3Q3SbJhEAM2tQ=">>,
+ hashing_algorithm => <<"rabbit_password_hashing_sha512">>,
+ tags => <<"management">>},
+ http_post(Config, "/definitions", ConfigDefault, {group, '2xx'}),
+
+ DefinitionsDefault = http_get(Config, "/definitions", ?OK),
+ UsersDefault = maps:get(users, DefinitionsDefault),
+
+ true = lists:any(fun(I) -> test_item(ExpectedDefault, I) end, UsersDefault),
+ passed.
+
+definitions_remove_things_test(Config) ->
+ {Conn, Ch} = open_connection_and_channel(Config),
+ amqp_channel:call(Ch, #'queue.declare'{ queue = <<"my-exclusive">>,
+ exclusive = true }),
+ http_get(Config, "/queues/%2F/my-exclusive", ?OK),
+ Definitions = http_get(Config, "/definitions", ?OK),
+ [] = maps:get(queues, Definitions),
+ [] = maps:get(exchanges, Definitions),
+ [] = maps:get(bindings, Definitions),
+ amqp_channel:close(Ch),
+ close_connection(Conn),
+ passed.
+
+definitions_server_named_queue_test(Config) ->
+ {Conn, Ch} = open_connection_and_channel(Config),
+ %% declares a durable server-named queue for the sake of exporting the definition
+ #'queue.declare_ok'{queue = QName} =
+ amqp_channel:call(Ch, #'queue.declare'{queue = <<"">>, durable = true}),
+ Path = "/queues/%2F/" ++ rabbit_http_util:quote_plus(QName),
+ http_get(Config, Path, ?OK),
+ Definitions = http_get(Config, "/definitions", ?OK),
+ close_channel(Ch),
+ close_connection(Conn),
+ http_delete(Config, Path, {group, '2xx'}),
+ http_get(Config, Path, ?NOT_FOUND),
+ http_post(Config, "/definitions", Definitions, {group, '2xx'}),
+ %% amq.* entities are not imported
+ http_get(Config, Path, ?NOT_FOUND),
+ passed.
+
+definitions_with_charset_test(Config) ->
+ Path = "/definitions",
+ Body0 = http_get(Config, Path, ?OK),
+ Headers = [auth_header("guest", "guest")],
+ Url = uri_base_from(Config, 0) ++ Path,
+ Body1 = format_for_upload(Body0),
+ Request = {Url, Headers, "application/json; charset=utf-8", Body1},
+ {ok, {{_, ?NO_CONTENT, _}, _, []}} = httpc:request(post, Request, ?HTTPC_OPTS, []),
+ passed.
+
+aliveness_test(Config) ->
+ #{status := <<"ok">>} = http_get(Config, "/aliveness-test/%2F", ?OK),
+ http_get(Config, "/aliveness-test/foo", ?NOT_FOUND),
+ http_delete(Config, "/queues/%2F/aliveness-test", {group, '2xx'}),
+ passed.
+
+arguments_test(Config) ->
+ XArgs = [{type, <<"headers">>},
+ {arguments, [{'alternate-exchange', <<"amq.direct">>}]}],
+ QArgs = [{arguments, [{'x-expires', 1800000}]}],
+ BArgs = [{routing_key, <<"">>},
+ {arguments, [{'x-match', <<"all">>},
+ {foo, <<"bar">>}]}],
+ http_delete(Config, "/exchanges/%2F/myexchange", {one_of, [201, 404]}),
+ http_put(Config, "/exchanges/%2F/myexchange", XArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/arguments_test", QArgs, {group, '2xx'}),
+ http_post(Config, "/bindings/%2F/e/myexchange/q/arguments_test", BArgs, {group, '2xx'}),
+ Definitions = http_get(Config, "/definitions", ?OK),
+ http_delete(Config, "/exchanges/%2F/myexchange", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/arguments_test", {group, '2xx'}),
+ http_post(Config, "/definitions", Definitions, {group, '2xx'}),
+ #{'alternate-exchange' := <<"amq.direct">>} =
+ maps:get(arguments, http_get(Config, "/exchanges/%2F/myexchange", ?OK)),
+ #{'x-expires' := 1800000} =
+ maps:get(arguments, http_get(Config, "/queues/%2F/arguments_test", ?OK)),
+
+ ArgsTable = [{<<"foo">>,longstr,<<"bar">>}, {<<"x-match">>, longstr, <<"all">>}],
+ Hash = table_hash(ArgsTable),
+ PropertiesKey = [$~] ++ Hash,
+
+ assert_item(
+ #{'x-match' => <<"all">>, foo => <<"bar">>},
+ maps:get(arguments,
+ http_get(Config, "/bindings/%2F/e/myexchange/q/arguments_test/" ++
+ PropertiesKey, ?OK))
+ ),
+ http_delete(Config, "/exchanges/%2F/myexchange", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/arguments_test", {group, '2xx'}),
+ passed.
+
+table_hash(Table) ->
+ binary_to_list(rabbit_mgmt_format:args_hash(Table)).
+
+arguments_table_test(Config) ->
+ Args = #{'upstreams' => [<<"amqp://localhost/%2F/upstream1">>,
+ <<"amqp://localhost/%2F/upstream2">>]},
+ XArgs = #{type => <<"headers">>,
+ arguments => Args},
+ http_delete(Config, "/exchanges/%2F/myexchange", {one_of, [201, 404]}),
+ http_put(Config, "/exchanges/%2F/myexchange", XArgs, {group, '2xx'}),
+ Definitions = http_get(Config, "/definitions", ?OK),
+ http_delete(Config, "/exchanges/%2F/myexchange", {group, '2xx'}),
+ http_post(Config, "/definitions", Definitions, {group, '2xx'}),
+ Args = maps:get(arguments, http_get(Config, "/exchanges/%2F/myexchange", ?OK)),
+ http_delete(Config, "/exchanges/%2F/myexchange", {group, '2xx'}),
+ passed.
+
+queue_purge_test(Config) ->
+ QArgs = #{},
+ http_put(Config, "/queues/%2F/myqueue", QArgs, {group, '2xx'}),
+ {Conn, Ch} = open_connection_and_channel(Config),
+ Publish = fun() ->
+ amqp_channel:call(
+ Ch, #'basic.publish'{exchange = <<"">>,
+ routing_key = <<"myqueue">>},
+ #amqp_msg{payload = <<"message">>})
+ end,
+ Publish(),
+ Publish(),
+ amqp_channel:call(
+ Ch, #'queue.declare'{queue = <<"exclusive">>, exclusive = true}),
+ {#'basic.get_ok'{}, _} =
+ amqp_channel:call(Ch, #'basic.get'{queue = <<"myqueue">>}),
+ http_delete(Config, "/queues/%2F/myqueue/contents", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/badqueue/contents", ?NOT_FOUND),
+ http_delete(Config, "/queues/%2F/exclusive/contents", ?BAD_REQUEST),
+ http_delete(Config, "/queues/%2F/exclusive", ?BAD_REQUEST),
+ #'basic.get_empty'{} =
+ amqp_channel:call(Ch, #'basic.get'{queue = <<"myqueue">>}),
+ close_channel(Ch),
+ close_connection(Conn),
+ http_delete(Config, "/queues/%2F/myqueue", {group, '2xx'}),
+ passed.
+
+queue_actions_test(Config) ->
+ http_put(Config, "/queues/%2F/q", #{}, {group, '2xx'}),
+ http_post(Config, "/queues/%2F/q/actions", [{action, sync}], {group, '2xx'}),
+ http_post(Config, "/queues/%2F/q/actions", [{action, cancel_sync}], {group, '2xx'}),
+ http_post(Config, "/queues/%2F/q/actions", [{action, change_colour}], ?BAD_REQUEST),
+ http_delete(Config, "/queues/%2F/q", {group, '2xx'}),
+ passed.
+
+exclusive_consumer_test(Config) ->
+ {Conn, Ch} = open_connection_and_channel(Config),
+ #'queue.declare_ok'{ queue = QName } =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ amqp_channel:subscribe(Ch, #'basic.consume'{queue = QName,
+ exclusive = true}, self()),
+ timer:sleep(1500), %% Sadly we need to sleep to let the stats update
+ http_get(Config, "/queues/%2F/"), %% Just check we don't blow up
+ close_channel(Ch),
+ close_connection(Conn),
+ passed.
+
+
+exclusive_queue_test(Config) ->
+ {Conn, Ch} = open_connection_and_channel(Config),
+ #'queue.declare_ok'{ queue = QName } =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ timer:sleep(1500), %% Sadly we need to sleep to let the stats update
+ Path = "/queues/%2F/" ++ rabbit_http_util:quote_plus(QName),
+ Queue = http_get(Config, Path),
+ assert_item(#{name => QName,
+ vhost => <<"/">>,
+ durable => false,
+ auto_delete => false,
+ exclusive => true,
+ arguments => #{}}, Queue),
+ amqp_channel:close(Ch),
+ close_connection(Conn),
+ passed.
+
+connections_channels_pagination_test(Config) ->
+ %% this test uses "unmanaged" (by Common Test helpers) connections to avoid
+ %% connection caching
+ Conn = open_unmanaged_connection(Config),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ Conn1 = open_unmanaged_connection(Config),
+ {ok, Ch1} = amqp_connection:open_channel(Conn1),
+ Conn2 = open_unmanaged_connection(Config),
+ {ok, Ch2} = amqp_connection:open_channel(Conn2),
+
+ %% for stats to update
+ timer:sleep(1500),
+ PageOfTwo = http_get(Config, "/connections?page=1&page_size=2", ?OK),
+ ?assertEqual(3, maps:get(total_count, PageOfTwo)),
+ ?assertEqual(3, maps:get(filtered_count, PageOfTwo)),
+ ?assertEqual(2, maps:get(item_count, PageOfTwo)),
+ ?assertEqual(1, maps:get(page, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_size, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_count, PageOfTwo)),
+
+
+ TwoOfTwo = http_get(Config, "/channels?page=2&page_size=2", ?OK),
+ ?assertEqual(3, maps:get(total_count, TwoOfTwo)),
+ ?assertEqual(3, maps:get(filtered_count, TwoOfTwo)),
+ ?assertEqual(1, maps:get(item_count, TwoOfTwo)),
+ ?assertEqual(2, maps:get(page, TwoOfTwo)),
+ ?assertEqual(2, maps:get(page_size, TwoOfTwo)),
+ ?assertEqual(2, maps:get(page_count, TwoOfTwo)),
+
+ amqp_channel:close(Ch),
+ amqp_connection:close(Conn),
+ amqp_channel:close(Ch1),
+ amqp_connection:close(Conn1),
+ amqp_channel:close(Ch2),
+ amqp_connection:close(Conn2),
+
+ passed.
+
+exchanges_pagination_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/guest", PermArgs, {group, '2xx'}),
+ http_get(Config, "/exchanges/vh1?page=1&page_size=2", ?OK),
+ http_put(Config, "/exchanges/%2F/test0", QArgs, {group, '2xx'}),
+ http_put(Config, "/exchanges/vh1/test1", QArgs, {group, '2xx'}),
+ http_put(Config, "/exchanges/%2F/test2_reg", QArgs, {group, '2xx'}),
+ http_put(Config, "/exchanges/vh1/reg_test3", QArgs, {group, '2xx'}),
+
+ %% for stats to update
+ timer:sleep(1500),
+
+ Total = length(rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_exchange, list_names, [])),
+
+ PageOfTwo = http_get(Config, "/exchanges?page=1&page_size=2", ?OK),
+ ?assertEqual(Total, maps:get(total_count, PageOfTwo)),
+ ?assertEqual(Total, maps:get(filtered_count, PageOfTwo)),
+ ?assertEqual(2, maps:get(item_count, PageOfTwo)),
+ ?assertEqual(1, maps:get(page, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_size, PageOfTwo)),
+ ?assertEqual(round(Total / 2), maps:get(page_count, PageOfTwo)),
+ assert_list([#{name => <<"">>, vhost => <<"/">>},
+ #{name => <<"amq.direct">>, vhost => <<"/">>}
+ ], maps:get(items, PageOfTwo)),
+
+ ByName = http_get(Config, "/exchanges?page=1&page_size=2&name=reg", ?OK),
+ ?assertEqual(Total, maps:get(total_count, ByName)),
+ ?assertEqual(2, maps:get(filtered_count, ByName)),
+ ?assertEqual(2, maps:get(item_count, ByName)),
+ ?assertEqual(1, maps:get(page, ByName)),
+ ?assertEqual(2, maps:get(page_size, ByName)),
+ ?assertEqual(1, maps:get(page_count, ByName)),
+ assert_list([#{name => <<"test2_reg">>, vhost => <<"/">>},
+ #{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], maps:get(items, ByName)),
+
+
+ RegExByName = http_get(Config,
+ "/exchanges?page=1&page_size=2&name=%5E(?=%5Ereg)&use_regex=true",
+ ?OK),
+ ?assertEqual(Total, maps:get(total_count, RegExByName)),
+ ?assertEqual(1, maps:get(filtered_count, RegExByName)),
+ ?assertEqual(1, maps:get(item_count, RegExByName)),
+ ?assertEqual(1, maps:get(page, RegExByName)),
+ ?assertEqual(2, maps:get(page_size, RegExByName)),
+ ?assertEqual(1, maps:get(page_count, RegExByName)),
+ assert_list([#{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], maps:get(items, RegExByName)),
+
+
+ http_get(Config, "/exchanges?page=1000", ?BAD_REQUEST),
+ http_get(Config, "/exchanges?page=-1", ?BAD_REQUEST),
+ http_get(Config, "/exchanges?page=not_an_integer_value", ?BAD_REQUEST),
+ http_get(Config, "/exchanges?page=1&page_size=not_an_integer_value", ?BAD_REQUEST),
+ http_get(Config, "/exchanges?page=1&page_size=501", ?BAD_REQUEST), %% max 500 allowed
+ http_get(Config, "/exchanges?page=-1&page_size=-2", ?BAD_REQUEST),
+ http_delete(Config, "/exchanges/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/exchanges/vh1/test1", {group, '2xx'}),
+ http_delete(Config, "/exchanges/%2F/test2_reg", {group, '2xx'}),
+ http_delete(Config, "/exchanges/vh1/reg_test3", {group, '2xx'}),
+ http_delete(Config, "/vhosts/vh1", {group, '2xx'}),
+ passed.
+
+exchanges_pagination_permissions_test(Config) ->
+ http_put(Config, "/users/admin", [{password, <<"admin">>},
+ {tags, <<"administrator">>}], {group, '2xx'}),
+ http_put(Config, "/users/non-admin", [{password, <<"non-admin">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ Perms = [{configure, <<".*">>},
+ {write, <<".*">>},
+ {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/non-admin", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/%2F/admin", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/admin", Perms, {group, '2xx'}),
+ QArgs = #{},
+ http_put(Config, "/exchanges/%2F/test0", QArgs, "admin", "admin", {group, '2xx'}),
+ http_put(Config, "/exchanges/vh1/test1", QArgs, "non-admin", "non-admin", {group, '2xx'}),
+
+ %% for stats to update
+ timer:sleep(1500),
+
+ FirstPage = http_get(Config, "/exchanges?page=1&name=test1", "non-admin", "non-admin", ?OK),
+
+ ?assertEqual(8, maps:get(total_count, FirstPage)),
+ ?assertEqual(1, maps:get(item_count, FirstPage)),
+ ?assertEqual(1, maps:get(page, FirstPage)),
+ ?assertEqual(100, maps:get(page_size, FirstPage)),
+ ?assertEqual(1, maps:get(page_count, FirstPage)),
+ assert_list([#{name => <<"test1">>, vhost => <<"vh1">>}
+ ], maps:get(items, FirstPage)),
+ http_delete(Config, "/exchanges/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/exchanges/vh1/test1", {group, '2xx'}),
+ http_delete(Config, "/users/admin", {group, '2xx'}),
+ http_delete(Config, "/users/non-admin", {group, '2xx'}),
+ passed.
+
+
+
+queue_pagination_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/guest", PermArgs, {group, '2xx'}),
+
+ http_get(Config, "/queues/vh1?page=1&page_size=2", ?OK),
+
+ http_put(Config, "/queues/%2F/test0", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/test1", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test2_reg", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/reg_test3", QArgs, {group, '2xx'}),
+
+ %% for stats to update
+ timer:sleep(1500),
+
+ Total = length(rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_amqqueue, list_names, [])),
+
+ PageOfTwo = http_get(Config, "/queues?page=1&page_size=2", ?OK),
+ ?assertEqual(Total, maps:get(total_count, PageOfTwo)),
+ ?assertEqual(Total, maps:get(filtered_count, PageOfTwo)),
+ ?assertEqual(2, maps:get(item_count, PageOfTwo)),
+ ?assertEqual(1, maps:get(page, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_size, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_count, PageOfTwo)),
+ assert_list([#{name => <<"test0">>, vhost => <<"/">>},
+ #{name => <<"test2_reg">>, vhost => <<"/">>}
+ ], maps:get(items, PageOfTwo)),
+
+ SortedByName = http_get(Config, "/queues?sort=name&page=1&page_size=2", ?OK),
+ ?assertEqual(Total, maps:get(total_count, SortedByName)),
+ ?assertEqual(Total, maps:get(filtered_count, SortedByName)),
+ ?assertEqual(2, maps:get(item_count, SortedByName)),
+ ?assertEqual(1, maps:get(page, SortedByName)),
+ ?assertEqual(2, maps:get(page_size, SortedByName)),
+ ?assertEqual(2, maps:get(page_count, SortedByName)),
+ assert_list([#{name => <<"reg_test3">>, vhost => <<"vh1">>},
+ #{name => <<"test0">>, vhost => <<"/">>}
+ ], maps:get(items, SortedByName)),
+
+
+ FirstPage = http_get(Config, "/queues?page=1", ?OK),
+ ?assertEqual(Total, maps:get(total_count, FirstPage)),
+ ?assertEqual(Total, maps:get(filtered_count, FirstPage)),
+ ?assertEqual(4, maps:get(item_count, FirstPage)),
+ ?assertEqual(1, maps:get(page, FirstPage)),
+ ?assertEqual(100, maps:get(page_size, FirstPage)),
+ ?assertEqual(1, maps:get(page_count, FirstPage)),
+ assert_list([#{name => <<"test0">>, vhost => <<"/">>},
+ #{name => <<"test1">>, vhost => <<"vh1">>},
+ #{name => <<"test2_reg">>, vhost => <<"/">>},
+ #{name => <<"reg_test3">>, vhost =><<"vh1">>}
+ ], maps:get(items, FirstPage)),
+
+
+ ReverseSortedByName = http_get(Config,
+ "/queues?page=2&page_size=2&sort=name&sort_reverse=true",
+ ?OK),
+ ?assertEqual(Total, maps:get(total_count, ReverseSortedByName)),
+ ?assertEqual(Total, maps:get(filtered_count, ReverseSortedByName)),
+ ?assertEqual(2, maps:get(item_count, ReverseSortedByName)),
+ ?assertEqual(2, maps:get(page, ReverseSortedByName)),
+ ?assertEqual(2, maps:get(page_size, ReverseSortedByName)),
+ ?assertEqual(2, maps:get(page_count, ReverseSortedByName)),
+ assert_list([#{name => <<"test0">>, vhost => <<"/">>},
+ #{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], maps:get(items, ReverseSortedByName)),
+
+
+ ByName = http_get(Config, "/queues?page=1&page_size=2&name=reg", ?OK),
+ ?assertEqual(Total, maps:get(total_count, ByName)),
+ ?assertEqual(2, maps:get(filtered_count, ByName)),
+ ?assertEqual(2, maps:get(item_count, ByName)),
+ ?assertEqual(1, maps:get(page, ByName)),
+ ?assertEqual(2, maps:get(page_size, ByName)),
+ ?assertEqual(1, maps:get(page_count, ByName)),
+ assert_list([#{name => <<"test2_reg">>, vhost => <<"/">>},
+ #{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], maps:get(items, ByName)),
+
+ RegExByName = http_get(Config,
+ "/queues?page=1&page_size=2&name=%5E(?=%5Ereg)&use_regex=true",
+ ?OK),
+ ?assertEqual(Total, maps:get(total_count, RegExByName)),
+ ?assertEqual(1, maps:get(filtered_count, RegExByName)),
+ ?assertEqual(1, maps:get(item_count, RegExByName)),
+ ?assertEqual(1, maps:get(page, RegExByName)),
+ ?assertEqual(2, maps:get(page_size, RegExByName)),
+ ?assertEqual(1, maps:get(page_count, RegExByName)),
+ assert_list([#{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], maps:get(items, RegExByName)),
+
+
+ http_get(Config, "/queues?page=1000", ?BAD_REQUEST),
+ http_get(Config, "/queues?page=-1", ?BAD_REQUEST),
+ http_get(Config, "/queues?page=not_an_integer_value", ?BAD_REQUEST),
+ http_get(Config, "/queues?page=1&page_size=not_an_integer_value", ?BAD_REQUEST),
+ http_get(Config, "/queues?page=1&page_size=501", ?BAD_REQUEST), %% max 500 allowed
+ http_get(Config, "/queues?page=-1&page_size=-2", ?BAD_REQUEST),
+ http_delete(Config, "/queues/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/test1", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/test2_reg", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/reg_test3", {group, '2xx'}),
+ http_delete(Config, "/vhosts/vh1", {group, '2xx'}),
+ passed.
+
+queue_pagination_columns_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, [?CREATED, ?NO_CONTENT]),
+ http_put(Config, "/permissions/vh1/guest", PermArgs, [?CREATED, ?NO_CONTENT]),
+
+ http_get(Config, "/queues/vh1?columns=name&page=1&page_size=2", ?OK),
+ http_put(Config, "/queues/%2F/queue_a", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/queue_b", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/queue_c", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/queue_d", QArgs, {group, '2xx'}),
+ PageOfTwo = http_get(Config, "/queues?columns=name&page=1&page_size=2", ?OK),
+ ?assertEqual(4, maps:get(total_count, PageOfTwo)),
+ ?assertEqual(4, maps:get(filtered_count, PageOfTwo)),
+ ?assertEqual(2, maps:get(item_count, PageOfTwo)),
+ ?assertEqual(1, maps:get(page, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_size, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_count, PageOfTwo)),
+ assert_list([#{name => <<"queue_a">>},
+ #{name => <<"queue_c">>}
+ ], maps:get(items, PageOfTwo)),
+
+ ColumnNameVhost = http_get(Config, "/queues/vh1?columns=name&page=1&page_size=2", ?OK),
+ ?assertEqual(2, maps:get(total_count, ColumnNameVhost)),
+ ?assertEqual(2, maps:get(filtered_count, ColumnNameVhost)),
+ ?assertEqual(2, maps:get(item_count, ColumnNameVhost)),
+ ?assertEqual(1, maps:get(page, ColumnNameVhost)),
+ ?assertEqual(2, maps:get(page_size, ColumnNameVhost)),
+ ?assertEqual(1, maps:get(page_count, ColumnNameVhost)),
+ assert_list([#{name => <<"queue_b">>},
+ #{name => <<"queue_d">>}
+ ], maps:get(items, ColumnNameVhost)),
+
+ ColumnsNameVhost = http_get(Config, "/queues?columns=name,vhost&page=2&page_size=2", ?OK),
+ ?assertEqual(4, maps:get(total_count, ColumnsNameVhost)),
+ ?assertEqual(4, maps:get(filtered_count, ColumnsNameVhost)),
+ ?assertEqual(2, maps:get(item_count, ColumnsNameVhost)),
+ ?assertEqual(2, maps:get(page, ColumnsNameVhost)),
+ ?assertEqual(2, maps:get(page_size, ColumnsNameVhost)),
+ ?assertEqual(2, maps:get(page_count, ColumnsNameVhost)),
+ assert_list([
+ #{name => <<"queue_b">>,
+ vhost => <<"vh1">>},
+ #{name => <<"queue_d">>,
+ vhost => <<"vh1">>}
+ ], maps:get(items, ColumnsNameVhost)),
+
+
+ http_delete(Config, "/queues/%2F/queue_a", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/queue_b", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/queue_c", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/queue_d", {group, '2xx'}),
+ http_delete(Config, "/vhosts/vh1", {group, '2xx'}),
+ passed.
+
+queues_pagination_permissions_test(Config) ->
+ http_put(Config, "/users/non-admin", [{password, <<"non-admin">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/users/admin", [{password, <<"admin">>},
+ {tags, <<"administrator">>}], {group, '2xx'}),
+ Perms = [{configure, <<".*">>},
+ {write, <<".*">>},
+ {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/non-admin", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/%2F/admin", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/admin", Perms, {group, '2xx'}),
+ QArgs = #{},
+ http_put(Config, "/queues/%2F/test0", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/test1", QArgs, "non-admin","non-admin", {group, '2xx'}),
+ FirstPage = http_get(Config, "/queues?page=1", "non-admin", "non-admin", ?OK),
+ ?assertEqual(1, maps:get(total_count, FirstPage)),
+ ?assertEqual(1, maps:get(item_count, FirstPage)),
+ ?assertEqual(1, maps:get(page, FirstPage)),
+ ?assertEqual(100, maps:get(page_size, FirstPage)),
+ ?assertEqual(1, maps:get(page_count, FirstPage)),
+ assert_list([#{name => <<"test1">>, vhost => <<"vh1">>}
+ ], maps:get(items, FirstPage)),
+
+ FirstPageAdm = http_get(Config, "/queues?page=1", "admin", "admin", ?OK),
+ ?assertEqual(2, maps:get(total_count, FirstPageAdm)),
+ ?assertEqual(2, maps:get(item_count, FirstPageAdm)),
+ ?assertEqual(1, maps:get(page, FirstPageAdm)),
+ ?assertEqual(100, maps:get(page_size, FirstPageAdm)),
+ ?assertEqual(1, maps:get(page_count, FirstPageAdm)),
+ assert_list([#{name => <<"test1">>, vhost => <<"vh1">>},
+ #{name => <<"test0">>, vhost => <<"/">>}
+ ], maps:get(items, FirstPageAdm)),
+
+ http_delete(Config, "/queues/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/test1","admin","admin", {group, '2xx'}),
+ http_delete(Config, "/users/admin", {group, '2xx'}),
+ http_delete(Config, "/users/non-admin", {group, '2xx'}),
+ passed.
+
+samples_range_test(Config) ->
+ {Conn, Ch} = open_connection_and_channel(Config),
+
+ %% Channels
+ timer:sleep(2000),
+ [ConnInfo | _] = http_get(Config, "/channels?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/channels?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ ConnDetails = maps:get(connection_details, ConnInfo),
+ ConnName0 = maps:get(name, ConnDetails),
+ ConnName = uri_string:recompose(#{path => binary_to_list(ConnName0)}),
+ ChanName = ConnName ++ uri_string:recompose(#{path => " (1)"}),
+
+ http_get(Config, "/channels/" ++ ChanName ++ "?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/channels/" ++ ChanName ++ "?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ http_get(Config, "/vhosts/%2F/channels?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/vhosts/%2F/channels?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ %% Connections.
+
+ http_get(Config, "/connections?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/connections?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ http_get(Config, "/connections/" ++ ConnName ++ "?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/connections/" ++ ConnName ++ "?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ http_get(Config, "/connections/" ++ ConnName ++ "/channels?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/connections/" ++ ConnName ++ "/channels?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ http_get(Config, "/vhosts/%2F/connections?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/vhosts/%2F/connections?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ amqp_channel:close(Ch),
+ amqp_connection:close(Conn),
+
+ %% Exchanges
+
+ http_get(Config, "/exchanges?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/exchanges?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ http_get(Config, "/exchanges/%2F/amq.direct?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/exchanges/%2F/amq.direct?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ %% Nodes
+ http_get(Config, "/nodes?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/nodes?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ %% Overview
+ http_get(Config, "/overview?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/overview?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ %% Queues
+ http_put(Config, "/queues/%2F/test-001", #{}, {group, '2xx'}),
+ timer:sleep(2000),
+
+ http_get(Config, "/queues/%2F?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/queues/%2F?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+ http_get(Config, "/queues/%2F/test-001?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/queues/%2F/test-001?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ http_delete(Config, "/queues/%2F/test-001", {group, '2xx'}),
+
+ %% Vhosts
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ timer:sleep(2000),
+
+ http_get(Config, "/vhosts?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/vhosts?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+ http_get(Config, "/vhosts/vh1?lengths_age=60&lengths_incr=1", ?OK),
+ http_get(Config, "/vhosts/vh1?lengths_age=6000&lengths_incr=1", ?BAD_REQUEST),
+
+ http_delete(Config, "/vhosts/vh1", {group, '2xx'}),
+
+ passed.
+
+sorting_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh19", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh19/guest", PermArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test0", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh19/test1", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test2", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh19/test3", QArgs, {group, '2xx'}),
+ timer:sleep(2000),
+ assert_list([#{name => <<"test0">>},
+ #{name => <<"test2">>},
+ #{name => <<"test1">>},
+ #{name => <<"test3">>}], http_get(Config, "/queues", ?OK)),
+ assert_list([#{name => <<"test0">>},
+ #{name => <<"test1">>},
+ #{name => <<"test2">>},
+ #{name => <<"test3">>}], http_get(Config, "/queues?sort=name", ?OK)),
+ assert_list([#{name => <<"test0">>},
+ #{name => <<"test2">>},
+ #{name => <<"test1">>},
+ #{name => <<"test3">>}], http_get(Config, "/queues?sort=vhost", ?OK)),
+ assert_list([#{name => <<"test3">>},
+ #{name => <<"test1">>},
+ #{name => <<"test2">>},
+ #{name => <<"test0">>}], http_get(Config, "/queues?sort_reverse=true", ?OK)),
+ assert_list([#{name => <<"test3">>},
+ #{name => <<"test2">>},
+ #{name => <<"test1">>},
+ #{name => <<"test0">>}], http_get(Config, "/queues?sort=name&sort_reverse=true", ?OK)),
+ assert_list([#{name => <<"test3">>},
+ #{name => <<"test1">>},
+ #{name => <<"test2">>},
+ #{name => <<"test0">>}], http_get(Config, "/queues?sort=vhost&sort_reverse=true", ?OK)),
+ %% Rather poor but at least test it doesn't blow up with dots
+ http_get(Config, "/queues?sort=owner_pid_details.name", ?OK),
+ http_delete(Config, "/queues/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/queues/vh19/test1", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/test2", {group, '2xx'}),
+ http_delete(Config, "/queues/vh19/test3", {group, '2xx'}),
+ http_delete(Config, "/vhosts/vh19", {group, '2xx'}),
+ passed.
+
+format_output_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh129", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh129/guest", PermArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test0", QArgs, {group, '2xx'}),
+ timer:sleep(2000),
+ assert_list([#{name => <<"test0">>,
+ consumer_utilisation => null,
+ exclusive_consumer_tag => null,
+ recoverable_slaves => null}], http_get(Config, "/queues", ?OK)),
+ http_delete(Config, "/queues/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/vhosts/vh129", {group, '2xx'}),
+ passed.
+
+columns_test(Config) ->
+ Path = "/queues/%2F/columns.test",
+ TTL = 30000,
+ http_delete(Config, Path, [{group, '2xx'}, 404]),
+ http_put(Config, Path, [{arguments, [{<<"x-message-ttl">>, TTL}]}],
+ {group, '2xx'}),
+ Item = #{arguments => #{'x-message-ttl' => TTL}, name => <<"columns.test">>},
+ timer:sleep(2000),
+ [Item] = http_get(Config, "/queues?columns=arguments.x-message-ttl,name", ?OK),
+ Item = http_get(Config, "/queues/%2F/columns.test?columns=arguments.x-message-ttl,name", ?OK),
+ http_delete(Config, Path, {group, '2xx'}),
+ passed.
+
+get_test(Config) ->
+ %% Real world example...
+ Headers = [{<<"x-forwarding">>, array,
+ [{table,
+ [{<<"uri">>, longstr,
+ <<"amqp://localhost/%2F/upstream">>}]}]}],
+ http_put(Config, "/queues/%2F/myqueue", #{}, {group, '2xx'}),
+ {Conn, Ch} = open_connection_and_channel(Config),
+ #'confirm.select_ok'{} = amqp_channel:call(Ch, #'confirm.select'{}),
+ Publish = fun (Payload) ->
+ amqp_channel:cast(
+ Ch, #'basic.publish'{exchange = <<>>,
+ routing_key = <<"myqueue">>},
+ #amqp_msg{props = #'P_basic'{headers = Headers},
+ payload = Payload}),
+ amqp_channel:wait_for_confirms_or_die(Ch, 5)
+ end,
+ Publish(<<"1aaa">>),
+ Publish(<<"2aaa">>),
+ Publish(<<"3aaa">>),
+ [Msg] = http_post(Config, "/queues/%2F/myqueue/get", [{ackmode, ack_requeue_false},
+ {count, 1},
+ {encoding, auto},
+ {truncate, 1}], ?OK),
+ false = maps:get(redelivered, Msg),
+ <<>> = maps:get(exchange, Msg),
+ <<"myqueue">> = maps:get(routing_key, Msg),
+ <<"1">> = maps:get(payload, Msg),
+ #{'x-forwarding' :=
+ [#{uri := <<"amqp://localhost/%2F/upstream">>}]} =
+ maps:get(headers, maps:get(properties, Msg)),
+
+ [M2, M3] = http_post(Config, "/queues/%2F/myqueue/get", [{ackmode, ack_requeue_true},
+ {count, 5},
+ {encoding, auto}], ?OK),
+ <<"2aaa">> = maps:get(payload, M2),
+ <<"3aaa">> = maps:get(payload, M3),
+ 2 = length(http_post(Config, "/queues/%2F/myqueue/get", [{ackmode, ack_requeue_false},
+ {count, 5},
+ {encoding, auto}], ?OK)),
+ Publish(<<"4aaa">>),
+ Publish(<<"5aaa">>),
+ [M4, M5] = http_post(Config, "/queues/%2F/myqueue/get",
+ [{ackmode, reject_requeue_true},
+ {count, 5},
+ {encoding, auto}], ?OK),
+
+ <<"4aaa">> = maps:get(payload, M4),
+ <<"5aaa">> = maps:get(payload, M5),
+ 2 = length(http_post(Config, "/queues/%2F/myqueue/get",
+ [{ackmode, ack_requeue_false},
+ {count, 5},
+ {encoding, auto}], ?OK)),
+
+ [] = http_post(Config, "/queues/%2F/myqueue/get", [{ackmode, ack_requeue_false},
+ {count, 5},
+ {encoding, auto}], ?OK),
+ http_delete(Config, "/queues/%2F/myqueue", {group, '2xx'}),
+ amqp_channel:close(Ch),
+ close_connection(Conn),
+
+ passed.
+
+get_encoding_test(Config) ->
+ Utf8Text = <<"Loïc was here!"/utf8>>,
+ Utf8Payload = base64:encode(Utf8Text),
+ BinPayload = base64:encode(<<0:64, 16#ff, 16#fd, 0:64>>),
+ Utf8Msg = msg(<<"get_encoding_test">>, #{}, Utf8Payload, <<"base64">>),
+ BinMsg = msg(<<"get_encoding_test">>, #{}, BinPayload, <<"base64">>),
+ http_put(Config, "/queues/%2F/get_encoding_test", #{}, {group, '2xx'}),
+ http_post(Config, "/exchanges/%2F/amq.default/publish", Utf8Msg, ?OK),
+ http_post(Config, "/exchanges/%2F/amq.default/publish", BinMsg, ?OK),
+ timer:sleep(250),
+ [RecvUtf8Msg1, RecvBinMsg1] = http_post(Config, "/queues/%2F/get_encoding_test/get",
+ [{ackmode, ack_requeue_false},
+ {count, 2},
+ {encoding, auto}], ?OK),
+ %% Utf-8 payload must be returned as a utf-8 string when auto encoding is used.
+ ?assertEqual(<<"string">>, maps:get(payload_encoding, RecvUtf8Msg1)),
+ ?assertEqual(Utf8Text, maps:get(payload, RecvUtf8Msg1)),
+ %% Binary payload must be base64-encoded when auto is used.
+ ?assertEqual(<<"base64">>, maps:get(payload_encoding, RecvBinMsg1)),
+ ?assertEqual(BinPayload, maps:get(payload, RecvBinMsg1)),
+ %% Good. Now try forcing the base64 encoding.
+ http_post(Config, "/exchanges/%2F/amq.default/publish", Utf8Msg, ?OK),
+ http_post(Config, "/exchanges/%2F/amq.default/publish", BinMsg, ?OK),
+ [RecvUtf8Msg2, RecvBinMsg2] = http_post(Config, "/queues/%2F/get_encoding_test/get",
+ [{ackmode, ack_requeue_false},
+ {count, 2},
+ {encoding, base64}], ?OK),
+ %% All payloads must be base64-encoded when base64 encoding is used.
+ ?assertEqual(<<"base64">>, maps:get(payload_encoding, RecvUtf8Msg2)),
+ ?assertEqual(Utf8Payload, maps:get(payload, RecvUtf8Msg2)),
+ ?assertEqual(<<"base64">>, maps:get(payload_encoding, RecvBinMsg2)),
+ ?assertEqual(BinPayload, maps:get(payload, RecvBinMsg2)),
+ http_delete(Config, "/queues/%2F/get_encoding_test", {group, '2xx'}),
+ passed.
+
+get_fail_test(Config) ->
+ http_put(Config, "/users/myuser", [{password, <<"password">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/queues/%2F/myqueue", #{}, {group, '2xx'}),
+ http_post(Config, "/queues/%2F/myqueue/get",
+ [{ackmode, ack_requeue_false},
+ {count, 1},
+ {encoding, auto}], "myuser", "password", ?NOT_AUTHORISED),
+ http_delete(Config, "/queues/%2F/myqueue", {group, '2xx'}),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ passed.
+
+
+-define(LARGE_BODY_BYTES, 25000000).
+
+publish_test(Config) ->
+ Headers = #{'x-forwarding' => [#{uri => <<"amqp://localhost/%2F/upstream">>}]},
+ Msg = msg(<<"publish_test">>, Headers, <<"Hello world">>),
+ http_put(Config, "/queues/%2F/publish_test", #{}, {group, '2xx'}),
+ ?assertEqual(#{routed => true},
+ http_post(Config, "/exchanges/%2F/amq.default/publish", Msg, ?OK)),
+ [Msg2] = http_post(Config, "/queues/%2F/publish_test/get", [{ackmode, ack_requeue_false},
+ {count, 1},
+ {encoding, auto}], ?OK),
+ assert_item(Msg, Msg2),
+ http_post(Config, "/exchanges/%2F/amq.default/publish", Msg2, ?OK),
+ [Msg3] = http_post(Config, "/queues/%2F/publish_test/get", [{ackmode, ack_requeue_false},
+ {count, 1},
+ {encoding, auto}], ?OK),
+ assert_item(Msg, Msg3),
+ http_delete(Config, "/queues/%2F/publish_test", {group, '2xx'}),
+ passed.
+
+publish_large_message_test(Config) ->
+ Headers = #{'x-forwarding' => [#{uri => <<"amqp://localhost/%2F/upstream">>}]},
+ Body = binary:copy(<<"a">>, ?LARGE_BODY_BYTES),
+ Msg = msg(<<"publish_accept_json_test">>, Headers, Body),
+ http_put(Config, "/queues/%2F/publish_accept_json_test", #{}, {group, '2xx'}),
+ ?assertEqual(#{routed => true},
+ http_post_accept_json(Config, "/exchanges/%2F/amq.default/publish",
+ Msg, ?OK)),
+
+ [Msg2] = http_post_accept_json(Config, "/queues/%2F/publish_accept_json_test/get",
+ [{ackmode, ack_requeue_false},
+ {count, 1},
+ {encoding, auto}], ?OK),
+ assert_item(Msg, Msg2),
+ http_post_accept_json(Config, "/exchanges/%2F/amq.default/publish", Msg2, ?OK),
+ [Msg3] = http_post_accept_json(Config, "/queues/%2F/publish_accept_json_test/get",
+ [{ackmode, ack_requeue_false},
+ {count, 1},
+ {encoding, auto}], ?OK),
+ assert_item(Msg, Msg3),
+ http_delete(Config, "/queues/%2F/publish_accept_json_test", {group, '2xx'}),
+ passed.
+
+publish_accept_json_test(Config) ->
+ Headers = #{'x-forwarding' => [#{uri => <<"amqp://localhost/%2F/upstream">>}]},
+ Msg = msg(<<"publish_accept_json_test">>, Headers, <<"Hello world">>),
+ http_put(Config, "/queues/%2F/publish_accept_json_test", #{}, {group, '2xx'}),
+ ?assertEqual(#{routed => true},
+ http_post_accept_json(Config, "/exchanges/%2F/amq.default/publish",
+ Msg, ?OK)),
+
+ [Msg2] = http_post_accept_json(Config, "/queues/%2F/publish_accept_json_test/get",
+ [{ackmode, ack_requeue_false},
+ {count, 1},
+ {encoding, auto}], ?OK),
+ assert_item(Msg, Msg2),
+ http_post_accept_json(Config, "/exchanges/%2F/amq.default/publish", Msg2, ?OK),
+ [Msg3] = http_post_accept_json(Config, "/queues/%2F/publish_accept_json_test/get",
+ [{ackmode, ack_requeue_false},
+ {count, 1},
+ {encoding, auto}], ?OK),
+ assert_item(Msg, Msg3),
+ http_delete(Config, "/queues/%2F/publish_accept_json_test", {group, '2xx'}),
+ passed.
+
+publish_fail_test(Config) ->
+ Msg = msg(<<"publish_fail_test">>, [], <<"Hello world">>),
+ http_put(Config, "/queues/%2F/publish_fail_test", #{}, {group, '2xx'}),
+ http_put(Config, "/users/myuser", [{password, <<"password">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_post(Config, "/exchanges/%2F/amq.default/publish", Msg, "myuser", "password",
+ ?NOT_AUTHORISED),
+ Msg2 = [{exchange, <<"">>},
+ {routing_key, <<"publish_fail_test">>},
+ {properties, [{user_id, <<"foo">>}]},
+ {payload, <<"Hello world">>},
+ {payload_encoding, <<"string">>}],
+ http_post(Config, "/exchanges/%2F/amq.default/publish", Msg2, ?BAD_REQUEST),
+ Msg3 = [{exchange, <<"">>},
+ {routing_key, <<"publish_fail_test">>},
+ {properties, []},
+ {payload, [<<"not a string">>]},
+ {payload_encoding, <<"string">>}],
+ http_post(Config, "/exchanges/%2F/amq.default/publish", Msg3, ?BAD_REQUEST),
+ MsgTemplate = [{exchange, <<"">>},
+ {routing_key, <<"publish_fail_test">>},
+ {payload, <<"Hello world">>},
+ {payload_encoding, <<"string">>}],
+ [http_post(Config, "/exchanges/%2F/amq.default/publish",
+ [{properties, [BadProp]} | MsgTemplate], ?BAD_REQUEST)
+ || BadProp <- [{priority, <<"really high">>},
+ {timestamp, <<"recently">>},
+ {expiration, 1234}]],
+ http_delete(Config, "/queues/%2F/publish_fail_test", {group, '2xx'}),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ passed.
+
+publish_base64_test(Config) ->
+ %% "abcd"
+ %% @todo Note that we used to accept [] instead of {struct, []} when we shouldn't have.
+ %% This is a breaking change and probably needs to be documented.
+ Msg = msg(<<"publish_base64_test">>, #{}, <<"YWJjZA==">>, <<"base64">>),
+ BadMsg1 = msg(<<"publish_base64_test">>, #{}, <<"flibble">>, <<"base64">>),
+ BadMsg2 = msg(<<"publish_base64_test">>, #{}, <<"YWJjZA==">>, <<"base99">>),
+ http_put(Config, "/queues/%2F/publish_base64_test", #{}, {group, '2xx'}),
+ http_post(Config, "/exchanges/%2F/amq.default/publish", Msg, ?OK),
+ http_post(Config, "/exchanges/%2F/amq.default/publish", BadMsg1, ?BAD_REQUEST),
+ http_post(Config, "/exchanges/%2F/amq.default/publish", BadMsg2, ?BAD_REQUEST),
+ timer:sleep(250),
+ [Msg2] = http_post(Config, "/queues/%2F/publish_base64_test/get", [{ackmode, ack_requeue_false},
+ {count, 1},
+ {encoding, auto}], ?OK),
+ ?assertEqual(<<"abcd">>, maps:get(payload, Msg2)),
+ http_delete(Config, "/queues/%2F/publish_base64_test", {group, '2xx'}),
+ passed.
+
+publish_unrouted_test(Config) ->
+ Msg = msg(<<"hmmm">>, #{}, <<"Hello world">>),
+ ?assertEqual(#{routed => false},
+ http_post(Config, "/exchanges/%2F/amq.default/publish", Msg, ?OK)).
+
+if_empty_unused_test(Config) ->
+ http_put(Config, "/exchanges/%2F/test", #{}, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test", #{}, {group, '2xx'}),
+ http_post(Config, "/bindings/%2F/e/test/q/test", #{}, {group, '2xx'}),
+ http_post(Config, "/exchanges/%2F/amq.default/publish",
+ msg(<<"test">>, #{}, <<"Hello world">>), ?OK),
+ http_delete(Config, "/queues/%2F/test?if-empty=true", ?BAD_REQUEST),
+ http_delete(Config, "/exchanges/%2F/test?if-unused=true", ?BAD_REQUEST),
+ http_delete(Config, "/queues/%2F/test/contents", {group, '2xx'}),
+
+ {Conn, _ConnPath, _ChPath, _ConnChPath} = get_conn(Config, "guest", "guest"),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ amqp_channel:subscribe(Ch, #'basic.consume'{queue = <<"test">> }, self()),
+ http_delete(Config, "/queues/%2F/test?if-unused=true", ?BAD_REQUEST),
+ amqp_connection:close(Conn),
+
+ http_delete(Config, "/queues/%2F/test?if-empty=true", {group, '2xx'}),
+ http_delete(Config, "/exchanges/%2F/test?if-unused=true", {group, '2xx'}),
+ passed.
+
+parameters_test(Config) ->
+ register_parameters_and_policy_validator(Config),
+
+ http_put(Config, "/parameters/test/%2F/good", [{value, <<"ignore">>}], {group, '2xx'}),
+ http_put(Config, "/parameters/test/%2F/maybe", [{value, <<"good">>}], {group, '2xx'}),
+ http_put(Config, "/parameters/test/%2F/maybe", [{value, <<"bad">>}], ?BAD_REQUEST),
+ http_put(Config, "/parameters/test/%2F/bad", [{value, <<"good">>}], ?BAD_REQUEST),
+ http_put(Config, "/parameters/test/um/good", [{value, <<"ignore">>}], ?NOT_FOUND),
+
+ Good = #{vhost => <<"/">>,
+ component => <<"test">>,
+ name => <<"good">>,
+ value => <<"ignore">>},
+ Maybe = #{vhost => <<"/">>,
+ component => <<"test">>,
+ name => <<"maybe">>,
+ value => <<"good">>},
+ List = [Good, Maybe],
+
+ assert_list(List, http_get(Config, "/parameters")),
+ assert_list(List, http_get(Config, "/parameters/test")),
+ assert_list(List, http_get(Config, "/parameters/test/%2F")),
+ assert_list([], http_get(Config, "/parameters/oops")),
+ http_get(Config, "/parameters/test/oops", ?NOT_FOUND),
+
+ assert_item(Good, http_get(Config, "/parameters/test/%2F/good", ?OK)),
+ assert_item(Maybe, http_get(Config, "/parameters/test/%2F/maybe", ?OK)),
+
+ http_delete(Config, "/parameters/test/%2F/good", {group, '2xx'}),
+ http_delete(Config, "/parameters/test/%2F/maybe", {group, '2xx'}),
+ http_delete(Config, "/parameters/test/%2F/bad", ?NOT_FOUND),
+
+ 0 = length(http_get(Config, "/parameters")),
+ 0 = length(http_get(Config, "/parameters/test")),
+ 0 = length(http_get(Config, "/parameters/test/%2F")),
+ unregister_parameters_and_policy_validator(Config),
+ passed.
+
+global_parameters_test(Config) ->
+ InitialParameters = http_get(Config, "/global-parameters"),
+ http_put(Config, "/global-parameters/good", [{value, [{a, <<"b">>}]}], {group, '2xx'}),
+ http_put(Config, "/global-parameters/maybe", [{value,[{c, <<"d">>}]}], {group, '2xx'}),
+
+ Good = #{name => <<"good">>,
+ value => #{a => <<"b">>}},
+ Maybe = #{name => <<"maybe">>,
+ value => #{c => <<"d">>}},
+ List = InitialParameters ++ [Good, Maybe],
+
+ assert_list(List, http_get(Config, "/global-parameters")),
+ http_get(Config, "/global-parameters/oops", ?NOT_FOUND),
+
+ assert_item(Good, http_get(Config, "/global-parameters/good", ?OK)),
+ assert_item(Maybe, http_get(Config, "/global-parameters/maybe", ?OK)),
+
+ http_delete(Config, "/global-parameters/good", {group, '2xx'}),
+ http_delete(Config, "/global-parameters/maybe", {group, '2xx'}),
+ http_delete(Config, "/global-parameters/bad", ?NOT_FOUND),
+
+ InitialCount = length(InitialParameters),
+ InitialCount = length(http_get(Config, "/global-parameters")),
+ passed.
+
+policy_test(Config) ->
+ register_parameters_and_policy_validator(Config),
+ PolicyPos = #{vhost => <<"/">>,
+ name => <<"policy_pos">>,
+ pattern => <<".*">>,
+ definition => #{testpos => [1,2,3]},
+ priority => 10},
+ PolicyEven = #{vhost => <<"/">>,
+ name => <<"policy_even">>,
+ pattern => <<".*">>,
+ definition => #{testeven => [1,2,3,4]},
+ priority => 10},
+ http_put(Config,
+ "/policies/%2F/policy_pos",
+ maps:remove(key, PolicyPos),
+ {group, '2xx'}),
+ http_put(Config,
+ "/policies/%2F/policy_even",
+ maps:remove(key, PolicyEven),
+ {group, '2xx'}),
+ assert_item(PolicyPos, http_get(Config, "/policies/%2F/policy_pos", ?OK)),
+ assert_item(PolicyEven, http_get(Config, "/policies/%2F/policy_even", ?OK)),
+ List = [PolicyPos, PolicyEven],
+ assert_list(List, http_get(Config, "/policies", ?OK)),
+ assert_list(List, http_get(Config, "/policies/%2F", ?OK)),
+
+ http_delete(Config, "/policies/%2F/policy_pos", {group, '2xx'}),
+ http_delete(Config, "/policies/%2F/policy_even", {group, '2xx'}),
+ 0 = length(http_get(Config, "/policies")),
+ 0 = length(http_get(Config, "/policies/%2F")),
+ unregister_parameters_and_policy_validator(Config),
+ passed.
+
+policy_permissions_test(Config) ->
+ register_parameters_and_policy_validator(Config),
+
+ http_put(Config, "/users/admin", [{password, <<"admin">>},
+ {tags, <<"administrator">>}], {group, '2xx'}),
+ http_put(Config, "/users/mon", [{password, <<"mon">>},
+ {tags, <<"monitoring">>}], {group, '2xx'}),
+ http_put(Config, "/users/policy", [{password, <<"policy">>},
+ {tags, <<"policymaker">>}], {group, '2xx'}),
+ http_put(Config, "/users/mgmt", [{password, <<"mgmt">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ Perms = [{configure, <<".*">>},
+ {write, <<".*">>},
+ {read, <<".*">>}],
+ http_put(Config, "/vhosts/v", none, {group, '2xx'}),
+ http_put(Config, "/permissions/v/admin", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/v/mon", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/v/policy", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/v/mgmt", Perms, {group, '2xx'}),
+
+ Policy = [{pattern, <<".*">>},
+ {definition, [{<<"ha-mode">>, <<"all">>}]}],
+ Param = [{value, <<"">>}],
+
+ http_put(Config, "/policies/%2F/HA", Policy, {group, '2xx'}),
+ http_put(Config, "/parameters/test/%2F/good", Param, {group, '2xx'}),
+
+ Pos = fun (U) ->
+ http_put(Config, "/policies/v/HA", Policy, U, U, {group, '2xx'}),
+ http_put(Config, "/parameters/test/v/good", Param, U, U, {group, '2xx'}),
+ http_get(Config, "/policies", U, U, {group, '2xx'}),
+ http_get(Config, "/parameters/test", U, U, {group, '2xx'}),
+ http_get(Config, "/parameters", U, U, {group, '2xx'}),
+ http_get(Config, "/policies/v", U, U, {group, '2xx'}),
+ http_get(Config, "/parameters/test/v", U, U, {group, '2xx'}),
+ http_get(Config, "/policies/v/HA", U, U, {group, '2xx'}),
+ http_get(Config, "/parameters/test/v/good", U, U, {group, '2xx'})
+ end,
+ Neg = fun (U) ->
+ http_put(Config, "/policies/v/HA", Policy, U, U, ?NOT_AUTHORISED),
+ http_put(Config,
+ "/parameters/test/v/good", Param, U, U, ?NOT_AUTHORISED),
+ http_put(Config,
+ "/parameters/test/v/admin", Param, U, U, ?NOT_AUTHORISED),
+ %% Policies are read-only for management and monitoring.
+ http_get(Config, "/policies", U, U, ?OK),
+ http_get(Config, "/policies/v", U, U, ?OK),
+ http_get(Config, "/parameters", U, U, ?NOT_AUTHORISED),
+ http_get(Config, "/parameters/test", U, U, ?NOT_AUTHORISED),
+ http_get(Config, "/parameters/test/v", U, U, ?NOT_AUTHORISED),
+ http_get(Config, "/policies/v/HA", U, U, ?NOT_AUTHORISED),
+ http_get(Config, "/parameters/test/v/good", U, U, ?NOT_AUTHORISED)
+ end,
+ AlwaysNeg =
+ fun (U) ->
+ http_put(Config, "/policies/%2F/HA", Policy, U, U, ?NOT_AUTHORISED),
+ http_put(Config, "/parameters/test/%2F/good", Param, U, U, ?NOT_AUTHORISED),
+ http_get(Config, "/policies/%2F/HA", U, U, ?NOT_AUTHORISED),
+ http_get(Config, "/parameters/test/%2F/good", U, U, ?NOT_AUTHORISED)
+ end,
+ AdminPos =
+ fun (U) ->
+ http_put(Config, "/policies/%2F/HA", Policy, U, U, {group, '2xx'}),
+ http_put(Config, "/parameters/test/%2F/good", Param, U, U, {group, '2xx'}),
+ http_get(Config, "/policies/%2F/HA", U, U, {group, '2xx'}),
+ http_get(Config, "/parameters/test/%2F/good", U, U, {group, '2xx'})
+ end,
+
+ [Neg(U) || U <- ["mon", "mgmt"]],
+ [Pos(U) || U <- ["admin", "policy"]],
+ [AlwaysNeg(U) || U <- ["mon", "mgmt", "policy"]],
+ [AdminPos(U) || U <- ["admin"]],
+
+ %% This one is deliberately different between admin and policymaker.
+ http_put(Config, "/parameters/test/v/admin", Param, "admin", "admin", {group, '2xx'}),
+ http_put(Config, "/parameters/test/v/admin", Param, "policy", "policy",
+ ?BAD_REQUEST),
+
+ http_delete(Config, "/vhosts/v", {group, '2xx'}),
+ http_delete(Config, "/users/admin", {group, '2xx'}),
+ http_delete(Config, "/users/mon", {group, '2xx'}),
+ http_delete(Config, "/users/policy", {group, '2xx'}),
+ http_delete(Config, "/users/mgmt", {group, '2xx'}),
+ http_delete(Config, "/policies/%2F/HA", {group, '2xx'}),
+
+ unregister_parameters_and_policy_validator(Config),
+ passed.
+
+issue67_test(Config)->
+ {ok, {{_, 401, _}, Headers, _}} = req(Config, get, "/queues",
+ [auth_header("user_no_access", "password_no_access")]),
+ ?assertEqual("application/json",
+ proplists:get_value("content-type",Headers)),
+ passed.
+
+extensions_test(Config) ->
+ [#{javascript := <<"dispatcher.js">>}] = http_get(Config, "/extensions", ?OK),
+ passed.
+
+cors_test(Config) ->
+ %% With CORS disabled. No header should be received.
+ R = req(Config, get, "/overview", [auth_header("guest", "guest")]),
+ io:format("CORS test R: ~p~n", [R]),
+ {ok, {_, HdNoCORS, _}} = R,
+ io:format("CORS test HdNoCORS: ~p~n", [HdNoCORS]),
+ false = lists:keymember("access-control-allow-origin", 1, HdNoCORS),
+ %% The Vary header should include "Origin" regardless of CORS configuration.
+ {_, "accept, accept-encoding, origin"} = lists:keyfind("vary", 1, HdNoCORS),
+ %% Enable CORS.
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_management, cors_allow_origins, ["https://rabbitmq.com"]]),
+ %% We should only receive allow-origin and allow-credentials from GET.
+ {ok, {_, HdGetCORS, _}} = req(Config, get, "/overview",
+ [{"origin", "https://rabbitmq.com"}, auth_header("guest", "guest")]),
+ true = lists:keymember("access-control-allow-origin", 1, HdGetCORS),
+ true = lists:keymember("access-control-allow-credentials", 1, HdGetCORS),
+ false = lists:keymember("access-control-expose-headers", 1, HdGetCORS),
+ false = lists:keymember("access-control-max-age", 1, HdGetCORS),
+ false = lists:keymember("access-control-allow-methods", 1, HdGetCORS),
+ false = lists:keymember("access-control-allow-headers", 1, HdGetCORS),
+ %% We should receive allow-origin, allow-credentials and allow-methods from OPTIONS.
+ {ok, {{_, 200, _}, HdOptionsCORS, _}} = req(Config, options, "/overview",
+ [{"origin", "https://rabbitmq.com"}]),
+ true = lists:keymember("access-control-allow-origin", 1, HdOptionsCORS),
+ true = lists:keymember("access-control-allow-credentials", 1, HdOptionsCORS),
+ false = lists:keymember("access-control-expose-headers", 1, HdOptionsCORS),
+ true = lists:keymember("access-control-max-age", 1, HdOptionsCORS),
+ true = lists:keymember("access-control-allow-methods", 1, HdOptionsCORS),
+ false = lists:keymember("access-control-allow-headers", 1, HdOptionsCORS),
+ %% We should receive allow-headers when request-headers is sent.
+ {ok, {_, HdAllowHeadersCORS, _}} = req(Config, options, "/overview",
+ [{"origin", "https://rabbitmq.com"},
+ auth_header("guest", "guest"),
+ {"access-control-request-headers", "x-piggy-bank"}]),
+ {_, "x-piggy-bank"} = lists:keyfind("access-control-allow-headers", 1, HdAllowHeadersCORS),
+ %% Disable preflight request caching.
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_management, cors_max_age, undefined]),
+ %% We shouldn't receive max-age anymore.
+ {ok, {_, HdNoMaxAgeCORS, _}} = req(Config, options, "/overview",
+ [{"origin", "https://rabbitmq.com"}, auth_header("guest", "guest")]),
+ false = lists:keymember("access-control-max-age", 1, HdNoMaxAgeCORS),
+
+ %% Check OPTIONS method in all paths
+ check_cors_all_endpoints(Config),
+ %% Disable CORS again.
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_management, cors_allow_origins, []]),
+ passed.
+
+check_cors_all_endpoints(Config) ->
+ Endpoints = get_all_http_endpoints(),
+
+ [begin
+ ct:pal("Options for ~p~n", [EP]),
+ {ok, {{_, 200, _}, _, _}} = req(Config, options, EP, [{"origin", "https://rabbitmq.com"}])
+ end
+ || EP <- Endpoints].
+
+get_all_http_endpoints() ->
+ [ Path || {Path, _, _} <- rabbit_mgmt_dispatcher:dispatcher() ].
+
+vhost_limits_list_test(Config) ->
+ [] = http_get(Config, "/vhost-limits", ?OK),
+
+ http_get(Config, "/vhost-limits/limit_test_vhost_1", ?NOT_FOUND),
+ rabbit_ct_broker_helpers:add_vhost(Config, <<"limit_test_vhost_1">>),
+
+ [] = http_get(Config, "/vhost-limits/limit_test_vhost_1", ?OK),
+ http_get(Config, "/vhost-limits/limit_test_vhost_2", ?NOT_FOUND),
+
+ rabbit_ct_broker_helpers:add_vhost(Config, <<"limit_test_vhost_2">>),
+
+ [] = http_get(Config, "/vhost-limits/limit_test_vhost_2", ?OK),
+
+ Limits1 = [#{vhost => <<"limit_test_vhost_1">>,
+ value => #{'max-connections' => 100, 'max-queues' => 100}}],
+ Limits2 = [#{vhost => <<"limit_test_vhost_2">>,
+ value => #{'max-connections' => 200}}],
+
+ Expected = Limits1 ++ Limits2,
+
+ lists:map(
+ fun(#{vhost := VHost, value := Val}) ->
+ Param = [ {atom_to_binary(K, utf8),V} || {K,V} <- maps:to_list(Val) ],
+ ok = rabbit_ct_broker_helpers:set_parameter(Config, 0, VHost, <<"vhost-limits">>, <<"limits">>, Param)
+ end,
+ Expected),
+
+ Expected = http_get(Config, "/vhost-limits", ?OK),
+ Limits1 = http_get(Config, "/vhost-limits/limit_test_vhost_1", ?OK),
+ Limits2 = http_get(Config, "/vhost-limits/limit_test_vhost_2", ?OK),
+
+ NoVhostUser = <<"no_vhost_user">>,
+ rabbit_ct_broker_helpers:add_user(Config, NoVhostUser),
+ rabbit_ct_broker_helpers:set_user_tags(Config, 0, NoVhostUser, [management]),
+ [] = http_get(Config, "/vhost-limits", NoVhostUser, NoVhostUser, ?OK),
+ http_get(Config, "/vhost-limits/limit_test_vhost_1", NoVhostUser, NoVhostUser, ?NOT_AUTHORISED),
+ http_get(Config, "/vhost-limits/limit_test_vhost_2", NoVhostUser, NoVhostUser, ?NOT_AUTHORISED),
+
+ Vhost1User = <<"limit_test_vhost_1_user">>,
+ rabbit_ct_broker_helpers:add_user(Config, Vhost1User),
+ rabbit_ct_broker_helpers:set_user_tags(Config, 0, Vhost1User, [management]),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, Vhost1User, <<"limit_test_vhost_1">>),
+ Limits1 = http_get(Config, "/vhost-limits", Vhost1User, Vhost1User, ?OK),
+ Limits1 = http_get(Config, "/vhost-limits/limit_test_vhost_1", Vhost1User, Vhost1User, ?OK),
+ http_get(Config, "/vhost-limits/limit_test_vhost_2", Vhost1User, Vhost1User, ?NOT_AUTHORISED),
+
+ Vhost2User = <<"limit_test_vhost_2_user">>,
+ rabbit_ct_broker_helpers:add_user(Config, Vhost2User),
+ rabbit_ct_broker_helpers:set_user_tags(Config, 0, Vhost2User, [management]),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, Vhost2User, <<"limit_test_vhost_2">>),
+ Limits2 = http_get(Config, "/vhost-limits", Vhost2User, Vhost2User, ?OK),
+ http_get(Config, "/vhost-limits/limit_test_vhost_1", Vhost2User, Vhost2User, ?NOT_AUTHORISED),
+ Limits2 = http_get(Config, "/vhost-limits/limit_test_vhost_2", Vhost2User, Vhost2User, ?OK).
+
+vhost_limit_set_test(Config) ->
+ [] = http_get(Config, "/vhost-limits", ?OK),
+ rabbit_ct_broker_helpers:add_vhost(Config, <<"limit_test_vhost_1">>),
+ [] = http_get(Config, "/vhost-limits/limit_test_vhost_1", ?OK),
+
+ %% Set a limit
+ http_put(Config, "/vhost-limits/limit_test_vhost_1/max-queues", [{value, 100}], ?NO_CONTENT),
+
+
+ Limits_Queues = [#{vhost => <<"limit_test_vhost_1">>,
+ value => #{'max-queues' => 100}}],
+
+ Limits_Queues = http_get(Config, "/vhost-limits/limit_test_vhost_1", ?OK),
+
+ %% Set another limit
+ http_put(Config, "/vhost-limits/limit_test_vhost_1/max-connections", [{value, 200}], ?NO_CONTENT),
+
+ Limits_Queues_Connections = [#{vhost => <<"limit_test_vhost_1">>,
+ value => #{'max-connections' => 200, 'max-queues' => 100}}],
+
+ Limits_Queues_Connections = http_get(Config, "/vhost-limits/limit_test_vhost_1", ?OK),
+
+ Limits1 = [#{vhost => <<"limit_test_vhost_1">>,
+ value => #{'max-connections' => 200, 'max-queues' => 100}}],
+ Limits1 = http_get(Config, "/vhost-limits", ?OK),
+
+ %% Update a limit
+ http_put(Config, "/vhost-limits/limit_test_vhost_1/max-connections", [{value, 1000}], ?NO_CONTENT),
+ Limits2 = [#{vhost => <<"limit_test_vhost_1">>,
+ value => #{'max-connections' => 1000, 'max-queues' => 100}}],
+ Limits2 = http_get(Config, "/vhost-limits/limit_test_vhost_1", ?OK),
+
+
+ Vhost1User = <<"limit_test_vhost_1_user">>,
+ rabbit_ct_broker_helpers:add_user(Config, Vhost1User),
+ rabbit_ct_broker_helpers:set_user_tags(Config, 0, Vhost1User, [management]),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, Vhost1User, <<"limit_test_vhost_1">>),
+
+ Limits3 = [#{vhost => <<"limit_test_vhost_1">>,
+ value => #{'max-connections' => 1000,
+ 'max-queues' => 100}}],
+ Limits3 = http_get(Config, "/vhost-limits/limit_test_vhost_1", Vhost1User, Vhost1User, ?OK),
+
+ %% Only admin can update limits
+ http_put(Config, "/vhost-limits/limit_test_vhost_1/max-connections", [{value, 300}], ?NO_CONTENT),
+
+ %% Clear a limit
+ http_delete(Config, "/vhost-limits/limit_test_vhost_1/max-connections", ?NO_CONTENT),
+ Limits4 = [#{vhost => <<"limit_test_vhost_1">>,
+ value => #{'max-queues' => 100}}],
+ Limits4 = http_get(Config, "/vhost-limits/limit_test_vhost_1", ?OK),
+
+ %% Only admin can clear limits
+ http_delete(Config, "/vhost-limits/limit_test_vhost_1/max-queues", Vhost1User, Vhost1User, ?NOT_AUTHORISED),
+
+ %% Unknown limit error
+ http_put(Config, "/vhost-limits/limit_test_vhost_1/max-channels", [{value, 200}], ?BAD_REQUEST).
+
+user_limits_list_test(Config) ->
+ ?assertEqual([], http_get(Config, "/user-limits", ?OK)),
+
+ Vhost1 = <<"limit_test_vhost_1">>,
+ rabbit_ct_broker_helpers:add_vhost(Config, <<"limit_test_vhost_1">>),
+
+ http_get(Config, "/user-limits/limit_test_user_1", ?NOT_FOUND),
+
+ User1 = <<"limit_test_user_1">>,
+ rabbit_ct_broker_helpers:add_user(Config, User1),
+ rabbit_ct_broker_helpers:set_user_tags(Config, 0, User1, [management]),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, User1, Vhost1),
+
+ ?assertEqual([], http_get(Config, "/user-limits/limit_test_user_1", ?OK)),
+ http_get(Config, "/user-limits/limit_test_user_2", ?NOT_FOUND),
+
+ User2 = <<"limit_test_user_2">>,
+ rabbit_ct_broker_helpers:add_user(Config, User2),
+ rabbit_ct_broker_helpers:set_user_tags(Config, 0, User2, [management]),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, User2, Vhost1),
+
+ ?assertEqual([], http_get(Config, "/user-limits/limit_test_user_2", ?OK)),
+
+ Limits1 = [
+ #{
+ user => User1,
+ value => #{
+ 'max-connections' => 100,
+ 'max-channels' => 100
+ }
+ }],
+ Limits2 = [
+ #{
+ user => User2,
+ value => #{
+ 'max-connections' => 200
+ }
+ }],
+
+ Expected = Limits1 ++ Limits2,
+
+ lists:map(
+ fun(#{user := Username, value := Val}) ->
+ rabbit_ct_broker_helpers:set_user_limits(Config, 0, Username, Val)
+ end,
+ Expected),
+
+ rabbit_ct_helpers:await_condition(
+ fun() ->
+ Expected =:= http_get(Config, "/user-limits", ?OK)
+ end),
+ Limits1 = http_get(Config, "/user-limits/limit_test_user_1", ?OK),
+ Limits2 = http_get(Config, "/user-limits/limit_test_user_2", ?OK),
+
+ %% Clear limits and assert
+ rabbit_ct_broker_helpers:clear_user_limits(Config, 0, User1,
+ <<"max-connections">>),
+
+ Limits3 = [#{user => User1, value => #{'max-channels' => 100}}],
+ ?assertEqual(Limits3, http_get(Config, "/user-limits/limit_test_user_1", ?OK)),
+
+ rabbit_ct_broker_helpers:clear_user_limits(Config, 0, User1,
+ <<"max-channels">>),
+ ?assertEqual([], http_get(Config, "/user-limits/limit_test_user_1", ?OK)),
+
+ rabbit_ct_broker_helpers:clear_user_limits(Config, 0, <<"limit_test_user_2">>,
+ <<"all">>),
+ ?assertEqual([], http_get(Config, "/user-limits/limit_test_user_2", ?OK)),
+
+ %% Limit user with no vhost
+ NoVhostUser = <<"no_vhost_user">>,
+ rabbit_ct_broker_helpers:add_user(Config, NoVhostUser),
+ rabbit_ct_broker_helpers:set_user_tags(Config, 0, NoVhostUser, [management]),
+
+ Limits4 = #{
+ user => NoVhostUser,
+ value => #{
+ 'max-connections' => 150,
+ 'max-channels' => 150
+ }
+ },
+ rabbit_ct_broker_helpers:set_user_limits(Config, 0, NoVhostUser, maps:get(value, Limits4)),
+
+ ?assertEqual([Limits4], http_get(Config, "/user-limits/no_vhost_user", ?OK)).
+
+user_limit_set_test(Config) ->
+ ?assertEqual([], http_get(Config, "/user-limits", ?OK)),
+
+ User1 = <<"limit_test_user_1">>,
+ rabbit_ct_broker_helpers:add_user(Config, User1),
+
+ ?assertEqual([], http_get(Config, "/user-limits/limit_test_user_1", ?OK)),
+
+ %% Set a user limit
+ http_put(Config, "/user-limits/limit_test_user_1/max-channels", [{value, 100}], ?NO_CONTENT),
+
+ MaxChannelsLimit = [#{user => User1, value => #{'max-channels' => 100}}],
+ ?assertEqual(MaxChannelsLimit, http_get(Config, "/user-limits/limit_test_user_1", ?OK)),
+
+ %% Set another user limit
+ http_put(Config, "/user-limits/limit_test_user_1/max-connections", [{value, 200}], ?NO_CONTENT),
+
+ MaxConnectionsAndChannelsLimit = [
+ #{
+ user => User1,
+ value => #{
+ 'max-connections' => 200,
+ 'max-channels' => 100
+ }
+ }
+ ],
+ ?assertEqual(MaxConnectionsAndChannelsLimit, http_get(Config, "/user-limits/limit_test_user_1", ?OK)),
+
+ Limits1 = [
+ #{
+ user => User1,
+ value => #{
+ 'max-connections' => 200,
+ 'max-channels' => 100
+ }
+ }],
+ ?assertEqual(Limits1, http_get(Config, "/user-limits", ?OK)),
+
+ %% Update a user limit
+ http_put(Config, "/user-limits/limit_test_user_1/max-connections", [{value, 1000}], ?NO_CONTENT),
+ Limits2 = [
+ #{
+ user => User1,
+ value => #{
+ 'max-connections' => 1000,
+ 'max-channels' => 100
+ }
+ }],
+ ?assertEqual(Limits2, http_get(Config, "/user-limits/limit_test_user_1", ?OK)),
+
+ Vhost1 = <<"limit_test_vhost_1">>,
+ rabbit_ct_broker_helpers:add_vhost(Config, Vhost1),
+
+ Vhost1User = <<"limit_test_vhost_1_user">>,
+ rabbit_ct_broker_helpers:add_user(Config, Vhost1User),
+ rabbit_ct_broker_helpers:set_user_tags(Config, 0, Vhost1User, [management]),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, Vhost1User, Vhost1),
+
+ Limits3 = [
+ #{
+ user => User1,
+ value => #{
+ 'max-connections' => 1000,
+ 'max-channels' => 100
+ }
+ }],
+ ?assertEqual(Limits3, http_get(Config, "/user-limits/limit_test_user_1", Vhost1User, Vhost1User, ?OK)),
+
+ %% Clear a limit
+ http_delete(Config, "/user-limits/limit_test_user_1/max-connections", ?NO_CONTENT),
+ Limits4 = [#{user => User1, value => #{'max-channels' => 100}}],
+ ?assertEqual(Limits4, http_get(Config, "/user-limits/limit_test_user_1", ?OK)),
+
+ %% Only admin can clear limits
+ http_delete(Config, "/user-limits/limit_test_user_1/max-channels", Vhost1User, Vhost1User, ?NOT_AUTHORISED),
+
+ %% Unknown limit error
+ http_put(Config, "/user-limits/limit_test_user_1/max-unknown", [{value, 200}], ?BAD_REQUEST).
+
+rates_test(Config) ->
+ http_put(Config, "/queues/%2F/myqueue", none, {group, '2xx'}),
+ {Conn, Ch} = open_connection_and_channel(Config),
+ Pid = spawn_link(fun() -> publish(Ch) end),
+
+ Condition = fun() ->
+ Overview = http_get(Config, "/overview"),
+ MsgStats = maps:get(message_stats, Overview),
+ QueueTotals = maps:get(queue_totals, Overview),
+
+ maps:get(messages_ready, QueueTotals) > 0 andalso
+ maps:get(messages, QueueTotals) > 0 andalso
+ maps:get(publish, MsgStats) > 0 andalso
+ maps:get(rate, maps:get(publish_details, MsgStats)) > 0 andalso
+ maps:get(rate, maps:get(messages_ready_details, QueueTotals)) > 0 andalso
+ maps:get(rate, maps:get(messages_details, QueueTotals)) > 0
+ end,
+ rabbit_ct_helpers:await_condition(Condition, 60000),
+ Pid ! stop_publish,
+ close_channel(Ch),
+ close_connection(Conn),
+ http_delete(Config, "/queues/%2F/myqueue", ?NO_CONTENT),
+ passed.
+
+cli_redirect_test(Config) ->
+ assert_permanent_redirect(Config, "cli", "/cli/index.html"),
+ passed.
+
+api_redirect_test(Config) ->
+ assert_permanent_redirect(Config, "api", "/api/index.html"),
+ passed.
+
+stats_redirect_test(Config) ->
+ assert_permanent_redirect(Config, "doc/stats.html", "/api/index.html"),
+ passed.
+
+oauth_test(Config) ->
+ Map1 = http_get(Config, "/auth", ?OK),
+ %% Defaults
+ ?assertEqual(false, maps:get(enable_uaa, Map1)),
+ ?assertEqual(<<>>, maps:get(uaa_client_id, Map1)),
+ ?assertEqual(<<>>, maps:get(uaa_location, Map1)),
+ %% Misconfiguration
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, enable_uaa, true]),
+ Map2 = http_get(Config, "/auth", ?OK),
+ ?assertEqual(false, maps:get(enable_uaa, Map2)),
+ ?assertEqual(<<>>, maps:get(uaa_client_id, Map2)),
+ ?assertEqual(<<>>, maps:get(uaa_location, Map2)),
+ %% Valid config
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, uaa_client_id, "rabbit_user"]),
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, uaa_location, "http://localhost:8080/uaa"]),
+ Map3 = http_get(Config, "/auth", ?OK),
+ ?assertEqual(true, maps:get(enable_uaa, Map3)),
+ ?assertEqual(<<"rabbit_user">>, maps:get(uaa_client_id, Map3)),
+ ?assertEqual(<<"http://localhost:8080/uaa">>, maps:get(uaa_location, Map3)),
+ %% cleanup
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
+ [rabbitmq_management, enable_uaa]).
+
+login_test(Config) ->
+ http_put(Config, "/users/myuser", [{password, <<"myuser">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ %% Let's do a post without any other form of authorization
+ {ok, {{_, CodeAct, _}, Headers, _}} =
+ req(Config, 0, post, "/login",
+ [{"content-type", "application/x-www-form-urlencoded"}],
+ <<"username=myuser&password=myuser">>),
+ ?assertEqual(200, CodeAct),
+
+ %% Extract the authorization header
+ [Cookie, _Version] = binary:split(list_to_binary(proplists:get_value("set-cookie", Headers)),
+ <<";">>, [global]),
+ [_, Auth] = binary:split(Cookie, <<"=">>, []),
+
+ %% Request the overview with the auth obtained
+ {ok, {{_, CodeAct1, _}, _, _}} =
+ req(Config, get, "/overview", [{"Authorization", "Basic " ++ binary_to_list(Auth)}]),
+ ?assertEqual(200, CodeAct1),
+
+ %% Let's request a login with an unknown user
+ {ok, {{_, CodeAct2, _}, Headers2, _}} =
+ req(Config, 0, post, "/login",
+ [{"content-type", "application/x-www-form-urlencoded"}],
+ <<"username=misteryusernumber1&password=myuser">>),
+ ?assertEqual(401, CodeAct2),
+ ?assert(not proplists:is_defined("set-cookie", Headers2)),
+
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ passed.
+
+csp_headers_test(Config) ->
+ AuthHeader = auth_header("guest", "guest"),
+ Headers = [{"origin", "https://rabbitmq.com"}, AuthHeader],
+ {ok, {_, HdGetCsp0, _}} = req(Config, get, "/whoami", Headers),
+ ?assert(lists:keymember("content-security-policy", 1, HdGetCsp0)),
+ {ok, {_, HdGetCsp1, _}} = req(Config, get_static, "/index.html", Headers),
+ ?assert(lists:keymember("content-security-policy", 1, HdGetCsp1)).
+
+disable_basic_auth_test(Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, disable_basic_auth, true]),
+ http_get(Config, "/overview", ?NOT_AUTHORISED),
+
+ %% Ensure that a request without auth header does not return a basic auth prompt
+ OverviewResponseHeaders = http_get_no_auth(Config, "/overview", ?NOT_AUTHORISED),
+ ?assertEqual(false, lists:keymember("www-authenticate", 1, OverviewResponseHeaders)),
+
+ http_get(Config, "/nodes", ?NOT_AUTHORISED),
+ http_get(Config, "/vhosts", ?NOT_AUTHORISED),
+ http_get(Config, "/vhost-limits", ?NOT_AUTHORISED),
+ http_put(Config, "/queues/%2F/myqueue", none, ?NOT_AUTHORISED),
+ Policy = [{pattern, <<".*">>},
+ {definition, [{<<"ha-mode">>, <<"all">>}]}],
+ http_put(Config, "/policies/%2F/HA", Policy, ?NOT_AUTHORISED),
+ http_delete(Config, "/queues/%2F/myqueue", ?NOT_AUTHORISED),
+ http_get(Config, "/definitions", ?NOT_AUTHORISED),
+ http_post(Config, "/definitions", [], ?NOT_AUTHORISED),
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, disable_basic_auth, 50]),
+ %% Defaults to 'false' when config is invalid
+ http_get(Config, "/overview", ?OK).
+
+auth_attempts_test(Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_core_metrics, reset_auth_attempt_metrics, []),
+ {Conn, _Ch} = open_connection_and_channel(Config),
+ close_connection(Conn),
+ [NodeData] = http_get(Config, "/nodes"),
+ Node = binary_to_atom(maps:get(name, NodeData), utf8),
+ Map = http_get(Config, "/auth/attempts/" ++ atom_to_list(Node), ?OK),
+ Http = get_auth_attempts(<<"http">>, Map),
+ Amqp091 = get_auth_attempts(<<"amqp091">>, Map),
+ ?assertEqual(false, maps:is_key(remote_address, Amqp091)),
+ ?assertEqual(false, maps:is_key(username, Amqp091)),
+ ?assertEqual(1, maps:get(auth_attempts, Amqp091)),
+ ?assertEqual(1, maps:get(auth_attempts_succeeded, Amqp091)),
+ ?assertEqual(0, maps:get(auth_attempts_failed, Amqp091)),
+ ?assertEqual(false, maps:is_key(remote_address, Http)),
+ ?assertEqual(false, maps:is_key(username, Http)),
+ ?assertEqual(2, maps:get(auth_attempts, Http)),
+ ?assertEqual(2, maps:get(auth_attempts_succeeded, Http)),
+ ?assertEqual(0, maps:get(auth_attempts_failed, Http)),
+
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbit, track_auth_attempt_source, true]),
+ {Conn2, _Ch2} = open_connection_and_channel(Config),
+ close_connection(Conn2),
+ Map2 = http_get(Config, "/auth/attempts/" ++ atom_to_list(Node) ++ "/source", ?OK),
+ Map3 = http_get(Config, "/auth/attempts/" ++ atom_to_list(Node), ?OK),
+ Http2 = get_auth_attempts(<<"http">>, Map2),
+ Http3 = get_auth_attempts(<<"http">>, Map3),
+ Amqp091_2 = get_auth_attempts(<<"amqp091">>, Map2),
+ Amqp091_3 = get_auth_attempts(<<"amqp091">>, Map3),
+ ?assertEqual(<<"127.0.0.1">>, maps:get(remote_address, Http2)),
+ ?assertEqual(<<"guest">>, maps:get(username, Http2)),
+ ?assertEqual(1, maps:get(auth_attempts, Http2)),
+ ?assertEqual(1, maps:get(auth_attempts_succeeded, Http2)),
+ ?assertEqual(0, maps:get(auth_attempts_failed, Http2)),
+
+ ?assertEqual(false, maps:is_key(remote_address, Http3)),
+ ?assertEqual(false, maps:is_key(username, Http3)),
+ ?assertEqual(4, maps:get(auth_attempts, Http3)),
+ ?assertEqual(4, maps:get(auth_attempts_succeeded, Http3)),
+ ?assertEqual(0, maps:get(auth_attempts_failed, Http3)),
+
+ ?assertEqual(true, <<>> =/= maps:get(remote_address, Amqp091_2)),
+ ?assertEqual(<<"guest">>, maps:get(username, Amqp091_2)),
+ ?assertEqual(1, maps:get(auth_attempts, Amqp091_2)),
+ ?assertEqual(1, maps:get(auth_attempts_succeeded, Amqp091_2)),
+ ?assertEqual(0, maps:get(auth_attempts_failed, Amqp091_2)),
+
+ ?assertEqual(false, maps:is_key(remote_address, Amqp091_3)),
+ ?assertEqual(false, maps:is_key(username, Amqp091_3)),
+ ?assertEqual(2, maps:get(auth_attempts, Amqp091_3)),
+ ?assertEqual(2, maps:get(auth_attempts_succeeded, Amqp091_3)),
+ ?assertEqual(0, maps:get(auth_attempts_failed, Amqp091_3)),
+
+ passed.
+
+%% -------------------------------------------------------------------
+%% Helpers.
+%% -------------------------------------------------------------------
+
+msg(Key, Headers, Body) ->
+ msg(Key, Headers, Body, <<"string">>).
+
+msg(Key, Headers, Body, Enc) ->
+ #{exchange => <<"">>,
+ routing_key => Key,
+ properties => #{delivery_mode => 2,
+ headers => Headers},
+ payload => Body,
+ payload_encoding => Enc}.
+
+local_port(Conn) ->
+ [{sock, Sock}] = amqp_connection:info(Conn, [sock]),
+ {ok, Port} = inet:port(Sock),
+ Port.
+
+spawn_invalid(_Config, 0) ->
+ ok;
+spawn_invalid(Config, N) ->
+ Self = self(),
+ spawn(fun() ->
+ timer:sleep(rand:uniform(250)),
+ {ok, Sock} = gen_tcp:connect("localhost", amqp_port(Config), [list]),
+ ok = gen_tcp:send(Sock, "Some Data"),
+ receive_msg(Self)
+ end),
+ spawn_invalid(Config, N-1).
+
+receive_msg(Self) ->
+ receive
+ {tcp, _, [$A, $M, $Q, $P | _]} ->
+ Self ! done
+ after
+ 60000 ->
+ Self ! no_reply
+ end.
+
+wait_for_answers(0) ->
+ ok;
+wait_for_answers(N) ->
+ receive
+ done ->
+ wait_for_answers(N-1);
+ no_reply ->
+ throw(no_reply)
+ end.
+
+publish(Ch) ->
+ amqp_channel:call(Ch, #'basic.publish'{exchange = <<"">>,
+ routing_key = <<"myqueue">>},
+ #amqp_msg{payload = <<"message">>}),
+ receive
+ stop_publish ->
+ ok
+ after 20 ->
+ publish(Ch)
+ end.
+
+wait_until(_Fun, 0) ->
+ ?assert(wait_failed);
+wait_until(Fun, N) ->
+ case Fun() of
+ true ->
+ timer:sleep(1500);
+ false ->
+ timer:sleep(?COLLECT_INTERVAL + 100),
+ wait_until(Fun, N - 1)
+ end.
+
+http_post_json(Config, Path, Body, Assertion) ->
+ http_upload_raw(Config, post, Path, Body, "guest", "guest",
+ Assertion, [{"content-type", "application/json"}]).
+
+%% @doc encode fields and file for HTTP post multipart/form-data.
+%% @reference Inspired by <a href="http://code.activestate.com/recipes/146306/">Python implementation</a>.
+format_multipart_filedata(Boundary, Files) ->
+ FileParts = lists:map(fun({FieldName, FileName, FileContent}) ->
+ [lists:concat(["--", Boundary]),
+ lists:concat(["content-disposition: form-data; name=\"", atom_to_list(FieldName), "\"; filename=\"", FileName, "\""]),
+ lists:concat(["content-type: ", "application/octet-stream"]),
+ "",
+ FileContent]
+ end, Files),
+ FileParts2 = lists:append(FileParts),
+ EndingParts = [lists:concat(["--", Boundary, "--"]), ""],
+ Parts = lists:append([FileParts2, EndingParts]),
+ string:join(Parts, "\r\n").
+
+get_auth_attempts(Protocol, Map) ->
+ [A] = lists:filter(fun(#{protocol := P}) ->
+ P == Protocol
+ end, Map),
+ A.
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_http_health_checks_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_http_health_checks_SUITE.erl
new file mode 100644
index 0000000000..8ef17ed29c
--- /dev/null
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_http_health_checks_SUITE.erl
@@ -0,0 +1,399 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_http_health_checks_SUITE).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl").
+
+-import(rabbit_mgmt_test_util, [http_get/3,
+ req/4,
+ auth_header/2]).
+
+-define(COLLECT_INTERVAL, 1000).
+-define(PATH_PREFIX, "/custom-prefix").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, all_tests},
+ {group, single_node}
+ ].
+
+groups() ->
+ [
+ {all_tests, [], all_tests()},
+ {single_node, [], [
+ alarms_test,
+ local_alarms_test,
+ is_quorum_critical_single_node_test,
+ is_mirror_sync_critical_single_node_test]}
+ ].
+
+all_tests() -> [
+ health_checks_test,
+ is_quorum_critical_test,
+ is_mirror_sync_critical_test,
+ virtual_hosts_test,
+ protocol_listener_test,
+ port_listener_test,
+ certificate_expiration_test
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_group(Group, Config0) ->
+ PathConfig = {rabbitmq_management, [{path_prefix, ?PATH_PREFIX}]},
+ Config1 = rabbit_ct_helpers:merge_app_env(Config0, PathConfig),
+ rabbit_ct_helpers:log_environment(),
+ inets:start(),
+ ClusterSize = case Group of
+ all_tests -> 3;
+ single_node -> 1
+ end,
+ NodeConf = [{rmq_nodename_suffix, Group},
+ {rmq_nodes_count, ClusterSize},
+ {tcp_ports_base}],
+ Config2 = rabbit_ct_helpers:set_config(Config1, NodeConf),
+ Ret = rabbit_ct_helpers:run_setup_steps(
+ Config2,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps()),
+ case Ret of
+ {skip, _} ->
+ Ret;
+ Config3 ->
+ EnableFF = rabbit_ct_broker_helpers:enable_feature_flag(
+ Config3, quorum_queue),
+ case EnableFF of
+ ok ->
+ Config3;
+ Skip ->
+ end_per_group(Group, Config3),
+ Skip
+ end
+ end.
+
+end_per_group(_, Config) ->
+ inets:stop(),
+ Teardown0 = rabbit_ct_client_helpers:teardown_steps(),
+ Teardown1 = rabbit_ct_broker_helpers:teardown_steps(),
+ Steps = Teardown0 ++ Teardown1,
+ rabbit_ct_helpers:run_teardown_steps(Config, Steps).
+
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase).
+
+end_per_testcase(is_quorum_critical_test = Testcase, Config) ->
+ [_, Server2, Server3] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
+ _ = rabbit_ct_broker_helpers:start_node(Config, Server2),
+ _ = rabbit_ct_broker_helpers:start_node(Config, Server3),
+ rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, delete_queues, []),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase);
+end_per_testcase(is_mirror_sync_critical_test = Testcase, Config) ->
+ [_, Server2, Server3] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
+ _ = rabbit_ct_broker_helpers:start_node(Config, Server2),
+ _ = rabbit_ct_broker_helpers:start_node(Config, Server3),
+ ok = rabbit_ct_broker_helpers:clear_policy(Config, 0, <<"ha">>),
+ rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, delete_queues, []),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase);
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_finished(Config, Testcase).
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+health_checks_test(Config) ->
+ Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mgmt),
+ http_get(Config, "/health/checks/certificate-expiration/1/days", ?OK),
+ http_get(Config, io_lib:format("/health/checks/port-listener/~p", [Port]), ?OK),
+ http_get(Config, "/health/checks/protocol-listener/http", ?OK),
+ http_get(Config, "/health/checks/virtual-hosts", ?OK),
+ http_get(Config, "/health/checks/node-is-mirror-sync-critical", ?OK),
+ http_get(Config, "/health/checks/node-is-quorum-critical", ?OK),
+ passed.
+
+alarms_test(Config) ->
+ Server = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+ rabbit_ct_broker_helpers:clear_all_alarms(Config, Server),
+
+ EndpointPath = "/health/checks/alarms",
+ Check0 = http_get(Config, EndpointPath, ?OK),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ ok = rabbit_ct_broker_helpers:set_alarm(Config, Server, memory),
+ rabbit_ct_helpers:await_condition(
+ fun() -> rabbit_ct_broker_helpers:get_alarms(Config, Server) =/= [] end
+ ),
+
+ Body = http_get_failed(Config, EndpointPath),
+ ?assertEqual(<<"failed">>, maps:get(<<"status">>, Body)),
+ ?assert(is_list(maps:get(<<"alarms">>, Body))),
+
+ rabbit_ct_broker_helpers:clear_all_alarms(Config, Server),
+ rabbit_ct_helpers:await_condition(
+ fun() -> rabbit_ct_broker_helpers:get_alarms(Config, Server) =:= [] end
+ ),
+ ct:pal("Alarms: ~p", [rabbit_ct_broker_helpers:get_alarms(Config, Server)]),
+
+ passed.
+
+local_alarms_test(Config) ->
+ Server = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+ rabbit_ct_broker_helpers:clear_all_alarms(Config, Server),
+
+ EndpointPath = "/health/checks/local-alarms",
+ Check0 = http_get(Config, EndpointPath, ?OK),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ ok = rabbit_ct_broker_helpers:set_alarm(Config, Server, file_descriptor_limit),
+ rabbit_ct_helpers:await_condition(
+ fun() -> rabbit_ct_broker_helpers:get_alarms(Config, Server) =/= [] end
+ ),
+
+ Body = http_get_failed(Config, EndpointPath),
+ ?assertEqual(<<"failed">>, maps:get(<<"status">>, Body)),
+ ?assert(is_list(maps:get(<<"alarms">>, Body))),
+
+ rabbit_ct_broker_helpers:clear_all_alarms(Config, Server),
+ rabbit_ct_helpers:await_condition(
+ fun() -> rabbit_ct_broker_helpers:get_local_alarms(Config, Server) =:= [] end
+ ),
+
+ passed.
+
+
+is_quorum_critical_single_node_test(Config) ->
+ Check0 = http_get(Config, "/health/checks/node-is-quorum-critical", ?OK),
+ ?assertEqual(<<"single node cluster">>, maps:get(reason, Check0)),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ Server = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+ Ch = rabbit_ct_client_helpers:open_channel(Config, Server),
+ Args = [{<<"x-queue-type">>, longstr, <<"quorum">>}],
+ QName = <<"is_quorum_critical_single_node_test">>,
+ ?assertEqual({'queue.declare_ok', QName, 0, 0},
+ amqp_channel:call(Ch, #'queue.declare'{queue = QName,
+ durable = true,
+ auto_delete = false,
+ arguments = Args})),
+ Check1 = http_get(Config, "/health/checks/node-is-quorum-critical", ?OK),
+ ?assertEqual(<<"single node cluster">>, maps:get(reason, Check1)),
+
+ passed.
+
+is_quorum_critical_test(Config) ->
+ Check0 = http_get(Config, "/health/checks/node-is-quorum-critical", ?OK),
+ ?assertEqual(false, maps:is_key(reason, Check0)),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ [Server1, Server2, Server3] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
+ Ch = rabbit_ct_client_helpers:open_channel(Config, Server1),
+ Args = [{<<"x-queue-type">>, longstr, <<"quorum">>}],
+ QName = <<"is_quorum_critical_test">>,
+ ?assertEqual({'queue.declare_ok', QName, 0, 0},
+ amqp_channel:call(Ch, #'queue.declare'{queue = QName,
+ durable = true,
+ auto_delete = false,
+ arguments = Args})),
+ Check1 = http_get(Config, "/health/checks/node-is-quorum-critical", ?OK),
+ ?assertEqual(false, maps:is_key(reason, Check1)),
+
+ ok = rabbit_ct_broker_helpers:stop_node(Config, Server2),
+ ok = rabbit_ct_broker_helpers:stop_node(Config, Server3),
+
+ Body = http_get_failed(Config, "/health/checks/node-is-quorum-critical"),
+ ?assertEqual(<<"failed">>, maps:get(<<"status">>, Body)),
+ ?assertEqual(true, maps:is_key(<<"reason">>, Body)),
+ [Queue] = maps:get(<<"queues">>, Body),
+ ?assertEqual(QName, maps:get(<<"name">>, Queue)),
+
+ passed.
+
+is_mirror_sync_critical_single_node_test(Config) ->
+ Check0 = http_get(Config, "/health/checks/node-is-mirror-sync-critical", ?OK),
+ ?assertEqual(<<"single node cluster">>, maps:get(reason, Check0)),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ ok = rabbit_ct_broker_helpers:set_policy(
+ Config, 0, <<"ha">>, <<"is_mirror_sync.*">>, <<"queues">>,
+ [{<<"ha-mode">>, <<"all">>}]),
+ Server = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+ Ch = rabbit_ct_client_helpers:open_channel(Config, Server),
+ QName = <<"is_mirror_sync_critical_single_node_test">>,
+ ?assertEqual({'queue.declare_ok', QName, 0, 0},
+ amqp_channel:call(Ch, #'queue.declare'{queue = QName,
+ durable = true,
+ auto_delete = false,
+ arguments = []})),
+ Check1 = http_get(Config, "/health/checks/node-is-mirror-sync-critical", ?OK),
+ ?assertEqual(<<"single node cluster">>, maps:get(reason, Check1)),
+
+ passed.
+
+is_mirror_sync_critical_test(Config) ->
+ Path = "/health/checks/node-is-mirror-sync-critical",
+ Check0 = http_get(Config, Path, ?OK),
+ ?assertEqual(false, maps:is_key(reason, Check0)),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ ok = rabbit_ct_broker_helpers:set_policy(
+ Config, 0, <<"ha">>, <<"is_mirror_sync.*">>, <<"queues">>,
+ [{<<"ha-mode">>, <<"all">>}]),
+ [Server1, Server2, Server3] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
+ Ch = rabbit_ct_client_helpers:open_channel(Config, Server1),
+ QName = <<"is_mirror_sync_critical_test">>,
+ ?assertEqual({'queue.declare_ok', QName, 0, 0},
+ amqp_channel:call(Ch, #'queue.declare'{queue = QName,
+ durable = true,
+ auto_delete = false,
+ arguments = []})),
+ rabbit_ct_helpers:await_condition(
+ fun() ->
+ {ok, {{_, Code, _}, _, _}} = req(Config, get, Path, [auth_header("guest", "guest")]),
+ Code == ?OK
+ end),
+ Check1 = http_get(Config, Path, ?OK),
+ ?assertEqual(false, maps:is_key(reason, Check1)),
+
+ ok = rabbit_ct_broker_helpers:stop_node(Config, Server2),
+ ok = rabbit_ct_broker_helpers:stop_node(Config, Server3),
+
+ Body = http_get_failed(Config, Path),
+ ?assertEqual(<<"failed">>, maps:get(<<"status">>, Body)),
+ ?assertEqual(true, maps:is_key(<<"reason">>, Body)),
+ [Queue] = maps:get(<<"queues">>, Body),
+ ?assertEqual(QName, maps:get(<<"name">>, Queue)),
+
+ passed.
+
+virtual_hosts_test(Config) ->
+ VHost1 = <<"vhost1">>,
+ VHost2 = <<"vhost2">>,
+ add_vhost(Config, VHost1),
+ add_vhost(Config, VHost2),
+
+ Path = "/health/checks/virtual-hosts",
+ Check0 = http_get(Config, Path, ?OK),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ rabbit_ct_broker_helpers:force_vhost_failure(Config, VHost1),
+
+ Body1 = http_get_failed(Config, Path),
+ ?assertEqual(<<"failed">>, maps:get(<<"status">>, Body1)),
+ ?assertEqual(true, maps:is_key(<<"reason">>, Body1)),
+ ?assertEqual([VHost1], maps:get(<<"virtual-hosts">>, Body1)),
+
+ rabbit_ct_broker_helpers:force_vhost_failure(Config, VHost2),
+
+ Body2 = http_get_failed(Config, Path),
+ ?assertEqual(<<"failed">>, maps:get(<<"status">>, Body2)),
+ ?assertEqual(true, maps:is_key(<<"reason">>, Body2)),
+ VHosts = lists:sort([VHost1, VHost2]),
+ ?assertEqual(VHosts, lists:sort(maps:get(<<"virtual-hosts">>, Body2))),
+
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost1),
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost2),
+ http_get(Config, Path, ?OK),
+
+ passed.
+
+protocol_listener_test(Config) ->
+ Check0 = http_get(Config, "/health/checks/protocol-listener/http", ?OK),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ http_get(Config, "/health/checks/protocol-listener/amqp", ?OK),
+ http_get(Config, "/health/checks/protocol-listener/amqp0.9.1", ?OK),
+ http_get(Config, "/health/checks/protocol-listener/amqp0-9-1", ?OK),
+
+ Body0 = http_get_failed(Config, "/health/checks/protocol-listener/mqtt"),
+ ?assertEqual(<<"failed">>, maps:get(<<"status">>, Body0)),
+ ?assertEqual(true, maps:is_key(<<"reason">>, Body0)),
+ ?assertEqual(<<"mqtt">>, maps:get(<<"missing">>, Body0)),
+ ?assert(lists:member(<<"http">>, maps:get(<<"protocols">>, Body0))),
+ ?assert(lists:member(<<"clustering">>, maps:get(<<"protocols">>, Body0))),
+ ?assert(lists:member(<<"amqp">>, maps:get(<<"protocols">>, Body0))),
+
+ http_get_failed(Config, "/health/checks/protocol-listener/doe"),
+ http_get_failed(Config, "/health/checks/protocol-listener/mqtts"),
+ http_get_failed(Config, "/health/checks/protocol-listener/stomp"),
+ http_get_failed(Config, "/health/checks/protocol-listener/stomp1.0"),
+
+ passed.
+
+port_listener_test(Config) ->
+ AMQP = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
+ MGMT = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mgmt),
+ MQTT = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt),
+
+ Path = fun(Port) ->
+ lists:flatten(io_lib:format("/health/checks/port-listener/~p", [Port]))
+ end,
+
+ Check0 = http_get(Config, Path(AMQP), ?OK),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ Check1 = http_get(Config, Path(MGMT), ?OK),
+ ?assertEqual(<<"ok">>, maps:get(status, Check1)),
+
+ http_get(Config, "/health/checks/port-listener/bananas", ?BAD_REQUEST),
+
+ Body0 = http_get_failed(Config, Path(MQTT)),
+ ?assertEqual(<<"failed">>, maps:get(<<"status">>, Body0)),
+ ?assertEqual(true, maps:is_key(<<"reason">>, Body0)),
+ ?assertEqual(MQTT, maps:get(<<"missing">>, Body0)),
+ ?assert(lists:member(AMQP, maps:get(<<"ports">>, Body0))),
+ ?assert(lists:member(MGMT, maps:get(<<"ports">>, Body0))),
+
+ passed.
+
+certificate_expiration_test(Config) ->
+ Check0 = http_get(Config, "/health/checks/certificate-expiration/1/weeks", ?OK),
+ ?assertEqual(<<"ok">>, maps:get(status, Check0)),
+
+ http_get(Config, "/health/checks/certificate-expiration/1/days", ?OK),
+ http_get(Config, "/health/checks/certificate-expiration/1/months", ?OK),
+
+ http_get(Config, "/health/checks/certificate-expiration/two/weeks", ?BAD_REQUEST),
+ http_get(Config, "/health/checks/certificate-expiration/2/week", ?BAD_REQUEST),
+ http_get(Config, "/health/checks/certificate-expiration/2/doe", ?BAD_REQUEST),
+
+ Body0 = http_get_failed(Config, "/health/checks/certificate-expiration/10/years"),
+ ?assertEqual(<<"failed">>, maps:get(<<"status">>, Body0)),
+ ?assertEqual(true, maps:is_key(<<"reason">>, Body0)),
+ [Expired] = maps:get(<<"expired">>, Body0),
+ ?assertEqual(<<"amqp/ssl">>, maps:get(<<"protocol">>, Expired)),
+ AMQP_TLS = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp_tls),
+ ?assertEqual(AMQP_TLS, maps:get(<<"port">>, Expired)),
+ Node = atom_to_binary(rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), utf8),
+ ?assertEqual(Node, maps:get(<<"node">>, Expired)),
+ ?assertEqual(true, maps:is_key(<<"cacertfile">>, Expired)),
+ ?assertEqual(true, maps:is_key(<<"certfile">>, Expired)),
+ ?assertEqual(true, maps:is_key(<<"certfile_expires_on">>, Expired)),
+ ?assertEqual(true, maps:is_key(<<"interface">>, Expired)),
+
+ passed.
+
+http_get_failed(Config, Path) ->
+ {ok, {{_, Code, _}, _, ResBody}} = req(Config, get, Path, [auth_header("guest", "guest")]),
+ ?assertEqual(Code, ?HEALTH_CHECK_FAILURE_STATUS),
+ rabbit_json:decode(rabbit_data_coercion:to_binary(ResBody)).
+
+delete_queues() ->
+ [rabbit_amqqueue:delete(Q, false, false, <<"dummy">>)
+ || Q <- rabbit_amqqueue:list()].
+
+add_vhost(Config, VHost) ->
+ rabbit_ct_broker_helpers:add_vhost(Config, VHost),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, <<"guest">>, VHost).
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_only_http_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_only_http_SUITE.erl
new file mode 100644
index 0000000000..38bb2bac1a
--- /dev/null
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_only_http_SUITE.erl
@@ -0,0 +1,1716 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_only_http_SUITE).
+
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl").
+
+-import(rabbit_ct_client_helpers, [close_connection/1, close_channel/1,
+ open_unmanaged_connection/1]).
+-import(rabbit_mgmt_test_util, [assert_list/2, assert_item/2, test_item/2,
+ assert_keys/2, assert_no_keys/2,
+ http_get/2, http_get/3, http_get/5,
+ http_get_no_map/2,
+ http_put/4, http_put/6,
+ http_post/4, http_post/6,
+ http_upload_raw/8,
+ http_delete/3, http_delete/5,
+ http_put_raw/4, http_post_accept_json/4,
+ req/4, auth_header/2,
+ assert_permanent_redirect/3,
+ uri_base_from/2, format_for_upload/1,
+ amqp_port/1]).
+
+-import(rabbit_misc, [pget/2]).
+
+-define(COLLECT_INTERVAL, 1000).
+-define(PATH_PREFIX, "/custom-prefix").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, all_tests_with_prefix},
+ {group, all_tests_without_prefix},
+ {group, stats_disabled_on_request}
+ ].
+
+groups() ->
+ [
+ {all_tests_with_prefix, [], all_tests()},
+ {all_tests_without_prefix, [], all_tests()},
+ {stats_disabled_on_request, [], [disable_with_disable_stats_parameter_test]},
+ {invalid_config, [], [invalid_config_test]}
+ ].
+
+all_tests() -> [
+ overview_test,
+ nodes_test,
+ vhosts_test,
+ connections_test,
+ exchanges_test,
+ queues_test,
+ mirrored_queues_test,
+ quorum_queues_test,
+ queues_well_formed_json_test,
+ permissions_vhost_test,
+ permissions_connection_channel_consumer_test,
+ consumers_cq_test,
+ consumers_qq_test,
+ arguments_test,
+ queue_actions_test,
+ exclusive_queue_test,
+ connections_channels_pagination_test,
+ exchanges_pagination_test,
+ exchanges_pagination_permissions_test,
+ queue_pagination_test,
+ queue_pagination_columns_test,
+ queues_pagination_permissions_test,
+ samples_range_test,
+ sorting_test,
+ columns_test,
+ if_empty_unused_test,
+ queues_enable_totals_test,
+ double_encoded_json_test
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+merge_app_env(Config, DisableStats) ->
+ Config1 = rabbit_ct_helpers:merge_app_env(Config,
+ {rabbit, [
+ {collect_statistics_interval, ?COLLECT_INTERVAL}
+ ]}),
+ rabbit_ct_helpers:merge_app_env(Config1,
+ {rabbitmq_management, [
+ {disable_management_stats, DisableStats},
+ {sample_retention_policies,
+ [{global, [{605, 1}]},
+ {basic, [{605, 1}]},
+ {detailed, [{10, 1}]}]
+ }]}).
+
+start_broker(Config) ->
+ Setup0 = rabbit_ct_broker_helpers:setup_steps(),
+ Setup1 = rabbit_ct_client_helpers:setup_steps(),
+ Steps = Setup0 ++ Setup1,
+ rabbit_ct_helpers:run_setup_steps(Config, Steps).
+
+finish_init(Group, Config) when Group == all_tests_with_prefix ->
+ finish_init(Group, Config, true);
+finish_init(Group, Config) when Group == all_tests_without_prefix ->
+ finish_init(Group, Config, true);
+finish_init(Group, Config) when Group == stats_disabled_on_request ->
+ finish_init(Group, Config, false);
+finish_init(Group, Config) when Group == invalid_config_test ->
+ finish_init(Group, Config, true).
+
+finish_init(Group, Config, DisableStats) ->
+ rabbit_ct_helpers:log_environment(),
+ inets:start(),
+ NodeConf = [{rmq_nodename_suffix, Group}],
+ Config1 = rabbit_ct_helpers:set_config(Config, NodeConf),
+ merge_app_env(Config1, DisableStats).
+
+init_per_group(all_tests_with_prefix=Group, Config0) ->
+ PathConfig = {rabbitmq_management, [{path_prefix, ?PATH_PREFIX}]},
+ Config1 = rabbit_ct_helpers:merge_app_env(Config0, PathConfig),
+ Config2 = finish_init(Group, Config1),
+ Config3 = start_broker(Config2),
+ Nodes = rabbit_ct_broker_helpers:get_node_configs(
+ Config3, nodename),
+ Ret = rabbit_ct_broker_helpers:rpc(
+ Config3, 0,
+ rabbit_feature_flags,
+ is_supported_remotely,
+ [Nodes, [quorum_queue], 60000]),
+ case Ret of
+ true ->
+ ok = rabbit_ct_broker_helpers:rpc(
+ Config3, 0, rabbit_feature_flags, enable, [quorum_queue]),
+ Config3;
+ false ->
+ end_per_group(Group, Config3),
+ {skip, "Quorum queues are unsupported"}
+ end;
+init_per_group(Group, Config0) ->
+ Config1 = finish_init(Group, Config0),
+ Config2 = start_broker(Config1),
+ Nodes = rabbit_ct_broker_helpers:get_node_configs(
+ Config2, nodename),
+ Ret = rabbit_ct_broker_helpers:rpc(
+ Config2, 0,
+ rabbit_feature_flags,
+ is_supported_remotely,
+ [Nodes, [quorum_queue], 60000]),
+ case Ret of
+ true ->
+ ok = rabbit_ct_broker_helpers:rpc(
+ Config2, 0, rabbit_feature_flags, enable, [quorum_queue]),
+ Config2;
+ false ->
+ end_per_group(Group, Config2),
+ {skip, "Quorum queues are unsupported"}
+ end.
+
+end_per_group(_, Config) ->
+ inets:stop(),
+ Teardown0 = rabbit_ct_client_helpers:teardown_steps(),
+ Teardown1 = rabbit_ct_broker_helpers:teardown_steps(),
+ Steps = Teardown0 ++ Teardown1,
+ rabbit_ct_helpers:run_teardown_steps(Config, Steps).
+
+init_per_testcase(Testcase = permissions_vhost_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost">>),
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost1">>),
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost2">>),
+ rabbit_ct_helpers:testcase_started(Config, Testcase);
+
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_broker_helpers:close_all_connections(Config, 0, <<"rabbit_mgmt_only_http_SUITE:init_per_testcase">>),
+ rabbit_ct_helpers:testcase_started(Config, Testcase).
+
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_broker_helpers:close_all_connections(Config, 0, <<"rabbit_mgmt_only_http_SUITE:end_per_testcase">>),
+ Config1 = end_per_testcase0(Testcase, Config),
+ rabbit_ct_helpers:testcase_finished(Config1, Testcase).
+
+end_per_testcase0(Testcase = queues_enable_totals_test, Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
+ [rabbitmq_management, enable_queue_totals]),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase);
+end_per_testcase0(Testcase = queues_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"downvhost">>),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase);
+end_per_testcase0(Testcase = permissions_vhost_test, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost1">>),
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"myvhost2">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"myuser1">>),
+ rabbit_ct_broker_helpers:delete_user(Config, <<"myuser2">>),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase);
+end_per_testcase0(_, Config) ->
+ Config.
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+overview_test(Config) ->
+ Overview = http_get(Config, "/overview"),
+ ?assert(maps:is_key(node, Overview)),
+ ?assert(maps:is_key(object_totals, Overview)),
+ ?assert(not maps:is_key(queue_totals, Overview)),
+ ?assert(not maps:is_key(churn_rates, Overview)),
+ ?assert(not maps:is_key(message_stats, Overview)),
+ http_put(Config, "/users/myuser", [{password, <<"myuser">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ OverviewU = http_get(Config, "/overview", "myuser", "myuser", ?OK),
+ ?assert(maps:is_key(node, OverviewU)),
+ ?assert(maps:is_key(object_totals, OverviewU)),
+ ?assert(not maps:is_key(queue_totals, OverviewU)),
+ ?assert(not maps:is_key(churn_rates, OverviewU)),
+ ?assert(not maps:is_key(message_stats, OverviewU)),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ passed.
+
+nodes_test(Config) ->
+ http_put(Config, "/users/user", [{password, <<"user">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/users/monitor", [{password, <<"monitor">>},
+ {tags, <<"monitoring">>}], {group, '2xx'}),
+ DiscNode = #{type => <<"disc">>, running => true},
+ assert_list([DiscNode], http_get(Config, "/nodes")),
+ assert_list([DiscNode], http_get(Config, "/nodes", "monitor", "monitor", ?OK)),
+ http_get(Config, "/nodes", "user", "user", ?NOT_AUTHORISED),
+ [Node] = http_get(Config, "/nodes"),
+ Path = "/nodes/" ++ binary_to_list(maps:get(name, Node)),
+ NodeReply = http_get(Config, Path, ?OK),
+ assert_item(DiscNode, NodeReply),
+ ?assert(not maps:is_key(channel_closed, NodeReply)),
+ ?assert(not maps:is_key(disk_free, NodeReply)),
+ ?assert(not maps:is_key(fd_used, NodeReply)),
+ ?assert(not maps:is_key(io_file_handle_open_attempt_avg_time, NodeReply)),
+ ?assert(maps:is_key(name, NodeReply)),
+ assert_item(DiscNode, http_get(Config, Path, "monitor", "monitor", ?OK)),
+ http_get(Config, Path, "user", "user", ?NOT_AUTHORISED),
+ http_delete(Config, "/users/user", {group, '2xx'}),
+ http_delete(Config, "/users/monitor", {group, '2xx'}),
+ passed.
+
+%% This test is rather over-verbose as we're trying to test understanding of
+%% Webmachine
+vhosts_test(Config) ->
+ assert_list([#{name => <<"/">>}], http_get(Config, "/vhosts")),
+ %% Create a new one
+ http_put(Config, "/vhosts/myvhost", none, {group, '2xx'}),
+ %% PUT should be idempotent
+ http_put(Config, "/vhosts/myvhost", none, {group, '2xx'}),
+ %% Check it's there
+ [GetFirst | _] = GetAll = http_get(Config, "/vhosts"),
+ assert_list([#{name => <<"/">>}, #{name => <<"myvhost">>}], GetAll),
+ ?assert(not maps:is_key(message_stats, GetFirst)),
+ ?assert(not maps:is_key(messages_ready_details, GetFirst)),
+ ?assert(not maps:is_key(recv_oct, GetFirst)),
+ ?assert(maps:is_key(cluster_state, GetFirst)),
+
+ %% Check individually
+ Get = http_get(Config, "/vhosts/%2F", ?OK),
+ assert_item(#{name => <<"/">>}, Get),
+ assert_item(#{name => <<"myvhost">>},http_get(Config, "/vhosts/myvhost")),
+ ?assert(not maps:is_key(message_stats, Get)),
+ ?assert(not maps:is_key(messages_ready_details, Get)),
+ ?assert(not maps:is_key(recv_oct, Get)),
+ ?assert(maps:is_key(cluster_state, Get)),
+
+ %% Crash it
+ rabbit_ct_broker_helpers:force_vhost_failure(Config, <<"myvhost">>),
+ [NodeData] = http_get(Config, "/nodes"),
+ Node = binary_to_atom(maps:get(name, NodeData), utf8),
+ assert_item(#{name => <<"myvhost">>, cluster_state => #{Node => <<"stopped">>}},
+ http_get(Config, "/vhosts/myvhost")),
+
+ %% Restart it
+ http_post(Config, "/vhosts/myvhost/start/" ++ atom_to_list(Node), [], {group, '2xx'}),
+ assert_item(#{name => <<"myvhost">>, cluster_state => #{Node => <<"running">>}},
+ http_get(Config, "/vhosts/myvhost")),
+
+ %% Delete it
+ http_delete(Config, "/vhosts/myvhost", {group, '2xx'}),
+ %% It's not there
+ http_get(Config, "/vhosts/myvhost", ?NOT_FOUND),
+ http_delete(Config, "/vhosts/myvhost", ?NOT_FOUND),
+
+ passed.
+
+connections_test(Config) ->
+ {Conn, _Ch} = open_connection_and_channel(Config),
+ LocalPort = local_port(Conn),
+ Path = binary_to_list(
+ rabbit_mgmt_format:print(
+ "/connections/127.0.0.1%3A~w%20-%3E%20127.0.0.1%3A~w",
+ [LocalPort, amqp_port(Config)])),
+ timer:sleep(1500),
+ Connection = http_get(Config, Path, ?OK),
+ ?assertEqual(1, maps:size(Connection)),
+ ?assert(maps:is_key(name, Connection)),
+ ?assert(not maps:is_key(recv_oct_details, Connection)),
+ http_delete(Config, Path, {group, '2xx'}),
+ %% TODO rabbit_reader:shutdown/2 returns before the connection is
+ %% closed. It may not be worth fixing.
+ Fun = fun() ->
+ try
+ http_get(Config, Path, ?NOT_FOUND),
+ true
+ catch
+ _:_ ->
+ false
+ end
+ end,
+ wait_until(Fun, 60),
+ close_connection(Conn),
+ passed.
+
+exchanges_test(Config) ->
+ %% Can list exchanges
+ http_get(Config, "/exchanges", {group, '2xx'}),
+ %% Can pass booleans or strings
+ Good = [{type, <<"direct">>}, {durable, <<"true">>}],
+ http_put(Config, "/vhosts/myvhost", none, {group, '2xx'}),
+ http_get(Config, "/exchanges/myvhost/foo", ?NOT_FOUND),
+ http_put(Config, "/exchanges/myvhost/foo", Good, {group, '2xx'}),
+ http_put(Config, "/exchanges/myvhost/foo", Good, {group, '2xx'}),
+ http_get(Config, "/exchanges/%2F/foo", ?NOT_FOUND),
+ Exchange = http_get(Config, "/exchanges/myvhost/foo"),
+ assert_item(#{name => <<"foo">>,
+ vhost => <<"myvhost">>,
+ type => <<"direct">>,
+ durable => true,
+ auto_delete => false,
+ internal => false,
+ arguments => #{}},
+ Exchange),
+ ?assert(not maps:is_key(message_stats, Exchange)),
+ http_put(Config, "/exchanges/badvhost/bar", Good, ?NOT_FOUND),
+ http_put(Config, "/exchanges/myvhost/bar", [{type, <<"bad_exchange_type">>}],
+ ?BAD_REQUEST),
+ http_put(Config, "/exchanges/myvhost/bar", [{type, <<"direct">>},
+ {durable, <<"troo">>}],
+ ?BAD_REQUEST),
+ http_put(Config, "/exchanges/myvhost/foo", [{type, <<"direct">>}],
+ ?BAD_REQUEST),
+
+ http_delete(Config, "/exchanges/myvhost/foo", {group, '2xx'}),
+ http_delete(Config, "/exchanges/myvhost/foo", ?NOT_FOUND),
+
+ http_delete(Config, "/vhosts/myvhost", {group, '2xx'}),
+ http_get(Config, "/exchanges/badvhost", ?NOT_FOUND),
+ passed.
+
+queues_test(Config) ->
+ Good = [{durable, true}],
+ GoodQQ = [{durable, true}, {arguments, [{'x-queue-type', 'quorum'}]}],
+ http_get(Config, "/queues/%2F/foo", ?NOT_FOUND),
+ http_put(Config, "/queues/%2F/foo", GoodQQ, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/foo", GoodQQ, {group, '2xx'}),
+
+ rabbit_ct_broker_helpers:add_vhost(Config, <<"downvhost">>),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, <<"downvhost">>),
+ http_put(Config, "/queues/downvhost/foo", Good, {group, '2xx'}),
+ http_put(Config, "/queues/downvhost/bar", Good, {group, '2xx'}),
+
+ rabbit_ct_broker_helpers:force_vhost_failure(Config, <<"downvhost">>),
+ %% The vhost is down
+ Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+ DownVHost = #{name => <<"downvhost">>, tracing => false, cluster_state => #{Node => <<"stopped">>}},
+ assert_item(DownVHost, http_get(Config, "/vhosts/downvhost")),
+
+ DownQueues = http_get(Config, "/queues/downvhost"),
+ DownQueue = http_get(Config, "/queues/downvhost/foo"),
+
+ assert_list([#{name => <<"bar">>,
+ vhost => <<"downvhost">>,
+ state => <<"stopped">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{}},
+ #{name => <<"foo">>,
+ vhost => <<"downvhost">>,
+ state => <<"stopped">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{}}], DownQueues),
+ assert_item(#{name => <<"foo">>,
+ vhost => <<"downvhost">>,
+ state => <<"stopped">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{}}, DownQueue),
+
+ http_put(Config, "/queues/badvhost/bar", Good, ?NOT_FOUND),
+ http_put(Config, "/queues/%2F/bar",
+ [{durable, <<"troo">>}],
+ ?BAD_REQUEST),
+ http_put(Config, "/queues/%2F/foo",
+ [{durable, false}],
+ ?BAD_REQUEST),
+
+ Policy = [{pattern, <<"baz">>},
+ {definition, [{<<"ha-mode">>, <<"all">>}]}],
+ http_put(Config, "/policies/%2F/HA", Policy, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/baz", Good, {group, '2xx'}),
+ Queues = http_get(Config, "/queues/%2F"),
+ Queue = http_get(Config, "/queues/%2F/foo"),
+
+ NodeBin = atom_to_binary(Node, utf8),
+ assert_list([#{name => <<"baz">>,
+ vhost => <<"/">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{},
+ node => NodeBin,
+ slave_nodes => [],
+ synchronised_slave_nodes => []},
+ #{name => <<"foo">>,
+ vhost => <<"/">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{'x-queue-type' => <<"quorum">>},
+ leader => NodeBin,
+ members => [NodeBin]}], Queues),
+ assert_item(#{name => <<"foo">>,
+ vhost => <<"/">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{'x-queue-type' => <<"quorum">>},
+ leader => NodeBin,
+ members => [NodeBin]}, Queue),
+
+ ?assert(not maps:is_key(messages, Queue)),
+ ?assert(not maps:is_key(message_stats, Queue)),
+ ?assert(not maps:is_key(messages_details, Queue)),
+ ?assert(not maps:is_key(reductions_details, Queue)),
+ ?assertEqual(NodeBin, maps:get(leader, Queue)),
+ ?assertEqual([NodeBin], maps:get(members, Queue)),
+ ?assertEqual([NodeBin], maps:get(online, Queue)),
+
+ http_delete(Config, "/queues/%2F/foo", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/baz", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/foo", ?NOT_FOUND),
+ http_get(Config, "/queues/badvhost", ?NOT_FOUND),
+
+ http_delete(Config, "/queues/downvhost/foo", {group, '2xx'}),
+ http_delete(Config, "/queues/downvhost/bar", {group, '2xx'}),
+ passed.
+
+queues_enable_totals_test(Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, enable_queue_totals, true]),
+
+ Good = [{durable, true}],
+ GoodQQ = [{durable, true}, {arguments, [{'x-queue-type', 'quorum'}]}],
+ http_get(Config, "/queues/%2F/foo", ?NOT_FOUND),
+ http_put(Config, "/queues/%2F/foo", GoodQQ, {group, '2xx'}),
+
+ Policy = [{pattern, <<"baz">>},
+ {definition, [{<<"ha-mode">>, <<"all">>}]}],
+ http_put(Config, "/policies/%2F/HA", Policy, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/baz", Good, {group, '2xx'}),
+
+ {Conn, Ch} = open_connection_and_channel(Config),
+ Publish = fun(Q) ->
+ amqp_channel:call(
+ Ch, #'basic.publish'{exchange = <<"">>,
+ routing_key = Q},
+ #amqp_msg{payload = <<"message">>})
+ end,
+ Publish(<<"baz">>),
+ Publish(<<"foo">>),
+ Publish(<<"foo">>),
+
+ Fun = fun() ->
+ length(rabbit_ct_broker_helpers:rpc(Config, 0, ets, tab2list,
+ [queue_coarse_metrics])) == 2
+ end,
+ wait_until(Fun, 60),
+
+ Queues = http_get(Config, "/queues/%2F"),
+ Queue = http_get(Config, "/queues/%2F/foo"),
+
+ Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+ NodeBin = atom_to_binary(Node, utf8),
+ assert_list([#{name => <<"baz">>,
+ vhost => <<"/">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{},
+ node => NodeBin,
+ slave_nodes => [],
+ messages => 1,
+ messages_ready => 1,
+ messages_unacknowledged => 0,
+ synchronised_slave_nodes => []},
+ #{name => <<"foo">>,
+ vhost => <<"/">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => null,
+ arguments => #{'x-queue-type' => <<"quorum">>},
+ leader => NodeBin,
+ messages => 2,
+ messages_ready => 2,
+ messages_unacknowledged => 0,
+ members => [NodeBin]}], Queues),
+ assert_item(#{name => <<"foo">>,
+ vhost => <<"/">>,
+ durable => true,
+ auto_delete => false,
+ exclusive => false,
+ arguments => #{'x-queue-type' => <<"quorum">>},
+ leader => NodeBin,
+ members => [NodeBin]}, Queue),
+
+ ?assert(not maps:is_key(messages, Queue)),
+ ?assert(not maps:is_key(messages_ready, Queue)),
+ ?assert(not maps:is_key(messages_unacknowledged, Queue)),
+ ?assert(not maps:is_key(message_stats, Queue)),
+ ?assert(not maps:is_key(messages_details, Queue)),
+ ?assert(not maps:is_key(reductions_details, Queue)),
+
+ http_delete(Config, "/queues/%2F/foo", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/baz", {group, '2xx'}),
+ close_connection(Conn),
+
+ passed.
+
+mirrored_queues_test(Config) ->
+ Policy = [{pattern, <<".*">>},
+ {definition, [{<<"ha-mode">>, <<"all">>}]}],
+ http_put(Config, "/policies/%2F/HA", Policy, {group, '2xx'}),
+
+ Good = [{durable, true}, {arguments, []}],
+ http_get(Config, "/queues/%2f/ha", ?NOT_FOUND),
+ http_put(Config, "/queues/%2f/ha", Good, {group, '2xx'}),
+
+ {Conn, Ch} = open_connection_and_channel(Config),
+ Publish = fun() ->
+ amqp_channel:call(
+ Ch, #'basic.publish'{exchange = <<"">>,
+ routing_key = <<"ha">>},
+ #amqp_msg{payload = <<"message">>})
+ end,
+ Publish(),
+ Publish(),
+
+ Queue = http_get(Config, "/queues/%2f/ha?lengths_age=60&lengths_incr=5&msg_rates_age=60&msg_rates_incr=5&data_rates_age=60&data_rates_incr=5"),
+
+ %% It's really only one node, but the only thing that matters in this test is to verify the
+ %% key exists
+ Nodes = lists:sort(rabbit_ct_broker_helpers:get_node_configs(Config, nodename)),
+
+ ?assert(not maps:is_key(messages, Queue)),
+ ?assert(not maps:is_key(messages_details, Queue)),
+ ?assert(not maps:is_key(reductions_details, Queue)),
+ ?assert(true, lists:member(maps:get(node, Queue), Nodes)),
+ ?assertEqual([], get_nodes(slave_nodes, Queue)),
+ ?assertEqual([], get_nodes(synchronised_slave_nodes, Queue)),
+
+ http_delete(Config, "/queues/%2f/ha", {group, '2xx'}),
+ close_connection(Conn).
+
+quorum_queues_test(Config) ->
+ Good = [{durable, true}, {arguments, [{'x-queue-type', 'quorum'}]}],
+ http_get(Config, "/queues/%2f/qq", ?NOT_FOUND),
+ http_put(Config, "/queues/%2f/qq", Good, {group, '2xx'}),
+
+ {Conn, Ch} = open_connection_and_channel(Config),
+ Publish = fun() ->
+ amqp_channel:call(
+ Ch, #'basic.publish'{exchange = <<"">>,
+ routing_key = <<"qq">>},
+ #amqp_msg{payload = <<"message">>})
+ end,
+ Publish(),
+ Publish(),
+
+ Queue = http_get(Config, "/queues/%2f/qq?lengths_age=60&lengths_incr=5&msg_rates_age=60&msg_rates_incr=5&data_rates_age=60&data_rates_incr=5"),
+
+ %% It's really only one node, but the only thing that matters in this test is to verify the
+ %% key exists
+ Nodes = lists:sort(rabbit_ct_broker_helpers:get_node_configs(Config, nodename)),
+
+ ?assert(not maps:is_key(messages, Queue)),
+ ?assert(not maps:is_key(messages_details, Queue)),
+ ?assert(not maps:is_key(reductions_details, Queue)),
+ ?assert(true, lists:member(maps:get(leader, Queue, undefined), Nodes)),
+ ?assertEqual(Nodes, get_nodes(members, Queue)),
+ ?assertEqual(Nodes, get_nodes(online, Queue)),
+
+ http_delete(Config, "/queues/%2f/qq", {group, '2xx'}),
+ close_connection(Conn).
+
+get_nodes(Tag, Queue) ->
+ lists:sort([binary_to_atom(B, utf8) || B <- maps:get(Tag, Queue)]).
+
+queues_well_formed_json_test(Config) ->
+ %% TODO This test should be extended to the whole API
+ Good = [{durable, true}],
+ http_put(Config, "/queues/%2F/foo", Good, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/baz", Good, {group, '2xx'}),
+
+ Queues = http_get_no_map(Config, "/queues/%2F"),
+ %% Ensure keys are unique
+ [begin
+ Sorted = lists:sort(Q),
+ Sorted = lists:usort(Q)
+ end || Q <- Queues],
+
+ http_delete(Config, "/queues/%2F/foo", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/baz", {group, '2xx'}),
+ passed.
+
+permissions_vhost_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/users/myadmin", [{password, <<"myadmin">>},
+ {tags, <<"administrator">>}], {group, '2xx'}),
+ http_put(Config, "/users/myuser", [{password, <<"myuser">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost1", none, {group, '2xx'}),
+ http_put(Config, "/vhosts/myvhost2", none, {group, '2xx'}),
+ http_put(Config, "/permissions/myvhost1/myuser", PermArgs, {group, '2xx'}),
+ http_put(Config, "/permissions/myvhost1/guest", PermArgs, {group, '2xx'}),
+ http_put(Config, "/permissions/myvhost2/guest", PermArgs, {group, '2xx'}),
+ assert_list([#{name => <<"/">>},
+ #{name => <<"myvhost1">>},
+ #{name => <<"myvhost2">>}], http_get(Config, "/vhosts", ?OK)),
+ assert_list([#{name => <<"myvhost1">>}],
+ http_get(Config, "/vhosts", "myuser", "myuser", ?OK)),
+ http_put(Config, "/queues/myvhost1/myqueue", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/myvhost2/myqueue", QArgs, {group, '2xx'}),
+ Test1 =
+ fun(Path) ->
+ Results = http_get(Config, Path, "myuser", "myuser", ?OK),
+ [case maps:get(vhost, Result) of
+ <<"myvhost2">> ->
+ throw({got_result_from_vhost2_in, Path, Result});
+ _ ->
+ ok
+ end || Result <- Results]
+ end,
+ Test2 =
+ fun(Path1, Path2) ->
+ http_get(Config, Path1 ++ "/myvhost1/" ++ Path2, "myuser", "myuser",
+ ?OK),
+ http_get(Config, Path1 ++ "/myvhost2/" ++ Path2, "myuser", "myuser",
+ ?NOT_AUTHORISED)
+ end,
+ Test3 =
+ fun(Path1) ->
+ http_get(Config, Path1 ++ "/myvhost1/", "myadmin", "myadmin",
+ ?OK)
+ end,
+ Test1("/exchanges"),
+ Test2("/exchanges", ""),
+ Test2("/exchanges", "amq.direct"),
+ Test3("/exchanges"),
+ Test1("/queues"),
+ Test2("/queues", ""),
+ Test3("/queues"),
+ Test2("/queues", "myqueue"),
+ Test1("/bindings"),
+ Test2("/bindings", ""),
+ Test3("/bindings"),
+ Test2("/queues", "myqueue/bindings"),
+ Test2("/exchanges", "amq.default/bindings/source"),
+ Test2("/exchanges", "amq.default/bindings/destination"),
+ Test2("/bindings", "e/amq.default/q/myqueue"),
+ Test2("/bindings", "e/amq.default/q/myqueue/myqueue"),
+ http_delete(Config, "/vhosts/myvhost1", {group, '2xx'}),
+ http_delete(Config, "/vhosts/myvhost2", {group, '2xx'}),
+ http_delete(Config, "/users/myuser", {group, '2xx'}),
+ http_delete(Config, "/users/myadmin", {group, '2xx'}),
+ passed.
+
+%% Opens a new connection and a channel on it.
+%% The channel is not managed by rabbit_ct_client_helpers and
+%% should be explicitly closed by the caller.
+open_connection_and_channel(Config) ->
+ Conn = rabbit_ct_client_helpers:open_connection(Config, 0),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ {Conn, Ch}.
+
+get_conn(Config, Username, Password) ->
+ Port = amqp_port(Config),
+ {ok, Conn} = amqp_connection:start(#amqp_params_network{
+ port = Port,
+ username = list_to_binary(Username),
+ password = list_to_binary(Password)}),
+ LocalPort = local_port(Conn),
+ ConnPath = rabbit_misc:format(
+ "/connections/127.0.0.1%3A~w%20-%3E%20127.0.0.1%3A~w",
+ [LocalPort, Port]),
+ ChPath = rabbit_misc:format(
+ "/channels/127.0.0.1%3A~w%20-%3E%20127.0.0.1%3A~w%20(1)",
+ [LocalPort, Port]),
+ ConnChPath = rabbit_misc:format(
+ "/connections/127.0.0.1%3A~w%20-%3E%20127.0.0.1%3A~w/channels",
+ [LocalPort, Port]),
+ {Conn, ConnPath, ChPath, ConnChPath}.
+
+permissions_connection_channel_consumer_test(Config) ->
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/users/user", [{password, <<"user">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/permissions/%2F/user", PermArgs, {group, '2xx'}),
+ http_put(Config, "/users/monitor", [{password, <<"monitor">>},
+ {tags, <<"monitoring">>}], {group, '2xx'}),
+ http_put(Config, "/permissions/%2F/monitor", PermArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test", #{}, {group, '2xx'}),
+
+ {Conn1, UserConn, UserCh, UserConnCh} = get_conn(Config, "user", "user"),
+ {Conn2, MonConn, MonCh, MonConnCh} = get_conn(Config, "monitor", "monitor"),
+ {Conn3, AdmConn, AdmCh, AdmConnCh} = get_conn(Config, "guest", "guest"),
+ {ok, Ch1} = amqp_connection:open_channel(Conn1),
+ {ok, Ch2} = amqp_connection:open_channel(Conn2),
+ {ok, Ch3} = amqp_connection:open_channel(Conn3),
+ [amqp_channel:subscribe(
+ Ch, #'basic.consume'{queue = <<"test">>}, self()) ||
+ Ch <- [Ch1, Ch2, Ch3]],
+ timer:sleep(1500),
+ AssertLength = fun (Path, User, Len) ->
+ Res = http_get(Config, Path, User, User, ?OK),
+ rabbit_ct_helpers:await_condition(
+ fun () ->
+ Len =:= length(Res)
+ end)
+ end,
+ AssertDisabled = fun(Path) ->
+ http_get(Config, Path, "user", "user", ?BAD_REQUEST),
+ http_get(Config, Path, "monitor", "monitor", ?BAD_REQUEST),
+ http_get(Config, Path, "guest", "guest", ?BAD_REQUEST),
+ http_get(Config, Path, ?BAD_REQUEST)
+ end,
+
+ AssertLength("/connections", "user", 1),
+ AssertLength("/connections", "monitor", 3),
+ AssertLength("/connections", "guest", 3),
+
+ AssertDisabled("/channels"),
+ AssertDisabled("/consumers"),
+ AssertDisabled("/consumers/%2F"),
+
+ AssertRead = fun(Path, UserStatus) ->
+ http_get(Config, Path, "user", "user", UserStatus),
+ http_get(Config, Path, "monitor", "monitor", ?OK),
+ http_get(Config, Path, ?OK)
+ end,
+
+ AssertRead(UserConn, ?OK),
+ AssertRead(MonConn, ?NOT_AUTHORISED),
+ AssertRead(AdmConn, ?NOT_AUTHORISED),
+ AssertDisabled(UserCh),
+ AssertDisabled(MonCh),
+ AssertDisabled(AdmCh),
+ AssertRead(UserConnCh, ?OK),
+ AssertRead(MonConnCh, ?NOT_AUTHORISED),
+ AssertRead(AdmConnCh, ?NOT_AUTHORISED),
+
+ AssertClose = fun(Path, User, Status) ->
+ http_delete(Config, Path, User, User, Status)
+ end,
+ AssertClose(UserConn, "monitor", ?NOT_AUTHORISED),
+ AssertClose(MonConn, "user", ?NOT_AUTHORISED),
+ AssertClose(AdmConn, "guest", {group, '2xx'}),
+ AssertClose(MonConn, "guest", {group, '2xx'}),
+ AssertClose(UserConn, "user", {group, '2xx'}),
+
+ http_delete(Config, "/users/user", {group, '2xx'}),
+ http_delete(Config, "/users/monitor", {group, '2xx'}),
+ http_get(Config, "/connections/foo", ?NOT_FOUND),
+ http_get(Config, "/channels/foo", ?BAD_REQUEST),
+ http_delete(Config, "/queues/%2F/test", {group, '2xx'}),
+ passed.
+
+consumers_cq_test(Config) ->
+ consumers_test(Config, [{'x-queue-type', <<"classic">>}]).
+
+consumers_qq_test(Config) ->
+ consumers_test(Config, [{'x-queue-type', <<"quorum">>}]).
+
+consumers_test(Config, Args) ->
+ http_delete(Config, "/queues/%2F/test", [?NO_CONTENT, ?NOT_FOUND]),
+ QArgs = [{auto_delete, false}, {durable, true},
+ {arguments, Args}],
+ http_put(Config, "/queues/%2F/test", QArgs, {group, '2xx'}),
+ {Conn, _ConnPath, _ChPath, _ConnChPath} = get_conn(Config, "guest", "guest"),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ amqp_channel:subscribe(
+ Ch, #'basic.consume'{queue = <<"test">>,
+ no_ack = false,
+ consumer_tag = <<"my-ctag">> }, self()),
+ timer:sleep(1500),
+
+ http_get(Config, "/consumers", ?BAD_REQUEST),
+
+ amqp_connection:close(Conn),
+ http_delete(Config, "/queues/%2F/test", {group, '2xx'}),
+ passed.
+
+defs(Config, Key, URI, CreateMethod, Args) ->
+ defs(Config, Key, URI, CreateMethod, Args,
+ fun(URI2) -> http_delete(Config, URI2, {group, '2xx'}) end).
+
+defs_v(Config, Key, URI, CreateMethod, Args) ->
+ Rep1 = fun (S, S2) -> re:replace(S, "<vhost>", S2, [{return, list}]) end,
+ ReplaceVHostInArgs = fun(M, V2) -> maps:map(fun(vhost, _) -> V2;
+ (_, V1) -> V1 end, M) end,
+
+ %% Test against default vhost
+ defs(Config, Key, Rep1(URI, "%2F"), CreateMethod, ReplaceVHostInArgs(Args, <<"/">>)),
+
+ %% Test against new vhost
+ http_put(Config, "/vhosts/test", none, {group, '2xx'}),
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/permissions/test/guest", PermArgs, {group, '2xx'}),
+ DeleteFun0 = fun(URI2) ->
+ http_delete(Config, URI2, {group, '2xx'})
+ end,
+ DeleteFun1 = fun(_) ->
+ http_delete(Config, "/vhosts/test", {group, '2xx'})
+ end,
+ defs(Config, Key, Rep1(URI, "test"),
+ CreateMethod, ReplaceVHostInArgs(Args, <<"test">>),
+ DeleteFun0, DeleteFun1).
+
+create(Config, CreateMethod, URI, Args) ->
+ case CreateMethod of
+ put -> http_put(Config, URI, Args, {group, '2xx'}),
+ URI;
+ put_update -> http_put(Config, URI, Args, {group, '2xx'}),
+ URI;
+ post -> Headers = http_post(Config, URI, Args, {group, '2xx'}),
+ rabbit_web_dispatch_util:unrelativise(
+ URI, pget("location", Headers))
+ end.
+
+defs(Config, Key, URI, CreateMethod, Args, DeleteFun) ->
+ defs(Config, Key, URI, CreateMethod, Args, DeleteFun, DeleteFun).
+
+defs(Config, Key, URI, CreateMethod, Args, DeleteFun0, DeleteFun1) ->
+ %% Create the item
+ URI2 = create(Config, CreateMethod, URI, Args),
+ %% Make sure it ends up in definitions
+ Definitions = http_get(Config, "/definitions", ?OK),
+ true = lists:any(fun(I) -> test_item(Args, I) end, maps:get(Key, Definitions)),
+
+ %% Delete it
+ DeleteFun0(URI2),
+
+ %% Post the definitions back, it should get recreated in correct form
+ http_post(Config, "/definitions", Definitions, {group, '2xx'}),
+ assert_item(Args, http_get(Config, URI2, ?OK)),
+
+ %% And delete it again
+ DeleteFun1(URI2),
+
+ passed.
+
+register_parameters_and_policy_validator(Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_runtime_parameters_util, register, []),
+ rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_runtime_parameters_util, register_policy_validator, []).
+
+unregister_parameters_and_policy_validator(Config) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_runtime_parameters_util, unregister_policy_validator, []),
+ rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_mgmt_runtime_parameters_util, unregister, []).
+
+arguments_test(Config) ->
+ XArgs = [{type, <<"headers">>},
+ {arguments, [{'alternate-exchange', <<"amq.direct">>}]}],
+ QArgs = [{arguments, [{'x-expires', 1800000}]}],
+ BArgs = [{routing_key, <<"">>},
+ {arguments, [{'x-match', <<"all">>},
+ {foo, <<"bar">>}]}],
+ http_put(Config, "/exchanges/%2F/myexchange", XArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/arguments_test", QArgs, {group, '2xx'}),
+ http_post(Config, "/bindings/%2F/e/myexchange/q/arguments_test", BArgs, {group, '2xx'}),
+ Definitions = http_get(Config, "/definitions", ?OK),
+ http_delete(Config, "/exchanges/%2F/myexchange", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/arguments_test", {group, '2xx'}),
+ http_post(Config, "/definitions", Definitions, {group, '2xx'}),
+ #{'alternate-exchange' := <<"amq.direct">>} =
+ maps:get(arguments, http_get(Config, "/exchanges/%2F/myexchange", ?OK)),
+ #{'x-expires' := 1800000} =
+ maps:get(arguments, http_get(Config, "/queues/%2F/arguments_test", ?OK)),
+
+ ArgsTable = [{<<"foo">>,longstr,<<"bar">>}, {<<"x-match">>, longstr, <<"all">>}],
+ Hash = table_hash(ArgsTable),
+ PropertiesKey = [$~] ++ Hash,
+
+ assert_item(
+ #{'x-match' => <<"all">>, foo => <<"bar">>},
+ maps:get(arguments,
+ http_get(Config, "/bindings/%2F/e/myexchange/q/arguments_test/" ++
+ PropertiesKey, ?OK))
+ ),
+ http_delete(Config, "/exchanges/%2F/myexchange", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/arguments_test", {group, '2xx'}),
+ passed.
+
+table_hash(Table) ->
+ binary_to_list(rabbit_mgmt_format:args_hash(Table)).
+
+queue_actions_test(Config) ->
+ http_put(Config, "/queues/%2F/q", #{}, {group, '2xx'}),
+ http_post(Config, "/queues/%2F/q/actions", [{action, sync}], {group, '2xx'}),
+ http_post(Config, "/queues/%2F/q/actions", [{action, cancel_sync}], {group, '2xx'}),
+ http_post(Config, "/queues/%2F/q/actions", [{action, change_colour}], ?BAD_REQUEST),
+ http_delete(Config, "/queues/%2F/q", {group, '2xx'}),
+ passed.
+
+double_encoded_json_test(Config) ->
+ Payload = rabbit_json:encode(rabbit_json:encode(#{<<"durable">> => true, <<"auto_delete">> => false})),
+ %% double-encoded JSON response is a 4xx, e.g. a Bad Request, and not a 500
+ http_put_raw(Config, "/queues/%2F/q", Payload, {group, '4xx'}),
+ passed.
+
+exclusive_queue_test(Config) ->
+ {Conn, Ch} = open_connection_and_channel(Config),
+ #'queue.declare_ok'{queue = QName} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ timer:sleep(2000),
+ Path = "/queues/%2F/" ++ rabbit_http_util:quote_plus(QName),
+ Queue = http_get(Config, Path),
+ assert_item(#{name => QName,
+ vhost => <<"/">>,
+ durable => false,
+ auto_delete => false,
+ exclusive => true,
+ arguments => #{}}, Queue),
+ amqp_channel:close(Ch),
+ close_connection(Conn),
+ passed.
+
+connections_channels_pagination_test(Config) ->
+ %% this test uses "unmanaged" (by Common Test helpers) connections to avoid
+ %% connection caching
+ Conn = open_unmanaged_connection(Config),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ Conn1 = open_unmanaged_connection(Config),
+ {ok, Ch1} = amqp_connection:open_channel(Conn1),
+ Conn2 = open_unmanaged_connection(Config),
+ {ok, Ch2} = amqp_connection:open_channel(Conn2),
+
+ rabbit_ct_helpers:await_condition(
+ fun() ->
+ PageOfTwo = http_get(Config, "/connections?page=1&page_size=2", ?OK),
+ 3 == maps:get(total_count, PageOfTwo) andalso
+ 3 == maps:get(filtered_count, PageOfTwo) andalso
+ 2 == maps:get(item_count, PageOfTwo) andalso
+ 1 == maps:get(page, PageOfTwo) andalso
+ 2 == maps:get(page_size, PageOfTwo) andalso
+ 2 == maps:get(page_count, PageOfTwo) andalso
+ lists:all(fun(C) ->
+ not maps:is_key(recv_oct_details, C)
+ end, maps:get(items, PageOfTwo))
+ end),
+
+ http_get(Config, "/channels?page=2&page_size=2", ?BAD_REQUEST),
+
+ amqp_channel:close(Ch),
+ amqp_connection:close(Conn),
+ amqp_channel:close(Ch1),
+ amqp_connection:close(Conn1),
+ amqp_channel:close(Ch2),
+ amqp_connection:close(Conn2),
+
+ passed.
+
+exchanges_pagination_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/guest", PermArgs, {group, '2xx'}),
+ http_get(Config, "/exchanges/vh1?page=1&page_size=2", ?OK),
+ http_put(Config, "/exchanges/%2F/test0", QArgs, {group, '2xx'}),
+ http_put(Config, "/exchanges/vh1/test1", QArgs, {group, '2xx'}),
+ http_put(Config, "/exchanges/%2F/test2_reg", QArgs, {group, '2xx'}),
+ http_put(Config, "/exchanges/vh1/reg_test3", QArgs, {group, '2xx'}),
+
+ %% for stats to update
+ timer:sleep(1500),
+
+ Total = length(rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_exchange, list_names, [])),
+
+ PageOfTwo = http_get(Config, "/exchanges?page=1&page_size=2", ?OK),
+ ?assertEqual(Total, maps:get(total_count, PageOfTwo)),
+ ?assertEqual(Total, maps:get(filtered_count, PageOfTwo)),
+ ?assertEqual(2, maps:get(item_count, PageOfTwo)),
+ ?assertEqual(1, maps:get(page, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_size, PageOfTwo)),
+ ?assertEqual(round(Total / 2), maps:get(page_count, PageOfTwo)),
+ Items1 = maps:get(items, PageOfTwo),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, Items1)),
+ assert_list([#{name => <<"">>, vhost => <<"/">>},
+ #{name => <<"amq.direct">>, vhost => <<"/">>}
+ ], Items1),
+
+ ByName = http_get(Config, "/exchanges?page=1&page_size=2&name=reg", ?OK),
+ ?assertEqual(Total, maps:get(total_count, ByName)),
+ ?assertEqual(2, maps:get(filtered_count, ByName)),
+ ?assertEqual(2, maps:get(item_count, ByName)),
+ ?assertEqual(1, maps:get(page, ByName)),
+ ?assertEqual(2, maps:get(page_size, ByName)),
+ ?assertEqual(1, maps:get(page_count, ByName)),
+ Items2 = maps:get(items, ByName),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, Items2)),
+ assert_list([#{name => <<"test2_reg">>, vhost => <<"/">>},
+ #{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], Items2),
+
+ RegExByName = http_get(Config,
+ "/exchanges?page=1&page_size=2&name=%5E(?=%5Ereg)&use_regex=true",
+ ?OK),
+ ?assertEqual(Total, maps:get(total_count, RegExByName)),
+ ?assertEqual(1, maps:get(filtered_count, RegExByName)),
+ ?assertEqual(1, maps:get(item_count, RegExByName)),
+ ?assertEqual(1, maps:get(page, RegExByName)),
+ ?assertEqual(2, maps:get(page_size, RegExByName)),
+ ?assertEqual(1, maps:get(page_count, RegExByName)),
+ Items3 = maps:get(items, RegExByName),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, Items3)),
+ assert_list([#{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], Items3),
+
+
+ http_get(Config, "/exchanges?page=1000", ?BAD_REQUEST),
+ http_get(Config, "/exchanges?page=-1", ?BAD_REQUEST),
+ http_get(Config, "/exchanges?page=not_an_integer_value", ?BAD_REQUEST),
+ http_get(Config, "/exchanges?page=1&page_size=not_an_integer_value", ?BAD_REQUEST),
+ http_get(Config, "/exchanges?page=1&page_size=501", ?BAD_REQUEST), %% max 500 allowed
+ http_get(Config, "/exchanges?page=-1&page_size=-2", ?BAD_REQUEST),
+ http_delete(Config, "/exchanges/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/exchanges/vh1/test1", {group, '2xx'}),
+ http_delete(Config, "/exchanges/%2F/test2_reg", {group, '2xx'}),
+ http_delete(Config, "/exchanges/vh1/reg_test3", {group, '2xx'}),
+ http_delete(Config, "/vhosts/vh1", {group, '2xx'}),
+ passed.
+
+exchanges_pagination_permissions_test(Config) ->
+ http_put(Config, "/users/admin", [{password, <<"admin">>},
+ {tags, <<"administrator">>}], {group, '2xx'}),
+ http_put(Config, "/users/non-admin", [{password, <<"non-admin">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ Perms = [{configure, <<".*">>},
+ {write, <<".*">>},
+ {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/non-admin", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/%2F/admin", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/admin", Perms, {group, '2xx'}),
+ QArgs = #{},
+ http_put(Config, "/exchanges/%2F/test0", QArgs, "admin", "admin", {group, '2xx'}),
+ http_put(Config, "/exchanges/vh1/test1", QArgs, "non-admin", "non-admin", {group, '2xx'}),
+
+ %% for stats to update
+ timer:sleep(1500),
+
+ FirstPage = http_get(Config, "/exchanges?page=1&name=test1", "non-admin", "non-admin", ?OK),
+
+ ?assertEqual(8, maps:get(total_count, FirstPage)),
+ ?assertEqual(1, maps:get(item_count, FirstPage)),
+ ?assertEqual(1, maps:get(page, FirstPage)),
+ ?assertEqual(100, maps:get(page_size, FirstPage)),
+ ?assertEqual(1, maps:get(page_count, FirstPage)),
+ Items = maps:get(items, FirstPage),
+ ?assert(lists:all(fun(C) ->
+ not maps:is_key(message_stats, C)
+ end, Items)),
+ assert_list([#{name => <<"test1">>, vhost => <<"vh1">>}
+ ], Items),
+ http_delete(Config, "/exchanges/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/exchanges/vh1/test1", {group, '2xx'}),
+ http_delete(Config, "/users/admin", {group, '2xx'}),
+ http_delete(Config, "/users/non-admin", {group, '2xx'}),
+ passed.
+
+
+
+queue_pagination_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/guest", PermArgs, {group, '2xx'}),
+
+ http_get(Config, "/queues/vh1?page=1&page_size=2", ?OK),
+
+ http_put(Config, "/queues/%2F/test0", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/test1", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test2_reg", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/reg_test3", QArgs, {group, '2xx'}),
+
+ %% for stats to update
+ timer:sleep(1500),
+
+ Total = length(rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_amqqueue, list_names, [])),
+
+ PageOfTwo = http_get(Config, "/queues?page=1&page_size=2", ?OK),
+ ?assertEqual(Total, maps:get(total_count, PageOfTwo)),
+ ?assertEqual(Total, maps:get(filtered_count, PageOfTwo)),
+ ?assertEqual(2, maps:get(item_count, PageOfTwo)),
+ ?assertEqual(1, maps:get(page, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_size, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_count, PageOfTwo)),
+ Items1 = maps:get(items, PageOfTwo),
+ ?assert(lists:all(fun(C) ->
+ not maps:is_key(message_stats, C)
+ end, Items1)),
+ assert_list([#{name => <<"test0">>, vhost => <<"/">>},
+ #{name => <<"test2_reg">>, vhost => <<"/">>}
+ ], Items1),
+
+ SortedByName = http_get(Config, "/queues?sort=name&page=1&page_size=2", ?OK),
+ ?assertEqual(Total, maps:get(total_count, SortedByName)),
+ ?assertEqual(Total, maps:get(filtered_count, SortedByName)),
+ ?assertEqual(2, maps:get(item_count, SortedByName)),
+ ?assertEqual(1, maps:get(page, SortedByName)),
+ ?assertEqual(2, maps:get(page_size, SortedByName)),
+ ?assertEqual(2, maps:get(page_count, SortedByName)),
+ Items2 = maps:get(items, SortedByName),
+ ?assert(lists:all(fun(C) ->
+ not maps:is_key(message_stats, C)
+ end, Items2)),
+ assert_list([#{name => <<"reg_test3">>, vhost => <<"vh1">>},
+ #{name => <<"test0">>, vhost => <<"/">>}
+ ], Items2),
+
+ FirstPage = http_get(Config, "/queues?page=1", ?OK),
+ ?assertEqual(Total, maps:get(total_count, FirstPage)),
+ ?assertEqual(Total, maps:get(filtered_count, FirstPage)),
+ ?assertEqual(4, maps:get(item_count, FirstPage)),
+ ?assertEqual(1, maps:get(page, FirstPage)),
+ ?assertEqual(100, maps:get(page_size, FirstPage)),
+ ?assertEqual(1, maps:get(page_count, FirstPage)),
+ Items3 = maps:get(items, FirstPage),
+ ?assert(lists:all(fun(C) ->
+ not maps:is_key(message_stats, C)
+ end, Items3)),
+ assert_list([#{name => <<"test0">>, vhost => <<"/">>},
+ #{name => <<"test1">>, vhost => <<"vh1">>},
+ #{name => <<"test2_reg">>, vhost => <<"/">>},
+ #{name => <<"reg_test3">>, vhost =><<"vh1">>}
+ ], Items3),
+
+ ReverseSortedByName = http_get(Config,
+ "/queues?page=2&page_size=2&sort=name&sort_reverse=true",
+ ?OK),
+ ?assertEqual(Total, maps:get(total_count, ReverseSortedByName)),
+ ?assertEqual(Total, maps:get(filtered_count, ReverseSortedByName)),
+ ?assertEqual(2, maps:get(item_count, ReverseSortedByName)),
+ ?assertEqual(2, maps:get(page, ReverseSortedByName)),
+ ?assertEqual(2, maps:get(page_size, ReverseSortedByName)),
+ ?assertEqual(2, maps:get(page_count, ReverseSortedByName)),
+ Items4 = maps:get(items, ReverseSortedByName),
+ ?assert(lists:all(fun(C) ->
+ not maps:is_key(message_stats, C)
+ end, Items4)),
+ assert_list([#{name => <<"test0">>, vhost => <<"/">>},
+ #{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], Items4),
+
+ ByName = http_get(Config, "/queues?page=1&page_size=2&name=reg", ?OK),
+ ?assertEqual(Total, maps:get(total_count, ByName)),
+ ?assertEqual(2, maps:get(filtered_count, ByName)),
+ ?assertEqual(2, maps:get(item_count, ByName)),
+ ?assertEqual(1, maps:get(page, ByName)),
+ ?assertEqual(2, maps:get(page_size, ByName)),
+ ?assertEqual(1, maps:get(page_count, ByName)),
+ Items5 = maps:get(items, ByName),
+ ?assert(lists:all(fun(C) ->
+ not maps:is_key(message_stats, C)
+ end, Items5)),
+ assert_list([#{name => <<"test2_reg">>, vhost => <<"/">>},
+ #{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], Items5),
+
+ RegExByName = http_get(Config,
+ "/queues?page=1&page_size=2&name=%5E(?=%5Ereg)&use_regex=true",
+ ?OK),
+ ?assertEqual(Total, maps:get(total_count, RegExByName)),
+ ?assertEqual(1, maps:get(filtered_count, RegExByName)),
+ ?assertEqual(1, maps:get(item_count, RegExByName)),
+ ?assertEqual(1, maps:get(page, RegExByName)),
+ ?assertEqual(2, maps:get(page_size, RegExByName)),
+ ?assertEqual(1, maps:get(page_count, RegExByName)),
+ Items6 = maps:get(items, RegExByName),
+ ?assert(lists:all(fun(C) ->
+ not maps:is_key(message_stats, C)
+ end, Items6)),
+ assert_list([#{name => <<"reg_test3">>, vhost => <<"vh1">>}
+ ], Items6),
+
+ http_get(Config, "/queues?page=1000", ?BAD_REQUEST),
+ http_get(Config, "/queues?page=-1", ?BAD_REQUEST),
+ http_get(Config, "/queues?page=not_an_integer_value", ?BAD_REQUEST),
+ http_get(Config, "/queues?page=1&page_size=not_an_integer_value", ?BAD_REQUEST),
+ http_get(Config, "/queues?page=1&page_size=501", ?BAD_REQUEST), %% max 500 allowed
+ http_get(Config, "/queues?page=-1&page_size=-2", ?BAD_REQUEST),
+ http_delete(Config, "/queues/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/test1", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/test2_reg", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/reg_test3", {group, '2xx'}),
+ http_delete(Config, "/vhosts/vh1", {group, '2xx'}),
+ passed.
+
+queue_pagination_columns_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, [?CREATED, ?NO_CONTENT]),
+ http_put(Config, "/permissions/vh1/guest", PermArgs, [?CREATED, ?NO_CONTENT]),
+
+ http_get(Config, "/queues/vh1?columns=name&page=1&page_size=2", ?OK),
+ http_put(Config, "/queues/%2F/queue_a", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/queue_b", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/queue_c", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/queue_d", QArgs, {group, '2xx'}),
+ PageOfTwo = http_get(Config, "/queues?columns=name&page=1&page_size=2", ?OK),
+ ?assertEqual(4, maps:get(total_count, PageOfTwo)),
+ ?assertEqual(4, maps:get(filtered_count, PageOfTwo)),
+ ?assertEqual(2, maps:get(item_count, PageOfTwo)),
+ ?assertEqual(1, maps:get(page, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_size, PageOfTwo)),
+ ?assertEqual(2, maps:get(page_count, PageOfTwo)),
+ assert_list([#{name => <<"queue_a">>},
+ #{name => <<"queue_c">>}
+ ], maps:get(items, PageOfTwo)),
+
+ ColumnNameVhost = http_get(Config, "/queues/vh1?columns=name&page=1&page_size=2", ?OK),
+ ?assertEqual(2, maps:get(total_count, ColumnNameVhost)),
+ ?assertEqual(2, maps:get(filtered_count, ColumnNameVhost)),
+ ?assertEqual(2, maps:get(item_count, ColumnNameVhost)),
+ ?assertEqual(1, maps:get(page, ColumnNameVhost)),
+ ?assertEqual(2, maps:get(page_size, ColumnNameVhost)),
+ ?assertEqual(1, maps:get(page_count, ColumnNameVhost)),
+ assert_list([#{name => <<"queue_b">>},
+ #{name => <<"queue_d">>}
+ ], maps:get(items, ColumnNameVhost)),
+
+ ColumnsNameVhost = http_get(Config, "/queues?columns=name,vhost&page=2&page_size=2", ?OK),
+ ?assertEqual(4, maps:get(total_count, ColumnsNameVhost)),
+ ?assertEqual(4, maps:get(filtered_count, ColumnsNameVhost)),
+ ?assertEqual(2, maps:get(item_count, ColumnsNameVhost)),
+ ?assertEqual(2, maps:get(page, ColumnsNameVhost)),
+ ?assertEqual(2, maps:get(page_size, ColumnsNameVhost)),
+ ?assertEqual(2, maps:get(page_count, ColumnsNameVhost)),
+ assert_list([
+ #{name => <<"queue_b">>,
+ vhost => <<"vh1">>},
+ #{name => <<"queue_d">>,
+ vhost => <<"vh1">>}
+ ], maps:get(items, ColumnsNameVhost)),
+
+
+ http_delete(Config, "/queues/%2F/queue_a", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/queue_b", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/queue_c", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/queue_d", {group, '2xx'}),
+ http_delete(Config, "/vhosts/vh1", {group, '2xx'}),
+ passed.
+
+queues_pagination_permissions_test(Config) ->
+ http_delete(Config, "/vhosts/vh1", [?NO_CONTENT, ?NOT_FOUND]),
+ http_delete(Config, "/queues/%2F/test0", [?NO_CONTENT, ?NOT_FOUND]),
+ http_delete(Config, "/users/admin", [?NO_CONTENT, ?NOT_FOUND]),
+ http_delete(Config, "/users/non-admin", [?NO_CONTENT, ?NOT_FOUND]),
+
+ http_put(Config, "/users/non-admin", [{password, <<"non-admin">>},
+ {tags, <<"management">>}], {group, '2xx'}),
+ http_put(Config, "/users/admin", [{password, <<"admin">>},
+ {tags, <<"administrator">>}], {group, '2xx'}),
+ Perms = [{configure, <<".*">>},
+ {write, <<".*">>},
+ {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/non-admin", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/%2F/admin", Perms, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/admin", Perms, {group, '2xx'}),
+ QArgs = #{},
+ http_put(Config, "/queues/%2F/test0", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/test1", QArgs, "non-admin","non-admin", {group, '2xx'}),
+ FirstPage = http_get(Config, "/queues?page=1", "non-admin", "non-admin", ?OK),
+ ?assertEqual(1, maps:get(total_count, FirstPage)),
+ ?assertEqual(1, maps:get(item_count, FirstPage)),
+ ?assertEqual(1, maps:get(page, FirstPage)),
+ ?assertEqual(100, maps:get(page_size, FirstPage)),
+ ?assertEqual(1, maps:get(page_count, FirstPage)),
+ assert_list([#{name => <<"test1">>, vhost => <<"vh1">>}
+ ], maps:get(items, FirstPage)),
+
+ FirstPageAdm = http_get(Config, "/queues?page=1", "admin", "admin", ?OK),
+ ?assertEqual(2, maps:get(total_count, FirstPageAdm)),
+ ?assertEqual(2, maps:get(item_count, FirstPageAdm)),
+ ?assertEqual(1, maps:get(page, FirstPageAdm)),
+ ?assertEqual(100, maps:get(page_size, FirstPageAdm)),
+ ?assertEqual(1, maps:get(page_count, FirstPageAdm)),
+ assert_list([#{name => <<"test1">>, vhost => <<"vh1">>},
+ #{name => <<"test0">>, vhost => <<"/">>}
+ ], maps:get(items, FirstPageAdm)),
+
+ http_delete(Config, "/queues/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/test1","admin","admin", {group, '2xx'}),
+ http_delete(Config, "/users/admin", {group, '2xx'}),
+ http_delete(Config, "/users/non-admin", {group, '2xx'}),
+ passed.
+
+samples_range_test(Config) ->
+ {Conn, Ch} = open_connection_and_channel(Config),
+
+ %% Connections
+ rabbit_ct_helpers:await_condition(
+ fun() ->
+ 1 == length(http_get(Config, "/connections?lengths_age=60&lengths_incr=1", ?OK))
+ end),
+ [Connection] = http_get(Config, "/connections?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(maps:is_key(name, Connection)),
+
+ amqp_channel:close(Ch),
+ amqp_connection:close(Conn),
+
+ %% Exchanges
+ [Exchange1 | _] = http_get(Config, "/exchanges?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(not maps:is_key(message_stats, Exchange1)),
+ Exchange2 = http_get(Config, "/exchanges/%2F/amq.direct?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(not maps:is_key(message_stats, Exchange2)),
+
+ %% Nodes
+ [Node] = http_get(Config, "/nodes?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(not maps:is_key(channel_closed_details, Node)),
+ ?assert(not maps:is_key(channel_closed, Node)),
+ ?assert(not maps:is_key(disk_free, Node)),
+ ?assert(not maps:is_key(io_read_count, Node)),
+
+ %% Overview
+ Overview = http_get(Config, "/overview?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(maps:is_key(node, Overview)),
+ ?assert(maps:is_key(object_totals, Overview)),
+ ?assert(not maps:is_key(queue_totals, Overview)),
+ ?assert(not maps:is_key(churn_rates, Overview)),
+ ?assert(not maps:is_key(message_stats, Overview)),
+
+ %% Queues
+ http_put(Config, "/queues/%2F/test0", #{}, {group, '2xx'}),
+
+ rabbit_ct_helpers:await_condition(
+ fun() ->
+ 1 == length(http_get(Config, "/queues/%2F?lengths_age=60&lengths_incr=1", ?OK))
+ end),
+
+ [Queue1] = http_get(Config, "/queues/%2F?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(not maps:is_key(message_stats, Queue1)),
+ ?assert(not maps:is_key(messages_details, Queue1)),
+ ?assert(not maps:is_key(reductions_details, Queue1)),
+
+ Queue2 = http_get(Config, "/queues/%2F/test0?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(not maps:is_key(message_stats, Queue2)),
+ ?assert(not maps:is_key(messages_details, Queue2)),
+ ?assert(not maps:is_key(reductions_details, Queue2)),
+
+ http_delete(Config, "/queues/%2F/test0", {group, '2xx'}),
+
+ %% Vhosts
+
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+
+ rabbit_ct_helpers:await_condition(
+ fun() ->
+ length(http_get(Config, "/vhosts?lengths_age=60&lengths_incr=1", ?OK)) > 1
+ end),
+
+ [VHost | _] = http_get(Config, "/vhosts?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(not maps:is_key(message_stats, VHost)),
+ ?assert(not maps:is_key(messages_ready_details, VHost)),
+ ?assert(not maps:is_key(recv_oct, VHost)),
+ ?assert(maps:is_key(cluster_state, VHost)),
+
+ http_delete(Config, "/vhosts/vh1", {group, '2xx'}),
+
+ passed.
+
+disable_with_disable_stats_parameter_test(Config) ->
+ {Conn, Ch} = open_connection_and_channel(Config),
+
+ %% Ensure we have some queue and exchange stats, needed later
+ http_put(Config, "/queues/%2F/test0", #{}, {group, '2xx'}),
+ timer:sleep(1500),
+ amqp_channel:call(Ch, #'basic.publish'{exchange = <<>>,
+ routing_key = <<"test0">>},
+ #amqp_msg{payload = <<"message">>}),
+
+ %% Channels.
+
+ timer:sleep(1500),
+ %% Check first that stats are available
+ http_get(Config, "/channels", ?OK),
+ %% Now we can disable them
+ http_get(Config, "/channels?disable_stats=true", ?BAD_REQUEST),
+
+
+ %% Connections.
+
+ %% Check first that stats are available
+ [ConnectionStats] = http_get(Config, "/connections", ?OK),
+ ?assert(maps:is_key(recv_oct_details, ConnectionStats)),
+ %% Now we can disable them
+ [Connection] = http_get(Config, "/connections?disable_stats=true", ?OK),
+ ?assert(maps:is_key(name, Connection)),
+ ?assert(not maps:is_key(recv_oct_details, Connection)),
+
+ amqp_channel:close(Ch),
+ amqp_connection:close(Conn),
+
+ %% Exchanges.
+
+ %% Check first that stats are available
+ %% Exchange stats aren't published - even as 0 - until some messages have gone
+ %% through. At the end of this test we ensure that at least the default exchange
+ %% has something to show.
+ ExchangeStats = http_get(Config, "/exchanges", ?OK),
+ ?assert(lists:any(fun(E) ->
+ maps:is_key(message_stats, E)
+ end, ExchangeStats)),
+ %% Now we can disable them
+ Exchanges = http_get(Config, "/exchanges?disable_stats=true", ?OK),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, Exchanges)),
+ Exchange = http_get(Config, "/exchanges/%2F/amq.direct?disable_stats=true&lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(not maps:is_key(message_stats, Exchange)),
+
+ %% Nodes.
+
+ %% Check that stats are available
+ [NodeStats] = http_get(Config, "/nodes", ?OK),
+ ?assert(maps:is_key(channel_closed_details, NodeStats)),
+ %% Now we can disable them
+ [Node] = http_get(Config, "/nodes?disable_stats=true", ?OK),
+ ?assert(not maps:is_key(channel_closed_details, Node)),
+ ?assert(not maps:is_key(channel_closed, Node)),
+ ?assert(not maps:is_key(disk_free, Node)),
+ ?assert(not maps:is_key(io_read_count, Node)),
+
+
+ %% Overview.
+
+ %% Check that stats are available
+ OverviewStats = http_get(Config, "/overview", ?OK),
+ ?assert(maps:is_key(message_stats, OverviewStats)),
+ %% Now we can disable them
+ Overview = http_get(Config, "/overview?disable_stats=true&lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(not maps:is_key(queue_totals, Overview)),
+ ?assert(not maps:is_key(churn_rates, Overview)),
+ ?assert(not maps:is_key(message_stats, Overview)),
+
+ %% Queues.
+
+ %% Check that stats are available
+ [QueueStats] = http_get(Config, "/queues/%2F?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(maps:is_key(message_stats, QueueStats)),
+ %% Now we can disable them
+ [Queue] = http_get(Config, "/queues/%2F?disable_stats=true", ?OK),
+ ?assert(not maps:is_key(message_stats, Queue)),
+ ?assert(not maps:is_key(messages_details, Queue)),
+ ?assert(not maps:is_key(reductions_details, Queue)),
+
+ http_delete(Config, "/queues/%2F/test0", {group, '2xx'}),
+
+ %% Vhosts.
+
+ %% Check that stats are available
+ VHostStats = http_get(Config, "/vhosts?lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(lists:all(fun(E) ->
+ maps:is_key(message_stats, E) and
+ maps:is_key(messages_ready_details, E)
+ end, VHostStats)),
+ %% Now we can disable them
+ VHosts = http_get(Config, "/vhosts?disable_stats=true&lengths_age=60&lengths_incr=1", ?OK),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E) and
+ not maps:is_key(messages_ready_details, E)
+ end, VHosts)),
+
+ passed.
+
+sorting_test(Config) ->
+ QArgs = #{},
+ PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
+ http_put(Config, "/vhosts/vh1", none, {group, '2xx'}),
+ http_put(Config, "/permissions/vh1/guest", PermArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test0", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/test1", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test2", QArgs, {group, '2xx'}),
+ http_put(Config, "/queues/vh1/test3", QArgs, {group, '2xx'}),
+ List1 = http_get(Config, "/queues", ?OK),
+ assert_list([#{name => <<"test0">>},
+ #{name => <<"test2">>},
+ #{name => <<"test1">>},
+ #{name => <<"test3">>}], List1),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, List1)),
+ List2 = http_get(Config, "/queues?sort=name", ?OK),
+ assert_list([#{name => <<"test0">>},
+ #{name => <<"test1">>},
+ #{name => <<"test2">>},
+ #{name => <<"test3">>}], List2),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, List2)),
+ List3 = http_get(Config, "/queues?sort=vhost", ?OK),
+ assert_list([#{name => <<"test0">>},
+ #{name => <<"test2">>},
+ #{name => <<"test1">>},
+ #{name => <<"test3">>}], List3),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, List3)),
+ List4 = http_get(Config, "/queues?sort_reverse=true", ?OK),
+ assert_list([#{name => <<"test3">>},
+ #{name => <<"test1">>},
+ #{name => <<"test2">>},
+ #{name => <<"test0">>}], List4),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, List4)),
+ List5 = http_get(Config, "/queues?sort=name&sort_reverse=true", ?OK),
+ assert_list([#{name => <<"test3">>},
+ #{name => <<"test2">>},
+ #{name => <<"test1">>},
+ #{name => <<"test0">>}], List5),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, List5)),
+ List6 = http_get(Config, "/queues?sort=vhost&sort_reverse=true", ?OK),
+ assert_list([#{name => <<"test3">>},
+ #{name => <<"test1">>},
+ #{name => <<"test2">>},
+ #{name => <<"test0">>}], List6),
+ ?assert(lists:all(fun(E) ->
+ not maps:is_key(message_stats, E)
+ end, List6)),
+ %% Rather poor but at least test it doesn't blow up with dots
+ http_get(Config, "/queues?sort=owner_pid_details.name", ?OK),
+ http_delete(Config, "/queues/%2F/test0", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/test1", {group, '2xx'}),
+ http_delete(Config, "/queues/%2F/test2", {group, '2xx'}),
+ http_delete(Config, "/queues/vh1/test3", {group, '2xx'}),
+ http_delete(Config, "/vhosts/vh1", {group, '2xx'}),
+ passed.
+
+columns_test(Config) ->
+ Path = "/queues/%2F/columns.test",
+ TTL = 30000,
+ http_delete(Config, Path, [{group, '2xx'}, 404]),
+ http_put(Config, Path, [{arguments, [{<<"x-message-ttl">>, TTL}]}],
+ {group, '2xx'}),
+ Item = #{arguments => #{'x-message-ttl' => TTL}, name => <<"columns.test">>},
+ timer:sleep(2000),
+ [Item] = http_get(Config, "/queues?columns=arguments.x-message-ttl,name", ?OK),
+ Item = http_get(Config, "/queues/%2F/columns.test?columns=arguments.x-message-ttl,name", ?OK),
+ ?assert(not maps:is_key(message_stats, Item)),
+ ?assert(not maps:is_key(messages_details, Item)),
+ ?assert(not maps:is_key(reductions_details, Item)),
+ http_delete(Config, Path, {group, '2xx'}),
+ passed.
+
+if_empty_unused_test(Config) ->
+ http_put(Config, "/exchanges/%2F/test", #{}, {group, '2xx'}),
+ http_put(Config, "/queues/%2F/test", #{}, {group, '2xx'}),
+ http_post(Config, "/bindings/%2F/e/test/q/test", #{}, {group, '2xx'}),
+ http_post(Config, "/exchanges/%2F/amq.default/publish",
+ msg(<<"test">>, #{}, <<"Hello world">>), ?OK),
+ http_delete(Config, "/queues/%2F/test?if-empty=true", ?BAD_REQUEST),
+ http_delete(Config, "/exchanges/%2F/test?if-unused=true", ?BAD_REQUEST),
+ http_delete(Config, "/queues/%2F/test/contents", {group, '2xx'}),
+
+ {Conn, _ConnPath, _ChPath, _ConnChPath} = get_conn(Config, "guest", "guest"),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ amqp_channel:subscribe(Ch, #'basic.consume'{queue = <<"test">> }, self()),
+ http_delete(Config, "/queues/%2F/test?if-unused=true", ?BAD_REQUEST),
+ amqp_connection:close(Conn),
+
+ http_delete(Config, "/queues/%2F/test?if-empty=true", {group, '2xx'}),
+ http_delete(Config, "/exchanges/%2F/test?if-unused=true", {group, '2xx'}),
+ passed.
+
+invalid_config_test(Config) ->
+ {Conn, _Ch} = open_connection_and_channel(Config),
+
+ timer:sleep(1500),
+
+ %% Check first that stats aren't available (configured on test setup)
+ http_get(Config, "/channels", ?BAD_REQUEST),
+ http_get(Config, "/connections", ?BAD_REQUEST),
+ http_get(Config, "/exchanges", ?BAD_REQUEST),
+
+ %% Now we can set an invalid config, stats are still available (defaults to 'false')
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, disable_management_stats, 50]),
+ http_get(Config, "/channels", ?OK),
+ http_get(Config, "/connections", ?OK),
+ http_get(Config, "/exchanges", ?OK),
+
+ %% Set a valid config again
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management, disable_management_stats, true]),
+ http_get(Config, "/channels", ?BAD_REQUEST),
+ http_get(Config, "/connections", ?BAD_REQUEST),
+ http_get(Config, "/exchanges", ?BAD_REQUEST),
+
+ %% Now we can set an invalid config in the agent, stats are still available
+ %% (defaults to 'false')
+ rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_management_agent, disable_metrics_collector, "koala"]),
+ http_get(Config, "/channels", ?OK),
+ http_get(Config, "/connections", ?OK),
+ http_get(Config, "/exchanges", ?OK),
+
+ amqp_connection:close(Conn),
+
+ passed.
+
+%% -------------------------------------------------------------------
+%% Helpers.
+%% -------------------------------------------------------------------
+
+msg(Key, Headers, Body) ->
+ msg(Key, Headers, Body, <<"string">>).
+
+msg(Key, Headers, Body, Enc) ->
+ #{exchange => <<"">>,
+ routing_key => Key,
+ properties => #{delivery_mode => 2,
+ headers => Headers},
+ payload => Body,
+ payload_encoding => Enc}.
+
+local_port(Conn) ->
+ [{sock, Sock}] = amqp_connection:info(Conn, [sock]),
+ {ok, Port} = inet:port(Sock),
+ Port.
+
+spawn_invalid(_Config, 0) ->
+ ok;
+spawn_invalid(Config, N) ->
+ Self = self(),
+ spawn(fun() ->
+ timer:sleep(rand:uniform(250)),
+ {ok, Sock} = gen_tcp:connect("localhost", amqp_port(Config), [list]),
+ ok = gen_tcp:send(Sock, "Some Data"),
+ receive_msg(Self)
+ end),
+ spawn_invalid(Config, N-1).
+
+receive_msg(Self) ->
+ receive
+ {tcp, _, [$A, $M, $Q, $P | _]} ->
+ Self ! done
+ after
+ 60000 ->
+ Self ! no_reply
+ end.
+
+wait_for_answers(0) ->
+ ok;
+wait_for_answers(N) ->
+ receive
+ done ->
+ wait_for_answers(N-1);
+ no_reply ->
+ throw(no_reply)
+ end.
+
+publish(Ch) ->
+ amqp_channel:call(Ch, #'basic.publish'{exchange = <<"">>,
+ routing_key = <<"myqueue">>},
+ #amqp_msg{payload = <<"message">>}),
+ receive
+ stop_publish ->
+ ok
+ after 50 ->
+ publish(Ch)
+ end.
+
+wait_until(_Fun, 0) ->
+ ?assert(wait_failed);
+wait_until(Fun, N) ->
+ case Fun() of
+ true ->
+ timer:sleep(1500);
+ false ->
+ timer:sleep(?COLLECT_INTERVAL + 100),
+ wait_until(Fun, N - 1)
+ end.
+
+http_post_json(Config, Path, Body, Assertion) ->
+ http_upload_raw(Config, post, Path, Body, "guest", "guest",
+ Assertion, [{"Content-Type", "application/json"}]).
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_rabbitmqadmin_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_rabbitmqadmin_SUITE.erl
new file mode 100644
index 0000000000..7a192f225a
--- /dev/null
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_rabbitmqadmin_SUITE.erl
@@ -0,0 +1,512 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_rabbitmqadmin_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-compile(export_all).
+
+all() ->
+ [ {group, list_to_atom(Py)} || Py <- find_pythons() ].
+
+groups() ->
+ Tests = [
+ help,
+ host,
+ base_uri,
+ config_file,
+ user,
+ fmt_long,
+ fmt_kvp,
+ fmt_tsv,
+ fmt_table,
+ fmt_bash,
+ vhosts,
+ users,
+ permissions,
+ alt_vhost,
+ exchanges,
+ queues,
+ queues_unicode,
+ bindings,
+ policies,
+ operator_policies,
+ parameters,
+ publish,
+ ignore_vhost,
+ sort
+ ],
+ [ {list_to_atom(Py), [], Tests} || Py <- find_pythons() ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ inets:start(),
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodename_suffix, ?MODULE}
+ ]),
+ rabbit_ct_helpers:run_setup_steps(Config1,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps() ++
+ [fun (C) ->
+ rabbit_ct_helpers:set_config(C,
+ {rabbitmqadmin_path,
+ rabbitmqadmin(C)})
+ end
+ ]).
+
+end_per_suite(Config) ->
+ ?assertNotEqual(os:getenv("HOME"), ?config(priv_dir, Config)),
+ rabbit_ct_helpers:run_teardown_steps(Config,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps()).
+
+init_per_group(python2, Config) ->
+ rabbit_ct_helpers:set_config(Config, {python, "python2"});
+init_per_group(python3, Config) ->
+ rabbit_ct_helpers:set_config(Config, {python, "python3"});
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+init_per_testcase(config_file, Config) ->
+ Home = os:getenv("HOME"),
+ os:putenv("HOME", ?config(priv_dir, Config)),
+ rabbit_ct_helpers:set_config(Config, {env_home, Home});
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase).
+
+end_per_testcase(config_file, Config) ->
+ Home = rabbit_ct_helpers:get_config(Config, env_home),
+ os:putenv("HOME", Home);
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_finished(Config, Testcase).
+
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+help(Config) ->
+ {ok, _} = run(Config, ["--help"]),
+ {ok, _} = run(Config, ["help", "subcommands"]),
+ {ok, _} = run(Config, ["help", "config"]),
+ {error, _, _} = run(Config, ["help", "astronomy"]).
+
+host(Config) ->
+ {ok, _} = run(Config, ["show", "overview"]),
+ {ok, _} = run(Config, ["--host", "localhost", "show", "overview"]),
+ {error, _, _} = run(Config, ["--host", "some-host-that-does-not-exist",
+ "show", "overview"]).
+
+base_uri(Config) ->
+ {ok, _} = run(Config, ["--base-uri", "http://localhost", "list", "exchanges"]),
+ {ok, _} = run(Config, ["--base-uri", "http://localhost/", "list", "exchanges"]),
+ {ok, _} = run(Config, ["--base-uri", "http://localhost", "--vhost", "/", "list", "exchanges"]),
+ {ok, _} = run(Config, ["--base-uri", "http://localhost/", "--vhost", "/", "list", "exchanges"]),
+ {error, _, _} = run(Config, ["--base-uri", "https://some-host-that-does-not-exist:15672/",
+ "list", "exchanges"]),
+ {error, _, _} = run(Config, ["--base-uri", "http://localhost:15672/", "--vhost", "some-vhost-that-does-not-exist",
+ "list", "exchanges"]).
+
+
+config_file(Config) ->
+ MgmtPort = integer_to_list(http_api_port(Config)),
+ {_DefConf, TestConf} = write_test_config(Config),
+
+ %% try using a non-existent config file
+ ?assertMatch({error, _, _}, run(Config, ["--config", "/tmp/no-such-config-file", "show", "overview"])),
+ %% use a config file section with a reachable endpoint and correct credentials
+ ?assertMatch({ok, _}, run(Config, ["--config", TestConf, "--node", "reachable", "show", "overview"])),
+
+ %% Default node in the config file uses an unreachable endpoint. Note that
+ %% the function that drives rabbitmqadmin will specify a --port and that will override
+ %% the config file value.
+ ?assertMatch({error, _, _}, run(Config, ["--config", TestConf, "show", "overview"])),
+
+ %% overrides hostname and port using --base-uri
+ BaseURI = rabbit_misc:format("http://localhost:~s", [MgmtPort]),
+ ?assertMatch({ok, _}, run(Config, ["--config", TestConf, "--base-uri", BaseURI, "show", "overview"])),
+
+ %% overrides --host and --port on the command line
+ ?assertMatch({ok, _}, run(Config, ["--config", TestConf, "--node", "default", "--host", "localhost", "--port", MgmtPort, "show", "overview"])),
+
+ ?assertMatch({ok, _}, run(Config, ["show", "overview"])),
+ ?assertMatch({error, _, _}, run(Config, ["--node", "bad_credentials", "show", "overview"])),
+ %% overrides --username and --password on the command line with correct credentials
+ ?assertMatch({ok, _}, run(Config, ["--node", "bad_credentials", "--username", "guest", "--password", "guest", "show", "overview"])),
+ %% overrides --username and --password on the command line with incorrect credentials
+ ?assertMatch({error, _, _}, run(Config, ["--node", "bad_credentials", "--username", "gu3st", "--password", "guesTTTT", "show", "overview"])).
+
+user(Config) ->
+ ?assertMatch({ok, _}, run(Config, ["--user", "guest", "--password", "guest", "show", "overview"])),
+ ?assertMatch({error, _, _}, run(Config, ["--user", "no", "--password", "guest", "show", "overview"])),
+ ?assertMatch({error, _, _}, run(Config, ["--user", "guest", "--password", "no", "show", "overview"])).
+
+fmt_long(Config) ->
+ Out = multi_line_string([
+ "",
+ "--------------------------------------------------------------------------------",
+ "",
+ " name: /",
+ "tracing: False",
+ "",
+ "--------------------------------------------------------------------------------",
+ "" ]),
+ ?assertEqual({ok, Out}, run(Config, ["--format", "long", "list", "vhosts", "name", "tracing"])).
+
+fmt_kvp(Config) ->
+ Out = multi_line_string(["name=\"/\" tracing=\"False\""]),
+ ?assertEqual({ok, Out}, run(Config, ["--format", "kvp", "list", "vhosts", "name", "tracing"])).
+
+fmt_tsv(Config) ->
+ Out = multi_line_string([
+ "name\ttracing",
+ "/\tFalse"
+ ]),
+ ?assertEqual({ok, Out}, run(Config, ["--format", "tsv", "list", "vhosts", "name", "tracing"])).
+
+fmt_table(Config) ->
+ Out = multi_line_string([
+ "+------+---------+",
+ "| name | tracing |",
+ "+------+---------+",
+ "| / | False |",
+ "+------+---------+"
+ ]),
+ ?assertEqual({ok, Out}, run(Config, ["list", "vhosts", "name", "tracing"])),
+ ?assertEqual({ok, Out}, run(Config, ["--format", "table", "list",
+ "vhosts", "name", "tracing"])).
+
+fmt_bash(Config) ->
+ {ok, "/\n"} = run(Config, ["--format", "bash", "list",
+ "vhosts", "name", "tracing"]).
+
+vhosts(Config) ->
+ {ok, ["/"]} = run_list(Config, l("vhosts")),
+ {ok, _} = run(Config, ["declare", "vhost", "name=foo"]),
+ {ok, ["/", "foo"]} = run_list(Config, l("vhosts")),
+ {ok, _} = run(Config, ["delete", "vhost", "name=foo"]),
+ {ok, ["/"]} = run_list(Config, l("vhosts")).
+
+users(Config) ->
+ {ok, ["guest"]} = run_list(Config, l("users")),
+ {error, _, _} = run(Config, ["declare", "user", "name=foo"]),
+ {ok, _} = run(Config, ["declare", "user", "name=foo", "password=pass", "tags="]),
+
+ {ok, _} = run(Config, ["declare", "user", "name=with_password_hash1", "password_hash=MmJiODBkNTM3YjFkYTNlMzhiZDMwMzYxYWE4NTU2ODZiZGUwZWFjZDcxNjJmZWY2YTI1ZmU5N2JmNTI3YTI1Yg==",
+ "tags=management"]),
+ {ok, _} = run(Config, ["declare", "user", "name=with_password_hash2",
+ "hashing_algorithm=rabbit_password_hashing_sha256", "password_hash=MmJiODBkNTM3YjFkYTNlMzhiZDMwMzYxYWE4NTU2ODZiZGUwZWFjZDcxNjJmZWY2YTI1ZmU5N2JmNTI3YTI1Yg==",
+ "tags=management"]),
+ {ok, _} = run(Config, ["declare", "user", "name=with_password_hash3",
+ "hashing_algorithm=rabbit_password_hashing_sha512", "password_hash=YmQyYjFhYWY3ZWY0ZjA5YmU5ZjUyY2UyZDhkNTk5Njc0ZDgxYWE5ZDZhNDQyMTY5NmRjNGQ5M2RkMDYxOWQ2ODJjZTU2YjRkNjRhOWVmMDk3NzYxY2VkOTllMGY2NzI2NWI1Zjc2MDg1ZTViMGVlN2NhNDY5NmIyYWQ2ZmUyYjI=",
+ "tags=management"]),
+
+ {error, _, _} = run(Config, ["declare", "user", "name=with_password_hash4",
+ "hashing_algorithm=rabbit_password_hashing_sha256", "password_hash=not-base64-encoded",
+ "tags=management"]),
+
+
+ {ok, ["foo", "guest",
+ "with_password_hash1",
+ "with_password_hash2",
+ "with_password_hash3"]} = run_list(Config, l("users")),
+
+ {ok, _} = run(Config, ["delete", "user", "name=foo"]),
+ {ok, _} = run(Config, ["delete", "user", "name=with_password_hash1"]),
+ {ok, _} = run(Config, ["delete", "user", "name=with_password_hash2"]),
+ {ok, _} = run(Config, ["delete", "user", "name=with_password_hash3"]),
+
+ {ok, ["guest"]} = run_list(Config, l("users")).
+
+permissions(Config) ->
+ {ok, _} = run(Config, ["declare", "vhost", "name=foo"]),
+ {ok, _} = run(Config, ["declare", "user", "name=bar", "password=pass", "tags="]),
+ %% The user that creates the vhosts gets permission automatically
+ %% See https://github.com/rabbitmq/rabbitmq-management/issues/444
+ {ok, [["guest", "/"],
+ ["guest", "foo"]]} = run_table(Config, ["list", "permissions",
+ "user", "vhost"]),
+ {ok, _} = run(Config, ["declare", "permission", "user=bar", "vhost=foo",
+ "configure=.*", "write=.*", "read=.*"]),
+ {ok, [["guest", "/"], ["bar", "foo"], ["guest", "foo"]]}
+ = run_table(Config, ["list", "permissions", "user", "vhost"]),
+ {ok, _} = run(Config, ["delete", "user", "name=bar"]),
+ {ok, _} = run(Config, ["delete", "vhost", "name=foo"]).
+
+alt_vhost(Config) ->
+ {ok, _} = run(Config, ["declare", "vhost", "name=foo"]),
+ {ok, _} = run(Config, ["declare", "permission", "user=guest", "vhost=foo",
+ "configure=.*", "write=.*", "read=.*"]),
+ {ok, _} = run(Config, ["declare", "queue", "name=in_/"]),
+ {ok, _} = run(Config, ["--vhost", "foo", "declare", "queue", "name=in_foo"]),
+ {ok, [["/", "in_/"], ["foo", "in_foo"]]} = run_table(Config, ["list", "queues",
+ "vhost", "name"]),
+ {ok, _} = run(Config, ["--vhost", "foo", "delete", "queue", "name=in_foo"]),
+ {ok, _} = run(Config, ["delete", "queue", "name=in_/"]),
+ {ok, _} = run(Config, ["delete", "vhost", "name=foo"]).
+
+exchanges(Config) ->
+ {ok, _} = run(Config, ["declare", "exchange", "name=foo", "type=direct"]),
+ {ok, ["amq.direct",
+ "amq.fanout",
+ "amq.headers",
+ "amq.match",
+ "amq.rabbitmq.trace",
+ "amq.topic",
+ "foo"]} = run_list(Config, l("exchanges")),
+ {ok, _} = run(Config, ["delete", "exchange", "name=foo"]).
+
+queues(Config) ->
+ {ok, _} = run(Config, ["declare", "queue", "name=foo"]),
+ {ok, ["foo"]} = run_list(Config, l("queues")),
+ {ok, _} = run(Config, ["delete", "queue", "name=foo"]).
+
+queues_unicode(Config) ->
+ {ok, _} = run(Config, ["declare", "queue", "name=ööö"]),
+ %% 'ö' is encoded as 0xC3 0xB6 in UTF-8. We use a a list of
+ %% integers here because a binary literal would not work with Erlang
+ %% R16B03.
+ QUEUE_NAME = [195,182, 195,182, 195,182],
+ {ok, [QUEUE_NAME]} = run_list(Config, l("queues")),
+ {ok, _} = run(Config, ["delete", "queue", "name=ööö"]).
+
+bindings(Config) ->
+ {ok, _} = run(Config, ["declare", "queue", "name=foo"]),
+ {ok, _} = run(Config, ["declare", "binding", "source=amq.direct",
+ "destination=foo", "destination_type=queue",
+ "routing_key=test"]),
+ {ok, [["foo", "queue", "foo"],
+ ["amq.direct", "foo", "queue", "test"]
+ ]} = run_table(Config,
+ ["list", "bindings",
+ "source", "destination",
+ "destination_type", "routing_key"]),
+ {ok, _} = run(Config, ["delete", "queue", "name=foo"]).
+
+policies(Config) ->
+ {ok, _} = run(Config, ["declare", "policy", "name=ha",
+ "pattern=.*", "definition={\"ha-mode\":\"all\"}"]),
+ {ok, [["ha", "/", ".*", "{\"ha-mode\": \"all\"}"]]} =
+ run_table(Config, ["list", "policies", "name",
+ "vhost", "pattern", "definition"]),
+ {ok, _} = run(Config, ["delete", "policy", "name=ha"]).
+
+operator_policies(Config) ->
+ {ok, _} = run(Config, ["declare", "operator_policy", "name=len",
+ "pattern=.*", "definition={\"max-length\":100}"]),
+ {ok, [["len", "/", ".*", "{\"max-length\": 100}"]]} =
+ run_table(Config, ["list", "operator_policies", "name",
+ "vhost", "pattern", "definition"]),
+ {ok, _} = run(Config, ["delete", "operator_policy", "name=len"]).
+
+parameters(Config) ->
+ ok = rpc(Config, rabbit_mgmt_runtime_parameters_util, register, []),
+ {ok, _} = run(Config, ["declare", "parameter", "component=test",
+ "name=good", "value=123"]),
+ {ok, [["test", "good", "/", "123"]]} = run_table(Config, ["list",
+ "parameters",
+ "component",
+ "name",
+ "vhost",
+ "value"]),
+ {ok, _} = run(Config, ["delete", "parameter", "component=test", "name=good"]),
+ ok = rpc(Config, rabbit_mgmt_runtime_parameters_util, unregister, []).
+
+publish(Config) ->
+ {ok, _} = run(Config, ["declare", "queue", "name=test"]),
+ {ok, _} = run(Config, ["publish", "routing_key=test", "payload=test_1"]),
+ {ok, _} = run(Config, ["publish", "routing_key=test", "payload=test_2"]),
+ %% publish with stdin
+ %% TODO: this must support Python 3 as well
+ Py = find_python2(),
+ {ok, _} = rabbit_ct_helpers:exec([Py, "-c",
+ publish_with_stdin_python_program(Config, "test_3")],
+ []),
+
+ M = exp_msg("test", 2, "False", "test_1"),
+ {ok, [M]} = run_table(Config, ["get", "queue=test", "ackmode=ack_requeue_false"]),
+ M2 = exp_msg("test", 1, "False", "test_2"),
+ {ok, [M2]} = run_table(Config, ["get", "queue=test", "ackmode=ack_requeue_true"]),
+ M3 = exp_msg("test", 1, "True", "test_2"),
+ {ok, [M3]} = run_table(Config, ["get",
+ "queue=test",
+ "ackmode=ack_requeue_false"]),
+ M4 = exp_msg("test", 0, "False", "test_3"),
+ {ok, [M4]} = run_table(Config, ["get",
+ "queue=test",
+ "ackmode=ack_requeue_false"]),
+ {ok, _} = run(Config, ["publish", "routing_key=test", "payload=test_4"]),
+ Fn = filename:join(?config(priv_dir, Config), "publish_test_4"),
+
+ {ok, _} = run(Config, ["get", "queue=test", "ackmode=ack_requeue_false", "payload_file=" ++ Fn]),
+ {ok, <<"test_4">>} = file:read_file(Fn),
+ {ok, _} = run(Config, ["delete", "queue", "name=test"]).
+
+ignore_vhost(Config) ->
+ {ok, _} = run(Config, ["--vhost", "/", "show", "overview"]),
+ {ok, _} = run(Config, ["--vhost", "/", "list", "users"]),
+ {ok, _} = run(Config, ["--vhost", "/", "list", "vhosts"]),
+ {ok, _} = run(Config, ["--vhost", "/", "list", "nodes"]),
+ {ok, _} = run(Config, ["--vhost", "/", "list", "permissions"]),
+ {ok, _} = run(Config, ["--vhost", "/", "declare", "user",
+ "name=foo", "password=pass", "tags="]),
+ {ok, _} = run(Config, ["delete", "user", "name=foo"]).
+
+sort(Config) ->
+ {ok, _} = run(Config, ["declare", "queue", "name=foo"]),
+ {ok, _} = run(Config, ["declare", "binding", "source=amq.direct",
+ "destination=foo", "destination_type=queue",
+ "routing_key=bbb"]),
+ {ok, _} = run(Config, ["declare", "binding", "source=amq.topic",
+ "destination=foo", "destination_type=queue",
+ "routing_key=aaa"]),
+ {ok, [["foo"],
+ ["amq.direct", "bbb"],
+ ["amq.topic", "aaa"]]} = run_table(Config, ["--sort", "source",
+ "list", "bindings",
+ "source", "routing_key"]),
+ {ok, [["amq.topic", "aaa"],
+ ["amq.direct", "bbb"],
+ ["foo"]]} = run_table(Config, ["--sort", "routing_key",
+ "list", "bindings", "source",
+ "routing_key"]),
+ {ok, [["amq.topic", "aaa"],
+ ["amq.direct", "bbb"],
+ ["foo"]]} = run_table(Config, ["--sort", "source",
+ "--sort-reverse", "list",
+ "bindings", "source",
+ "routing_key"]),
+ {ok, _} = run(Config, ["delete", "queue", "name=foo"]).
+
+%% -------------------------------------------------------------------
+%% Utilities
+%% -------------------------------------------------------------------
+
+exp_msg(Key, Count, Redelivered, Payload) ->
+ % routing_key, message_count,
+ % payload, payload_bytes,
+ % payload_encoding, redelivered
+ [Key, integer_to_list(Count),
+ Payload, integer_to_list(length(Payload)),
+ "string", Redelivered].
+
+rpc(Config, M, F, A) ->
+ rabbit_ct_broker_helpers:rpc(Config, 0, M, F, A).
+
+l(Thing) ->
+ ["list", Thing, "name"].
+
+multi_line_string(Lines) ->
+ lists:flatten([string:join(Lines, io_lib:nl()), io_lib:nl()]).
+
+run_table(Config, Args) ->
+ {ok, Lines} = run_list(Config, Args),
+ Tokens = [string:tokens(L, "\t") || L <- Lines],
+ {ok, Tokens}.
+
+run_list(Config, Args) ->
+ A = ["-f", "tsv", "-q"],
+ case run(Config, A ++ Args) of
+ {ok, Out} -> {ok, string:tokens(Out, io_lib:nl())};
+ Err -> Err
+ end.
+
+run(Config, Args) ->
+ Py = rabbit_ct_helpers:get_config(Config, python),
+ MgmtPort = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mgmt),
+ RmqAdmin = rabbit_ct_helpers:get_config(Config, rabbitmqadmin_path),
+ rabbit_ct_helpers:exec([Py,
+ RmqAdmin,
+ "-P",
+ integer_to_list(MgmtPort)] ++ Args,
+ [drop_stdout]).
+
+rabbitmqadmin(Config) ->
+ filename:join([?config(current_srcdir, Config), "bin", "rabbitmqadmin"]).
+
+find_pythons() ->
+ Py2 = rabbit_ct_helpers:exec(["python2", "-V"]),
+ Py3 = rabbit_ct_helpers:exec(["python3", "-V"]),
+ case {Py2, Py3} of
+ {{ok, _}, {ok, _}} -> ["python2", "python3"];
+ {{ok, _}, _} -> ["python2"];
+ {_, {ok, _}} -> ["python3"];
+ _ -> erlang:error("python not found")
+ end.
+
+find_python2() ->
+ Py2 = rabbit_ct_helpers:exec(["python2", "-V"]),
+ Py27 = rabbit_ct_helpers:exec(["python2.7", "-V"]),
+ case {Py2, Py27} of
+ {{ok, _}, {ok, _}} -> ["python2.7"];
+ {{ok, _}, _} -> ["python2"];
+ {_, {ok, _}} -> ["python2.7"];
+ _ -> "python2"
+ end.
+
+publish_with_stdin_python_program(Config, In) ->
+ MgmtPort = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mgmt),
+ RmqAdmin = rabbit_ct_helpers:get_config(Config, rabbitmqadmin_path),
+ Py = find_python2(),
+ "import subprocess;" ++
+ "proc = subprocess.Popen(['" ++ Py ++ "', '" ++ RmqAdmin ++ "', '-P', '" ++ integer_to_list(MgmtPort) ++
+ "', 'publish', 'routing_key=test'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE);" ++
+ "(stdout, stderr) = proc.communicate('" ++ In ++ "');" ++
+ "exit(proc.returncode)".
+
+write_test_config(Config) ->
+ MgmtPort = integer_to_list(http_api_port(Config)),
+ PrivDir = ?config(priv_dir, Config),
+ DefaultConfig = [
+ "[bad_credentials]",
+ "hostname = localhost",
+ "port =" ++ MgmtPort,
+ "username = gu/est",
+ "password = gu\\est",
+ "declare_vhost = /",
+ "vhost = /",
+ "",
+ "[bad_host]",
+ "hostname = non-existent.acme.com",
+ "port = " ++ MgmtPort,
+ "username = guest",
+ "password = guest"
+ ],
+ TestConfig = [
+ "[reachable]",
+ "hostname = localhost",
+ "port = " ++ MgmtPort,
+ "username = guest",
+ "password = guest",
+ "declare_vhost = /",
+ "vhost = /",
+ "",
+ "[default]",
+ "hostname = non-existent.acme.com",
+ "port = 99799",
+ "username = guest",
+ "password = guest"
+ ],
+ DefaultConfig1 = [string:join(DefaultConfig, io_lib:nl()), io_lib:nl()],
+ TestConfig1 = [string:join(TestConfig, io_lib:nl()), io_lib:nl()],
+ FnDefault = filename:join(PrivDir, ".rabbitmqadmin.conf"),
+ FnTest = filename:join(PrivDir, "test-config"),
+ file:write_file(FnDefault, DefaultConfig1),
+ file:write_file(FnTest, TestConfig1),
+ {FnDefault, FnTest}.
+
+http_api_port(Config) ->
+ rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mgmt).
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_runtime_parameters_util.erl b/deps/rabbitmq_management/test/rabbit_mgmt_runtime_parameters_util.erl
new file mode 100644
index 0000000000..0ac911b7c0
--- /dev/null
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_runtime_parameters_util.erl
@@ -0,0 +1,63 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_runtime_parameters_util).
+-behaviour(rabbit_runtime_parameter).
+-behaviour(rabbit_policy_validator).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-export([validate/5, notify/5, notify_clear/4]).
+-export([register/0, unregister/0]).
+-export([validate_policy/1]).
+-export([register_policy_validator/0, unregister_policy_validator/0]).
+
+%----------------------------------------------------------------------------
+
+register() ->
+ rabbit_registry:register(runtime_parameter, <<"test">>, ?MODULE).
+
+unregister() ->
+ rabbit_registry:unregister(runtime_parameter, <<"test">>).
+
+validate(_, <<"test">>, <<"good">>, _Term, _User) -> ok;
+validate(_, <<"test">>, <<"maybe">>, <<"good">>, _User) -> ok;
+validate(_, <<"test">>, <<"admin">>, _Term, none) -> ok;
+validate(_, <<"test">>, <<"admin">>, _Term, User) ->
+ case lists:member(administrator, User#user.tags) of
+ true -> ok;
+ false -> {error, "meh", []}
+ end;
+validate(_, <<"test">>, _, _, _) -> {error, "meh", []}.
+
+notify(_, _, _, _, _) -> ok.
+notify_clear(_, _, _, _) -> ok.
+
+%----------------------------------------------------------------------------
+
+register_policy_validator() ->
+ rabbit_registry:register(policy_validator, <<"testeven">>, ?MODULE),
+ rabbit_registry:register(policy_validator, <<"testpos">>, ?MODULE).
+
+unregister_policy_validator() ->
+ rabbit_registry:unregister(policy_validator, <<"testeven">>),
+ rabbit_registry:unregister(policy_validator, <<"testpos">>).
+
+validate_policy([{<<"testeven">>, Terms}]) when is_list(Terms) ->
+ case length(Terms) rem 2 =:= 0 of
+ true -> ok;
+ false -> {error, "meh", []}
+ end;
+
+validate_policy([{<<"testpos">>, Terms}]) when is_list(Terms) ->
+ case lists:all(fun (N) -> is_integer(N) andalso N > 0 end, Terms) of
+ true -> ok;
+ false -> {error, "meh", []}
+ end;
+
+validate_policy(_) ->
+ {error, "meh", []}.
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_stats_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_stats_SUITE.erl
new file mode 100644
index 0000000000..7ebb04da69
--- /dev/null
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_stats_SUITE.erl
@@ -0,0 +1,458 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_stats_SUITE).
+
+-include_lib("proper/include/proper.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+-compile(export_all).
+-compile({no_auto_import, [ceil/1]}).
+
+all() ->
+ [
+ {group, parallel_tests}
+ ].
+
+groups() ->
+ [
+ {parallel_tests, [parallel], [
+ format_rate_no_range_test,
+ format_zero_rate_no_range_test,
+ format_incremental_rate_no_range_test,
+ format_incremental_zero_rate_no_range_test,
+ format_total_no_range_test,
+ format_incremental_total_no_range_test,
+ format_rate_range_test,
+ format_incremental_rate_range_test,
+ format_incremental_zero_rate_range_test,
+ format_total_range_test,
+ format_incremental_total_range_test,
+ format_samples_range_test,
+ format_incremental_samples_range_test,
+ format_avg_rate_range_test,
+ format_incremental_avg_rate_range_test,
+ format_avg_range_test,
+ format_incremental_avg_range_test
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ Config.
+
+end_per_suite(Config) ->
+ Config.
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, _Config) ->
+ ok.
+
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(_, _Config) ->
+ ok.
+
+%% -------------------------------------------------------------------
+%% Generators.
+%% -------------------------------------------------------------------
+elements_gen() ->
+ ?LET(Length, oneof([1, 2, 3, 7, 8, 20]),
+ ?LET(Elements, list(vector(Length, int())),
+ [erlang:list_to_tuple(E) || E <- Elements])).
+
+stats_tables() ->
+ [connection_stats_coarse_conn_stats, vhost_stats_coarse_conn_stats,
+ channel_stats_fine_stats, channel_exchange_stats_fine_stats,
+ channel_queue_stats_deliver_stats, vhost_stats_fine_stats,
+ queue_stats_deliver_stats, vhost_stats_deliver_stats,
+ channel_stats_deliver_stats, channel_process_stats,
+ queue_stats_publish, queue_exchange_stats_publish,
+ exchange_stats_publish_out, exchange_stats_publish_in,
+ queue_msg_stats, vhost_msg_stats, queue_process_stats,
+ node_coarse_stats, node_persister_stats,
+ node_node_coarse_stats, queue_msg_rates, vhost_msg_rates,
+ connection_churn_rates
+ ].
+
+sample_size(large) ->
+ choose(15, 200);
+sample_size(small) ->
+ choose(0, 1).
+
+sample_gen(_Table, 0) ->
+ [];
+sample_gen(Table, 1) ->
+ ?LET(Stats, stats_gen(Table), [Stats || _ <- lists:seq(1, 5)]);
+sample_gen(Table, N) ->
+ vector(N, stats_gen(Table)).
+
+content_gen(Size) ->
+ ?LET({Table, SampleSize}, {oneof(stats_tables()), sample_size(Size)},
+ ?LET(Stats, sample_gen(Table, SampleSize),
+ {Table, Stats})).
+
+interval_gen() ->
+ %% Keep it at most 150ms, so the test runs in a reasonable time
+ choose(1, 150).
+
+stats_gen(Table) ->
+ ?LET(Vector, vector(length(?stats_per_table(Table)), choose(1, 100)),
+ list_to_tuple(Vector)).
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+%% Rates for 3 or more monotonically increasing samples will always be > 0
+format_rate_no_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, rate_check(fun(Rate) -> Rate > 0 end),
+ false, fun no_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+prop_format(SampleSize, Check, Incremental, RangeFun) ->
+ ?FORALL(
+ {{Table, Data}, Interval}, {content_gen(SampleSize), interval_gen()},
+ begin
+ {LastTS, Slide, Total, Samples}
+ = create_slide(Data, Interval, Incremental, SampleSize),
+ Range = RangeFun(Slide, LastTS, Interval),
+ SamplesFun = fun() -> [Slide] end,
+ InstantRateFun = fun() -> [Slide] end,
+ Results = rabbit_mgmt_stats:format_range(Range, LastTS, Table, 5000,
+ InstantRateFun,
+ SamplesFun),
+ ?WHENFAIL(io:format("Got: ~p~nSlide: ~p~nRange~p~n", [Results, Slide, Range]),
+ Check(Results, Total, Samples, Table))
+ end).
+
+%% Rates for 1 or no samples will always be 0.0 as there aren't
+%% enough datapoints to calculate the instant rate
+format_zero_rate_no_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(small, rate_check(fun(Rate) -> Rate == 0.0 end),
+ false, fun no_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+%% Rates for 3 or more monotonically increasing incremental samples will always be > 0
+format_incremental_rate_no_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, rate_check(fun(Rate) -> Rate > 0 end),
+ true, fun no_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+%% Rates for 1 or no samples will always be 0.0 as there aren't
+%% enough datapoints to calculate the instant rate
+format_incremental_zero_rate_no_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(small, rate_check(fun(Rate) -> Rate == 0.0 end),
+ true, fun no_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+%% Checking totals
+format_total_no_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_total/4, false, fun no_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+format_incremental_total_no_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_total/4, true, fun no_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+%%---------------------
+%% Requests using range
+%%---------------------
+format_rate_range_test(_Config) ->
+ %% Request a range bang on the middle, so we ensure no padding is applied
+ Fun = fun() ->
+ prop_format(large, rate_check(fun(Rate) -> Rate > 0 end),
+ false, fun full_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+%% Rates for 3 or more monotonically increasing incremental samples will always be > 0
+format_incremental_rate_range_test(_Config) ->
+ %% Request a range bang on the middle, so we ensure no padding is applied
+ Fun = fun() ->
+ prop_format(large, rate_check(fun(Rate) -> Rate > 0 end),
+ true, fun full_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+%% Rates for 1 or no samples will always be 0.0 as there aren't
+%% enough datapoints to calculate the instant rate
+format_incremental_zero_rate_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(small, rate_check(fun(Rate) -> Rate == 0.0 end),
+ true, fun full_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+%% Checking totals
+format_total_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_total/4, false, fun full_range_plus_interval/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+format_incremental_total_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_total/4, true, fun full_range_plus_interval/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+format_samples_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_samples/4, false, fun full_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+format_incremental_samples_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_samples/4, true, fun full_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+format_avg_rate_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_avg_rate/4, false, fun full_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+format_incremental_avg_rate_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_avg_rate/4, true, fun full_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+format_avg_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_avg/4, false, fun full_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+
+format_incremental_avg_range_test(_Config) ->
+ Fun = fun() ->
+ prop_format(large, fun check_avg/4, true, fun full_range/3)
+ end,
+ rabbit_ct_proper_helpers:run_proper(Fun, [], 100).
+%% -------------------------------------------------------------------
+%% Helpers
+%% -------------------------------------------------------------------
+details(Table) ->
+ [list_to_atom(atom_to_list(S) ++ "_details") || S <- ?stats_per_table(Table)].
+
+add(T1, undefined) ->
+ T1;
+add(T1, T2) ->
+ list_to_tuple(lists:zipwith(fun(A, B) -> A + B end, tuple_to_list(T1), tuple_to_list(T2))).
+
+create_slide(Data, Interval, Incremental, SampleSize) ->
+ %% Use the samples as increments for data generation,
+ %% so we have always increasing counters
+ Now = 0,
+ Slide = exometer_slide:new(Now, 60 * 1000,
+ [{interval, Interval}, {incremental, Incremental}]),
+ Sleep = min_wait(Interval, Data),
+ lists:foldl(
+ fun(E, {TS0, Acc, Total, Samples}) ->
+ TS1 = TS0 + Sleep,
+ NewTotal = add(E, Total),
+ %% We use small sample sizes to keep a constant rate
+ Sample = create_sample(E, Incremental, SampleSize, NewTotal),
+ {TS1, exometer_slide:add_element(TS1, Sample, Acc), NewTotal,
+ [NewTotal | Samples]}
+ end, {Now, Slide, undefined, []}, Data).
+
+create_sample(E, Incremental, SampleSize, NewTotal) ->
+ case {Incremental, SampleSize} of
+ {false, small} -> E;
+ {true, small} ->
+ zero_tuple(E);
+ {false, _} ->
+ %% Guarantees a monotonically increasing counter
+ NewTotal;
+ {true, _} -> E
+ end.
+
+zero_tuple(E) ->
+ Length = length(tuple_to_list(E)),
+ list_to_tuple([0 || _ <- lists:seq(1, Length)]).
+
+min_wait(_, []) ->
+ 0;
+min_wait(Interval, Data) ->
+ %% Send at constant intervals for Interval * 3 ms. This eventually ensures several samples
+ %% on the same interval, max execution time of Interval * 5 and also enough samples to
+ %% generate a rate.
+ case round((Interval * 3) / length(Data)) of
+ 0 -> 1;
+ Min -> Min
+ end.
+
+is_average_time(Atom) ->
+ case re:run(atom_to_list(Atom), "_avg_time$") of
+ nomatch ->
+ false;
+ _ ->
+ true
+ end.
+
+rate_check(RateCheck) ->
+ fun(Results, _, _, Table) ->
+ Check =
+ fun(Detail) ->
+ Rate = proplists:get_value(rate, proplists:get_value(Detail, Results), 0),
+ RateCheck(Rate)
+ end,
+ lists:all(Check, details(Table))
+ end.
+
+check_total(Results, Totals, _Samples, Table) ->
+ Expected = lists:zip(?stats_per_table(Table), tuple_to_list(Totals)),
+ lists:all(fun({K, _} = E) ->
+ case is_average_time(K) of
+ false -> lists:member(E, Results);
+ true -> lists:keymember(K, 1, Results)
+ end
+ end, Expected).
+
+is_avg_time_details(Detail) ->
+ match == re:run(atom_to_list(Detail), "avg_time_details$", [{capture, none}]).
+
+check_samples(Results, _Totals, Samples, Table) ->
+ Details = details(Table),
+ %% Lookup list for the position of the key in the stats tuple
+ Pairs = lists:zip(Details, lists:seq(1, length(Details))),
+
+ NonAvgTimeDetails = lists:filter(fun(D) ->
+ not is_avg_time_details(D)
+ end, Details),
+
+ %% Check that all samples in the results match one of the samples in the inputs
+ lists:all(fun(Detail) ->
+ RSamples = get_from_detail(samples, Detail, Results),
+ lists:all(fun(RS) ->
+ Value = proplists:get_value(sample, RS),
+ case Value of
+ 0 ->
+ true;
+ _ ->
+ lists:keymember(Value,
+ proplists:get_value(Detail, Pairs),
+ Samples)
+ end
+ end, RSamples)
+ end, NonAvgTimeDetails)
+ %% ensure that not all samples are 0
+ andalso lists:all(fun(Detail) ->
+ RSamples = get_from_detail(samples, Detail, Results),
+ lists:any(fun(RS) ->
+ 0 =/= proplists:get_value(sample, RS)
+ end, RSamples)
+ end, Details).
+
+check_avg_rate(Results, _Totals, _Samples, Table) ->
+ Details = details(Table),
+
+ NonAvgTimeDetails = lists:filter(fun(D) ->
+ not is_avg_time_details(D)
+ end, Details),
+
+ AvgTimeDetails = lists:filter(fun(D) ->
+ is_avg_time_details(D)
+ end, Details),
+
+ lists:all(fun(Detail) ->
+ AvgRate = get_from_detail(avg_rate, Detail, Results),
+ Samples = get_from_detail(samples, Detail, Results),
+ S2 = proplists:get_value(sample, hd(Samples)),
+ T2 = proplists:get_value(timestamp, hd(Samples)),
+ S1 = proplists:get_value(sample, lists:last(Samples)),
+ T1 = proplists:get_value(timestamp, lists:last(Samples)),
+ AvgRate == ((S2 - S1) * 1000 / (T2 - T1))
+ end, NonAvgTimeDetails) andalso
+ lists:all(fun(Detail) ->
+ Avg = get_from_detail(avg_rate, Detail, Results),
+ Samples = get_from_detail(samples, Detail, Results),
+ First = proplists:get_value(sample, hd(Samples)),
+ Avg == First
+ end, AvgTimeDetails).
+
+check_avg(Results, _Totals, _Samples, Table) ->
+ Details = details(Table),
+
+ NonAvgTimeDetails = lists:filter(fun(D) ->
+ not is_avg_time_details(D)
+ end, Details),
+
+ AvgTimeDetails = lists:filter(fun(D) ->
+ is_avg_time_details(D)
+ end, Details),
+
+ lists:all(fun(Detail) ->
+ Avg = get_from_detail(avg, Detail, Results),
+ Samples = get_from_detail(samples, Detail, Results),
+ Sum = lists:sum([proplists:get_value(sample, S) || S <- Samples]),
+ Avg == (Sum / length(Samples))
+ end, NonAvgTimeDetails) andalso
+ lists:all(fun(Detail) ->
+ Avg = get_from_detail(avg, Detail, Results),
+ Samples = get_from_detail(samples, Detail, Results),
+ First = proplists:get_value(sample, hd(Samples)),
+ Avg == First
+ end, AvgTimeDetails).
+
+get_from_detail(Tag, Detail, Results) ->
+ proplists:get_value(Tag, proplists:get_value(Detail, Results), []).
+
+full_range(Slide, Last, Interval) ->
+ LastTS = case exometer_slide:last_two(Slide) of
+ [] -> Last;
+ [{L, _} | _] -> L
+ end,
+ #range{first = 0, last = LastTS, incr = Interval}.
+
+full_range_plus_interval(Slide, Last, Interval) ->
+ LastTS = case exometer_slide:last_two(Slide) of
+ [] -> Last;
+ [{L, _} | _] -> L
+ end,
+ % were adding two intervals here due to rounding occasionally pushing the last
+ % sample into the next time "bucket"
+ #range{first = 0, last = LastTS + Interval + Interval, incr = Interval}.
+
+no_range(_Slide, _LastTS, _Interval) ->
+ no_range.
+
+%% Generate a well-formed interval from Start using Interval steps
+last_ts(First, Last, Interval) ->
+ ceil(((Last - First) / Interval)) * Interval + First.
+
+ceil(X) when X < 0 ->
+ trunc(X);
+ceil(X) ->
+ T = trunc(X),
+ case X - T == 0 of
+ true -> T;
+ false -> T + 1
+ end.
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_test_db_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_test_db_SUITE.erl
new file mode 100644
index 0000000000..03a36ee138
--- /dev/null
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_test_db_SUITE.erl
@@ -0,0 +1,469 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_test_db_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("rabbit_common/include/rabbit_core_metrics.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl").
+-import(rabbit_mgmt_test_util, [assert_list/2,
+ reset_management_settings/1]).
+
+-import(rabbit_misc, [pget/2]).
+
+-compile(export_all).
+-compile({no_auto_import, [ceil/1]}).
+
+all() ->
+ [
+ {group, non_parallel_tests}
+ ].
+
+groups() ->
+ [
+ {non_parallel_tests, [], [
+ queue_coarse_test,
+ connection_coarse_test,
+ fine_stats_aggregation_time_test,
+ fine_stats_aggregation_test,
+ all_consumers_test
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ inets:start(),
+ Config.
+
+end_per_suite(Config) ->
+ Config.
+
+init_per_group(_, Config) ->
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodename_suffix, ?MODULE}
+ ]),
+ Config2 = rabbit_ct_helpers:merge_app_env(
+ rabbit_mgmt_test_util:merge_stats_app_env(Config1, 1000, 1),
+ {rabbitmq_management_agent, [{rates_mode, detailed}]}),
+ rabbit_ct_helpers:run_setup_steps(Config2,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps()).
+
+end_per_group(_, Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps()).
+
+init_per_testcase(Testcase, Config) ->
+ reset_management_settings(Config),
+ rabbit_ct_helpers:testcase_started(Config, Testcase).
+
+end_per_testcase(Testcase, Config) ->
+ reset_management_settings(Config),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase).
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+trace_fun(Config, MFs) ->
+ Nodename1 = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+ dbg:tracer(process, {fun(A,_) ->
+ ct:pal(?LOW_IMPORTANCE,
+ "TRACE: ~p", [A])
+ end, ok}),
+ dbg:n(Nodename1),
+ dbg:p(all,c),
+ [ dbg:tpl(M, F, cx) || {M, F} <- MFs].
+
+queue_coarse_test(Config) ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, queue_coarse_test1, [Config]).
+
+queue_coarse_test1(_Config) ->
+ [rabbit_mgmt_metrics_collector:override_lookups(T, [{exchange, fun dummy_lookup/1},
+ {queue, fun dummy_lookup/1}])
+ || {T, _} <- ?CORE_TABLES],
+ First = exometer_slide:timestamp(),
+ stats_series(fun stats_q/2, [[{test, 1}, {test2, 1}], [{test, 10}], [{test, 20}]]),
+ timer:sleep(1150 * 2), %% The x2 factor is arbitrary: it makes CI happy.
+ Last = exometer_slide:timestamp(),
+ Interval = 1,
+ R = range(First, Last, Interval),
+ simple_details(get_q(test, R), messages, 20, R),
+ simple_details(get_vhost(R), messages, 21, R),
+ simple_details(get_overview_q(R), messages, 21, R),
+ delete_q(test),
+ timer:sleep(1150),
+ Next = last_ts(First, Interval),
+ R1 = range(First, Next, Interval),
+ simple_details(get_vhost(R1), messages, 1, R1),
+ simple_details(get_overview_q(R1), messages, 1, R1),
+ delete_q(test2),
+ timer:sleep(1150),
+ Next2 = last_ts(First, Interval),
+ R2 = range(First, Next2, Interval),
+ simple_details(get_vhost(R2), messages, 0, R2),
+ simple_details(get_overview_q(R2), messages, 0, R2),
+ [rabbit_mgmt_metrics_collector:reset_lookups(T) || {T, _} <- ?CORE_TABLES],
+ ok.
+
+%% Generate a well-formed interval from Start using Interval steps
+last_ts(First, Interval) ->
+ Now = exometer_slide:timestamp(),
+ ceil(((Now - First) / Interval * 1000)) * Interval + First.
+
+ceil(X) when X < 0 ->
+ trunc(X);
+ceil(X) ->
+ T = trunc(X),
+ case X - T == 0 of
+ true -> T;
+ false -> T + 1
+ end.
+
+connection_coarse_test(Config) ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, connection_coarse_test1, [Config]).
+
+connection_coarse_test1(_Config) ->
+ First = exometer_slide:timestamp(),
+ create_conn(test),
+ create_conn(test2),
+ stats_series(fun stats_conn/2, [[{test, 2}, {test2, 5}], [{test, 5}, {test2, 1}],
+ [{test, 10}]]),
+ Last = last_ts(First, 5),
+ R = range(First, Last, 5),
+ simple_details(get_conn(test, R), recv_oct, 10, R),
+ simple_details(get_conn(test2, R), recv_oct, 1, R),
+ delete_conn(test),
+ delete_conn(test2),
+ timer:sleep(1150),
+ assert_list([], rabbit_mgmt_db:get_all_connections(R)),
+ ok.
+
+fine_stats_aggregation_test(Config) ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, fine_stats_aggregation_test1, [Config]).
+
+fine_stats_aggregation_test1(_Config) ->
+ [rabbit_mgmt_metrics_collector:override_lookups(T, [{exchange, fun dummy_lookup/1},
+ {queue, fun dummy_lookup/1}])
+ || {T, _} <- ?CORE_TABLES],
+ First = exometer_slide:timestamp(),
+ create_conn(test),
+ create_conn(test2),
+ create_ch(ch1, [{connection, pid(test)}]),
+ create_ch(ch2, [{connection, pid(test2)}]),
+ %% Publish differences
+ channel_series(ch1, [{[{x, 50}], [{q1, x, 15}, {q2, x, 2}], [{q1, 5}, {q2, 5}]},
+ {[{x, 25}], [{q1, x, 10}, {q2, x, 3}], [{q1, -2}, {q2, -3}]},
+ {[{x, 25}], [{q1, x, 25}, {q2, x, 5}], [{q1, -1}, {q2, -1}]}]),
+ channel_series(ch2, [{[{x, 5}], [{q1, x, 15}, {q2, x, 1}], []},
+ {[{x, 2}], [{q1, x, 10}, {q2, x, 2}], []},
+ {[{x, 3}], [{q1, x, 25}, {q2, x, 2}], []}]),
+ timer:sleep(1150),
+
+ fine_stats_aggregation_test0(true, First),
+ delete_q(q2),
+ timer:sleep(1150),
+ fine_stats_aggregation_test0(false, First),
+ delete_ch(ch1),
+ delete_ch(ch2),
+ delete_conn(test),
+ delete_conn(test2),
+ delete_x(x),
+ delete_v(<<"/">>),
+ [rabbit_mgmt_metrics_collector:reset_lookups(T) || {T, _} <- ?CORE_TABLES],
+ ok.
+
+fine_stats_aggregation_test0(Q2Exists, First) ->
+ Last = exometer_slide:timestamp(),
+ R = range(First, Last, 1),
+ Ch1 = get_ch(ch1, R),
+
+ Ch2 = get_ch(ch2, R),
+ X = get_x(x, R),
+ Q1 = get_q(q1, R),
+ V = get_vhost(R),
+ O = get_overview(R),
+ assert_fine_stats(m, publish, 100, Ch1, R),
+ assert_fine_stats(m, publish, 10, Ch2, R),
+ assert_fine_stats(m, publish_in, 110, X, R),
+ assert_fine_stats(m, publish_out, 115, X, R),
+ assert_fine_stats(m, publish, 100, Q1, R),
+ assert_fine_stats(m, deliver_get, 2, Q1, R),
+ assert_fine_stats(m, deliver_get, 3, Ch1, R),
+ assert_fine_stats(m, publish, 110, V, R),
+ assert_fine_stats(m, deliver_get, 3, V, R),
+ assert_fine_stats(m, publish, 110, O, R),
+ assert_fine_stats(m, deliver_get, 3, O, R),
+ assert_fine_stats({pub, x}, publish, 100, Ch1, R),
+ assert_fine_stats({pub, x}, publish, 10, Ch2, R),
+ assert_fine_stats({in, ch1}, publish, 100, X, R),
+ assert_fine_stats({in, ch2}, publish, 10, X, R),
+ assert_fine_stats({out, q1}, publish, 100, X, R),
+ assert_fine_stats({in, x}, publish, 100, Q1, R),
+ assert_fine_stats({del, ch1}, deliver_get, 2, Q1, R),
+ assert_fine_stats({del, q1}, deliver_get, 2, Ch1, R),
+ case Q2Exists of
+ true -> Q2 = get_q(q2, R),
+ assert_fine_stats(m, publish, 15, Q2, R),
+ assert_fine_stats(m, deliver_get, 1, Q2, R),
+ assert_fine_stats({out, q2}, publish, 15, X, R),
+ assert_fine_stats({in, x}, publish, 15, Q2, R),
+ assert_fine_stats({del, ch1}, deliver_get, 1, Q2, R),
+ assert_fine_stats({del, q2}, deliver_get, 1, Ch1, R);
+ false -> assert_fine_stats_neg({out, q2}, X),
+ assert_fine_stats_neg({del, q2}, Ch1)
+ end,
+ ok.
+
+fine_stats_aggregation_time_test(Config) ->
+ %% trace_fun(Config, [{rabbit_mgmt_db, get_data_from_nodes}]),
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, fine_stats_aggregation_time_test1, [Config]).
+
+fine_stats_aggregation_time_test1(_Config) ->
+ [rabbit_mgmt_metrics_collector:override_lookups(T, [{exchange, fun dummy_lookup/1},
+ {queue, fun dummy_lookup/1}])
+ || {T, _} <- ?CORE_TABLES],
+ First = exometer_slide:timestamp(),
+ create_ch(ch),
+ channel_series(ch, [{[{x, 50}], [{q, x, 15}], [{q, 5}]},
+ {[{x, 25}], [{q, x, 10}], [{q, 5}]},
+ {[{x, 25}], [{q, x, 25}], [{q, 10}]}]),
+ timer:sleep(1150),
+ Last = exometer_slide:timestamp(),
+
+ channel_series(ch, [{[{x, 10}], [{q, x, 5}], [{q, 2}]}]),
+ Next = exometer_slide:timestamp(),
+
+
+ R1 = range(First, Last, 1),
+ assert_fine_stats(m, publish, 100, get_ch(ch, R1), R1),
+ assert_fine_stats(m, publish, 50, get_q(q, R1), R1),
+ assert_fine_stats(m, deliver_get, 20, get_q(q, R1), R1),
+
+
+ R2 = range(Last, Next, 1),
+ assert_fine_stats(m, publish, 110, get_ch(ch, R2), R2),
+ assert_fine_stats(m, publish, 55, get_q(q, R2), R2),
+ assert_fine_stats(m, deliver_get, 22, get_q(q, R2), R2),
+
+ delete_q(q),
+ delete_ch(ch),
+ delete_x(x),
+ delete_v(<<"/">>),
+
+ [rabbit_mgmt_metrics_collector:reset_lookups(T) || {T, _} <- ?CORE_TABLES],
+ ok.
+
+assert_fine_stats(m, Type, N, Obj, R) ->
+ Act = pget(message_stats, Obj),
+ simple_details(Act, Type, N, R);
+assert_fine_stats({T2, Name}, Type, N, Obj, R) ->
+ Act = find_detailed_stats(Name, pget(expand(T2), Obj)),
+ simple_details(Act, Type, N, R).
+
+assert_fine_stats_neg({T2, Name}, Obj) ->
+ detailed_stats_absent(Name, pget(expand(T2), Obj)).
+
+ %% {{{resource,<<"/">>,queue,<<"test_lazy">>},
+ %% <0.953.0>,<<"amq.ctag-sPlBtNl8zwIGkYhNjJrATA">>},
+ %% false,true,0,[]},
+all_consumers_test(Config) ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, all_consumers_test1, [Config]).
+
+all_consumers_test1(_Config) ->
+ %% Tests that we can list all the consumers while channels are in the process of
+ %% being deleted. Thus, channel details might be missing.
+ %% `merge_channel_into_obj` is also used when listing queues, which might now report
+ %% empty channel details while the queue is being deleted. But will not crash.
+ [rabbit_mgmt_metrics_collector:override_lookups(T, [{exchange, fun dummy_lookup/1},
+ {queue, fun dummy_lookup/1}])
+ || {T, _} <- ?CORE_TABLES],
+ create_cons(q1, ch1, <<"ctag">>, false, true, 0, []),
+ timer:sleep(1150),
+ [Consumer] = rabbit_mgmt_db:get_all_consumers(),
+ [] = proplists:get_value(channel_details, Consumer),
+ %% delete_cons(co),
+ [rabbit_mgmt_metrics_collector:reset_lookups(T) || {T, _} <- ?CORE_TABLES],
+ ok.
+%%----------------------------------------------------------------------------
+%% Events in
+%%----------------------------------------------------------------------------
+
+create_conn(Name) ->
+ rabbit_core_metrics:connection_created(pid(Name), [{pid, pid(Name)},
+ {name, a2b(Name)}]).
+
+create_ch(Name, Extra) ->
+ rabbit_core_metrics:channel_created(pid(Name), [{pid, pid(Name)},
+ {name, a2b(Name)}] ++ Extra).
+create_ch(Name) ->
+ create_ch(Name, []).
+
+create_cons(QName, ChName, Tag, Exclusive, AckRequired, PrefetchCount, Args) ->
+ rabbit_core_metrics:consumer_created(pid(ChName), Tag, Exclusive,
+ AckRequired, q(QName),
+ PrefetchCount, false, waiting, Args).
+
+stats_series(Fun, ListsOfPairs) ->
+ [begin
+ [Fun(Name, Msgs) || {Name, Msgs} <- List],
+ timer:sleep(1150)
+ end || List <- ListsOfPairs].
+
+stats_q(Name, Msgs) ->
+ rabbit_core_metrics:queue_stats(q(Name), Msgs, Msgs, Msgs, Msgs).
+
+stats_conn(Name, Oct) ->
+ rabbit_core_metrics:connection_stats(pid(Name), Oct, Oct, Oct).
+
+channel_series(Name, ListOfStats) ->
+ [begin
+ stats_ch(Name, XStats, QXStats, QStats),
+ timer:sleep(1150)
+ end || {XStats, QXStats, QStats} <- ListOfStats].
+
+stats_ch(Name, XStats, QXStats, QStats) ->
+ [rabbit_core_metrics:channel_stats(exchange_stats, publish, {pid(Name), x(XName)}, N)
+ || {XName, N} <- XStats],
+ [rabbit_core_metrics:channel_stats(queue_exchange_stats, publish,
+ {pid(Name), {q(QName), x(XName)}}, N)
+ || {QName, XName, N} <- QXStats],
+ [rabbit_core_metrics:channel_stats(queue_stats, deliver_no_ack, {pid(Name), q(QName)}, N)
+ || {QName, N} <- QStats],
+ ok.
+
+delete_q(Name) ->
+ rabbit_core_metrics:queue_deleted(q(Name)),
+ rabbit_event:notify(queue_deleted, [{name, q(Name)}]).
+
+delete_conn(Name) ->
+ Pid = pid_del(Name),
+ rabbit_core_metrics:connection_closed(Pid),
+ rabbit_event:notify(connection_closed, [{pid, Pid}]).
+
+delete_cons(QName, ChName, Tag) ->
+ Pid = pid_del(ChName),
+ rabbit_core_metrics:consumer_deleted(Pid, Tag, q(QName)),
+ rabbit_event:notify(consumer_deleted, [{channel, Pid},
+ {queue, q(QName)},
+ {consumer_tag, Tag}]).
+
+delete_ch(Name) ->
+ Pid = pid_del(Name),
+ rabbit_core_metrics:channel_closed(Pid),
+ rabbit_core_metrics:channel_exchange_down({Pid, x(x)}),
+ rabbit_event:notify(channel_closed, [{pid, Pid}]).
+
+delete_x(Name) ->
+ rabbit_event:notify(exchange_deleted, [{name, x(Name)}]).
+
+delete_v(Name) ->
+ rabbit_event:notify(vhost_deleted, [{name, Name}]).
+
+%%----------------------------------------------------------------------------
+%% Events out
+%%----------------------------------------------------------------------------
+
+range(F, L, I) ->
+ R = #range{first = F, last = L, incr = I * 1000},
+ {R, R, R, R}.
+
+get_x(Name, Range) ->
+ [X] = rabbit_mgmt_db:augment_exchanges([x2(Name)], Range, full),
+ X.
+
+get_q(Name, Range) ->
+ [Q] = rabbit_mgmt_db:augment_queues([q2(Name)], Range, full),
+ Q.
+
+get_vhost(Range) ->
+ [VHost] = rabbit_mgmt_db:augment_vhosts([[{name, <<"/">>}]], Range),
+ VHost.
+
+get_conn(Name, Range) -> rabbit_mgmt_db:get_connection(a2b(Name), Range).
+get_ch(Name, Range) -> rabbit_mgmt_db:get_channel(a2b(Name), Range).
+
+get_overview(Range) -> rabbit_mgmt_db:get_overview(Range).
+get_overview_q(Range) -> pget(queue_totals, get_overview(Range)).
+
+details0(R, AR, A, L) ->
+ [{rate, R},
+ {samples, [[{sample, S}, {timestamp, T}] || {T, S} <- L]},
+ {avg_rate, AR},
+ {avg, A}].
+
+simple_details(Result, Thing, N, {#range{first = First, last = Last}, _, _, _} = _R) ->
+ ?assertEqual(N, proplists:get_value(Thing, Result)),
+ Details = proplists:get_value(atom_suffix(Thing, "_details"), Result),
+ ?assert(0 =/= proplists:get_value(rate, Details)),
+ Samples = proplists:get_value(samples, Details),
+ TSs = [proplists:get_value(timestamp, S) || S <- Samples],
+ ?assert(First =< lists:min(TSs)),
+ ?assert(Last >= lists:max(TSs)).
+
+atom_suffix(Atom, Suffix) ->
+ list_to_atom(atom_to_list(Atom) ++ Suffix).
+
+find_detailed_stats(Name, List) ->
+ [S] = filter_detailed_stats(Name, List),
+ S.
+
+detailed_stats_absent(Name, List) ->
+ [] = filter_detailed_stats(Name, List).
+
+filter_detailed_stats(Name, List) ->
+ lists:foldl(fun(L, Acc) ->
+ {[{stats, Stats}], [{_, Details}]} =
+ lists:partition(fun({K, _}) -> K == stats end, L),
+ case (pget(name, Details) =:= a2b(Name)) of
+ true ->
+ [Stats | Acc];
+ false ->
+ Acc
+ end
+ end, [], List).
+
+expand(in) -> incoming;
+expand(out) -> outgoing;
+expand(del) -> deliveries;
+expand(pub) -> publishes.
+
+%%----------------------------------------------------------------------------
+%% Util
+%%----------------------------------------------------------------------------
+
+x(Name) -> rabbit_misc:r(<<"/">>, exchange, a2b(Name)).
+x2(Name) -> q2(Name).
+q(Name) -> rabbit_misc:r(<<"/">>, queue, a2b(Name)).
+q2(Name) -> [{name, a2b(Name)},
+ {pid, self()}, % fake a local pid
+ {vhost, <<"/">>}].
+
+pid(Name) ->
+ case get({pid, Name}) of
+ undefined -> P = spawn(fun() -> ok end),
+ put({pid, Name}, P),
+ P;
+ Pid -> Pid
+ end.
+
+pid_del(Name) ->
+ Pid = pid(Name),
+ erase({pid, Name}),
+ Pid.
+
+a2b(A) -> list_to_binary(atom_to_list(A)).
+
+dummy_lookup(_Thing) -> true.
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_test_unit_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_test_unit_SUITE.erl
new file mode 100644
index 0000000000..32194bd5c8
--- /dev/null
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_test_unit_SUITE.erl
@@ -0,0 +1,88 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_test_unit_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, parallel_tests}
+ ].
+
+groups() ->
+ [
+ {parallel_tests, [parallel], [
+ tokenise_test,
+ pack_binding_test,
+ path_prefix_test
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+%% -------------------------------------------------------------------
+%% Test cases.
+%% -------------------------------------------------------------------
+
+tokenise_test(_Config) ->
+ [] = rabbit_mgmt_format:tokenise(""),
+ ["foo"] = rabbit_mgmt_format:tokenise("foo"),
+ ["foo", "bar"] = rabbit_mgmt_format:tokenise("foo~bar"),
+ ["foo", "", "bar"] = rabbit_mgmt_format:tokenise("foo~~bar"),
+ ok.
+
+pack_binding_test(_Config) ->
+ assert_binding(<<"~">>,
+ <<"">>, []),
+ assert_binding(<<"foo">>,
+ <<"foo">>, []),
+ assert_binding(<<"foo%7Ebar%2Fbash">>,
+ <<"foo~bar/bash">>, []),
+ assert_binding(<<"foo%7Ebar%7Ebash">>,
+ <<"foo~bar~bash">>, []),
+ ok.
+
+path_prefix_test(_Config) ->
+ Got0 = rabbit_mgmt_util:get_path_prefix(),
+ ?assertEqual("", Got0),
+
+ Pfx0 = "/custom-prefix",
+ application:set_env(rabbitmq_management, path_prefix, Pfx0),
+ Got1 = rabbit_mgmt_util:get_path_prefix(),
+ ?assertEqual(Pfx0, Got1),
+
+ Pfx1 = "custom-prefix",
+ application:set_env(rabbitmq_management, path_prefix, Pfx1),
+ Got2 = rabbit_mgmt_util:get_path_prefix(),
+ ?assertEqual(Pfx0, Got2),
+
+ Pfx2 = <<"custom-prefix">>,
+ application:set_env(rabbitmq_management, path_prefix, Pfx2),
+ Got3 = rabbit_mgmt_util:get_path_prefix(),
+ ?assertEqual(Pfx0, Got3).
+
+%%--------------------------------------------------------------------
+
+assert_binding(Packed, Routing, Args) ->
+ case rabbit_mgmt_format:pack_binding_props(Routing, Args) of
+ Packed ->
+ ok;
+ Act ->
+ throw({pack, Routing, Args, expected, Packed, got, Act})
+ end.
diff --git a/deps/rabbitmq_management/test/stats_SUITE.erl b/deps/rabbitmq_management/test/stats_SUITE.erl
new file mode 100644
index 0000000000..99de1a532e
--- /dev/null
+++ b/deps/rabbitmq_management/test/stats_SUITE.erl
@@ -0,0 +1,178 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(stats_SUITE).
+
+-include_lib("proper/include/proper.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+-compile(export_all).
+
+-import(rabbit_misc, [pget/2]).
+
+all() ->
+ [
+ {group, parallel_tests}
+ ].
+
+groups() ->
+ [
+ {parallel_tests, [parallel], [
+ format_range_empty_slide,
+ format_range,
+ format_range_missing_middle,
+ format_range_missing_middle_drop,
+ format_range_incremental_pad,
+ format_range_incremental_pad2,
+ format_range_constant
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, _Config) ->
+ ok.
+
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(_, _Config) ->
+ ok.
+
+format_range_empty_slide(_Config) ->
+ Slide = exometer_slide:new(0, 60000, [{incremental, true},
+ {interval, 5000}]),
+ Range = #range{first = 100, last = 200, incr = 10},
+ Table = vhost_stats_fine_stats,
+ SamplesFun = fun() -> [Slide] end,
+ Got = rabbit_mgmt_stats:format_range(Range, 200, Table, 0, fun() ->
+ ok
+ end,
+ SamplesFun),
+ PublishDetails = proplists:get_value(publish_details, Got),
+ Samples = proplists:get_value(samples, PublishDetails),
+ 11 = length(Samples).
+
+format_range(_Config) ->
+ Slide = exometer_slide:new(0, 60000, [{incremental, true},
+ {interval, 10}]),
+ Slide1 = exometer_slide:add_element(197, {10}, Slide),
+ Range = #range{first = 100, last = 200, incr = 10},
+ Table = queue_stats_publish,
+ SamplesFun = fun() -> [Slide1] end,
+ Got = rabbit_mgmt_stats:format_range(Range, 200, Table, 0, fun() ->
+ ok
+ end,
+ SamplesFun),
+ PublishDetails = proplists:get_value(publish_details, Got),
+ [S1, S2 | _Rest] = Samples = proplists:get_value(samples, PublishDetails),
+ 0 = proplists:get_value(sample, S2),
+ 10 = proplists:get_value(sample, S1),
+ 11 = length(Samples).
+
+format_range_missing_middle(_Config) ->
+ Slide = exometer_slide:new(0, 60000, [{incremental, false},
+ {interval, 10}]),
+ Slide1 = exometer_slide:add_element(167, {10}, Slide),
+ Slide2 = exometer_slide:add_element(197, {5}, Slide1),
+ Range = #range{first = 100, last = 200, incr = 10},
+ Table = queue_stats_publish,
+ SamplesFun = fun() -> [Slide2] end,
+ Got = rabbit_mgmt_stats:format_range(Range, 200, Table, 0, fun() ->
+ ok
+ end,
+ SamplesFun),
+ PublishDetails = proplists:get_value(publish_details, Got),
+ [S1, S2, S3, S4 | Rest] = Samples = proplists:get_value(samples, PublishDetails),
+ 10 = proplists:get_value(sample, S4),
+ 10 = proplists:get_value(sample, S3),
+ 10 = proplists:get_value(sample, S2),
+ 5 = proplists:get_value(sample, S1),
+ true = lists:all(fun(P) ->
+ 0 == proplists:get_value(sample, P)
+ end, Rest),
+ 11 = length(Samples).
+
+format_range_missing_middle_drop(_Config) ->
+ Slide = exometer_slide:new(0, 60000, [{incremental, false},
+ {max_n, 12},
+ {interval, 10}]),
+ Slide1 = exometer_slide:add_element(167, {10}, Slide),
+ Slide2 = exometer_slide:add_element(197, {10}, Slide1),
+ Range = #range{first = 100, last = 200, incr = 10},
+ Table = queue_stats_publish,
+ SamplesFun = fun() -> [Slide2] end,
+ Got = rabbit_mgmt_stats:format_range(Range, 200, Table, 0, fun() ->
+ ok
+ end,
+ SamplesFun),
+ PublishDetails = proplists:get_value(publish_details, Got),
+ [S1, S2, S3, S4 | Rest] = Samples = proplists:get_value(samples, PublishDetails),
+ 10 = proplists:get_value(sample, S4),
+ 10 = proplists:get_value(sample, S3),
+ 10 = proplists:get_value(sample, S2),
+ 10 = proplists:get_value(sample, S1),
+ true = lists:all(fun(P) ->
+ 0 == proplists:get_value(sample, P)
+ end, Rest),
+ 11 = length(Samples).
+
+format_range_incremental_pad(_Config) ->
+ Slide = exometer_slide:new(0, 10, [{incremental, true},
+ {interval, 5}]),
+ Slide1 = exometer_slide:add_element(15, {3}, Slide),
+ Range = #range{first = 5, last = 15, incr = 5},
+ Table = queue_stats_publish,
+ SamplesFun = fun() -> [Slide1] end,
+ Got = rabbit_mgmt_stats:format_range(Range, 0, Table, 0, fun() -> ok end,
+ SamplesFun),
+ PublishDetails = proplists:get_value(publish_details, Got),
+ [{3, 15}, {0,10}, {0, 5}] = [{pget(sample, V), pget(timestamp, V)}
+ || V <- pget(samples, PublishDetails)].
+
+format_range_incremental_pad2(_Config) ->
+ Slide = exometer_slide:new(0, 10, [{incremental, true},
+ {interval, 5}]),
+ {_, Slide1} = lists:foldl(fun (V, {TS, S}) ->
+ {TS + 5, exometer_slide:add_element(TS, {V}, S)}
+ end, {5, Slide}, [1,1,0,0,0,1]),
+ Range = #range{first = 10, last = 30, incr = 5},
+ Table = queue_stats_publish,
+ SamplesFun = fun() -> [Slide1] end,
+ Got = rabbit_mgmt_stats:format_range(Range, 0, Table, 0, fun() -> ok end,
+ SamplesFun),
+ PublishDetails = pget(publish_details, Got),
+ [{3, 30}, {2, 25}, {2, 20}, {2, 15}, {2, 10}] =
+ [{pget(sample, V), pget(timestamp, V)}
+ || V <- pget(samples, PublishDetails)].
+
+format_range_constant(_Config) ->
+ Now = 0,
+ Slide = exometer_slide:new(0, 20, [{incremental, false},
+ {max_n, 100},
+ {interval, 5}]),
+ Slide1 = lists:foldl(fun(N, Acc) ->
+ exometer_slide:add_element(Now + N, {5}, Acc)
+ end, Slide, lists:seq(0, 100, 5)),
+ Range = #range{first = 5, last = 50, incr = 5},
+ Table = queue_stats_publish,
+ SamplesFun = fun() -> [Slide1] end,
+ Got = rabbit_mgmt_stats:format_range(Range, 0, Table, 0, fun() -> ok end,
+ SamplesFun),
+ 5 = proplists:get_value(publish, Got),
+ PD = proplists:get_value(publish_details, Got),
+ 0.0 = proplists:get_value(rate, PD).